/* ──────────────────────────────────────────────────────────────────
   PORTAL RESPONSIVE CONVENTION (post-2026-06 refactor)

   Tablet UX applies when EITHER:
     - viewport < 1200px (xl), OR
     - viewport >= 1200px AND device is touch-primary
       (no hover, coarse pointer)

   Desktop UX applies when ALL of:
     - viewport >= 1200px, AND
     - device has hover capability, AND
     - device has fine pointer

   Why: iPad Pro 12.9" landscape reports a 1366px viewport, which
   Bootstrap classifies as desktop (xl). The pointer/hover clauses let
   that device — and any future touch device at xl+ widths — receive the
   tablet layout (larger touch targets, fixed-width tables, folded
   columns) instead of the dense mouse-tuned desktop layout.

   Standard tablet media query (single source of truth for tablet styles):
     @media (max-width: 1199.98px),
            (min-width: 1200px) and (hover: none) and (pointer: coarse)

   Standard desktop media query:
     @media (min-width: 1200px) and (hover: hover) and (pointer: fine)

   Bootstrap utility override for touch-at-xl (Bootstrap's d-xl-* / d-lg-*
   utilities are width-only; override them with !important inside):
     @media (min-width: 1200px) and (hover: none) and (pointer: coarse)

   Scope: this convention governs the TABLET↔DESKTOP boundary only (the
   1200px / xl line). Mobile breakpoints (max-width: 767.98px and below)
   stay width-only — mobile UX is already touch-optimised for every
   device. See CLAUDE.md "Responsive Convention" for the full rule.
   ──────────────────────────────────────────────────────────────── */
:root {
    --bg: #f0f2f8;
    --text: #212529;
    --card-bg: rgba(255, 255, 255, 0.74);
    --border: #dee2e6;
    --muted: #6c757d;
    --table-striped: rgba(0,0,0,0.02);
    --table-head-bg: #f1f3f5;
    --link: #0d6efd;
    --btn-light-bg: #f8f9fa;
    --btn-light-border: #ced4da;
    --glass-accent-rgb: 93, 163, 255;
    --glass-surface: rgba(var(--glass-accent-rgb), 0.08);
    --glass-surface-strong: rgba(var(--glass-accent-rgb), 0.14);
    --glass-border: rgba(var(--glass-accent-rgb), 0.24);
    --glass-highlight: rgba(var(--glass-accent-rgb), 0.16);
    --header-glass-bg: rgba(18, 30, 49, 0.72);
    --header-glass-border: rgba(var(--glass-accent-rgb), 0.2);
    --selected-row-bg: rgba(var(--glass-accent-rgb), 0.2);
    --selected-row-accent: rgba(41, 124, 230, 0.95);
    --btn-hover-bg: rgba(var(--glass-accent-rgb), 0.12);
    --btn-active-bg: rgba(var(--glass-accent-rgb), 0.18);
    --glass-shadow: 0 8px 24px rgba(24, 44, 78, 0.08);
    --brand-divider: rgba(232, 128, 47, 0.8);
    /* Neutral tint for content section headers (e.g. Labor Review Project/Lot
       group headers) — visibly distinct from PO rows but still gray, not brand
       color. Orange (--brand-divider) stays reserved for page chrome. */
    --section-header-bg: rgba(0, 0, 0, 0.07);
    /* Blue accent for the Labor Review group-header left bar (replaces the
       earlier orange accent). Dark mode uses a lighter shade for contrast
       against the dark header band. */
    --labor-review-accent: #3282e7;
    --lot-card-bg: #ffffff;
    --lot-card-border: rgba(29, 52, 84, 0.12);
    --lot-card-shadow: 0 6px 16px rgba(28, 40, 63, 0.08);
    --lot-meta: #5f6f86;
    --parts-lookup-bg: linear-gradient(160deg, rgba(221, 237, 255, 0.94) 0%, rgba(245, 251, 255, 0.92) 52%, rgba(225, 240, 255, 0.95) 100%);
    --parts-lookup-border: rgba(var(--glass-accent-rgb), 0.42);
    --parts-lookup-hover: rgba(var(--glass-accent-rgb), 0.22);
    --add-parts-modal-bg: linear-gradient(158deg, rgba(248, 252, 255, 0.93) 0%, rgba(239, 247, 255, 0.91) 48%, rgba(246, 250, 255, 0.94) 100%);
    --po-detail-reference-bg: #ffffff;
    --po-detail-edit-bg: #ffffff;
    --ordq-bulk-modified-bg: rgba(255, 213, 102, 0.26);
    --ordq-bulk-modified-accent: #fbbf24;
    --ordq-bulk-new-bg: rgba(126, 203, 149, 0.24);
    --ordq-bulk-new-accent: #6ee7b7;
}

body {
    background-color: var(--bg);
    color: var(--text);
    /* Disable mobile pull-to-refresh portal-wide. On iOS Safari / Android
       Chrome, a downward swipe that starts at scrollY=0 is interpreted as a
       pull-to-refresh gesture and reloads the page back to the top — fatal
       inside multi-section workflows (Payment Processor especially, where
       date-driven AJAX state would be discarded). `contain` keeps normal
       scroll/scrolling-chaining intact; it only blocks the overscroll
       refresh action. No effect on desktop browsers. */
    overscroll-behavior-y: contain;
}

.portal-brand {
    display: flex;
    align-items: center;
    gap: 0.6rem;
    min-width: 0;
}

.portal-brand img {
    height: 46px;
    width: auto;
    object-fit: contain;
    flex-shrink: 0;
}

.portal-brand-text {
    font-weight: 600;
    line-height: 1.2;
    white-space: nowrap;
}

.card {
    background-color: var(--card-bg);
    color: var(--text);
    box-shadow: var(--glass-shadow);
    border: 1px solid var(--border);
    backdrop-filter: blur(8px);
    -webkit-backdrop-filter: blur(8px);
}

.navbar {
    background-color: #ffffff !important;
    border-bottom: 2px solid var(--brand-divider);
    box-shadow: 0 1px 0 rgba(21, 34, 57, 0.06);
    backdrop-filter: none;
    -webkit-backdrop-filter: none;
}

.navbar .navbar-brand,
.navbar .navbar-brand:hover {
    color: #1b2432;
}

.navbar .btn-outline-light {
    color: #1b2432;
    border-color: rgba(31, 52, 83, 0.26);
}

.navbar .btn-outline-light:hover,
.navbar .btn-outline-light:focus {
    color: #1b2432;
    border-color: rgba(31, 52, 83, 0.35);
    background-color: rgba(93, 163, 255, 0.08);
}

.card[data-section] .card-header {
    background-color: var(--glass-surface);
    border-bottom: 1px solid var(--glass-border);
}

.card[data-section] .section-toggle:hover {
    background-color: var(--glass-surface-strong);
}

.table {
    color: var(--text);
}

.table thead th {
    background-color: var(--table-head-bg);
    color: var(--text);
    padding: 0.65rem 0.9rem;
}

.workspace-main {
    max-width: 100%;
}

.workspace-sort-link {
    display: inline-flex;
    align-items: center;
    gap: 0.22rem;
    white-space: nowrap;
}

.portal-sort-icon {
    display: inline-block;
    position: relative;
    width: 0.7rem;
    height: 0.7rem;
    vertical-align: middle;
    color: currentColor;
}

.portal-sort-icon::before,
.portal-sort-icon::after {
    content: "";
    position: absolute;
    left: 50%;
    transform: translateX(-50%);
    width: 0;
    height: 0;
    border-left: 0.22rem solid transparent;
    border-right: 0.22rem solid transparent;
}

.portal-sort-icon-asc::before {
    top: 0.08rem;
    border-bottom: 0.36rem solid currentColor;
}

.portal-sort-icon-desc::before {
    bottom: 0.08rem;
    border-top: 0.36rem solid currentColor;
}

.portal-sort-icon-none::before {
    top: 0.03rem;
    border-bottom: 0.26rem solid currentColor;
    opacity: 0.7;
}

.portal-sort-icon-none::after {
    bottom: 0.03rem;
    border-top: 0.26rem solid currentColor;
    opacity: 0.7;
}

.workspace-po-table thead th {
    position: sticky;
    top: 0;
    z-index: 2;
    background-color: var(--table-head-bg);
}

/* Header label swap — defaults to the full label. Tablet block flips it. */
.workspace-po-table .th-label-short { display: none; }

/* Stacked "Vendor" cell — reusable two-line cell. On tablet the standalone
   VENDX column folds into the VENDOR_NAM column, rendering VENDX as a muted
   secondary line below the vendor name. The __id line is hidden by default
   (desktop, where VENDX keeps its own column) and revealed in the tablet
   block. Each line is a block so it can ellipsis-truncate inside a
   fixed-width cell. */
.vendor-stack { display: block; min-width: 0; line-height: 1.2; }
.vendor-stack__name { display: block; min-width: 0; }
.vendor-stack__id {
    display: none;
    min-width: 0;
    font-size: 0.8em;
    color: var(--muted);
}

.po-focus .ordquant-panel {
    display: none;
}

.po-focus .po-table {
    max-height: 75vh !important;
}

.table-striped > tbody > tr:nth-of-type(odd) > * {
    background-color: var(--table-striped);
}

.table-striped > tbody > tr > * {
    color: var(--text);
    padding: 0.55rem 0.9rem;
}

@media (min-width: 640px) {
    .workspace-main {
        max-width: min(80vw, 1380px);
        margin-left: auto;
        margin-right: auto;
    }

    .workspace-po-table,
    .workspace-ordquant-table {
        width: 100%;
    }

    .workspace-po-table {
        min-width: 62rem;
    }

    .workspace-ordquant-table {
        min-width: 44rem;
    }

    .workspace-po-table thead th,
    .workspace-po-table tbody td,
    .workspace-ordquant-table thead th,
    .workspace-ordquant-table tbody td {
        vertical-align: middle;
    }

    /* Base (auto-layout) sizing for the free-text columns. Class-based so it
       survives the tablet Vendor consolidation. Tablet overrides the widths;
       desktop overrides nowrap → wrap (see the desktop query). */
    .workspace-po-table .workspace-col-vendor-name {
        min-width: 10.5rem;
        max-width: 13rem;
        overflow: hidden;
    }
    .workspace-po-table .workspace-col-vendor-name .vendor-stack__name {
        white-space: nowrap;
        overflow: hidden;
        text-overflow: ellipsis;
    }

    .workspace-po-table .workspace-col-descriptio {
        min-width: 8rem;
        max-width: 10rem;
        white-space: nowrap;
        overflow: hidden;
        text-overflow: ellipsis;
    }

    .workspace-po-table .workspace-col-costtype {
        min-width: 3rem;
        max-width: 5rem;
        white-space: nowrap;
        text-align: center;
    }

    .workspace-ordquant-table thead th:nth-child(2),
    .workspace-ordquant-table tbody td:nth-child(2) {
        min-width: 11rem;
        max-width: 16rem;
        white-space: normal;
        word-break: break-word;
    }
}

@media (min-width: 640px) and (max-width: 1199.98px),
       (min-width: 1200px) and (hover: none) and (pointer: coarse) {
    .workspace-ordquant-table thead th:nth-child(2),
    .workspace-ordquant-table tbody td:nth-child(2),
    .po-detail-ordquant-table .ordq-col-descriptio,
    .po-detail-ordquant-bulk-table .ordq-descriptio-col,
    .po-detail-ordquant-bulk-table tbody td:nth-child(2) {
        max-width: 13rem;
        white-space: normal;
        word-break: break-word;
        overflow-wrap: anywhere;
    }
}

@media (min-width: 1200px) and (hover: hover) and (pointer: fine) {
    /* Part A — restore word-wrap on the free-text columns at desktop only.
       The 640px base sets these nowrap+ellipsis (needed by the tablet fixed
       budget); on non-touch desktop there is room to wrap instead of
       truncate. VENDX keeps its own column here, so the Vendor cell's __id
       sub-line stays hidden (base default). */
    .workspace-po-table .workspace-col-vendor-name,
    .workspace-po-table .workspace-col-vendor-name .vendor-stack__name,
    .workspace-po-table .workspace-col-descriptio {
        white-space: normal;
        overflow: visible;
        text-overflow: clip;
        overflow-wrap: break-word;
        word-break: normal;
    }
    /* Top-align body cells so a multi-line wrapped VENDOR_NAM/DESCRIPTIO sits
       flush with the single-line cells beside it. The table carries Bootstrap's
       `.align-middle` utility (vertical-align:middle !important), so this
       override needs !important — the documented Bootstrap-utility exception. */
    .workspace-po-table tbody td {
        vertical-align: top !important;
    }
    /* Let long headers wrap too, so a nowrap sort-link can't force the column
       wider than its content needs (prevents desktop horizontal overflow). */
    .workspace-po-table thead th .workspace-sort-link {
        white-space: normal;
        overflow-wrap: break-word;
    }

    /* Desktop: use viewport height so larger screens naturally show more PO rows when ORDQUANT is closed. */
    /* `- var(--portal-tab-bar-h)` reserves room for the in-portal tab bar.
       Each height rule is paired vh / dvh so older browsers fall back to
       the static viewport while modern ones use the dynamic one. */
    #workspace-root:not(.ordquant-open) .po-table.desktop-from-640 {
        max-height: clamp(24rem, calc(100vh - 20rem - var(--portal-tab-bar-h)), 44rem) !important;
        max-height: clamp(24rem, calc(100dvh - 20rem - var(--portal-tab-bar-h)), 44rem) !important;
    }

    #workspace-root:not(.ordquant-open) .po-panel {
        min-height: clamp(26rem, calc(100vh - 18rem - var(--portal-tab-bar-h)), 46rem) !important;
        min-height: clamp(26rem, calc(100dvh - 18rem - var(--portal-tab-bar-h)), 46rem) !important;
    }

    /* Desktop focus mode: when ORDQUANT is open, keep only a few PO rows visible. */
    #workspace-root.ordquant-open .po-table.desktop-from-640 {
        max-height: clamp(12rem, 22vh, 14rem) !important;
        max-height: clamp(12rem, 22dvh, 14rem) !important;
    }

    #workspace-root.ordquant-open .po-panel {
        min-height: clamp(12rem, 22vh, 14rem) !important;
        min-height: clamp(12rem, 22dvh, 14rem) !important;
    }

    #workspace-root.ordquant-open .ordquant-panel .desktop-from-640 {
        max-height: clamp(28rem, calc(100vh - 19rem - var(--portal-tab-bar-h)), 68vh) !important;
        max-height: clamp(28rem, calc(100dvh - 19rem - var(--portal-tab-bar-h)), 68dvh) !important;
    }

    #workspace-root.ordquant-open .ordquant-card {
        min-height: clamp(29rem, calc(100vh - 18rem - var(--portal-tab-bar-h)), 69vh) !important;
        min-height: clamp(29rem, calc(100dvh - 18rem - var(--portal-tab-bar-h)), 69dvh) !important;
    }
}

/* iPad/tablet refinement: fit all 11 PO Workspace columns inside the
   tablet viewport (768–1199px) without horizontal scroll. Also applies
   on touch devices at xl+ widths (e.g. iPad Pro 12.9" landscape) and on
   narrow-desktop laptops (1200–1599px, non-touch) where the 10-column
   table overflowed its content column at 1366–1440px with the sidebar
   expanded. The percentage column budget below is table-layout:fixed
   width:100%, so it scales to any width and never overflows; wide
   desktops (≥1600px) keep the roomy auto layout. */
@media (min-width: 768px) and (max-width: 1199.98px),
       (min-width: 1200px) and (hover: none) and (pointer: coarse),
       (min-width: 1200px) and (max-width: 1599.98px) and (hover: hover) and (pointer: fine) {
    .workspace-main {
        max-width: min(95vw, 1320px);
    }

    /* NOTE: the PO-table column layout (fixed width budget + tablet "Vendor"
       consolidation) is NOT here — it lives in the tablet-only query below so
       it does not apply to non-touch desktops ≥1200px, which keep the
       auto-layout split VENDX/VENDOR_NAM columns. This 3-clause query keeps
       only the page width, the ORDQUANT table layout, and the panel heights,
       all of which are correct on narrow desktops too. */

    /* ORDQUANT side-panel table (AJAX-loaded; 4 cols for Super, 6 for
       others). The 640px+ block pins it to min-width:44rem (704px), which
       overflowed the panel on iPad portrait (768px). Cancel that floor and
       use the same fixed width:100% layout so it scales to the panel width;
       DESCRIPTIO already wraps via the 640–1199 block above. */
    .workspace-ordquant-table {
        table-layout: fixed;
        width: 100%;
        min-width: 0;
    }
    .workspace-ordquant-table thead th,
    .workspace-ordquant-table tbody td {
        padding: 0.4rem 0.35rem;
        font-size: 0.78rem;
        /* Wrap (and break long tokens) instead of staying nowrap and
           spilling past the fixed column. !important overrides the
           Bootstrap `text-nowrap` utility the template puts on most cells
           — the documented utility-override exception. No ellipsis, so the
           QTY cell's "Proposed/Original Qty" lines stay fully readable. */
        white-space: normal !important;
        overflow-wrap: break-word;
    }
    .workspace-ordquant-table thead th .workspace-sort-link {
        white-space: normal;
    }

    #workspace-root .po-table.desktop-from-640,
    #workspace-root .ordquant-panel .desktop-from-640,
    #workspace-root .po-panel,
    #workspace-root .ordquant-card {
        transition: max-height 0.2s ease, min-height 0.2s ease;
    }

    /* When ORDQUANT is closed, let PO table use viewport height more naturally. */
    /* `- var(--portal-tab-bar-h)` accounts for the in-portal tab bar above.
       Paired vh / dvh so older browsers fall back to the static
       viewport while modern ones track the visible viewport. */
    #workspace-root:not(.ordquant-open) .po-table.desktop-from-640 {
        max-height: clamp(19rem, calc(100vh - 22.5rem - var(--portal-tab-bar-h)), 31rem) !important;
        max-height: clamp(19rem, calc(100dvh - 22.5rem - var(--portal-tab-bar-h)), 31rem) !important;
    }

    #workspace-root:not(.ordquant-open) .po-panel {
        min-height: clamp(20.5rem, calc(100vh - 20.5rem - var(--portal-tab-bar-h)), 33rem) !important;
        min-height: clamp(20.5rem, calc(100dvh - 20.5rem - var(--portal-tab-bar-h)), 33rem) !important;
    }

    /* iPad focus: when ORDQUANT is open, show fewer PO rows and prioritize ORDQUANT height. */
    /* The two panels stack vertically; together they used to claim 80vh,
       which was already a tight fit before the in-portal tab bar. Each
       formula now reserves a share of `--portal-tab-bar-h` proportional
       to its size (PO panel = 1/4, ordquant = 3/4) so the combined
       height fits the viewport with no page scroll. Paired vh / dvh. */
    #workspace-root.ordquant-open .po-table.desktop-from-640 {
        max-height: calc(24vh - var(--portal-tab-bar-h) / 4) !important;
        max-height: calc(24dvh - var(--portal-tab-bar-h) / 4) !important;
    }

    #workspace-root.ordquant-open .po-panel {
        min-height: calc(24vh - var(--portal-tab-bar-h) / 4) !important;
        min-height: calc(24dvh - var(--portal-tab-bar-h) / 4) !important;
    }

    #workspace-root.ordquant-open .ordquant-panel .desktop-from-640 {
        max-height: calc(56vh - var(--portal-tab-bar-h) * 3 / 4) !important;
        max-height: calc(56dvh - var(--portal-tab-bar-h) * 3 / 4) !important;
    }

    #workspace-root.ordquant-open .ordquant-card {
        min-height: calc(56vh - var(--portal-tab-bar-h) * 3 / 4) !important;
        min-height: calc(56dvh - var(--portal-tab-bar-h) * 3 / 4) !important;
    }
}

/* ── PO-table column layout: TABLET UX only ──────────────────────────────
   Applies when the viewport is < 1200px OR the device is a touch device at
   xl+ (iPad Pro 12.9" landscape) — the portal's standard hybrid tablet
   boundary (see CLAUDE.md "Responsive Convention"). Non-touch desktops
   ≥1200px deliberately fall through to the auto-layout split-column view.

   Two changes vs desktop:
     1. VENDX + VENDOR_NAM consolidate into one "Vendor" column — the
        standalone VENDX column is hidden and VENDX renders as a muted
        sub-line under the vendor name (`.vendor-stack__id`).
     2. table-layout:fixed with a class-based width budget (11 visible
        columns summing to 100%) so nothing overflows the viewport. */
@media (min-width: 768px) and (max-width: 1199.98px),
       (min-width: 1200px) and (hover: none) and (pointer: coarse) {
    /* Fixed layout so the budget below can't collectively overflow;
       min-width:0 cancels the 62rem floor from the 640px+ block. */
    .workspace-po-table {
        table-layout: fixed;
        width: 100%;
        min-width: 0;
    }

    .workspace-po-table thead th,
    .workspace-po-table tbody td {
        padding: 0.4rem 0.35rem;
        font-size: 0.85rem;
    }

    /* Top-align so a multi-line wrapped Vendor/Description cell doesn't leave
       the single-line cells floating in the middle (matches the desktop fix).
       Overrides Bootstrap's .align-middle utility — documented !important
       exception. */
    .workspace-po-table tbody td {
        vertical-align: top !important;
    }

    /* Clip header text at the cell edge under fixed layout. */
    .workspace-po-table thead th {
        overflow: hidden;
        text-overflow: ellipsis;
    }

    /* Consolidate VENDX into the Vendor column. */
    .workspace-po-table .workspace-col-vendx { display: none; }
    .workspace-po-table .vendor-stack__id { display: block; }

    /* Short header labels (Vendor / CODE / TYPE) for the narrow budget. */
    .workspace-po-table .th-label-full { display: none; }
    .workspace-po-table .th-label-short { display: inline; }

    /* Class-based width budget — 11 visible columns sum to 100% (VENDX
       hidden). The Vendor column gets the widest text allocation since it
       carries two stacked lines. */
    .workspace-po-table .workspace-col-pox { width: 10%; padding-left: 0.7rem; }
    .workspace-po-table .workspace-col-proj { width: 6%; }
    .workspace-po-table .workspace-col-lot { width: 6%; }
    .workspace-po-table .workspace-col-status { width: 10%; }
    .workspace-po-table .workspace-col-vendor-name {
        width: 15%;
        min-width: 0;
        max-width: none;
    }
    .workspace-po-table .workspace-col-costcode { width: 7%; }
    .workspace-po-table .workspace-col-descriptio {
        width: 12%;
        min-width: 0;
        max-width: none;
        white-space: normal;
        overflow: hidden;
    }
    /* iPad readability: wrap DESCRIPTIO up to 3 lines (ellipsis beyond),
       within the existing fixed-width budget. Clamp lives on the inner span
       because -webkit-line-clamp requires display:-webkit-box, which would
       break the <td>'s table-cell layout. */
    .workspace-po-table .workspace-col-descriptio .po-cell-clamp {
        display: -webkit-box;
        -webkit-box-orient: vertical;
        -webkit-line-clamp: 3;
        line-clamp: 3;
        overflow: hidden;
        text-overflow: ellipsis;
        white-space: normal;
        word-break: break-word;
    }
    .workspace-po-table .workspace-col-costtype {
        width: 6%;
        min-width: 0;
        max-width: none;
    }
    .workspace-po-table .workspace-col-order-date {
        width: 11%;
        min-width: 0;
        white-space: nowrap;
    }
    .workspace-po-table .workspace-col-delivery-date {
        width: 11%;
        min-width: 0;
        white-space: nowrap;
    }
    .workspace-po-table .workspace-col-percent {
        width: 6%;
        min-width: 0;
        white-space: nowrap;
        /* Match POX's 0.7rem left padding (above) so the table's outer edges
           are symmetric — the general 0.35rem cell padding otherwise leaves
           the last column tighter than the first. table-layout is fixed, so
           this eats content space, not column width (no budget impact). */
        padding-right: 0.7rem;
    }

    /* iPad readability: the vendor NAME wraps up to 3 lines (ellipsis beyond);
       the VENDX sub-line below it stays single-line. 3 name + 1 id ≈ a 4-line
       cell, bounding row height — name readability takes priority over compact
       rows. Clamp needs display:-webkit-box on the span. Selector matches the
       640px base rule's specificity (.workspace-col-vendor-name
       .vendor-stack__name) so this override wins it. */
    .workspace-po-table .workspace-col-vendor-name .vendor-stack__name {
        display: -webkit-box;
        -webkit-box-orient: vertical;
        -webkit-line-clamp: 3;
        line-clamp: 3;
        white-space: normal;
        overflow: hidden;
        text-overflow: ellipsis;
        word-break: break-word;
    }
    .workspace-po-table .vendor-stack__id {
        white-space: nowrap;
        overflow: hidden;
        text-overflow: ellipsis;
    }
}

/* ── PO-table column layout: NARROW DESKTOP (1200–1599px, non-touch) ──────
   These desktops keep the split VENDX/VENDOR_NAM columns (desktop UX) but the
   12-column auto layout overflows the content column once the sidebar is
   expanded (~1366–1440px). Use the same fixed-layout strategy as tablet, but
   with a 12-column budget that includes the standalone VENDX column. Wide
   desktops (≥1600px) keep the roomy auto layout. VENDOR_NAM/DESCRIPTIO still
   wrap here (Part A) — the rules in the ≥1200 desktop query above apply. */
@media (min-width: 1200px) and (max-width: 1599.98px) and (hover: hover) and (pointer: fine) {
    .workspace-po-table {
        table-layout: fixed;
        width: 100%;
        min-width: 0;
        /* Collapse so inter-cell border-spacing can't add to the fixed
           budget and push a few px past the container at ~1366px. */
        border-collapse: collapse;
    }
    .workspace-po-table thead th,
    .workspace-po-table tbody td {
        padding: 0.4rem 0.3rem;
        font-size: 0.85rem;
        /* Clip any sub-pixel horizontal spill from the fixed budget; wrapped
           cells still grow vertically, so no content is lost. */
        overflow: hidden;
    }

    /* 12 visible columns sum to 98% (VENDX shown) — 2% slack absorbs
       sub-pixel rounding across the column set. */
    .workspace-po-table .workspace-col-pox { width: 9%; padding-left: 0.5rem; }
    .workspace-po-table .workspace-col-proj { width: 6%; }
    .workspace-po-table .workspace-col-lot { width: 6%; }
    .workspace-po-table .workspace-col-status { width: 9%; }
    .workspace-po-table .workspace-col-vendx { width: 6%; min-width: 0; white-space: nowrap; }
    .workspace-po-table .workspace-col-vendor-name { width: 12%; min-width: 0; max-width: none; }
    .workspace-po-table .workspace-col-costcode { width: 7%; }
    .workspace-po-table .workspace-col-descriptio { width: 13%; min-width: 0; max-width: none; }
    .workspace-po-table .workspace-col-costtype { width: 5%; min-width: 0; max-width: none; }
    .workspace-po-table .workspace-col-order-date { width: 10%; min-width: 0; white-space: nowrap; }
    .workspace-po-table .workspace-col-delivery-date { width: 10%; min-width: 0; white-space: nowrap; }
    /* Match POX's 0.5rem left padding (above) for symmetric outer edges. */
    .workspace-po-table .workspace-col-percent { width: 5%; min-width: 0; white-space: nowrap; padding-right: 0.5rem; }
}

/* Selected row highlight */
.table .table-active > * {
    background-color: var(--selected-row-bg) !important;
    box-shadow: inset 4px 0 0 var(--selected-row-accent);
}

.theme-dark .table .table-active > * {
    background-color: var(--selected-row-bg) !important;
    box-shadow: inset 4px 0 0 var(--selected-row-accent);
    color: var(--text);
}

.theme-dark .table-striped > tbody > tr:nth-of-type(odd) > * {
    background-color: var(--table-striped);
    color: var(--text);
}

.theme-dark .table-striped > tbody > tr > * {
    color: var(--text);
    padding: 0.55rem 0.9rem;
}

.btn-outline-secondary {
    color: var(--text);
    border-color: var(--border);
}

.btn-outline-secondary:hover,
.btn-outline-primary:hover,
.btn-light:hover {
    background-color: var(--btn-hover-bg);
    border-color: var(--glass-border);
    color: var(--text);
}

.btn-outline-secondary:active,
.btn-outline-primary:active,
.btn-light:active {
    background-color: var(--btn-active-bg) !important;
    border-color: var(--glass-border) !important;
    color: var(--text) !important;
}

.dropdown-menu {
    background-color: var(--card-bg);
    color: var(--text);
    border: 1px solid var(--glass-border);
    backdrop-filter: blur(10px);
    -webkit-backdrop-filter: blur(10px);
}

.bg-light {
    background-color: var(--card-bg) !important;
}

.dropdown-item {
    color: var(--text);
}

.dropdown-item:active,
.dropdown-item:focus,
.dropdown-item:hover {
    color: var(--text);
}

.btn-light {
    background-color: var(--btn-light-bg);
    color: var(--text);
    border-color: var(--btn-light-border);
}

.add-parts-modal .modal-content {
    background: var(--add-parts-modal-bg);
    color: var(--text);
    border: 1px solid var(--glass-border);
    box-shadow: 0 14px 30px rgba(24, 44, 78, 0.16);
    backdrop-filter: blur(12px);
    -webkit-backdrop-filter: blur(12px);
}

.add-parts-modal .modal-header,
.add-parts-modal .modal-footer {
    border-color: var(--glass-border);
}

.add-parts-modal .btn-close {
    filter: none;
}

.theme-dark .add-parts-modal .btn-close {
    filter: invert(1) grayscale(1);
}

.part-lookup-wrap {
    position: relative;
}

.parts-lookup-menu {
    position: absolute;
    top: calc(100% + 0.2rem);
    left: 0;
    right: 0;
    max-height: 14rem;
    overflow-y: auto;
    z-index: 1200;
    border: 1px solid var(--parts-lookup-border);
    border-radius: 0.45rem;
    background: var(--parts-lookup-bg);
    box-shadow: 0 14px 26px rgba(24, 44, 78, 0.14);
    backdrop-filter: blur(12px);
    -webkit-backdrop-filter: blur(12px);
}

.parts-lookup-item {
    display: block;
    width: 100%;
    text-align: left;
    border: 0;
    background: transparent;
    color: var(--text);
    padding: 0.45rem 0.6rem;
    font-size: 0.84rem;
}

.parts-lookup-item:hover,
.parts-lookup-item:focus {
    background-color: var(--parts-lookup-hover);
    color: var(--text);
}

.add-parts-row {
    align-items: flex-start;
}

.part-duplicate-warning {
    min-height: 1rem;
    visibility: hidden;
    opacity: 0;
    transition: opacity 0.15s ease;
}

.part-duplicate-warning.is-visible {
    visibility: visible;
    opacity: 1;
}

.part-duplicate-warning-inline {
    min-height: 0;
    display: none;
}

.part-duplicate-warning-inline.is-visible {
    display: block;
}

.add-parts-qualify-control {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    min-height: calc(2.25rem + 2px);
    border: 1px solid var(--glass-border);
    border-radius: 0.45rem;
    background-color: var(--glass-surface);
    padding: 0.4rem 0.55rem;
    cursor: pointer;
    width: 100%;
}

.add-parts-qualify-input {
    width: 1.05rem;
    height: 1.05rem;
    margin: 0;
    flex-shrink: 0;
}

.add-parts-qualify-text {
    font-size: 0.86rem;
    color: var(--text);
    line-height: 1.2;
}

.add-parts-remove-col {
    align-items: flex-end;
    padding-top: 1.6rem;
}

.add-parts-remove-btn {
    white-space: nowrap;
    min-height: calc(2.25rem + 2px);
    font-size: 0.78rem;
    padding-left: 0.35rem;
    padding-right: 0.35rem;
}

.add-parts-add-row-wrap {
    margin-top: 0.25rem;
    margin-bottom: 0.6rem;
    text-align: left;
}

@media (max-width: 767.98px) {
    .add-parts-remove-col {
        padding-top: 0;
    }

    .add-parts-remove-btn {
        min-height: 2.1rem;
        font-size: 0.83rem;
    }
}

.existing-parts-panel {
    background-color: var(--glass-surface);
    border: 1px solid var(--glass-border);
    border-radius: 0.55rem;
    padding: 0.65rem;
}

.existing-parts-title {
    font-size: 0.85rem;
    font-weight: 600;
    color: var(--muted);
    margin-bottom: 0.4rem;
}

.theme-dark {
    --bg: #111217;
    --text: #e8e8ef;
    --card-bg: rgba(27, 30, 38, 0.72);
    --border: #2d2f38;
    --muted: #a0a3ad;
    --table-striped: rgba(255,255,255,0.04);
    --table-head-bg: #1f2028;
    --link: #75b6ff;
    --btn-light-bg: #23242c;
    --btn-light-border: #3a3c48;
    --glass-accent-rgb: 117, 182, 255;
    --glass-surface: rgba(var(--glass-accent-rgb), 0.11);
    --glass-surface-strong: rgba(var(--glass-accent-rgb), 0.16);
    --glass-border: rgba(var(--glass-accent-rgb), 0.26);
    --glass-highlight: rgba(var(--glass-accent-rgb), 0.2);
    --header-glass-bg: rgba(9, 12, 18, 0.76);
    --header-glass-border: rgba(var(--glass-accent-rgb), 0.24);
    --selected-row-bg: rgba(var(--glass-accent-rgb), 0.22);
    --selected-row-accent: rgba(137, 198, 255, 0.95);
    --btn-hover-bg: rgba(var(--glass-accent-rgb), 0.17);
    --btn-active-bg: rgba(var(--glass-accent-rgb), 0.24);
    --glass-shadow: 0 10px 30px rgba(0, 0, 0, 0.24);
    --brand-divider: rgba(108, 149, 205, 0.5);
    --section-header-bg: rgba(255, 255, 255, 0.09);
    --labor-review-accent: #84bff6;
    --lot-card-bg: rgba(30, 34, 44, 0.88);
    --lot-card-border: rgba(122, 171, 238, 0.26);
    --lot-card-shadow: 0 8px 24px rgba(0, 0, 0, 0.32);
    --lot-meta: #aebbd0;
    --parts-lookup-bg: linear-gradient(160deg, rgba(31, 48, 73, 0.9) 0%, rgba(29, 37, 56, 0.93) 52%, rgba(25, 44, 70, 0.91) 100%);
    --parts-lookup-border: rgba(140, 191, 255, 0.42);
    --parts-lookup-hover: rgba(var(--glass-accent-rgb), 0.26);
    --add-parts-modal-bg: linear-gradient(160deg, rgba(29, 44, 66, 0.9) 0%, rgba(25, 34, 50, 0.92) 52%, rgba(28, 47, 70, 0.9) 100%);
    --po-detail-reference-bg: rgba(30, 39, 56, 0.9);
    --po-detail-edit-bg: rgba(21, 28, 40, 0.92);
    --ordq-bulk-modified-bg: rgba(189, 144, 51, 0.28);
    --ordq-bulk-modified-accent: #fbbf24;
    --ordq-bulk-new-bg: rgba(78, 136, 93, 0.3);
    --ordq-bulk-new-accent: #6ee7b7;
}

.theme-dark body {
    background-color: var(--bg);
}

.theme-dark .table {
    color: var(--text);
}

.theme-dark .navbar {
    background-color: rgba(13, 16, 23, 0.96) !important;
    border-bottom: 2px solid var(--brand-divider);
    box-shadow: 0 1px 0 rgba(0, 0, 0, 0.28);
}

.theme-dark .navbar .navbar-brand,
.theme-dark .navbar .navbar-brand:hover {
    color: var(--text);
}

.theme-dark .navbar .btn-outline-light {
    color: var(--text);
    border-color: rgba(131, 164, 206, 0.42);
}

.theme-dark .navbar .btn-outline-light:hover,
.theme-dark .navbar .btn-outline-light:focus {
    color: var(--text);
    border-color: rgba(157, 194, 239, 0.55);
    background-color: rgba(117, 182, 255, 0.18);
}

.theme-dark .table > :not(caption) > * > * {
    color: var(--text);
    background-color: transparent;
}

.theme-dark .table thead th {
    background-color: var(--table-head-bg);
    color: var(--text);
}

.theme-dark .btn-outline-secondary {
    color: var(--text);
    border-color: var(--border);
}

.theme-dark .btn-light {
    background-color: var(--btn-light-bg);
    color: var(--text);
    border-color: var(--btn-light-border);
}

.theme-dark .dropdown-menu {
    background-color: var(--card-bg);
    color: var(--text);
    border: 1px solid var(--glass-border);
}

.theme-dark .dropdown-item,
.theme-dark .dropdown-item:active,
.theme-dark .dropdown-item:focus,
.theme-dark .dropdown-item:hover {
    color: var(--text);
    background-color: transparent;
}

.theme-dark .form-control,
.theme-dark .form-select,
.theme-dark textarea,
.theme-dark input {
    background-color: #1d1f27;
    color: var(--text);
    border-color: var(--border);
}

.theme-dark .form-control:focus,
.theme-dark .form-select:focus,
.theme-dark textarea:focus,
.theme-dark input:focus {
    background-color: #1f212a;
    color: var(--text);
    border-color: #4a90e2;
    box-shadow: none;
}

.theme-dark .alert-info,
.theme-dark .alert-success,
.theme-dark .alert-warning,
.theme-dark .alert-danger {
    color: #111;
}

.theme-dark .text-muted {
    color: var(--muted) !important;
}

.theme-dark a,
.theme-dark a:hover {
    color: var(--link);
}

.theme-dark .page-link {
    color: var(--link);
    background-color: var(--btn-light-bg);
    border-color: var(--border);
}

.theme-dark .page-link:hover,
.theme-dark .page-link:focus {
    color: var(--link);
    background-color: var(--btn-hover-bg);
    border-color: var(--border);
    box-shadow: none;
}

.theme-dark .page-item.disabled .page-link {
    color: var(--muted);
    background-color: var(--btn-light-bg);
    border-color: var(--border);
}

.dashboard-body {
    background-color: var(--bg);
}

.dashboard-search-card {
    border-color: var(--glass-border);
}

.dashboard-search-input {
    background-color: var(--card-bg);
    border-color: var(--glass-border);
    color: var(--text);
}

.dashboard-search-input::placeholder {
    color: var(--muted);
}

.dashboard-search-input:focus {
    background-color: var(--card-bg);
    color: var(--text);
    border-color: var(--glass-border);
    box-shadow: none;
}

.section-search-empty {
    border-color: var(--glass-border);
}

.recent-lots-list {
    display: flex;
    gap: 0.55rem;
    flex-wrap: nowrap;
    overflow-x: auto;
    padding-bottom: 0.1rem;
    scroll-snap-type: x proximity;
    -webkit-overflow-scrolling: touch;
}

.recent-lot-link {
    display: inline-flex;
    flex-direction: column;
    gap: 0.15rem;
    padding: 0.4rem 0.7rem;
    border-radius: 0.5rem;
    border: 1px solid var(--glass-border);
    background-color: var(--card-bg);
    color: var(--text);
    text-decoration: none;
    min-width: 10.5rem;
    max-width: 12.2rem;
    scroll-snap-align: start;
    flex: 0 0 auto;
}

.recent-lot-link:hover {
    color: var(--text);
    background-color: var(--glass-surface);
}

.recent-lot-title {
    font-weight: 600;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.recent-lot-meta {
    color: var(--muted);
    font-size: 0.73rem;
    line-height: 1.15;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.dashboard-refresh-icon {
    display: inline-block;
    transform-origin: 50% 50%;
}

.dashboard-refresh-icon.is-spinning {
    animation: dashboard-spin 360ms ease-out 1;
}

@keyframes dashboard-spin {
    from { transform: rotate(0deg); }
    to { transform: rotate(360deg); }
}

/* My Lots cards — Bootstrap card pattern. The card border encloses a header
   (lot #), body (project + metadata), and a footer (Plans/Notes actions). The
   header+body form one link into PO Workspace; the footer holds the secondary
   actions. Cards stretch to equal height per grid row so footers line up. */
.my-lot-card {
    height: 100%;
    border: 1px solid var(--lot-card-border);
    border-radius: 0.65rem;
    background-color: var(--lot-card-bg);
    box-shadow: var(--lot-card-shadow);
    color: var(--text);
    overflow: hidden; /* clip header/footer fills to the rounded corners */
}

.my-lot-card:hover {
    border-color: var(--glass-border);
}

/* Clickable region: header + body. Grows so the footer pins to the bottom. */
.my-lot-card-link {
    display: flex;
    flex-direction: column;
    flex: 1 1 auto;
    min-height: 0;
    color: var(--text);
    text-decoration: none;
}

.my-lot-card-link:hover,
.my-lot-card-link:focus {
    color: var(--text);
}

/* Dark theme has a global `.theme-dark a { color: var(--link) }` rule that
   would otherwise tint the lot title/project link blue (it outranks the single
   class above). Match it with two classes so the lot link uses --text in BOTH
   themes — keeping the title subordinate, not an accent-colored link. */
.theme-dark .my-lot-card-link,
.theme-dark .my-lot-card-link:hover,
.theme-dark .my-lot-card-link:focus {
    color: var(--text);
}

/* Header/footer use theme tokens, not Bootstrap's --bs-card-cap-bg tint, so
   they don't fight the card body or the buttons in either theme. */
.my-lot-header {
    padding: 0.5rem 0.85rem;
    background-color: transparent;
    border-bottom: 1px solid var(--lot-card-border);
}

/* The dashboard section rule `.card[data-section] .card-header` (0,3,0) paints
   the SECTION header with a --glass-surface tint, but it also matches these
   nested lot card headers (they carry .card-header too) — which both tinted
   them blue and made them mimic the section header band. Override at equal
   specificity (later source wins) so the lot header stays flush/transparent,
   visually subordinate to the tinted section header. */
.card[data-section] .my-lot-header {
    background-color: transparent;
    border-bottom: 1px solid var(--lot-card-border);
}

.my-lot-body {
    padding: 0.55rem 0.85rem;
    flex: 1 1 auto;
}

/* Lot title is the card-level (child) heading: subordinate to the "My Lots"
   section header (h5, 1.25rem) and the "Project …" group header above it.
   Lighter weight + smaller size keep the section header visually dominant while
   the lot number stays the legible primary identifier of the card. */
.my-lot-title {
    font-size: 0.9rem;
    font-weight: 600;
    line-height: 1.25;
}

.my-lot-project {
    font-size: 0.86rem;
}

.my-lot-meta {
    margin-top: 0.22rem;
    font-size: 0.76rem;
    color: var(--lot-meta);
    display: flex;
    align-items: center;
    gap: 0.28rem;
    flex-wrap: wrap;
}

/* Footer holds the per-lot secondary actions (Plans link + Billing Notes). */
.my-lot-footer {
    padding: 0.45rem 0.6rem;
    background-color: transparent;
    border-top: 1px solid var(--lot-card-border);
    display: flex;
    gap: 0.4rem;
}

.my-lot-action {
    display: inline-flex;
    align-items: center;
    gap: 0.3rem;
    flex: 1 1 0;
    justify-content: center;
    position: relative;
    min-height: 2rem;
}

.my-lot-action .bi {
    font-size: 0.95rem;
}

/* Disabled Plans affordance: muted, non-interactive (no PDF on file). Still
   rendered so it occupies footer space and card heights stay uniform. */
.my-lot-action.disabled {
    opacity: 0.5;
    pointer-events: none;
    cursor: default;
}

/* Notes-present indicator dot */
.my-lot-notes-dot {
    width: 0.42rem;
    height: 0.42rem;
    border-radius: 50%;
    background-color: var(--bs-primary, #0d6efd);
    flex: 0 0 auto;
}

.my-lot-action.has-notes {
    border-color: var(--bs-primary, #0d6efd);
}

/* Read-only billing notes display preserves line breaks and theme text color */
.billing-notes-readonly-text {
    white-space: pre-wrap;
    word-break: break-word;
    font-family: inherit;
    font-size: 0.92rem;
    color: var(--text);
    margin: 0;
}

/* Mobile: match the PO Workspace mobile card action buttons (.po-mobile-action)
   — compact, low-profile outline buttons (~28px tall) instead of the previous
   44px floor, so Plans/Notes don't dominate the card footer. Values mirror
   .po-mobile-action; keep them in sync. The buttons stay full-width (flex:1 1 0,
   ~half a now-single-column card) so they remain comfortably tappable. */
@media (max-width: 767.98px) {
    .my-lot-action {
        padding: 0.2rem 0.7rem;
        font-size: 0.875rem;
        line-height: 1.4;
        min-height: 0;
    }
}

.my-lot-group-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 0.5rem;
    cursor: pointer;
    padding: 0.15rem 0;
}

.my-lot-group-indicator {
    font-size: 0.84rem;
    color: var(--muted);
}

.my-lot-group + .my-lot-group {
    border-top: 1px solid var(--glass-border);
    padding-top: 0.45rem;
    margin-top: 0.35rem;
}

.needs-attention-list {
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
}

.needs-attention-group {
    border: 1px solid var(--glass-border);
    border-radius: 0.6rem;
    background: var(--card-bg);
    overflow: hidden;
}

.needs-attention-group-header {
    display: flex;
    justify-content: space-between;
    align-items: center;
    gap: 0.5rem;
    padding: 0.55rem 0.8rem;
    cursor: pointer;
    color: var(--text);
}

.needs-attention-group-header:hover {
    background-color: var(--glass-surface);
}

.needs-attention-group-title {
    font-weight: 600;
}

.needs-attention-indicator {
    font-size: 0.84rem;
    color: var(--muted);
    width: 0.9rem;
    display: inline-block;
    text-align: center;
}

.needs-attention-group-body {
    padding: 0.5rem 0.8rem 0.7rem;
    border-top: 1px solid var(--glass-border);
}

.needs-attention-mode-toggle {
    display: inline-flex;
    gap: 0.25rem;
    flex-wrap: wrap;
}

.needs-attention-mode-toggle .btn.is-active {
    background-color: var(--btn-active-bg);
    border-color: var(--glass-border);
    color: var(--text);
}

.needs-attention-rows {
    display: flex;
    flex-direction: column;
    gap: 0.4rem;
}

.needs-attention-row {
    display: flex;
    justify-content: space-between;
    align-items: flex-start;
    gap: 0.75rem;
    padding: 0.45rem 0.5rem;
    border-radius: 0.45rem;
    border: 1px solid var(--glass-border);
    background: var(--glass-surface);
}

.needs-attention-group[data-mode="next"] .needs-attention-row.is-extra {
    display: none;
}

.needs-attention-row-main {
    min-width: 0;
    display: flex;
    flex-direction: column;
    gap: 0.15rem;
}

.needs-attention-row-task {
    font-weight: 600;
    font-size: 0.9rem;
}

.needs-attention-row-cost {
    font-size: 0.85rem;
    color: var(--text);
    display: flex;
    flex-wrap: wrap;
    gap: 0.3rem;
    align-items: center;
}

.needs-attention-row-costcode {
    font-weight: 500;
}

.needs-attention-row-sep {
    color: var(--muted);
}

.needs-attention-row-descript {
    color: var(--text);
}

.needs-attention-row-costtype {
    font-size: 0.7rem;
    font-weight: 600;
    letter-spacing: 0.04em;
    padding: 0.05rem 0.4rem;
    border-radius: 999px;
    background: var(--glass-surface-strong);
    color: var(--text);
}

.needs-attention-row-missing {
    font-size: 0.78rem;
    color: var(--muted);
}

.needs-attention-row-side {
    display: flex;
    flex-direction: column;
    align-items: flex-end;
    gap: 0.25rem;
    white-space: nowrap;
}

.needs-attention-progress {
    font-size: 0.76rem;
    font-weight: 600;
    padding: 0.1rem 0.5rem;
    border-radius: 999px;
    border: 1px solid transparent;
}

.needs-attention-progress.is-near {
    color: #8a5a00;
    background-color: rgba(255, 193, 7, 0.18);
    border-color: rgba(255, 193, 7, 0.4);
}

.needs-attention-progress.is-far {
    color: #a32023;
    background-color: rgba(220, 53, 69, 0.14);
    border-color: rgba(220, 53, 69, 0.36);
}

.theme-dark .needs-attention-progress.is-near {
    color: #ffd97a;
    background-color: rgba(255, 193, 7, 0.18);
    border-color: rgba(255, 193, 7, 0.36);
}

.theme-dark .needs-attention-progress.is-far {
    color: #ffadb1;
    background-color: rgba(220, 53, 69, 0.22);
    border-color: rgba(220, 53, 69, 0.42);
}

.needs-attention-row-link {
    padding: 0;
    font-size: 0.82rem;
}

@media (max-width: 575.98px) {
    .needs-attention-row {
        flex-direction: column;
    }
    .needs-attention-row-side {
        flex-direction: row;
        align-items: center;
        align-self: stretch;
        justify-content: space-between;
        white-space: normal;
    }
}

.labor-review-table {
    border-collapse: separate;
    border-spacing: 0 0.5rem;
}

.labor-review-list {
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
}

/* Group: Project / Lot header + its rows. Rows are grouped by (PROJ, LOT_BLK);
   the header is architecturally distinct from a data row (uppercase, bold, on a
   subtle tinted surface) so the queue scans by work location first. */
.labor-review-group {
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
}

/* Generous separation between groups so each reads as a distinct section. The
   parent .labor-review-list adds a 0.5rem flex gap on top of this, landing the
   total inter-group space at ~28px. The first group has no extra top margin —
   it sits flush below the sort bar. */
.labor-review-group + .labor-review-group {
    margin-top: 1.25rem;
}

.labor-review-group-header {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 0.75rem;
    padding: 0.45rem 0.65rem;
    background: var(--section-header-bg);
    /* Left accent bar: a small vertical blue stripe anchoring the start of the
       header band. Distinct from the horizontal top-nav (orange) border — it's
       short, vertical, and inside the content area, not at the chrome edge. */
    border-left: 4px solid var(--labor-review-accent);
    border-radius: 0.4rem;
}

.labor-review-group-title {
    font-size: 0.92rem;
    font-weight: 700;
    letter-spacing: 0.02em;
    text-transform: uppercase;
    color: var(--text);
}

.labor-review-group-sep {
    color: var(--muted);
    font-weight: 600;
    margin: 0 0.12rem;
}

.labor-review-group-count {
    font-size: 0.74rem;
    font-weight: 600;
    color: var(--muted);
    white-space: nowrap;
    flex-shrink: 0;
}

.labor-review-group-items {
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
    /* Indent rows beneath the header so the header visually "parents" its
       children. Header sits at full content width; rows sit slightly inset. */
    padding-left: 1.25rem;
}

/* Costcode + Description is the dominant "what is this row about" identifier
   (matches the PO Workspace mobile card subheader prominence). POX / vendor
   drop to supporting body text in .labor-review-row-meta-secondary. */
.labor-review-row-costcode {
    font-size: 1.0625rem;
    font-weight: 700;
    line-height: 1.25;
    color: var(--text);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

/* Mobile: tighter horizontal real estate — shrink the row indent and group
   spacing. The accent bar (4px) and tinted band carry the hierarchy here. */
@media (max-width: 767.98px) {
    .labor-review-group-items {
        padding-left: 0.6rem;
    }

    .labor-review-group + .labor-review-group {
        margin-top: 1rem;
    }
}

.labor-review-list-item {
    border: 1px solid var(--glass-border);
    border-radius: 0.6rem;
    background: var(--card-bg);
    overflow: hidden;
    transition: background-color 180ms ease, border-color 180ms ease, opacity 220ms ease, transform 220ms ease, max-height 220ms ease, margin 220ms ease, padding 220ms ease;
    max-height: 900px;
}

.labor-review-summary {
    cursor: pointer;
    display: flex;
    justify-content: space-between;
    align-items: center;
    gap: 0.75rem;
    padding: 0.65rem 0.8rem;
}

.labor-review-summary-main {
    min-width: 0;
    display: flex;
    flex-direction: column;
    gap: 0.14rem;
}

.labor-review-row-title {
    font-weight: 700;
    line-height: 1.2;
}

.labor-review-row-meta {
    font-size: 0.86rem;
    color: var(--text);
    line-height: 1.2;
}

.labor-review-row-meta-secondary {
    font-size: 0.8rem;
    color: var(--muted);
    line-height: 1.2;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.labor-review-summary-side {
    display: inline-flex;
    align-items: center;
    gap: 0.45rem;
    flex-shrink: 0;
}

.labor-review-row-ident {
    font-size: 0.78rem;
    color: var(--muted);
    font-weight: 600;
    letter-spacing: 0.02em;
    text-transform: uppercase;
}

.labor-review-row-percent-pill {
    font-weight: 700;
    font-size: 0.9rem;
    line-height: 1;
    padding: 0.22rem 0.45rem;
    border-radius: 999px;
    background: color-mix(in srgb, #f59e0b 18%, var(--card-bg));
    color: var(--text);
}

.labor-review-row-status {
    font-size: 0.72rem;
    line-height: 1;
    font-weight: 600;
    color: var(--muted);
    padding: 0.18rem 0.38rem;
    border: 1px solid var(--glass-border);
    border-radius: 999px;
    background: color-mix(in srgb, var(--card-bg), var(--brand-soft) 7%);
}

.labor-review-table .labor-review-detail-row.collapse:not(.show) {
    display: none;
}

.labor-review-table .labor-review-detail-row.collapse.show {
    display: table-row;
}

.labor-review-summary:hover {
    background: color-mix(in srgb, var(--card-bg), var(--brand-soft) 14%);
}

.labor-review-chevron-col {
    width: 2rem;
}

.labor-review-chevron {
    font-size: 0.82rem;
    color: var(--muted);
}

.labor-review-panel {
    padding: 1rem;
    border: 1px solid var(--glass-border);
    border-radius: 0.6rem;
    background: #fcfcfd;
}

/* Setup-required panel — shown on inline expand for 0INCOMPLETE / empty labor
   POs (an AM acting as Super still needs to populate them). The inline approval
   form is only for 1REVIEW rows; these route out to full PO Detail instead. */
.labor-review-setup {
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
}

.labor-review-setup-title {
    font-weight: 700;
    font-size: 0.95rem;
    color: var(--text);
}

.labor-review-setup-text {
    font-size: 0.86rem;
    color: var(--muted);
    line-height: 1.4;
}

.labor-review-list-item.is-approved {
    border-color: #4ade80;
    background: #ecfdf3;
}

.labor-review-list-item.is-approved .labor-review-summary {
    background: #ecfdf3;
}

.labor-review-list-item.is-fading-out {
    opacity: 0;
    transform: translateY(-4px);
    max-height: 0;
    margin-top: 0;
    margin-bottom: 0;
}

.labor-review-block {
    background: #ffffff;
    border: 1px solid var(--glass-border);
    border-radius: 0.5rem;
    padding: 0.75rem;
    height: 100%;
    display: flex;
    flex-direction: column;
}

.labor-review-input {
    min-height: 3rem;
    font-size: 1.05rem;
    line-height: 1.25;
    padding: 0.65rem 0.1rem 0.45rem;
    background: transparent;
    border: 0;
    border-bottom: 3px solid #f59e0b;
    border-radius: 0;
    box-shadow: none;
    font-weight: 700;
    color: var(--text);
}

.labor-review-static-value {
    min-height: 3rem;
    display: flex;
    align-items: flex-end;
    padding: 0.65rem 0.1rem 0.45rem;
    font-size: 1.05rem;
    line-height: 1.25;
    font-weight: 700;
    color: var(--text);
}

.labor-review-input:focus {
    background: transparent;
    border-color: #ea580c;
    box-shadow: none;
    color: var(--text);
}

.labor-review-confirm-btn {
    min-height: 3rem;
    font-size: 1rem;
    padding-inline: 1rem;
}

.labor-review-compare {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 0;
    height: 100%;
}

.labor-review-block-left {
    border-top-right-radius: 0;
    border-bottom-right-radius: 0;
}

.labor-review-block-right {
    border-left: 0;
    border-top-left-radius: 0;
    border-bottom-left-radius: 0;
}

.labor-review-action-btn {
    min-height: 1.95rem;
    font-size: 0.8rem;
    padding: 0.2rem 0.6rem;
}

.labor-review-chevron {
    font-size: 1rem;
    line-height: 1;
}

.labor-review-item-subheader {
    margin-bottom: 0.2rem;
}

.labor-review-item-costcode {
    font-size: 0.96rem;
    font-weight: 700;
    line-height: 1.2;
}

.labor-review-item-description {
    font-size: 0.83rem;
    color: var(--muted);
    line-height: 1.25;
    margin-top: 0.1rem;
}

.labor-review-actions-row {
    margin-top: -0.2rem;
}

.labor-review-static-value {
    min-height: 3.2rem;
    display: flex;
    align-items: flex-end;
    padding: 0.65rem 0.1rem 0.45rem;
    box-sizing: border-box;
}

.labor-review-percent-display {
    display: inline-flex;
    align-items: baseline;
    gap: 0.15rem;
}

.labor-review-percent-number {
    font-size: 1.75rem;
    font-weight: 700;
    line-height: 1;
    letter-spacing: -0.01em;
}

.labor-review-percent-sign {
    font-size: 1rem;
    line-height: 1;
    font-weight: 600;
    color: var(--muted);
}

.labor-review-percent-empty {
    font-size: 1.35rem;
    line-height: 1;
    color: var(--muted);
}

.labor-review-percent-field {
    display: inline-flex;
    align-items: flex-end;
    width: fit-content;
    gap: 0.15rem;
    min-height: 3.2rem;
    padding: 0.65rem 0.1rem 0.45rem;
    box-sizing: border-box;
}

.labor-review-percent-field:focus-within {
    border-bottom-color: transparent;
}

.labor-review-percent-field .labor-review-input {
    min-height: 0;
    padding: 0 0 0.12rem;
    border-bottom: 0;
    font-size: 1.75rem;
    line-height: 1;
    width: 6ch;
    box-shadow: inset 0 -3px 0 var(--labor-review-accent);
    color: var(--text);
}

.labor-review-percent-field:focus-within .labor-review-input {
    box-shadow: inset 0 -3px 0 var(--labor-review-accent);
}

.theme-dark .labor-review-summary td {
    background: #171a21;
}

.theme-dark .labor-review-summary:hover td {
    background: #212735;
}

.theme-dark .labor-review-list-item {
    background: #171a21;
    border-color: #2a3040;
}

.theme-dark .labor-review-summary:hover {
    background: #212735;
}

.theme-dark .labor-review-list-item.is-approved {
    border-color: #22c55e;
    background: #10251b;
}

.theme-dark .labor-review-list-item.is-approved .labor-review-summary {
    background: #10251b;
}

.theme-dark .labor-review-row-percent-pill {
    background: color-mix(in srgb, #f59e0b 28%, #171a21);
}

.theme-dark .labor-review-row-status {
    border-color: #2a3040;
    background: #1c2230;
    color: #a7b0c2;
}

.theme-dark .labor-review-panel {
    background: #1a1b22;
}

.theme-dark .labor-review-block {
    background: #11151d;
    border-color: #2a3040;
}

.theme-dark .labor-review-input {
    background: transparent;
    border-bottom-color: #f59e0b;
    color: var(--text);
}

.theme-dark .labor-review-input:focus {
    background: transparent;
    border-bottom-color: #fb923c;
    color: var(--text);
}

.theme-dark .labor-review-percent-sign,
.theme-dark .labor-review-percent-empty {
    color: #a7b0c2;
}

.theme-dark .labor-review-item-description {
    color: #a7b0c2;
}

.theme-dark .labor-review-group-count,
.theme-dark .labor-review-group-sep {
    color: #a7b0c2;
}

.theme-dark .labor-review-percent-field .labor-review-input {
    box-shadow: inset 0 -3px 0 var(--labor-review-accent);
}

.theme-dark .labor-review-percent-field:focus-within .labor-review-input {
    box-shadow: inset 0 -3px 0 var(--labor-review-accent);
}

@media (max-width: 991.98px) {
    .labor-review-compare {
        grid-template-columns: 1fr;
        gap: 0.5rem;
    }

    .labor-review-block-left,
    .labor-review-block-right {
        border-radius: 0.5rem;
        border-left: 1px solid var(--glass-border);
    }
}

.ordquant-card {
    border-top: 2px solid var(--brand-divider);
}

.workspace-sheet {
    height: 78vh !important;
    background-color: var(--card-bg);
    color: var(--text);
    border-top: 1px solid var(--glass-border);
}

.po-detail-sheet {
    background-color: #ffffff;
}

.ordquant-detail-sheet {
    background-color: #ffffff;
}

.theme-dark .po-detail-sheet {
    background-color: #1c1d24;
}

.theme-dark .ordquant-detail-sheet {
    background-color: #1c1d24;
}

.po-sheet-body {
    position: relative;
    display: grid;
    grid-template-rows: minmax(0, 1fr) auto;
    min-height: 0;
    overflow: hidden;
    padding: 0;
    height: 100%;
}

.po-sheet-scroll {
    min-height: 0;
    overflow: auto;
    padding: 0 1rem 0.75rem;
}

#po-mobile-sheet-content {
    padding: 0 0.85rem 0.6rem;
}

.po-sheet-actions {
    position: relative;
    z-index: 2;
    padding: 0.65rem 1rem calc(0.65rem + env(safe-area-inset-bottom));
    border-top: 1px solid var(--glass-border);
    background-color: var(--card-bg);
    display: flex;
    gap: 0.5rem;
}

.po-sheet-actions .btn {
    flex: 1 1 0;
}

.ordq-sheet-actions .btn {
    flex: 1 1 0;
}

.theme-dark .workspace-sheet .btn-close {
    filter: invert(1) grayscale(1);
}

.mobile-kv-list {
    display: flex;
    flex-direction: column;
    gap: 0.45rem;
}

.mobile-kv-row {
    display: grid;
    grid-template-columns: 7.5rem 1fr;
    gap: 0.5rem;
    align-items: start;
    padding-bottom: 0.3rem;
    border-bottom: 1px solid var(--border);
}

.mobile-kv-row-emphasis {
    padding: 0.42rem 0 0.5rem;
    border-bottom-color: var(--glass-border);
}

.mobile-kv-row-emphasis .mobile-kv-label {
    color: var(--link);
    font-size: 0.76rem;
    font-weight: 600;
}

.mobile-kv-row-emphasis .mobile-kv-value {
    font-size: 0.97rem;
    font-weight: 600;
}

.mobile-kv-label {
    font-size: 0.74rem;
    color: var(--muted);
    text-transform: uppercase;
    letter-spacing: 0.02em;
}

.mobile-kv-value {
    font-size: 0.88rem;
    word-break: break-word;
}

.po-mobile-card,
.ordq-mobile-card {
    border: 1px solid var(--glass-border);
    border-radius: 0.7rem;
    background-color: var(--card-bg);
    color: var(--text);
    box-shadow: var(--glass-shadow);
    padding: 0.65rem 0.75rem;
    margin-bottom: 0.55rem;
    touch-action: manipulation;
}

.po-mobile-card {
    display: flex;
    align-items: stretch;
    padding: 0;
    overflow: hidden;
}

.po-mobile-body {
    flex: 1;
    padding: 0.65rem 0.75rem;
}

.po-mobile-more {
    width: 2.8rem;
    border: 0;
    border-left: 1px solid var(--glass-border);
    background-color: var(--glass-surface);
    color: var(--text);
    font-size: 1.25rem;
    line-height: 1;
    display: flex;
    align-items: center;
    justify-content: center;
}

.po-mobile-more:focus,
.po-mobile-more:hover {
    background-color: var(--glass-surface-strong);
}

.po-mobile-card.active {
    border-color: var(--selected-row-accent);
    box-shadow: inset 3px 0 0 var(--selected-row-accent), var(--glass-shadow);
    background-color: var(--selected-row-bg);
}

.po-mobile-card.active .po-mobile-more {
    border-left-color: var(--selected-row-accent);
}

.po-mobile-title,
.ordq-mobile-title {
    font-weight: 700;
    font-size: 1rem;
}

.po-mobile-sub,
.ordq-mobile-sub {
    color: var(--muted);
    font-size: 0.84rem;
    margin-top: 0.15rem;
}

.ordq-qty-secondary {
    color: var(--muted);
    font-size: 0.76rem;
}

.ordq-qty-primary {
    font-weight: 400;
    font-size: inherit;
    color: var(--text);
}

.ordq-qty-stack {
    display: inline-flex;
    flex-direction: column;
    gap: 0.1rem;
}

.ordq-varqty-form {
    min-width: 12.5rem;
}

.ordq-varqty-input-group {
    display: flex;
    gap: 0.35rem;
    align-items: center;
}

.ordq-varqty-input-group .form-control {
    min-width: 5.25rem;
}

.ordq-varqty-form-mobile {
    min-width: 0;
}

.ordq-mobile-edit-shell {
    display: flex;
    flex-direction: column;
    gap: 0.75rem;
}

.ordq-mobile-edit-info-card {
    border: 1px solid var(--glass-border);
    border-radius: 0.55rem;
    background-color: var(--glass-surface);
    padding: 0.62rem 0.68rem;
}

.ordq-mobile-edit-desc {
    color: var(--text);
    font-size: 0.88rem;
    line-height: 1.35;
    font-weight: 500;
}

.ordq-mobile-edit-unit-row {
    margin-top: 0.42rem;
    display: flex;
    align-items: baseline;
    gap: 0.4rem;
}

.ordq-mobile-edit-unit-label {
    font-size: 0.72rem;
    text-transform: uppercase;
    letter-spacing: 0.02em;
    color: var(--muted);
}

.ordq-mobile-edit-unit-value {
    font-size: 0.9rem;
    color: var(--text);
    font-weight: 600;
}

.ordq-mobile-qty-panels {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 0.55rem;
}

.ordq-mobile-qty-panel {
    border: 1px solid var(--glass-border);
    border-radius: 0.55rem;
    background-color: var(--glass-surface);
    padding: 0.58rem 0.62rem 0.62rem;
    min-width: 0;
}

.ordq-mobile-qty-label {
    display: block;
    font-size: 0.72rem;
    color: var(--muted);
    text-transform: uppercase;
    letter-spacing: 0.02em;
    margin-bottom: 0.3rem;
}

.ordq-mobile-qty-value {
    min-height: 1.9rem;
    display: flex;
    align-items: center;
    font-size: 1.24rem;
    line-height: 1.1;
    font-weight: 700;
    color: var(--text);
}

.ordq-mobile-proposed-input {
    font-size: 16px;
    line-height: 1.25;
    min-height: 2.35rem;
    border-color: var(--glass-border);
    background-color: var(--card-bg);
}

.ordq-mobile-delta-text {
    font-size: 0.79rem;
    color: var(--muted);
    padding: 0 0.2rem;
    display: inline-flex;
    align-items: center;
    gap: 0.35rem;
}

.ordq-mobile-delta-icon {
    font-size: 0.88rem;
    font-weight: 700;
    line-height: 1;
}

.ordq-mobile-delta-text.is-increase {
    color: #198754;
}

.ordq-mobile-delta-text.is-decrease {
    color: #dc3545;
}

.theme-dark .ordq-mobile-delta-text.is-increase {
    color: #58d39b;
}

.theme-dark .ordq-mobile-delta-text.is-decrease {
    color: #ff7f8a;
}

.ordq-mobile-delta-text.is-empty {
    display: none;
}

.ordq-mobile-qualify {
    display: flex;
    align-items: center;
    gap: 0.55rem;
    font-size: 1rem;
    font-weight: 600;
    color: var(--text);
    padding: 0.1rem 0.15rem;
}

.ordq-mobile-qualify .form-check-input {
    width: 1.22rem;
    height: 1.22rem;
    margin-top: 0;
    border-color: var(--glass-border);
}

.po-mobile-meta,
.ordq-mobile-meta {
    display: flex;
    gap: 0.75rem;
    flex-wrap: wrap;
    color: var(--muted);
    font-size: 0.78rem;
}

.po-mobile-meta-list {
    display: flex;
    flex-direction: column;
    gap: 0.25rem;
    align-items: flex-start;
}

.po-mobile-line {
    width: 100%;
    line-height: 1.25;
}

.po-mobile-line-dates {
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

/* Mobile card field labels (Vendor / Ord Date / Del Date / Percent): reserve a
   consistent width so values start at the same x-position down the card, giving
   a clean scanning rhythm and making mixed populated/empty states obvious at a
   glance. min-width is tuned to the widest label ("Ord Date:") plus breathing
   space. Date-grid labels align within their own grid column (Del Date within
   the right half) — internal per-column alignment, not to the leftmost labels. */
.po-card-field-label {
    display: inline-block;
    min-width: 5em;
}

.po-mobile-actions {
    display: flex;
    gap: 0.8rem;
}

.po-status-chip {
    border: 1px solid var(--glass-border);
    border-radius: 999px;
    padding: 0.1rem 0.48rem;
    font-size: 0.72rem;
    font-weight: 600;
    background-color: var(--glass-surface);
    white-space: nowrap;
}

/* Cost Type — plain muted text (no pill: no border / background) so the Status
   badge is the dominant element in the top-right and Cost Type reads as a
   subtle annotation. Single neutral treatment for all L/M/E values. */
.po-costtype-chip {
    font-size: 0.68rem;
    font-weight: 500;
    color: var(--muted);
    white-space: nowrap;
}

/* Mobile card date row: two equal columns (replaces the old pipe-separated
   single line). The Percent row stays on its own line below this grid. */
.po-mobile-date-grid {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 0.25rem 0.75rem;
}

/* Card action buttons: compact, low-profile outline buttons (~28px tall) so
   they don't dominate the card bottom. Tighter vertical padding than btn-sm;
   the visible border keeps the button affordance, and the generous horizontal
   padding preserves a comfortable tap width. */
.po-mobile-action {
    padding: 0.2rem 0.7rem;
    font-size: 0.875rem;
    line-height: 1.4;
    min-height: 0;
    display: inline-flex;
    align-items: center;
    justify-content: center;
}

.po-detail-status-badge {
    border-radius: 999px;
    font-size: 0.74rem;
    font-weight: 600;
    letter-spacing: 0.01em;
    padding: 0.22rem 0.55rem;
}

.po-detail-reference-card {
    position: sticky;
    top: 0.75rem;
    background-color: var(--po-detail-reference-bg);
}

.po-detail-edit-card {
    background-color: var(--po-detail-edit-bg);
}

.po-desktop-edit-field {
    background-color: var(--glass-surface);
    border-color: var(--glass-border);
}

.po-desktop-edit-field:focus {
    background-color: var(--glass-surface-strong);
    border-color: var(--glass-border);
    box-shadow: 0 0 0 0.18rem rgba(var(--glass-accent-rgb), 0.16);
}

.vendor-lookup-wrap {
    position: relative;
}

.vendor-lookup-menu {
    position: absolute;
    top: calc(100% + 0.2rem);
    left: 0;
    right: 0;
    max-height: 13.5rem;
    overflow-y: auto;
    z-index: 1200;
    border: 1px solid var(--parts-lookup-border);
    border-radius: 0.45rem;
    background: var(--parts-lookup-bg);
    box-shadow: 0 14px 26px rgba(24, 44, 78, 0.14);
    backdrop-filter: blur(12px);
    -webkit-backdrop-filter: blur(12px);
}

.vendor-lookup-item {
    display: block;
    width: 100%;
    text-align: left;
    border: 0;
    background: transparent;
    color: var(--text);
    padding: 0.44rem 0.58rem;
    font-size: 0.84rem;
}

.vendor-lookup-item:hover,
.vendor-lookup-item:focus {
    background-color: var(--parts-lookup-hover);
    color: var(--text);
}

.po-detail-cost-anchor {
    border: 0;
    border-radius: 0.55rem;
    background: transparent;
    padding: 0.1rem 0;
}

.po-detail-block {
    padding: 0.1rem 0;
}

.po-detail-pox-label {
    color: var(--muted);
    font-size: 0.74rem;
    text-transform: uppercase;
    letter-spacing: 0.035em;
}

.po-detail-pox-value {
    color: var(--text);
    font-size: 1rem;
    font-weight: 600;
    line-height: 1.2;
}

.po-detail-group-divider {
    border-top: 1px solid var(--glass-border);
    margin: 0.55rem 0;
}

.po-detail-cost-label {
    color: var(--muted);
    font-size: 0.72rem;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    margin-bottom: 0.1rem;
}

.po-detail-cost-code {
    font-size: 1.16rem;
    font-weight: 700;
    color: var(--text);
    line-height: 1.2;
}

.po-detail-cost-desc {
    color: var(--text);
    font-size: 0.85rem;
    line-height: 1.25;
    margin-top: 0.2rem;
    opacity: 0.88;
}

.po-detail-readonly {
    border: 1px solid var(--glass-border);
    border-radius: 0.45rem;
    background-color: var(--glass-surface);
    padding: 0.45rem 0.6rem;
    min-height: calc(1.5em + 0.75rem + 2px);
}

/* Part 2 — edit-mode inputs mirror the read-only display treatment so the
   view <-> edit transition is seamless. Same glass tokens as
   .po-detail-readonly (works in both themes). Placed after the
   .theme-dark .form-control rule so it wins the resting state in dark mode;
   focus keeps Bootstrap's / the theme's accent ring.
   Box dimensions (padding / radius / min-height) also match .po-detail-readonly
   above so toggling view<->edit on mobile causes no size shift — the inputs are
   form-control-sm, which is smaller than the read-only box without this. */
.po-detail-edit-card .form-control,
.po-detail-mobile-shell .form-control {
    background-color: var(--glass-surface);
    border-color: var(--glass-border);
    padding: 0.45rem 0.6rem;
    border-radius: 0.45rem;
    min-height: calc(1.5em + 0.75rem + 2px);
}

/* Part 1 — iOS Safari zooms on tap when an input's font-size is < 16px. Force
   16px on all PO Detail inputs/selects at mobile widths. Scoped to the page
   (po-detail-page body class) so other surfaces with intentional smaller
   inputs are untouched; can be widened portal-wide in a follow-up. */
@media (max-width: 767.98px) {
    .po-detail-page input[type="text"],
    .po-detail-page input[type="number"],
    .po-detail-page input[type="date"],
    .po-detail-page input[type="email"],
    .po-detail-page input[type="tel"],
    .po-detail-page input[type="search"],
    .po-detail-page input[type="password"],
    .po-detail-page textarea,
    .po-detail-page select {
        font-size: 16px;
    }
}

.po-detail-meta-grid {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 0.3rem 0.85rem;
}

.po-detail-meta-item {
    display: flex;
    align-items: baseline;
    gap: 0.35rem;
    padding: 0.08rem 0;
}

.po-detail-meta-label {
    color: var(--muted);
    font-size: 0.8rem;
    text-transform: none;
    letter-spacing: 0;
    font-weight: 500;
    white-space: nowrap;
}

.po-detail-meta-value {
    color: var(--text);
    font-size: 0.86rem;
    line-height: 1.25;
}

.po-detail-status-text {
    color: var(--text);
    font-size: 0.94rem;
    font-weight: 600;
    line-height: 1.25;
}

.po-detail-tabs .nav-link {
    color: var(--text);
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 100%;
    gap: 0.35rem;
}

.po-detail-tabs .nav-item {
    flex: 1 1 0;
}

.po-detail-tabs .nav-link.active {
    background-color: var(--card-bg);
    border-color: var(--glass-border) var(--glass-border) transparent;
}

.po-detail-tab-dot {
    width: 0.42rem;
    height: 0.42rem;
    border-radius: 50%;
    background-color: #f0ad4e;
    display: inline-block;
}

.po-detail-ordquant-card {
    border-top: 2px solid var(--brand-divider);
}

.po-detail-ordquant-title {
    font-weight: 600;
    letter-spacing: 0.01em;
}

.po-detail-ordquant-table thead th {
    background-color: var(--glass-surface);
    border-bottom-color: var(--glass-border);
    font-size: 0.79rem;
    text-transform: uppercase;
    letter-spacing: 0.02em;
    white-space: nowrap;
}

.po-detail-ordquant-table tbody td {
    border-bottom-color: var(--glass-border);
    vertical-align: middle;
}

.po-detail-ordquant-table .ordq-col-descriptio,
.po-detail-ordquant-bulk-table .ordq-descriptio-col,
.po-detail-ordquant-bulk-table tbody td:nth-child(2) {
    white-space: normal;
    word-break: break-word;
    overflow-wrap: anywhere;
}

.po-detail-ordquant-table tbody tr.table-active > * {
    background-color: var(--selected-row-bg);
}

.po-detail-ordquant-bulk-table thead th {
    background-color: var(--glass-surface);
    border-bottom-color: var(--glass-border);
    font-size: 0.76rem;
    text-transform: uppercase;
    letter-spacing: 0.02em;
}

.po-detail-ordquant-bulk-table tbody td {
    border-bottom-color: var(--glass-border);
    vertical-align: middle;
    padding-top: 0.42rem;
    padding-bottom: 0.42rem;
}

.po-detail-ordquant-bulk-table tbody tr.js-ordq-new-row > td,
.po-detail-ordquant-bulk-table tbody tr.js-ws-ordq-new-row > td {
    vertical-align: top;
}

.po-detail-ordquant-bulk-table tbody tr.ordq-row-modified > * {
    background-color: var(--ordq-bulk-modified-bg);
}

.po-detail-ordquant-bulk-table tbody tr.ordq-row-modified > td:first-child {
    box-shadow: inset 3px 0 0 var(--ordq-bulk-modified-accent);
}

.po-detail-ordquant-bulk-table tbody tr.ordq-row-new > * {
    background-color: var(--ordq-bulk-new-bg);
}

.po-detail-ordquant-bulk-table tbody tr.ordq-row-new > td:first-child {
    box-shadow: inset 3px 0 0 var(--ordq-bulk-new-accent);
}

.ordq-bulk-legend {
    align-items: center;
    gap: 0.65rem;
    color: var(--muted);
    font-size: 0.78rem;
}

.ordq-bulk-legend-item {
    display: inline-flex;
    align-items: center;
    gap: 0.3rem;
}

.ordq-bulk-legend-dot {
    width: 0.58rem;
    height: 0.58rem;
    border-radius: 999px;
    border: 1px solid var(--glass-border);
    display: inline-block;
}

.ordq-bulk-legend-dot-modified {
    background-color: var(--ordq-bulk-modified-accent);
    border-color: var(--ordq-bulk-modified-accent);
}

.ordq-bulk-legend-dot-new {
    background-color: var(--ordq-bulk-new-accent);
    border-color: var(--ordq-bulk-new-accent);
}

.ordq-bulk-input {
    max-width: 8rem;
}

.ordq-bulk-qualify-control {
    display: inline-flex;
    align-items: center;
    gap: 0.35rem;
    font-size: 0.82rem;
    color: var(--text);
}

.ordq-bulk-qualify-control .form-check-input {
    margin-top: 0;
    width: 1rem;
    height: 1rem;
    cursor: pointer;
}

@media (min-width: 768px) and (max-width: 1199.98px),
       (min-width: 1200px) and (hover: none) and (pointer: coarse) {
    .po-detail-ordquant-table .ordq-col-tax,
    .po-detail-ordquant-table .ordq-col-apply_tax {
        display: none;
    }

    .po-detail-ordquant-table.is-supervisor .ordq-col-price,
    .po-detail-ordquant-table.is-supervisor .ordq-col-total_cost {
        display: none;
    }
}

@media (min-width: 768px) {
    .po-detail-layout .card {
        min-height: 100%;
    }
}

@media (max-width: 767.98px) {
    .po-detail-meta-grid {
        grid-template-columns: 1fr;
    }
}

.desktop-from-640 {
    display: block;
}

.mobile-only-640 {
    display: none;
}

@media (max-width: 639.98px) {
    .portal-brand {
        gap: 0.2rem;
    }

    .portal-brand-text {
        display: none;
    }

    .desktop-from-640 {
        display: none !important;
    }

    .mobile-only-640 {
        display: block !important;
    }

    /* Keep Bootstrap offcanvas layout model (flex column) on mobile. */
    .offcanvas.mobile-only-640 {
        display: flex !important;
        flex-direction: column;
    }

    .po-mobile-list,
    .ordquant-mobile-list {
        overflow: auto;
    }

    .workspace-sheet.po-detail-sheet {
        height: min(82vh, 700px) !important;
    }

    #po-mobile-sheet-content {
        max-height: calc(100% - (1.35rem + env(safe-area-inset-bottom)));
        overflow-y: auto;
    }

    .po-sheet-actions {
        min-height: calc(3.35rem + env(safe-area-inset-bottom));
    }

    .workspace-pagination {
        margin-top: 0.4rem !important;
        margin-bottom: 0.35rem;
    }

    .po-detail-tabs {
        width: 100%;
    }

    .po-detail-ordquant-mobile-list {
        max-height: none;
        padding-bottom: 0.25rem;
    }

    .po-panel {
        min-height: 0 !important;
    }

    /* Each panel reserves a share of `--portal-tab-bar-h` so the two
       stacked lists (35dvh + 60dvh = 95dvh) plus chrome don't overflow
       the viewport on phones. PO list takes ~1/3 of the reserve;
       ordquant takes the larger ~2/3. Paired vh / dvh declarations. */
    .po-mobile-list {
        min-height: 12rem;
        max-height: calc(35vh - var(--portal-tab-bar-h) / 3);
        max-height: calc(35dvh - var(--portal-tab-bar-h) / 3);
    }

    .ordquant-mobile-list {
        max-height: calc(60vh - var(--portal-tab-bar-h) * 2 / 3);
        max-height: calc(60dvh - var(--portal-tab-bar-h) * 2 / 3);
        overflow: auto;
    }

    /* Remove the extra card-layer surface behind mobile PO cards. */
    .po-panel {
        background: transparent;
        border: 0;
        box-shadow: none;
        backdrop-filter: none;
        -webkit-backdrop-filter: none;
    }

    .po-panel > .card-body {
        background: transparent;
    }

    .po-mobile-list {
        padding: 0 !important;
    }

    .mobile-ordquant-hidden .ordquant-panel {
        display: none;
    }

    /* `- var(--portal-tab-bar-h)` accounts for the in-portal tab bar above.
       Paired vh / dvh so we track the visible viewport on iOS.
       The 261px / 291px constants come from a precise overflow
       measurement on iPhone Safari (430×721): the panel was rendering
       at its min-height of 477px while the visible page only allowed
       396px, so the constants were each bumped by the measured 81px
       to bring the formula in line with the actual chrome (navbar +
       main padding + heading + filter row + pagination). */
    .mobile-ordquant-hidden .po-panel {
        min-height: calc(100vh - 261px - var(--portal-tab-bar-h)) !important;
        min-height: calc(100dvh - 261px - var(--portal-tab-bar-h)) !important;
    }

    .mobile-ordquant-hidden .po-mobile-list {
        max-height: calc(100vh - 291px - var(--portal-tab-bar-h));
        max-height: calc(100dvh - 291px - var(--portal-tab-bar-h));
    }

    /* Keep My Lots cards uniform and scannable on phones. Single-column,
       full-width cards match the portal's other mobile card patterns (PO
       Workspace, Reports, Safety). The default Bootstrap `col-6` rendered
       two cards per row, leaving each card ~150px wide on a 375px viewport;
       that cramped the footer's two icon+label action buttons (clipping
       "Plans"/"Notes") and the tight 2-up gutters read as odd spacing.
       `flex-basis: 100%` overrides the column's `width: 50%` so cards stack
       one per row. */
    [data-section="my-lots"] [data-search-item] {
        display: flex;
        flex: 0 0 100%;
        max-width: 100%;
    }

    /* Fill the equal-height column. Height is no longer fixed: the footer
       lives inside the card now, so a hard height would clip the buttons.
       The body content (title/project/meta) is clamped below for scannability,
       which keeps cards close to uniform per row-pair. */
    [data-section="my-lots"] .my-lot-card {
        width: 100%;
        height: 100%;
    }

    [data-section="my-lots"] .my-lot-title,
    [data-section="my-lots"] .my-lot-project {
        display: block;
        white-space: nowrap;
        overflow: hidden;
        text-overflow: ellipsis;
    }

    [data-section="my-lots"] .my-lot-meta {
        display: -webkit-box;
        -webkit-box-orient: vertical;
        -webkit-line-clamp: 2;
        overflow: hidden;
        line-height: 1.2;
        max-height: 2.4em;
    }
}

/* Responsive tweaks for mobile/tablet */
@media (max-width: 768px) {
    .portal-brand img {
        height: 40px;
    }

    .portal-brand-text {
        font-size: 0.95rem;
        white-space: normal;
    }

    .filter-row .form-select,
    .filter-row .form-control {
        font-size: 16px; /* avoid iOS zoom on input focus */
    }

    .filter-actions .btn {
        width: 100%;
    }

    .filter-actions {
        width: 100%;
        gap: 0.25rem;
    }

    .table thead th {
        white-space: nowrap;
        padding: 0.6rem 0.8rem;
    }

    .table td {
        padding: 0.5rem 0.75rem;
    }

    .labor-review-input {
        font-size: 16px;
    }
}

/* ------------------------------------------------------------------ */
/* Payment Processor                                                   */
/* ------------------------------------------------------------------ */
:root {
    --pp-status-ok-bg: rgba(46, 160, 67, 0.14);
    --pp-status-ok-fg: #1f7a32;
    --pp-status-warn-bg: rgba(245, 158, 11, 0.16);
    --pp-status-warn-fg: #8a5a06;
    --pp-status-danger-bg: rgba(220, 53, 69, 0.14);
    --pp-status-danger-fg: #a3242f;
    --pp-status-muted-bg: rgba(108, 117, 125, 0.16);
    --pp-status-muted-fg: var(--muted);
    --pp-status-info-bg: rgba(13, 110, 253, 0.14);
    --pp-status-info-fg: #0a4ea2;
    --pp-row-excluded-bg: rgba(108, 117, 125, 0.08);
    --pp-row-insurance-bg: rgba(220, 53, 69, 0.10);
    --pp-row-insurance-border: rgba(220, 53, 69, 0.55);
    --pp-row-selected-bg: rgba(41, 124, 230, 0.10);
    --pp-net-fg: #176c3a;
    --pp-summary-total-bg: var(--glass-surface);
}

.theme-dark {
    --pp-status-ok-bg: rgba(63, 185, 80, 0.18);
    --pp-status-ok-fg: #7ee08e;
    --pp-status-warn-bg: rgba(245, 158, 11, 0.22);
    --pp-status-warn-fg: #f3c266;
    --pp-status-danger-bg: rgba(248, 113, 113, 0.20);
    --pp-status-danger-fg: #f8a4ac;
    --pp-status-muted-bg: rgba(160, 163, 173, 0.18);
    --pp-status-muted-fg: var(--muted);
    --pp-status-info-bg: rgba(117, 182, 255, 0.20);
    --pp-status-info-fg: #9ec8ff;
    --pp-row-excluded-bg: rgba(160, 163, 173, 0.08);
    --pp-row-insurance-bg: rgba(248, 113, 113, 0.13);
    --pp-row-insurance-border: rgba(248, 113, 113, 0.65);
    --pp-row-selected-bg: rgba(137, 198, 255, 0.18);
    --pp-net-fg: #7ee0a4;
    --pp-summary-total-bg: var(--glass-surface);
}

.pp-card {
    background: var(--card-bg);
    border: 1px solid var(--border);
    color: var(--text);
    backdrop-filter: blur(8px);
}

.pp-card .card-header {
    background: var(--glass-surface);
    border-bottom: 1px solid var(--border);
}

.pp-search { flex: 1 1 240px; min-width: 200px; max-width: 360px; }

.pp-filter-form {
    background: var(--glass-surface);
    border-color: var(--border) !important;
    color: var(--text);
}

.pp-table-wrap {
    overflow-x: auto;
    border: 1px solid var(--border);
    border-radius: 6px;
    background: var(--card-bg);
}

.pp-table {
    margin-bottom: 0;
    color: var(--text);
}

.pp-table thead th {
    background: var(--table-head-bg);
    color: var(--text);
    border-bottom: 1px solid var(--border);
    white-space: nowrap;
}

.pp-table tbody tr {
    border-bottom: 1px solid var(--border);
}

.pp-table tbody tr:nth-of-type(odd) > * {
    background: var(--table-striped);
}

.pp-table tbody tr:hover > * {
    background: var(--btn-hover-bg);
}

/* Header th and body td both carry .pp-col-check, so a single rule keeps
   the Select-all and row checkboxes in the same column position. The
   .pp-table prefix outranks `.table-striped > tbody > tr > *` (which
   would otherwise pad row cells to 0.55rem/0.9rem and offset them). */
.pp-table .pp-col-check {
    width: 36px;
    padding-left: 0.5rem;
    padding-right: 0.5rem;
    text-align: center;
    vertical-align: middle;
}

/* Tighter horizontal padding on the Approved-for-Payment desktop table —
   Bootstrap's default 0.5rem per-cell pushed the rightmost columns past
   the sidebar-expanded viewport at 1280–1366px. Compressing horizontal
   padding while keeping vertical padding intact (so rows stay legible)
   reclaims enough room that "Amount to Pay" stays fully visible. Scoped
   to the Pay Hold section so the Processing / Vendor Summary tables
   retain their existing density. */
[data-section="pp-pay-hold"] .pp-table > :not(caption) > * > * {
    padding-left: 0.4rem;
    padding-right: 0.4rem;
}
[data-section="pp-pay-hold"] .pp-table .pp-col-check {
    padding-left: 0.4rem;
    padding-right: 0.4rem;
}

/* Let the POX / DUE DATE header info-icon popovers escape the table-wrap
   on the Pay Hold section. The shared `.pp-table-wrap` defaults to
   `overflow-x: auto` for horizontal scrolling on very wide layouts, but
   the previous task already locked desktop horizontal width to fit
   the viewport (no scroll needed), and the tablet block overrides the
   wrap to `overflow-x: visible` anyway. The popover (`position: absolute`
   inside the th) gets clipped by either the wrap's effective `overflow`
   cascade (since `overflow-x: auto` + `overflow-y: visible` computes to
   `auto / auto` per CSS spec) or, at tablet, by the th's `overflow:
   hidden` ellipsis rule. Scoping `overflow: visible` to the pay-hold
   wrap solves the wrap-side clip; the th-side clip is solved per-cell
   in the tablet block below. */
[data-section="pp-pay-hold"] .pp-table-wrap {
    overflow: visible;
}
.pp-col-action { width: 110px; }
.pp-money { font-variant-numeric: tabular-nums; }

/* Universal PO Void — BATCH: driven by the multi-select + the top "Void PO
   Record" button (no per-row control). The confirmation modal lists every
   selected POX in this scrollable box so the full blast radius is always
   visible, however many are selected. */
.pp-void-pox-list {
    max-height: 9rem;
    overflow-y: auto;
    font-variant-numeric: tabular-nums;
    font-size: 0.85rem;
    word-break: break-word;
    background: var(--surface, transparent);
}
/* Pay Hold header label swap: full label on desktop, short on tablet.
   Default = full shown, short hidden; the tablet block flips it so e.g.
   "Amount to Pay" becomes "Amount" and isn't ellipsis-clipped in its
   compact column (the numeric data fits in far less width than the
   label). See pp_sort_link(short_label=...). */
.pp-th-short { display: none; }
.pp-net { color: var(--pp-net-fg); font-weight: 600; }

/* POX cell — primary PO number with optional PO_TYPE suffix. The suffix
   (V = Variance, B = Backcharge, occasional R) reads as a row-level
   annotation, not a second value: smaller font + muted color, separated
   by a thin gap. Empty PO_TYPE renders no suffix so regular POs look
   identical to the pre-combine treatment.

   Header companion (.pp-pox-head) lays out the sort link + info icon
   inline; the info popover reuses .bv-info / .bv-info-tip. */
.pp-col-pox {
    white-space: nowrap;
}
.pp-pox-value {
    font-variant-numeric: tabular-nums;
}
.pp-po-type-suffix {
    font-size: 0.78rem;
    color: var(--muted);
    font-weight: 500;
    margin-left: 0.1rem;
    letter-spacing: 0.02em;
}
.pp-pox-head {
    display: inline-flex;
    align-items: center;
    gap: 0.15rem;
}

/* AGED DAYS cell. Displays the stored AGED_DAYS_CALC value directly
   (days since INV_DATE; computed by FILL_PAY_HOLD). Closed AP aging
   buckets, aligned 1:1 with the filter modes so each filter bucket
   maps to exactly one color band:
     - d < 0      (rare future-dated invoice) : muted neutral (signed)
     - 0..30      (fresh)                     : muted neutral
     - 31..60     (aging)                     : bold amber
     - 61..90     (significantly aged)        : bold red
     - >= 91      (critically aged)           : bold red, heavier weight
     - NULL       (no invoice date)           : muted em-dash
   Reuses the pp-status-* theme tokens so light + dark mode work
   without hardcoded colors. tabular-nums keeps digits aligned. */
.pp-aged {
    font-variant-numeric: tabular-nums;
    display: inline-block;
    min-width: 2.5ch;
    text-align: right;
}
.pp-aged--future   { color: var(--muted); font-weight: 400; }
.pp-aged--fresh    { color: var(--muted); font-weight: 400; }
.pp-aged--aging    { color: var(--pp-status-warn-fg);   font-weight: 700; }
.pp-aged--late     { color: var(--pp-status-danger-fg); font-weight: 700; }
.pp-aged--critical { color: var(--pp-status-danger-fg); font-weight: 800; }
.pp-aged--none     { color: var(--muted); font-weight: 400; }

/* Stacked INV DATE / AGED DAYS cell — combines two columns into one,
   freeing horizontal space on desktop. Line 1 is the invoice INV_DATE,
   line 2 is the AGED_DAYS_CALC value. The cell stays nowrap; lines
   stack via flex. */
.pp-col-inv-date {
    white-space: nowrap;
    font-variant-numeric: tabular-nums;
    vertical-align: top;
}
.pp-inv-date-stack {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: 0.05rem;
    line-height: 1.2;
}
.pp-inv-date-line {
    font-variant-numeric: tabular-nums;
}
.pp-inv-date-aged {
    font-size: 0.78rem;
    line-height: 1.1;
}

/* INV DATE column header — primary sort link + info icon on row 1,
   secondary AGED DAYS sort link on row 2. The info icon mirrors the
   Budget View tablet PRICING column's pure-CSS popover (.bv-info /
   .bv-info-tip); no per-instance styling needed beyond layout. */
.pp-inv-date-head {
    display: inline-flex;
    align-items: center;
    gap: 0.15rem;
}
.pp-inv-date-subhead {
    display: block;
    font-size: 0.72rem;
    font-weight: 500;
    color: var(--muted);
    margin-top: 0.05rem;
    line-height: 1;
}
.pp-inv-date-subhead .workspace-sort-link {
    color: inherit;
}

.pp-desc {
    max-width: 180px;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
/* Date columns: nowrap so YYYY-MM-DD stays on one line, tabular-nums so
   columns don't jitter on sort. No explicit width — the browser auto-sizes
   to the natural header/data width so we don't widen the table past what
   the content actually needs. */
.pp-col-date {
    white-space: nowrap;
    font-variant-numeric: tabular-nums;
}

/* Approved-for-Payment footer: grand total (always visible) on the left,
   selected-total (visible only when something is selected) just after it,
   pagination on the right. Wraps on narrow viewports. min-width:0 on the
   flex children lets them shrink past their min-content if needed so the
   row never pushes its container wider. */
.pp-table-footer {
    display: flex;
    justify-content: space-between;
    align-items: center;
    flex-wrap: wrap;
    gap: 0.75rem;
    max-width: 100%;
}
.pp-table-footer > * { min-width: 0; }
.pp-totals {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: 0.25rem 0.75rem;
    color: var(--text);
    font-variant-numeric: tabular-nums;
    min-width: 0;
}
.pp-grand-total {
    font-weight: 600;
}
.pp-selected-total {
    color: var(--muted);
    font-weight: 500;
}

.pp-row-excluded > *,
.pp-row-card.pp-row-excluded {
    background: var(--pp-row-excluded-bg) !important;
    color: var(--muted);
}

/* Subtle selection highlight for the Approved for Payment table.
   Theme-aware via --pp-row-selected-bg defined alongside the other
   pp-* tokens. Distinct from the red insurance highlight. */
[data-section="pp-pay-hold"] .pp-table tbody tr {
    cursor: pointer;
}
.pp-table tbody tr.pp-row-selected > * {
    background: var(--pp-row-selected-bg) !important;
}

.pp-row-insurance > *,
.pp-row-card.pp-row-insurance {
    background: var(--pp-row-insurance-bg) !important;
    box-shadow: inset 3px 0 0 0 var(--pp-row-insurance-border);
}

.pp-badge {
    display: inline-block;
    font-size: 0.72rem;
    font-weight: 600;
    line-height: 1;
    padding: 0.32em 0.6em;
    border-radius: 999px;
    letter-spacing: 0.02em;
    white-space: nowrap;
}
.pp-badge-ok      { background: var(--pp-status-ok-bg);     color: var(--pp-status-ok-fg); }
.pp-badge-warn    { background: var(--pp-status-warn-bg);   color: var(--pp-status-warn-fg); }
.pp-badge-danger  { background: var(--pp-status-danger-bg); color: var(--pp-status-danger-fg); }
.pp-badge-muted   { background: var(--pp-status-muted-bg);  color: var(--pp-status-muted-fg); }
.pp-badge-info    { background: var(--pp-status-info-bg);   color: var(--pp-status-info-fg); }

.pp-card-list {
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
}

.pp-row-card {
    display: block;
    border: 1px solid var(--border);
    background: var(--card-bg);
    border-radius: 8px;
    padding: 0.6rem 0.75rem;
    color: var(--text);
}

.pp-row-card-head {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    font-weight: 600;
}

.pp-row-card-vendor { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; }
.pp-row-card-amount { font-variant-numeric: tabular-nums; }

.pp-row-card-meta {
    display: flex;
    flex-wrap: wrap;
    gap: 0.25rem 0.75rem;
    color: var(--muted);
    font-size: 0.85rem;
    margin-top: 0.25rem;
}

.pp-summary-total > * {
    background: var(--pp-summary-total-bg) !important;
    font-weight: 600;
}

.pp-print-bar {
    display: flex;
    flex-wrap: wrap;
    gap: 0.75rem;
    align-items: center;
    padding: 0.75rem;
    border: 1px solid var(--border);
    border-radius: 8px;
    background: var(--glass-surface);
}

.pp-payment-type {
    display: flex;
    gap: 1rem;
    align-items: center;
    margin: 0;
    padding: 0;
    border: 0;
}

.pp-radio {
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    padding: 0.35rem 0.7rem;
    border: 1px solid var(--border);
    border-radius: 999px;
    cursor: pointer;
    user-select: none;
}

.pp-radio input { accent-color: var(--link); }

.pp-radio:hover { background: var(--btn-hover-bg); }

#pp-selection-count {
    font-variant-numeric: tabular-nums;
}

@media (max-width: 575.98px) {
    .pp-print-bar { flex-direction: column; align-items: stretch; }
    .pp-payment-type { justify-content: space-between; }
}

/* Action bar holds only the Filters toggle + search form. On every width
   they sit side-by-side: Filters button at natural width, search form
   filling the rest of the row via flex:1. Pull button + count moved to
   .pp-pull-bar below the filter collapse. */
@media (max-width: 767.98px) {
    .pp-action-bar {
        gap: 0.5rem !important;
        align-items: center !important;
        flex-wrap: nowrap !important;
    }
    .pp-action-bar > * {
        margin: 0 !important;
    }
    /* Search form: fill remaining row width, locked to 44px tall. The
       default .pp-search rule sets flex:1 1 240px, which in some flex
       contexts forces a 240px main-axis basis — pinning flex:1 here
       collapses the basis to 0% so width is purely the remaining row. */
    #pp-search-form.pp-search {
        flex: 1 1 0% !important;
        min-width: 0 !important;
        max-width: none !important;
        height: 44px;
        min-height: 44px;
        margin: 0 !important;
    }
    .pp-action-bar > .btn {
        flex: 0 0 auto;
        min-height: 44px;
    }
}

/* Mobile / small-tablet touch polish. Single block, gated to <768px so
   desktop and large-tablet layouts (≥768px, where Bootstrap's d-md-block
   table view is in use) are untouched. */
@media (max-width: 767.98px) {
    /* 44px minimum tap targets on the action bar controls. The search
       form's input inherits both height and 16px font (iOS zoom fix). */
    .pp-action-bar > .btn,
    .pp-action-bar > #pp-pull-btn {
        min-height: 44px;
    }
    .pp-search input,
    .pp-action-bar input[type="search"] {
        min-height: 44px;
        font-size: 16px;
    }

    /* iOS zoom prevention on the filter panel form controls. The global
       (max-width: 768px) block at line 2412 covers .filter-row but not the
       Payment Processor's .pp-filter-form, so extend it here. Bumping the
       hit area at the same time. */
    .pp-filter-form .form-select,
    .pp-filter-form .form-control {
        font-size: 16px;
        min-height: 44px;
    }

    /* Payment Date input — same touch-target + zoom treatment. The 200px
       max-width is kept (the date input itself is plenty wide), only the
       interaction sizing changes. */
    .pp-payment-date-input {
        min-height: 44px;
        font-size: 16px;
    }
}

/* ================================================================ */
/* Pay-Hold CARD layout — scoped to the WHOLE tablet+mobile band     */
/* (max-width: 1199.98px), not just <768. The Pay Hold table folds   */
/* to cards at xl (see its template wrapper + Section 1 of the       */
/* tablet table block below), so payhold cards are visible across    */
/* 768–1199 too and need this full layout there. The Processing and  */
/* Vendor Summary card rules that ride along stay display:none in    */
/* 768–1199 (their tables show), so re-scoping them is a no-op.      */
/* ================================================================ */
@media (max-width: 1199.98px) {
    /* Mobile card view (the d-block d-md-none variant). Thumb-friendly
       padding, a real minimum height so cards don't squish to one line
       when meta is sparse, and a slightly larger primary-line type pair. */
    .pp-row-card {
        padding: 0.85rem 1rem;
        min-height: 72px;
    }
    .pp-row-card-head {
        gap: 0.75rem;
    }
    /* Legacy Processing card head — keep larger type only there. */
    .pp-row-card--processing .pp-row-card-vendor,
    .pp-row-card--processing .pp-row-card-amount {
        font-size: 1.05rem;
    }
    .pp-row-card-amount {
        font-variant-numeric: tabular-nums;
    }
    /* The whole pp-row-card is a <label> wrapping the checkbox, so the
       label itself is the 44px tap target. Bump the checkbox so it's
       comfortably hittable inside that label too. */
    .pp-row-card .pp-row-check {
        min-width: 22px;
        min-height: 22px;
    }

    /* ---- PAY_HOLD card redesign (PO Workspace-style hierarchy) ---- */
    .pp-row-card--payhold {
        position: relative;
        padding: 0.85rem 1rem;
        padding-right: 2.75rem; /* room for the top-right checkbox */
        box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08);
        transition: box-shadow 0.15s ease, border-color 0.15s ease,
                    background-color 0.15s ease;
        -webkit-tap-highlight-color: transparent;
    }
    .pp-row-card--payhold:active {
        box-shadow: 0 2px 6px rgba(0, 0, 0, 0.12);
    }
    .pp-row-card--payhold .pp-row-card-checkbox {
        position: absolute;
        top: 0.85rem;
        right: 0.85rem;
        margin: 0;
    }
    /* PAY_HOLD card reserves padding-right: 2.75rem for the absolute
       checkbox. The header/details/footer borders extend into that
       reserved area so they reach the right edge of the card and pass
       under the checkbox column, matching the left side. */
    .pp-row-card--payhold .pp-row-card-header,
    .pp-row-card--payhold .pp-row-card-invoice-details,
    .pp-row-card--payhold .pp-row-card-footer {
        margin-right: -2.75rem;
        padding-right: 2.75rem;
    }

    .pp-row-card-header {
        display: flex;
        justify-content: space-between;
        align-items: baseline;
        gap: 0.75rem;
        /* Border-bottom extends to the card's inner edges: pull the
           element outside the card's horizontal padding via negative
           margins, then restore the content position with matching
           padding. Right offset is per-variant below (PAY_HOLD reserves
           extra room for the absolute checkbox). */
        margin-left: -1rem;
        margin-right: -1rem;
        padding-left: 1rem;
        padding-right: 1rem;
        padding-bottom: 0.5rem;
        margin-bottom: 0.5rem;
        border-bottom: 1px solid var(--border);
    }
    .pp-row-card-pox {
        font-size: 1.1rem;
        font-weight: 600;
        color: var(--link);
        font-family: var(--bs-font-monospace, ui-monospace, Menlo, monospace);
        letter-spacing: 0.01em;
    }
    .pp-row-card-total {
        font-size: 1.1rem;
        font-weight: 700;
        color: var(--text);
        font-variant-numeric: tabular-nums;
    }

    .pp-row-card--payhold .pp-row-card-vendor {
        display: flex;
        flex-wrap: wrap;
        align-items: baseline;
        gap: 0.4rem;
        flex: none;
        overflow: visible;
        text-overflow: clip;
        white-space: normal;
        font-size: 0.95rem;
        font-weight: 600;
        color: var(--text);
        line-height: 1.3;
        margin-bottom: 0.4rem;
    }
    .pp-row-card-vendor-name {
        flex: 0 1 auto;
        min-width: 0;
    }
    .pp-row-card-vendx {
        font-size: 0.78rem;
        font-weight: 500;
        color: var(--muted);
        font-family: var(--bs-font-monospace, ui-monospace, Menlo, monospace);
        white-space: nowrap;
    }

    .pp-row-card-location {
        display: flex;
        flex-wrap: wrap;
        gap: 0.25rem 1rem;
        font-size: 0.85rem;
        color: var(--muted);
        margin-bottom: 0.5rem;
    }
    .pp-row-card-loc-item {
        display: inline-flex;
        gap: 0.35rem;
        align-items: baseline;
    }
    .pp-row-card-loc-label {
        font-weight: 600;
        color: var(--text);
        letter-spacing: 0.02em;
    }

    .pp-row-card-invoice-details {
        display: grid;
        grid-template-columns: repeat(2, minmax(0, 1fr));
        gap: 0.5rem 0.75rem;
        /* Same edge-to-edge border trick as the header. */
        margin-left: -1rem;
        margin-right: -1rem;
        padding-left: 1rem;
        padding-right: 1rem;
        padding-top: 0.5rem;
        border-top: 1px solid var(--border);
        font-size: 0.85rem;
    }
    .pp-row-card-detail {
        display: flex;
        flex-direction: column;
        min-width: 0;
        gap: 0.1rem;
    }
    .pp-row-card-detail-label {
        font-size: 0.7rem;
        text-transform: uppercase;
        letter-spacing: 0.04em;
        color: var(--muted);
        font-weight: 600;
    }
    .pp-row-card-detail-value {
        color: var(--text);
        font-weight: 500;
        font-variant-numeric: tabular-nums;
        overflow: hidden;
        text-overflow: ellipsis;
        white-space: nowrap;
    }

    .pp-row-card-footer {
        display: flex;
        align-items: baseline;
        flex-wrap: wrap;
        gap: 0.4rem;
        /* Same edge-to-edge border trick as the header. */
        margin-left: -1rem;
        margin-right: -1rem;
        padding-left: 1rem;
        padding-right: 1rem;
        margin-top: 0.6rem;
        padding-top: 0.5rem;
        border-top: 1px solid var(--border);
        font-size: 0.82rem;
        line-height: 1.35;
        color: var(--muted);
    }
    .pp-footer-separator {
        flex: 0 0 auto;
        color: var(--muted);
        opacity: 0.7;
    }
    .pp-row-card-description {
        flex: 1 1 auto;
        min-width: 0;
        color: var(--muted);
        word-break: break-word;
    }
    .pp-row-card-costtype {
        display: inline-block;
        padding: 0.15rem 0.55rem;
        border-radius: 4px;
        background: var(--glass-surface);
        border: 1px solid var(--border);
        color: var(--text);
        font-weight: 600;
        font-size: 0.7rem;
        letter-spacing: 0.04em;
        text-transform: uppercase;
    }
    .pp-row-card-costcode {
        flex: 0 0 auto;
        font-family: var(--bs-font-monospace, ui-monospace, Menlo, monospace);
        font-weight: 600;
        color: var(--text);
        font-variant-numeric: tabular-nums;
        letter-spacing: 0.02em;
    }

    /* ---- Vendor Summary mobile section divider — strong visual break
       between the Processing detail cards and the Vendor Summary cards
       so users notice the section change as they scroll. ---- */
    .pp-vendor-summary-divider {
        margin-top: 1.25rem;
        padding-top: 0.85rem;
        margin-bottom: 0.75rem;
        border-top: 2px solid var(--link);
    }
    .pp-vendor-summary-divider-label {
        display: flex;
        align-items: center;
        gap: 0.5rem;
        font-size: 1.05rem;
        font-weight: 600;
        color: var(--text);
        line-height: 1.2;
    }
    .pp-vendor-summary-divider-label .bi {
        color: var(--link);
        font-size: 1.15rem;
    }
    .pp-vendor-summary-divider-meta {
        font-size: 0.8rem;
        color: var(--muted);
        margin-top: 0.2rem;
    }

    /* Selected state — same token used for table-row selection so light/dark
       both stay consistent across desktop/mobile. */
    .pp-row-card.pp-row-selected {
        border-color: var(--link);
        background: var(--pp-row-selected-bg);
    }
}

@media (max-width: 767.98px) {
    /* ---- Mobile spacing: stop the date row + action bar from eating
       a quarter of the viewport before any data is visible. Bootstrap's
       .gap-* / .mb-* utilities use !important, so overrides do too. ---- */
    .pp-payment-date-row {
        gap: 0.5rem 0.75rem !important;
        margin-bottom: 0.75rem !important;
    }
    .pp-payment-date-hint {
        flex-basis: 100%;
        font-size: 0.78rem;
        margin-top: -0.25rem;
    }
    .pp-action-bar {
        margin-bottom: 0.75rem !important;
    }

    /* Sticky grand-total + pagination footer on mobile only. Sticks to the
       viewport bottom while the cards scroll above it, so AP always sees
       the total without scrolling to the end of the list. Solid card
       background prevents underlying content from bleeding through. */
    .pp-table-footer {
        position: sticky;
        bottom: 0;
        background: var(--card-bg);
        border-top: 1px solid var(--border);
        padding: 0.5rem 0.25rem;
        z-index: 5;
    }

    /* ---- Search form: flex layout so the visually-hidden label/button
       don't add vertical height. Result: form height ≈ input height. ---- */
    #pp-search-form.pp-search {
        display: flex;
        align-items: center;
        margin: 0;
        padding: 0;
    }
    #pp-search-form > input[type="search"] {
        flex: 1 1 auto;
        height: 44px;
        min-height: 44px;
        line-height: 1.2;
        padding-top: 0;
        padding-bottom: 0;
        font-size: 16px;
    }

    /* ---- Mobile Select All / Deselect All button row. ---- */
    .pp-mobile-select-actions {
        margin-top: 0.25rem;
    }
    .pp-mobile-select-actions .btn {
        min-height: 40px;
        font-size: 0.875rem;
        padding: 0.4rem 0.5rem;
    }
    .pp-mobile-select-actions .bi {
        margin-right: 0.25rem;
    }

    /* ---- Filter Apply / Reset stay inline with the "Filters (AND)"
       heading on every width — no column-stack on mobile. ---- */
    .pp-filter-header {
        flex-wrap: nowrap !important;
    }
    .pp-filter-actions {
        flex-shrink: 0;
    }
    .pp-filter-actions .btn {
        white-space: nowrap;
    }

    /* ---- Pull bar (Pull Into Processing + selection count). ---- */
    .pp-pull-bar .pp-pull-btn-main {
        flex: 1 1 auto;
        min-height: 44px;
    }

    /* ---- Header declutter on mobile. Tighter heading + subtitle so the
       first card (Approved for Payment) lifts up into the viewport, with
       just enough top/bottom padding around the title block so the
       heading + subtitle don't crash into the page edge or the date row. ---- */
    .pp-page-header {
        margin-bottom: 0.75rem !important;
        gap: 0.5rem !important;
    }
    .pp-page-header .h5,
    .pp-page-header h1 {
        font-size: 1.15rem;
        line-height: 1.25;
    }
    .pp-page-header-subtitle {
        font-size: 0.78rem;
        line-height: 1.3;
        padding-bottom: 0.25rem;
    }
    .pp-page-header-title {
        flex: 1 1 auto;
        min-width: 0;
        padding-top: 0.4rem;
        padding-bottom: 0.1rem;
    }
    .pp-header-actions-mobile {
        flex: 0 0 auto;
    }
    .pp-header-actions-mobile .dropdown-toggle {
        min-height: 36px;
    }
    .pp-header-actions-mobile .dropdown-menu .bi {
        margin-right: 0.4rem;
    }

    /* ---- Processing card: vendor + status badge side-by-side. ---- */
    .pp-processing-card-vendor-status {
        display: flex;
        justify-content: space-between;
        align-items: center;
        gap: 0.5rem;
        margin-bottom: 0.5rem;
        flex-wrap: wrap;
    }
    .pp-processing-card-vendor-status .pp-row-card-vendor {
        flex: 1 1 auto;
        font-size: 0.95rem;
        font-weight: 600;
        margin: 0;
    }
    .pp-processing-card-vendor-status .pp-badge {
        flex: 0 0 auto;
    }

    /* ---- Vendor Summary cards ---- */
    .pp-vendor-summary-list {
        display: flex;
        flex-direction: column;
        gap: 0.5rem;
    }
    .pp-vendor-summary-card {
        background: var(--card-bg);
        border: 1px solid var(--border);
        border-radius: 8px;
        padding: 0.85rem 1rem;
        color: var(--text);
        box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08);
    }
    .pp-vendor-summary-header {
        margin-bottom: 0.6rem;
        padding-bottom: 0.5rem;
        border-bottom: 1px solid var(--border);
    }
    .pp-vendor-summary-name {
        font-size: 1rem;
        font-weight: 600;
        color: var(--text);
        line-height: 1.3;
    }
    .pp-vendor-summary-vendx {
        font-size: 0.8rem;
        color: var(--muted);
        font-family: var(--bs-font-monospace, ui-monospace, Menlo, monospace);
        margin-top: 0.15rem;
    }
    .pp-vendor-summary-grid {
        display: grid;
        grid-template-columns: repeat(2, minmax(0, 1fr));
        gap: 0.5rem 0.75rem;
        margin-bottom: 0.5rem;
    }
    .pp-vendor-summary-item {
        display: flex;
        flex-direction: column;
        gap: 0.1rem;
        min-width: 0;
    }
    .pp-vendor-summary-label {
        font-size: 0.7rem;
        text-transform: uppercase;
        letter-spacing: 0.04em;
        color: var(--muted);
        font-weight: 600;
    }
    .pp-vendor-summary-value {
        font-size: 0.95rem;
        font-weight: 600;
        color: var(--text);
        font-variant-numeric: tabular-nums;
    }
    .pp-vendor-net {
        font-size: 1.05rem;
        font-weight: 700;
        color: var(--link);
    }
    .pp-vendor-summary-status {
        margin-bottom: 0.25rem;
    }
    .pp-vendor-summary-total {
        display: flex;
        justify-content: space-between;
        align-items: baseline;
        padding: 0.75rem 1rem;
        margin-top: 0.25rem;
        border-radius: 8px;
        background: var(--pp-summary-total-bg);
        border: 1px solid var(--border);
        font-weight: 700;
        font-variant-numeric: tabular-nums;
        color: var(--text);
    }
    .pp-vendor-summary-total .pp-net {
        font-size: 1.1rem;
    }
    /* Excluded / insurance-expired vendor card state — own modifier classes
       so the global `.pp-row-excluded > *` rule doesn't paint each child. */
    .pp-vendor-summary-card--excluded {
        background: var(--pp-row-excluded-bg);
        color: var(--muted);
    }
    .pp-vendor-summary-card--insurance {
        background: var(--pp-row-insurance-bg);
        border-color: var(--pp-row-insurance-border);
        box-shadow: inset 3px 0 0 0 var(--pp-row-insurance-border),
                    0 1px 3px rgba(0, 0, 0, 0.08);
    }

    /* ---- Toast container: span screen edge-to-edge on mobile (minus 1rem
       on each side) instead of pinning a 280px-wide toast to the right.
       Inline styles are set in portal_toast.js so the overrides need
       !important. Desktop positioning is unaffected.
       The 1rem bottom offset is padded by env(safe-area-inset-bottom) so
       toasts clear the home indicator on notched iPhones. ---- */
    #pp-toast-container {
        left: 1rem !important;
        right: 1rem !important;
        bottom: calc(1rem + env(safe-area-inset-bottom, 0px)) !important;
        width: auto !important;
        max-width: none !important;
    }
    #pp-toast-container > .alert {
        min-width: 0 !important;
        max-width: none !important;
        width: 100% !important;
    }
}

/* Tablet (768–1199.98px): tablet-optimized layouts for all three Payment
   Processor tables. Each table needs its own rule block because all three
   share the .pp-table class but have different column counts and roles:
     - Pay Hold (Section 1): 14 cols, scoped via [data-section="pp-pay-hold"]
       (renders only at xl+ — folds to cards below 1200; see Section 1 block)
     - Processing (Section 2): 10 cols, scoped via .pp-processing-table
     - Vendor Summary (Section 2): 8 cols, scoped via .pp-summary
   All three use `table-layout: fixed` so column widths are authoritative,
   `text-overflow: ellipsis` for non-critical truncation, and `tbody td`-only
   wrap rules on the columns whose data must remain fully readable. Headers
   stay single-line + bottom-aligned so the header row sits visually flat
   regardless of how tall any wrapped body cell makes its row. Mobile (<768px)
   uses the card view; wide desktops (≥1600px) use the default auto layout.
   Touch devices at xl+ (iPad Pro 12.9" landscape) get this tablet layout
   too via the pointer/hover clause.

   Narrow-desktop laptops (1200–1599px, non-touch) ALSO get this compact
   fixed layout: with the sidebar expanded the 13-column Pay Hold table
   overflowed its content column at 1366–1440px and the rightmost
   "Amount to Pay" was clipped (wrap is overflow:visible, no scrollbar).
   The fixed width:100% layout scales the column budget to fit any width,
   so it never overflows; ≥1600px has room for the roomy auto layout. */
@media (min-width: 768px) and (max-width: 1199.98px),
       (min-width: 1200px) and (hover: none) and (pointer: coarse),
       (min-width: 1200px) and (max-width: 1599.98px) and (hover: hover) and (pointer: fine) {
    /* Common: kill horizontal scroll on every PP table wrap. */
    .pp-table-wrap {
        overflow-x: visible;
    }
    /* Common: tighter card body so all three tables get more room. */
    .pp-card > .card-body {
        padding: 0.5rem 1rem;
    }

    /* Header label swap — show the short labels (e.g. "Amount" instead of
       "Amount to Pay") so headers aren't ellipsis-clipped in the compact
       columns. */
    .pp-th-full { display: none; }
    .pp-th-short { display: inline; }

    /* ================================================================ */
    /* Section 1 — Pay Hold table (14 columns).                         */
    /* Col 13 is the APPROVED DATE column (added after DUE DATE); the    */
    /* Amount-to-Pay money column moved to col 14. The table now folds   */
    /* to CARDS below xl (1200px) — its template wrapper is              */
    /* d-none d-xl-block — because three full-date columns (INV / DUE /  */
    /* APPROVED, each needing ~86px for an un-truncated YYYY-MM-DD) plus */
    /* 11 others can't fit below ~1150px of content. So this budget only */
    /* paints at xl+. Sum = 848px, which fits the TIGHTEST xl case: a    */
    /* 1200px NON-touch desktop with the 240px sidebar pushed open       */
    /* (content ≈ 866px) — 848 < 866, so columns expand slightly and the */
    /* dates never clip. Wider (iPad Pro 1366 / desktop) has more slack; */
    /* it spreads evenly across all columns (Amount has an explicit      */
    /* width now, so it no longer absorbs it all as the lone auto column */
    /* — that was the old dead gap). INV/DUE/APPROVED widened 70/70→86.  */
    /* Donors (already wrap in tbody, so it's extra wrap lines not lost  */
    /* data): VENDOR 98→76, DESCRIPTION 72→58, INVOICE 50→46, plus small */
    /* trims to LOT/VENDX/COSTCODE. (The 768–1199 slice of this media    */
    /* query now styles a display:none table — harmless; Processing/     */
    /* Vendor tables are narrower and still fold at md.)                 */
    /* The standalone TYPE column (was col 13, 26px)                     */
    /* was folded into the POX cell as a muted suffix; POX widened by   */
    /* 24px to absorb the info-icon + suffix without overflow.          */
    /*                                                                  */
    /* PROJ widened 34 → 54px (+20) to fit 4–5 char codes like "WELP",  */
    /* "VPLH", "CCKL2" without ellipsis clip. Donor columns: VENDOR     */
    /* 110 → 98 (-12) and DESCRIPTION 80 → 72 (-8). Both already wrap   */
    /* in tbody (cols 6 + 8 are in the wrap-override list below), so    */
    /* the donation surfaces as one or two more wrap lines on the few   */
    /* longest vendor names / descriptions rather than information loss.*/
    /* ================================================================ */
    [data-section="pp-pay-hold"] .pp-table {
        table-layout: fixed;
        font-size: 0.78rem;
        width: 100%;
    }
    [data-section="pp-pay-hold"] .pp-table thead th {
        /* Tablet header keeps tight horizontal padding to match desktop,
           with extra vertical room so the two-line DUE DATE header
           (sort link + DTD sub-link + info icon) breathes. */
        padding: 0.55rem 0.35rem;
        font-size: 0.72rem;
        font-weight: 600;
        white-space: nowrap !important;
        overflow: hidden;
        text-overflow: ellipsis;
        vertical-align: bottom;
    }
    [data-section="pp-pay-hold"] .pp-table tbody td {
        /* Tablet row padding deliberately taller than the previous
           0.4rem/0.3rem — the cramped read was the explicit complaint
           in this task. Horizontal stays at 0.35rem so the column
           widths below still hold. */
        padding: 0.65rem 0.35rem;
        white-space: nowrap;
        overflow: hidden;
        text-overflow: ellipsis;
        vertical-align: top;
    }
    [data-section="pp-pay-hold"] .pp-table tbody td:nth-child(1) {
        vertical-align: middle;
    }
    /* th cells carrying info-icon popovers (POX, INV DATE) need to opt
       out of the global `overflow: hidden` ellipsis rule above —
       otherwise the popover hangs OUT of the th box (`position:
       absolute` + `top: 100% + 0.35rem`) and gets clipped by the th's
       own clipping rectangle. Cells widen-or-shrink to fit their
       header text anyway (POX at 80px, INV DATE at 70px, both already
       cover their header text + info icon without ellipsis). */
    [data-section="pp-pay-hold"] .pp-table thead th.pp-col-pox,
    [data-section="pp-pay-hold"] .pp-table thead th.pp-col-inv-date {
        overflow: visible;
    }
    [data-section="pp-pay-hold"] .pp-table thead th:nth-child(1),
    [data-section="pp-pay-hold"] .pp-table tbody td:nth-child(1)  { width: 30px; }                                                /* checkbox */
    [data-section="pp-pay-hold"] .pp-table thead th:nth-child(2),
    [data-section="pp-pay-hold"] .pp-table tbody td:nth-child(2)  { width: 82px; white-space: nowrap !important; }                /* POX (incl. info icon + PO_TYPE suffix) */
    [data-section="pp-pay-hold"] .pp-table thead th:nth-child(3),
    [data-section="pp-pay-hold"] .pp-table tbody td:nth-child(3)  { width: 54px; }                                                /* PROJ (was 34, widened +20 — fits 5-char "CCKL2" with ~4px headroom; 4-char codes like "WELP"/"VPLH" no longer ellipsis-clip) */
    [data-section="pp-pay-hold"] .pp-table thead th:nth-child(4),
    [data-section="pp-pay-hold"] .pp-table tbody td:nth-child(4)  { width: 38px; }                                                /* LOT (body wraps) */
    [data-section="pp-pay-hold"] .pp-table thead th:nth-child(5),
    [data-section="pp-pay-hold"] .pp-table tbody td:nth-child(5)  { width: 40px; }                                                /* VENDX */
    [data-section="pp-pay-hold"] .pp-table thead th:nth-child(6),
    [data-section="pp-pay-hold"] .pp-table tbody td:nth-child(6)  { width: 72px; }                                                /* VENDOR (body wraps — donates to the 3 date columns; long names take another wrap line) */
    [data-section="pp-pay-hold"] .pp-table thead th:nth-child(7),
    [data-section="pp-pay-hold"] .pp-table tbody td:nth-child(7)  { width: 48px; }                                                /* COSTCODE */
    [data-section="pp-pay-hold"] .pp-table thead th:nth-child(8),
    [data-section="pp-pay-hold"] .pp-table tbody td:nth-child(8)  { width: 56px; }                                                /* DESCRIPTION (body wraps — donates to the 3 date columns; long descriptions take another wrap line) */
    [data-section="pp-pay-hold"] .pp-table thead th:nth-child(9),
    [data-section="pp-pay-hold"] .pp-table tbody td:nth-child(9)  { width: 34px; text-align: center; }                            /* COSTTYPE */
    [data-section="pp-pay-hold"] .pp-table thead th:nth-child(10),
    [data-section="pp-pay-hold"] .pp-table tbody td:nth-child(10) { width: 88px; white-space: nowrap !important; }                /* INV DATE (stacked w/ AGED DAYS) — 86px holds a full YYYY-MM-DD without clipping */
    [data-section="pp-pay-hold"] .pp-table thead th:nth-child(11),
    [data-section="pp-pay-hold"] .pp-table tbody td:nth-child(11) { width: 44px; }                                                /* INVOICE # (body wraps) */
    [data-section="pp-pay-hold"] .pp-table thead th:nth-child(12),
    [data-section="pp-pay-hold"] .pp-table tbody td:nth-child(12) { width: 88px; white-space: nowrap !important; }                /* DUE DATE (standalone) — 86px holds a full YYYY-MM-DD without clipping */
    [data-section="pp-pay-hold"] .pp-table thead th:nth-child(13),
    [data-section="pp-pay-hold"] .pp-table tbody td:nth-child(13) { width: 88px; white-space: nowrap !important; }                /* APPROVED DATE (standalone ISO date) — 86px + nowrap so YYYY-MM-DD never clips */
    [data-section="pp-pay-hold"] .pp-table thead th:nth-child(14),
    [data-section="pp-pay-hold"] .pp-table tbody td:nth-child(14) { width: 84px; text-align: right; font-variant-numeric: tabular-nums; }   /* Amount to Pay (money) — explicit width so it stops absorbing all slack as the lone auto column (the old dead gap) */
    [data-section="pp-pay-hold"] .pp-table tbody td:nth-child(4),
    [data-section="pp-pay-hold"] .pp-table tbody td:nth-child(6),
    [data-section="pp-pay-hold"] .pp-table tbody td:nth-child(8),
    [data-section="pp-pay-hold"] .pp-table tbody td:nth-child(11) {
        white-space: normal !important;
        overflow: visible;
        text-overflow: clip;
        word-break: break-word;
        line-height: 1.25;
    }

    /* ================================================================ */
    /* Section 2 — Processing table (10 columns).                       */
    /* Columns: STATUS, POX, VENDX, VENDOR, INV DATE, INVOICE #, TOTAL, */
    /* DISCOUNT, NET, ACTION. Sum = 696px (room to spare at 768).       */
    /* Wraps body cells of STATUS (badge fits "Insurance Expired"),     */
    /* VENDOR (long names), INVOICE # (long invoice numbers).           */
    /* ================================================================ */
    .pp-processing-table {
        table-layout: fixed;
        font-size: 0.78rem;
        width: 100%;
    }
    .pp-processing-table thead th {
        padding: 0.4rem 0.3rem;
        font-size: 0.72rem;
        font-weight: 600;
        white-space: nowrap !important;
        overflow: hidden;
        text-overflow: ellipsis;
        vertical-align: bottom;
    }
    .pp-processing-table tbody td {
        padding: 0.4rem 0.3rem;
        white-space: nowrap;
        overflow: hidden;
        text-overflow: ellipsis;
        vertical-align: top;
    }
    .pp-processing-table thead th:nth-child(1),
    .pp-processing-table tbody td:nth-child(1)  { width: 78px; }                                              /* STATUS (body wraps; badge wraps via override below) */
    .pp-processing-table thead th:nth-child(2),
    .pp-processing-table tbody td:nth-child(2)  { width: 56px; white-space: nowrap !important; }              /* POX */
    .pp-processing-table thead th:nth-child(3),
    .pp-processing-table tbody td:nth-child(3)  { width: 42px; }                                              /* VENDX */
    .pp-processing-table thead th:nth-child(4),
    .pp-processing-table tbody td:nth-child(4)  { width: 110px; }                                             /* VENDOR (body wraps) */
    .pp-processing-table thead th:nth-child(5),
    .pp-processing-table tbody td:nth-child(5)  { width: 65px; white-space: nowrap !important; }              /* INV DATE */
    .pp-processing-table thead th:nth-child(6),
    .pp-processing-table tbody td:nth-child(6)  { width: 55px; }                                              /* INVOICE # (body wraps) */
    .pp-processing-table thead th:nth-child(7),
    .pp-processing-table tbody td:nth-child(7)  { width: 70px; text-align: right; font-variant-numeric: tabular-nums; }   /* TOTAL */
    .pp-processing-table thead th:nth-child(8),
    .pp-processing-table tbody td:nth-child(8)  { width: 58px; text-align: right; font-variant-numeric: tabular-nums; }   /* DISCOUNT */
    .pp-processing-table thead th:nth-child(9),
    .pp-processing-table tbody td:nth-child(9)  { width: 72px; text-align: right; font-weight: 600; font-variant-numeric: tabular-nums; }  /* NET */
    .pp-processing-table thead th:nth-child(10),
    .pp-processing-table tbody td:nth-child(10) { width: 90px; text-align: center; }                          /* ACTION */
    .pp-processing-table tbody td:nth-child(1),
    .pp-processing-table tbody td:nth-child(4),
    .pp-processing-table tbody td:nth-child(6) {
        white-space: normal !important;
        overflow: visible;
        text-overflow: clip;
        word-break: break-word;
        line-height: 1.25;
    }
    /* `.pp-badge` is `white-space: nowrap` globally; let "Insurance Expired"
       wrap inside the narrow tablet STATUS cell rather than overflow. */
    .pp-processing-table tbody td:nth-child(1) .pp-badge {
        white-space: normal;
        line-height: 1.15;
    }

    /* ================================================================ */
    /* Section 2 — Vendor Summary table (8 columns).                    */
    /* Columns: VENDX, VENDOR, # Records, Gross, Discount, Net, STATUS, */
    /* ACTION. Sum = 590px (compact, no gap-stretching at the end).     */
    /* tfoot has colspan="3" for "Run Total" — the markup centers it    */
    /* over the first three columns; nth-child widths below apply.      */
    /* ================================================================ */
    .pp-summary .pp-table {
        table-layout: fixed;
        font-size: 0.78rem;
        width: 100%;
    }
    .pp-summary .pp-table thead th,
    .pp-summary .pp-table tfoot th {
        padding: 0.4rem 0.3rem;
        font-size: 0.72rem;
        font-weight: 600;
        white-space: nowrap !important;
        overflow: hidden;
        text-overflow: ellipsis;
        vertical-align: bottom;
    }
    .pp-summary .pp-table tbody td {
        padding: 0.4rem 0.3rem;
        white-space: nowrap;
        overflow: hidden;
        text-overflow: ellipsis;
        vertical-align: middle;
    }
    .pp-summary .pp-table th:nth-child(1),
    .pp-summary .pp-table td:nth-child(1) { width: 50px; }                                                  /* VENDX */
    .pp-summary .pp-table th:nth-child(2),
    .pp-summary .pp-table td:nth-child(2) { width: 135px; }                                                 /* VENDOR (body wraps) */
    .pp-summary .pp-table th:nth-child(3),
    .pp-summary .pp-table td:nth-child(3) { width: 45px; text-align: right; }                               /* # Records */
    .pp-summary .pp-table th:nth-child(4),
    .pp-summary .pp-table td:nth-child(4) { width: 72px; text-align: right; font-variant-numeric: tabular-nums; }   /* Gross */
    .pp-summary .pp-table th:nth-child(5),
    .pp-summary .pp-table td:nth-child(5) { width: 62px; text-align: right; font-variant-numeric: tabular-nums; }   /* Discount */
    .pp-summary .pp-table th:nth-child(6),
    .pp-summary .pp-table td:nth-child(6) { width: 72px; text-align: right; font-weight: 600; font-variant-numeric: tabular-nums; }   /* Net */
    .pp-summary .pp-table th:nth-child(7),
    .pp-summary .pp-table td:nth-child(7) { width: 62px; }                                                  /* STATUS */
    .pp-summary .pp-table th:nth-child(8),
    .pp-summary .pp-table td:nth-child(8) { width: 92px; text-align: center; }                              /* ACTION */
    .pp-summary .pp-table tbody td:nth-child(2),
    .pp-summary .pp-table tbody td:nth-child(7) {
        white-space: normal;
        overflow: visible;
        text-overflow: clip;
        word-break: break-word;
        line-height: 1.25;
    }
    /* STATUS badge inside the summary STATUS cell wraps too. */
    .pp-summary .pp-table tbody td:nth-child(7) .pp-badge {
        white-space: normal;
        line-height: 1.15;
    }
}

/* (Removed) The former ≤991.98px "hide 4 columns" block for the Pay Hold
   table is gone: the 14th column (APPROVED DATE) pushed three full-date
   columns into the table, which no longer fits below ~1150px content, so
   the Pay Hold table now folds to the CARD view below xl (1200px) — see its
   template wrapper (d-none d-xl-block) and Section 1 of the tablet table
   block. Below 1200 there is no Pay Hold table to trim, so column-hiding
   there is moot; the full data lives in the card and in PO Detail. */

/* ---------------------------------------------------------------- */
/* Payment Date prominence (Item 1)                                 */
/* ---------------------------------------------------------------- */
.pp-payment-date-row {
    background-color: #ffffff;
    border: 1px solid #cbe1fd;
    border-radius: 6px;
    padding: 0.75rem 1rem;
}
.theme-dark .pp-payment-date-row {
    background-color: var(--surface);
    border: 1px solid var(--border);
}
.pp-payment-date-label {
    font-weight: 700;
    font-size: 1rem;
    color: var(--text);
    margin-bottom: 0;
}
.pp-required { color: var(--pp-status-danger-fg); margin-left: 0.15rem; }
.pp-payment-date-input { max-width: 200px; }
.pp-payment-date-hint { color: var(--muted); font-size: 0.85rem; }

/* ================================================================ */
/* Portal sidebar (Phase 1: Dashboard + Payment Processor)          */
/* ================================================================ */

:root {
    --portal-sidebar-width: 240px;
    --portal-sidebar-collapsed-width: 60px;
    --portal-navbar-h: 70px; /* approximate navbar height — logo 46px + padding/border */
    --portal-sidebar-bg: var(--surface, #f5f7fb);
    --portal-sidebar-border: var(--border);
    --portal-sidebar-active-bg: rgba(41, 124, 230, 0.12);
    --portal-sidebar-active-fg: var(--text);
    --portal-sidebar-active-accent: var(--selected-row-accent, #297ce6);
    --portal-sidebar-hover-bg: var(--btn-hover-bg, rgba(0, 0, 0, 0.04));
    --portal-sidebar-disabled-fg: var(--muted, #888);
}
.theme-dark {
    --portal-sidebar-bg: var(--card-bg);
    --portal-sidebar-active-bg: rgba(137, 198, 255, 0.18);
    --portal-sidebar-disabled-fg: var(--muted, #888);
}

/* Dark-mode sidebar contrast tuning — non-active nav items and group heads
   use a lighter alpha so they read clearly against the dark sidebar bg
   while staying visibly subordinate to the active item. Light mode keeps
   its existing var(--text) / var(--portal-sidebar-active-fg) treatment. */
.theme-dark .portal-sidebar-item {
    color: rgba(255, 255, 255, 0.75);
}
.theme-dark .portal-sidebar-item:hover {
    color: rgba(255, 255, 255, 0.92);
}
.theme-dark .portal-sidebar-item.active {
    color: #ffffff;
}
.theme-dark .portal-sidebar-group-head {
    color: rgba(255, 255, 255, 0.5);
}
.theme-dark .portal-sidebar-group-head:hover {
    color: rgba(255, 255, 255, 0.7);
}
.theme-dark .portal-sidebar-foot-item {
    color: rgba(255, 255, 255, 0.75);
}
.theme-dark .portal-sidebar-foot-item:hover {
    color: rgba(255, 255, 255, 0.92);
}

/* Top bar — hamburger + username are only visible on pages that include
   the sidebar partial (gated by body.with-sidebar). The orange brand
   divider already lives on the existing .navbar rule above. */
.portal-topbar-toggle,
.portal-topbar-user { display: none; }
body.with-sidebar .portal-topbar-toggle {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    background: transparent;
    border: 1px solid rgba(31, 52, 83, 0.26);
    color: #1b2432;
    font-size: 1.25rem;
    line-height: 1;
    padding: 0.3rem 0.55rem;
    border-radius: 6px;
    cursor: pointer;
    margin-right: 0.6rem;
}
body.with-sidebar .portal-topbar-toggle:hover {
    background: rgba(93, 163, 255, 0.08);
}
/* `body.with-sidebar .portal-topbar-toggle` (light) is (0,2,1); compose
   .theme-dark with .with-sidebar on body to win specificity in dark mode. */
body.theme-dark.with-sidebar .portal-topbar-toggle {
    color: rgba(255, 255, 255, 0.85);
    border-color: var(--border);
}
body.with-sidebar .portal-topbar-user {
    display: inline-flex;
    align-items: center;
    gap: 0.35rem;
    color: #1b2432;
    font-weight: 600;
    font-size: 0.9rem;
}
/* Selector must combine .theme-dark and .with-sidebar on the same body
   element — both classes live on <body>, so a descendant selector never
   matched. Composed selector is (0,3,1) and wins over the light-mode
   `body.with-sidebar .portal-topbar-user` rule (0,2,1). */
body.theme-dark.with-sidebar .portal-topbar-user { color: rgba(255, 255, 255, 0.9); }
.portal-topbar-user-name {
    max-width: 160px;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* Pin the navbar at the top and pad main so content clears it,
   but only on sidebar pages — other pages keep their existing flow. */
body.with-sidebar > nav.navbar {
    position: fixed;
    top: 0;
    left: 0;
    right: 0;
    z-index: 1031;
}
/* Hide the navbar Settings dropdown on sidebar pages — those controls now
   live in the sidebar footer. The dropdown stays in the DOM (with its
   #theme-toggle-btn listener intact) so the sidebar's theme-toggle shim
   can fire it programmatically. */
body.with-sidebar .topbar-settings-menu { display: none; }
/* Page layout wrapper — block flow. Sidebar is position:fixed at all
   viewports so its width never affects the content layout. Login and
   other non-sidebar pages render with no special framing. */
.portal-layout { /* default: no styling so login etc. render normally */ }
body.with-sidebar .portal-layout {
    margin-top: var(--portal-navbar-h);
    /* `100dvh` paired with `100vh` fallback. On iOS / iPadOS Safari
       `100vh` resolves to the *large* viewport (toolbars retracted),
       which is taller than the actually visible area when toolbars are
       showing — that mismatch is what was forcing page scroll on
       tablet and mobile. `100dvh` tracks the visible viewport
       dynamically. Older browsers ignore the dvh declaration and use
       the vh one. */
    min-height: calc(100vh - var(--portal-navbar-h));
    min-height: calc(100dvh - var(--portal-navbar-h));
}
/* Content area — full width by default (tablet/mobile). Desktop adds a
   CONSTANT 240px left margin in the (min-width: 1200px) block below.
   The margin never changes on sidebar collapse — collapsing only
   shrinks the sidebar's visual footprint inside its fixed 240px lane. */
.portal-content {
    min-width: 0;
    overflow-x: auto;
    /* No transition on .portal-content. */
}

/* Sidebar — position:fixed on every viewport. Desktop: stays at 240px
   (or 60px collapsed) in its fixed left lane. Tablet/mobile: slides
   off-screen via translateX, hamburger toggles the slide-in overlay. */
.portal-sidebar {
    position: fixed;
    top: var(--portal-navbar-h);
    left: 0;
    bottom: 0;
    width: var(--portal-sidebar-width);
    z-index: 1030;
    background: var(--portal-sidebar-bg);
    border-right: 1px solid var(--portal-sidebar-border);
    color: var(--text);
    display: flex;
    flex-direction: column;
    overflow: hidden;
    transition: width 0.2s ease, transform 0.2s ease;
}

.portal-sidebar-nav {
    padding: 0.25rem 0;
    flex: 1 1 auto;
    overflow-y: auto;
    min-height: 0;
}

/* Footer area pinned at the bottom of the sidebar */
.portal-sidebar-footer {
    flex: 0 0 auto;
    border-top: 1px solid var(--portal-sidebar-border);
    padding: 0.4rem 0;
    background: var(--portal-sidebar-bg);
}
.portal-sidebar-foot-item,
.portal-sidebar-foot-user {
    display: flex;
    align-items: center;
    gap: 0.7rem;
    width: 100%;
    background: transparent;
    border: 0;
    padding: 0.45rem 0.85rem;
    color: var(--text);
    text-decoration: none;
    font-size: 0.92rem;
    cursor: pointer;
    text-align: left;
}
.portal-sidebar-foot-item:hover {
    background: var(--portal-sidebar-hover-bg);
    color: var(--text);
}
.portal-sidebar-foot-user { cursor: default; }
.portal-sidebar-foot-user .portal-sidebar-label {
    font-weight: 600;
    overflow: hidden;
    text-overflow: ellipsis;
}

/* (Old in-sidebar logo / mobile topbar rules removed — the top bar
   in base.html now owns the brand identity and the hamburger.) */

/* Group block ---------------------------------------------------- */
.portal-sidebar-group { margin: 0; }
.portal-sidebar-group-head {
    display: flex;
    align-items: center;
    gap: 0.6rem;
    width: 100%;
    background: transparent;
    border: 0;
    color: var(--text);
    text-align: left;
    padding: 0.55rem 0.85rem;
    font-weight: 600;
    font-size: 0.85rem;
    letter-spacing: 0.02em;
    text-transform: uppercase;
    cursor: pointer;
}
.portal-sidebar-group-head:hover { background: var(--portal-sidebar-hover-bg); }
.portal-sidebar-caret {
    margin-left: auto;
    transition: transform 0.18s ease;
    font-size: 0.85rem;
}
.portal-sidebar-group.group-collapsed .portal-sidebar-caret {
    transform: rotate(-90deg);
}
.portal-sidebar-group.group-collapsed .portal-sidebar-group-body {
    display: none;
}

/* Item ----------------------------------------------------------- */
.portal-sidebar-item {
    display: flex;
    align-items: center;
    gap: 0.7rem;
    padding: 0.5rem 0.85rem 0.5rem 1.6rem;
    color: var(--text);
    text-decoration: none;
    font-size: 0.93rem;
    border-left: 3px solid transparent;
    line-height: 1.3;
}
.portal-sidebar-item:hover {
    background: var(--portal-sidebar-hover-bg);
    color: var(--text);
}
.portal-sidebar-item.active {
    background: var(--portal-sidebar-active-bg);
    color: var(--portal-sidebar-active-fg);
    border-left-color: var(--portal-sidebar-active-accent);
    font-weight: 600;
}
.portal-sidebar-item.disabled {
    color: var(--portal-sidebar-disabled-fg);
    cursor: not-allowed;
    opacity: 0.7;
}
.portal-sidebar-item.disabled:hover { background: transparent; }
.portal-sidebar-icon {
    font-size: 1.05rem;
    width: 1.25rem;
    flex: 0 0 auto;
    text-align: center;
}
.portal-sidebar-label {
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

/* Hamburger button (lives inside the mobile topbar) -------------- */
.portal-sidebar-hamburger {
    background: transparent;
    color: var(--text);
    border: 1px solid var(--border);
    border-radius: 6px;
    padding: 0.25rem 0.5rem;
    line-height: 1;
    font-size: 1.1rem;
    cursor: pointer;
}
.portal-sidebar-hamburger:hover { background: var(--portal-sidebar-hover-bg); }

/* Backdrop (tablet / mobile when open) --------------------------- */
.portal-sidebar-backdrop {
    position: fixed;
    inset: 0;
    background: rgba(0, 0, 0, 0.35);
    z-index: 1029;
    display: none;
}
body.sidebar-open .portal-sidebar-backdrop { display: block; }

/* Desktop layout — fixed sidebar in its own 240px lane; content has a
   CONSTANT 240px left margin that never changes when the sidebar
   collapses. Collapsing shrinks the sidebar visually within its lane,
   leaving an intentional 180px gap (Gmail / GitHub pattern) — content
   does not move.

   The auto right margin mirrors the 240px left margin: capping the
   element's max-width at `viewport - 2*sidebar` lets margin-right: auto
   absorb exactly 240px on the right, so the content sits visually
   balanced (sidebar-width gap on the left, equal sidebar-width gap on
   the right). The Bootstrap container class (.container or
   .container-fluid) is on the same <main> element as .portal-content
   in base.html, so the centering fix lives here.
   Desktop push-layout only on non-touch devices — touch devices at xl+
   (iPad Pro 12.9" landscape) fall through to the tablet overlay block
   below via the pointer/hover clause. */
@media (min-width: 1200px) and (hover: hover) and (pointer: fine) {
    /* Permanent 240px left margin to clear the fixed sidebar. Never
       changes when the sidebar collapses to 60px — the resulting gap
       is intentional. No transitions, no JS overrides. The max-width
       caps the content at the available width (parent minus the
       sidebar lane) so wide tables wrap/scroll inside the content
       column rather than overflowing the page.
       Uses `100%` rather than `100vw` so the cap is taken from the
       body's content box (which already excludes the vertical
       scrollbar lane); using `100vw` here adds ~15px of horizontal
       overflow whenever the page is tall enough to show a scrollbar
       on systems with classic, space-consuming scrollbars. */
    .portal-content {
        margin-left: 240px;
        max-width: calc(100% - 240px);
        padding-left: 30px;
        padding-right: 30px;
        box-sizing: border-box;
    }
    /* Sidebar collapsed — re-center content within the viewport by
       matching the 60px sidebar width on the left and adding a 60px
       gutter on the right. max-width is recalculated so padding +
       border-box still keep total footprint within bounds. */
    body.sidebar-collapsed .portal-content {
        margin-left: var(--portal-sidebar-collapsed-width);
        margin-right: var(--portal-sidebar-collapsed-width);
        max-width: calc(100% - (var(--portal-sidebar-collapsed-width) * 2));
    }
    body.sidebar-collapsed .portal-sidebar {
        width: var(--portal-sidebar-collapsed-width);
    }
    body.sidebar-collapsed .portal-sidebar-label { display: none; }
    body.sidebar-collapsed .portal-sidebar-caret { display: none; }
    body.sidebar-collapsed .portal-sidebar-group-head {
        justify-content: center;
        padding: 0.55rem 0.4rem;
    }
    body.sidebar-collapsed .portal-sidebar-item {
        padding: 0.5rem 0.4rem;
        justify-content: center;
        border-left-width: 0;
    }
    body.sidebar-collapsed .portal-sidebar-item.active {
        border-left-width: 3px;
    }
    body.sidebar-collapsed .portal-sidebar-foot-item {
        justify-content: center;
        padding: 0.5rem 0.4rem;
    }

    /* Hover tooltips when the sidebar is collapsed — labels are hidden,
       so each icon needs an inline label cue. Theme-aware. */
    body.sidebar-collapsed .portal-sidebar-item,
    body.sidebar-collapsed .portal-sidebar-foot-item {
        position: relative;
    }
    body.sidebar-collapsed .portal-sidebar-item[data-tooltip]:hover::after,
    body.sidebar-collapsed .portal-sidebar-item[data-tooltip]:focus-visible::after,
    body.sidebar-collapsed .portal-sidebar-foot-item[data-tooltip]:hover::after,
    body.sidebar-collapsed .portal-sidebar-foot-item[data-tooltip]:focus-visible::after {
        content: attr(data-tooltip);
        position: absolute;
        left: calc(100% + 6px);
        top: 50%;
        transform: translateY(-50%);
        background: var(--card-bg, #fff);
        color: var(--text);
        border: 1px solid var(--border);
        padding: 0.3rem 0.6rem;
        border-radius: 4px;
        white-space: nowrap;
        font-size: 0.85rem;
        font-weight: normal;
        line-height: 1.2;
        z-index: 1100;
        box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
        pointer-events: none;
    }
}

/* Tablet / mobile layout — sidebar is off-screen (always 240px wide)
   and slides in as overlay; content stays full width with no left
   margin. Sidebar is already position:fixed in the base rule above.
   Also applies on touch devices at xl+ widths (iPad Pro 12.9" landscape)
   so they get the overlay sidebar + full-width content, not the 240px
   desktop push layout. */
@media (max-width: 1199.98px),
       (min-width: 1200px) and (hover: none) and (pointer: coarse) {
    /* Reset desktop's margin/max-width so Bootstrap's `.container`
       max-width doesn't leave the content left-justified inside a
       narrower box. Full-width fill with even 16px gutters on both
       sides; box-sizing keeps padding inside the viewport. */
    .portal-content {
        margin-left: 50px;
        margin-right: 50px;
        max-width: calc(100% - 100px);
        padding-left: 24px;
        padding-right: 24px;
        box-sizing: border-box;
    }
    .portal-sidebar {
        width: var(--portal-sidebar-width);
        transform: translateX(-100%);
    }
    body.sidebar-open .portal-sidebar {
        transform: translateX(0);
        box-shadow: 0 4px 16px rgba(0, 0, 0, 0.18);
    }
    /* Backdrop should sit below the navbar but above content. */
    .portal-sidebar-backdrop { top: var(--portal-navbar-h); }
}

/* Mobile-only override — drop the 50px tablet margins and tighten
   horizontal padding so cards and tables use the full narrow viewport.
   8px keeps content off the bezel but doesn't waste valuable width. */
@media (max-width: 767.98px) {
    .portal-content {
        margin-left: 0;
        margin-right: 0;
        max-width: 100%;
        padding-left: 8px;
        padding-right: 8px;
        box-sizing: border-box;
    }
}

/* ---------------------------------------------------------------- */
/* Print Ledger Report                                              */
/* ---------------------------------------------------------------- */

.plr-main { padding-top: 1rem; }
.plr-precard-wrap { padding-top: 6vh; }
.plr-precard {
    width: 100%;
    max-width: 520px;
    background-color: #fbfcfd;
    border: 1px solid #dee2e6;
}
/* Dark mode: keep the existing pp-card surface + border. */
.theme-dark .plr-precard {
    background-color: var(--card-bg);
    border-color: var(--border);
}

/* Post-generate page header card: title + subtitle + Check Run Date input
   + Generate / Print / Download CSV / Download PDF buttons. */
.plr-header-card {
    background-color: #fbfcfd;
    border: 1px solid #dee2e6;
    border-radius: 6px;
    padding: 0.75rem 1rem;
}
.theme-dark .plr-header-card {
    background-color: var(--card-bg);
    border-color: var(--border);
}
.plr-report { color: var(--text); }
.plr-header h2 { color: var(--text); }
.plr-vendor {
    border: 1px solid var(--border);
    border-radius: 6px;
    padding: 0.75rem 1rem;
    background: #fbfcfd;
}
/* Dark mode: keep the existing surface tone for vendor sections. */
.theme-dark .plr-vendor {
    background: var(--surface);
}
.plr-vendor-name { font-weight: 600; font-size: 1rem; }
.plr-vendor-total { font-weight: 600; font-variant-numeric: tabular-nums; }
.plr-grand-total {
    font-size: 1.1rem;
    font-weight: 700;
    font-variant-numeric: tabular-nums;
    color: var(--text);
}
.plr-table thead th {
    background: var(--table-head-bg);
    color: var(--text);
}
.plr-money { font-variant-numeric: tabular-nums; }
/* Check Run Report — company header + per-vendor subtotal banding. */
.plr-company {
    font-weight: 700;
    font-size: 1.05rem;
    color: var(--text);
    margin-bottom: 0.15rem;
}
.plr-report-title { color: var(--text); }
.plr-vendor-subtotal > td {
    background: rgba(0, 0, 0, 0.04);
    border-top: 1px solid var(--border);
    border-bottom: 2px solid var(--border);
}
.theme-dark .plr-vendor-subtotal > td {
    background: rgba(255, 255, 255, 0.06);
}
.plr-grand-total-row > th {
    border-top: 2px solid var(--border);
    font-weight: 700;
}
.plr-footer {
    margin-top: 1rem;
    padding-top: 0.75rem;
    border-top: 1px solid var(--border);
}

/* Per-row eye toggle: client-side, session-only excluder. A hidden row stays
   in the DOM (struck-through and dimmed) so the user can toggle it back —
   the export forms and recompute() read the .plr-row-hidden class. The
   toggle column is .plr-no-print so the printable HTML view and the
   theme-independent PDF render keep today's five-column layout. */
.plr-toggle-col, .plr-toggle-cell {
    width: 36px;
    text-align: center;
    padding-left: 4px;
    padding-right: 4px;
}
.plr-toggle-btn {
    background: transparent;
    border: 1px solid transparent;
    padding: 2px 6px;
    color: var(--muted);
    cursor: pointer;
    border-radius: 4px;
    line-height: 1;
}
.plr-toggle-btn:hover,
.plr-toggle-btn:focus-visible {
    color: var(--text);
    border-color: var(--border);
    background: var(--btn-hover-bg);
    outline: none;
}
.plr-toggle-btn[aria-pressed="true"] {
    color: var(--danger, #b02a37);
}
.theme-dark .plr-toggle-btn[aria-pressed="true"] {
    color: #ff8b94;
}
.plr-data-row.plr-row-hidden > td {
    opacity: 0.5;
    text-decoration: line-through;
}
/* The toggle cell itself must stay fully opaque and not struck through so
   the user can clearly see and click the eye to bring the row back. */
.plr-data-row.plr-row-hidden > td.plr-toggle-cell {
    opacity: 1;
    text-decoration: none;
}

/* Recent Runs — card-style date picker on the pre-generate screen. */
.plr-recent-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(96px, 1fr));
    gap: 0.5rem;
}
.plr-recent-card {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 0.15rem;
    padding: 0.6rem 0.5rem;
    background: var(--card-bg);
    border: 1px solid var(--border);
    border-radius: 8px;
    color: var(--text);
    cursor: pointer;
    transition: border-color 120ms ease, background-color 120ms ease, transform 120ms ease;
}
.plr-recent-card:hover,
.plr-recent-card:focus-visible {
    border-color: var(--link);
    background: var(--btn-hover-bg);
    transform: translateY(-1px);
    outline: none;
}
.plr-recent-md {
    font-weight: 600;
    font-size: 0.95rem;
    line-height: 1.1;
    color: var(--text);
}
.plr-recent-yr {
    font-size: 0.78rem;
    color: var(--muted);
    line-height: 1.1;
}

@media print {
    /* Force a clean printable layout regardless of light/dark theme. */
    body, html { background: #fff !important; color: #000 !important; }
    .plr-no-print, nav, header.app-header, footer.app-footer { display: none !important; }
    .plr-main { padding: 0 !important; }
    .plr-vendor {
        background: #fff !important;
        color: #000 !important;
        border-color: #888 !important;
        page-break-inside: avoid;
    }
    .plr-table { color: #000 !important; }
    .plr-table thead th {
        background: #eee !important;
        color: #000 !important;
        border-bottom: 1px solid #888 !important;
    }
    .plr-vendor-subtotal > td {
        background: #f1f1f1 !important;
        border-top: 1px solid #888 !important;
        border-bottom: 2px solid #888 !important;
        color: #000 !important;
    }
    .plr-grand-total-row > th {
        border-top: 2px solid #888 !important;
        color: #000 !important;
    }
    .plr-company { color: #000 !important; }
    .plr-footer { border-top-color: #888 !important; }
}

/* ================================================================ */
/* Notification Center — bell button + slide-in panel                */
/* ================================================================ */

/* Bell trigger sits in the topbar to the left of the username. Light mode
   uses var(--text) on a white navbar; .theme-dark restores the white-ish
   styling against the dark navbar. Mirrors the .navbar .btn-outline-light
   pattern used elsewhere in the topbar. */
.portal-notify-toggle {
    position: relative;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 36px;
    height: 36px;
    padding: 0;
    border: 1px solid rgba(31, 52, 83, 0.26);
    border-radius: 6px;
    background: transparent;
    color: var(--text);
    font-size: 1.1rem;
    line-height: 1;
    cursor: pointer;
    transition: background-color 0.15s ease, border-color 0.15s ease;
}
.portal-notify-toggle:hover,
.portal-notify-toggle:focus-visible {
    background-color: rgba(93, 163, 255, 0.08);
    border-color: rgba(31, 52, 83, 0.35);
    outline: none;
}
.theme-dark .portal-notify-toggle {
    border-color: rgba(131, 164, 206, 0.42);
    color: var(--text);
}
.theme-dark .portal-notify-toggle:hover,
.theme-dark .portal-notify-toggle:focus-visible {
    background-color: rgba(117, 182, 255, 0.18);
    border-color: rgba(157, 194, 239, 0.55);
}
.portal-notify-badge {
    position: absolute;
    top: -4px;
    right: -4px;
    min-width: 18px;
    height: 18px;
    padding: 0 4px;
    border-radius: 9px;
    background: var(--selected-row-accent, #297ce6);
    color: #fff;
    font-size: 0.68rem;
    font-weight: 700;
    line-height: 18px;
    text-align: center;
    box-shadow: 0 0 0 2px var(--bs-dark, #212529);
    pointer-events: none;
}
.portal-notify-badge[hidden] { display: none; }

/* Backdrop sits behind the panel (above content, below topbar). */
.portal-notify-backdrop {
    position: fixed;
    top: var(--portal-navbar-h, 70px);
    left: 0;
    right: 0;
    bottom: 0;
    background: rgba(0, 0, 0, 0.32);
    opacity: 0;
    transition: opacity 0.2s ease;
    z-index: 1029;
}
.portal-notify-backdrop[hidden] { display: none; }
.portal-notify-backdrop.is-open { opacity: 1; }

/* Panel — fixed to the right edge, full height below the topbar.
   Translates off-screen by default; .is-open slides it in. Frosted-glass
   treatment: near-opaque background + backdrop blur so text reads cleanly
   while a hint of the page behind shows through. */
.portal-notify-panel {
    position: fixed;
    top: var(--portal-navbar-h, 70px);
    right: 0;
    bottom: 0;
    width: 320px;
    max-width: 100vw;
    background: rgba(255, 255, 255, 0.95);
    backdrop-filter: blur(12px);
    -webkit-backdrop-filter: blur(12px);
    color: var(--text);
    border-left: 1px solid rgba(0, 0, 0, 0.1);
    box-shadow: -4px 0 24px rgba(0, 0, 0, 0.08);
    transform: translateX(100%);
    transition: transform 0.25s ease;
    z-index: 1030;
    display: flex;
    flex-direction: column;
    overflow: hidden;
}
.theme-dark .portal-notify-panel {
    background: rgba(30, 32, 40, 0.97);
    border-left: 1px solid rgba(255, 255, 255, 0.08);
    box-shadow: -4px 0 24px rgba(0, 0, 0, 0.3);
}
.portal-notify-panel.is-open { transform: translateX(0); }

.portal-notify-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 0.5rem;
    padding: 0.75rem 0.9rem;
    border-bottom: 1px solid var(--border, #dee2e6);
    background: transparent;
}
.portal-notify-title {
    margin: 0;
    font-size: 1rem;
    font-weight: 600;
    color: var(--text);
}
.portal-notify-head-actions {
    display: flex;
    align-items: center;
    gap: 0.25rem;
}
.portal-notify-clear {
    padding: 0.15rem 0.45rem;
    font-size: 0.85rem;
    color: var(--selected-row-accent, #297ce6);
    text-decoration: none;
}
.portal-notify-clear:hover { text-decoration: underline; }
.portal-notify-close {
    border: 0;
    background: transparent;
    color: var(--muted);
    width: 32px;
    height: 32px;
    border-radius: 6px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    cursor: pointer;
}
.portal-notify-close:hover,
.portal-notify-close:focus-visible {
    background-color: rgba(127, 127, 127, 0.12);
    color: var(--text);
    outline: none;
}

.portal-notify-list {
    flex: 1;
    min-height: 0;
    overflow-y: auto;
    padding: 0.25rem 0;
}
.portal-notify-list[hidden] { display: none; }

.portal-notify-item {
    display: flex;
    align-items: flex-start;
    gap: 0.6rem;
    padding: 0.7rem 0.9rem;
    border-bottom: 1px solid var(--border, #dee2e6);
    background: transparent;
}
.portal-notify-item.is-unread {
    background: color-mix(in srgb, var(--selected-row-accent, #297ce6) 8%, transparent);
}
.portal-notify-item-icon {
    flex: 0 0 auto;
    width: 26px;
    height: 26px;
    border-radius: 50%;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    font-size: 0.95rem;
    line-height: 1;
}
.pn-type-success {
    background: color-mix(in srgb, #2da06b 18%, transparent);
    color: #2da06b;
}
.pn-type-danger {
    background: color-mix(in srgb, #d9534f 18%, transparent);
    color: #d9534f;
}
.pn-type-warning {
    background: color-mix(in srgb, #d2961f 20%, transparent);
    color: #d2961f;
}
.pn-type-info {
    background: color-mix(in srgb, var(--selected-row-accent, #297ce6) 18%, transparent);
    color: var(--selected-row-accent, #297ce6);
}
.portal-notify-item-body {
    min-width: 0;
    flex: 1;
}
.portal-notify-item-msg {
    font-size: 0.9rem;
    line-height: 1.35;
    color: var(--text);
    word-break: break-word;
}
.portal-notify-item-meta {
    margin-top: 0.2rem;
    font-size: 0.75rem;
    color: var(--muted);
    display: flex;
    align-items: center;
    gap: 0.35rem;
}
.portal-notify-item-sep { opacity: 0.6; }

.portal-notify-empty {
    padding: 2rem 1rem;
    text-align: center;
    color: var(--muted);
    font-size: 0.9rem;
}
.portal-notify-empty[hidden] { display: none; }

/* Mobile — full-width panel. */
@media (max-width: 575.98px) {
    .portal-notify-panel { width: 100vw; }
}

/* =================================================================
   Payment History — read-only browser over rr3_PAID.
   Mirrors Payment Processor structurally (.pp-* analogues) but uses
   theme tokens directly so light/dark both work without overrides.
   ================================================================= */
.ph-card {
    background: var(--card-bg);
    border: 1px solid var(--border);
    color: var(--text);
}

.ph-table-wrap {
    overflow-x: auto;
}

.ph-table {
    color: var(--text);
    margin-bottom: 0;
}

.ph-table thead th {
    background: var(--table-header-bg, var(--card-bg));
    color: var(--text);
    border-bottom: 1px solid var(--border);
    white-space: nowrap;
    font-size: 0.85rem;
}

.ph-table tbody tr > * {
    background: transparent;
    color: var(--text);
    border-color: var(--border);
}

.ph-table tbody tr:nth-of-type(odd) > * {
    background: var(--table-row-alt-bg, transparent);
}

.ph-table tbody tr:hover > * {
    background: var(--table-row-hover-bg, var(--btn-hover-bg));
}

.ph-money { font-variant-numeric: tabular-nums; }
.ph-num   { font-variant-numeric: tabular-nums; }

.ph-desc {
    max-width: 360px;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.ph-col-caret {
    width: 1.75rem;
    text-align: center;
    color: var(--muted);
}

.ph-summary-row { cursor: pointer; }

.ph-caret {
    transition: transform 120ms ease-in-out;
    display: inline-block;
}

.ph-summary-row[aria-expanded="true"] .ph-caret {
    transform: rotate(90deg);
}

.ph-summary-row-open > * {
    background: var(--table-row-hover-bg, var(--btn-hover-bg)) !important;
    font-weight: 600;
}

.ph-detail-row > * {
    background: var(--glass-surface, var(--card-bg));
    border-top: 0;
}

.ph-detail-cell {
    padding: 0.75rem 1rem;
}

.ph-detail-inner {
    color: var(--text);
    margin-bottom: 0;
}

.ph-detail-inner thead th {
    background: transparent;
    color: var(--muted);
    border-bottom: 1px solid var(--border);
    font-size: 0.78rem;
    text-transform: uppercase;
    letter-spacing: 0.02em;
}

.ph-detail-inner tbody tr > * {
    background: transparent;
    color: var(--text);
    border-color: var(--border);
}

/* Mobile cards — same visual treatment as .pp-row-card. */
.ph-card-list {
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
}

.ph-row-card {
    display: block;
    border: 1px solid var(--border);
    background: var(--card-bg);
    border-radius: 8px;
    padding: 0.6rem 0.75rem;
    color: var(--text);
}

.ph-row-card.ph-summary-row { cursor: pointer; }

.ph-row-card-head {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    font-weight: 600;
}

.ph-row-card-vendor {
    flex: 1 1 auto;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
}

.ph-row-card-amount { font-variant-numeric: tabular-nums; }

.ph-row-card-meta {
    display: flex;
    flex-wrap: wrap;
    gap: 0.25rem 0.75rem;
    color: var(--muted);
    font-size: 0.85rem;
    margin-top: 0.25rem;
}

.ph-row-card-meta .ph-desc {
    max-width: 100%;
    white-space: normal;
}

.ph-loading {
    opacity: 0.55;
    pointer-events: none;
    transition: opacity 120ms ease-in-out;
}

.ph-search {
    flex: 1 1 240px;
    max-width: 360px;
    min-width: 200px;
}

.ph-view-toggle .btn.active {
    background: var(--link);
    color: var(--card-bg);
    border-color: var(--link);
}

/* ---------- Date range presets ---------- */
.ph-presets {
    /* Subtle row — not a card, just a strip of small action buttons. */
    margin-top: 0.25rem;
}

.ph-preset-btn {
    /* Match the small outline style used elsewhere in the portal. The
       active state below uses --link to align with the view toggle. */
    font-size: 0.85rem;
}

.ph-preset-btn.active {
    background: var(--link);
    color: var(--card-bg);
    border-color: var(--link);
}

.ph-preset-range {
    font-variant-numeric: tabular-nums;
}

.ph-custom-range {
    /* Sits directly below the preset row when Custom Range is active. */
    padding-top: 0.25rem;
}

/* ---------- Vendor Payment Summary card ---------- */
.ph-vendor-card {
    background: var(--card-bg);
    border: 1px solid var(--border);
    color: var(--text);
}

.ph-vendor-card .card-body {
    padding: 0.85rem 1rem;
}

.ph-vendor-head h3 {
    color: var(--text);
}

.ph-metric-grid {
    display: grid;
    grid-template-columns: repeat(3, minmax(0, 1fr));
    gap: 0.6rem 1rem;
}

@media (max-width: 991.98px) {
    .ph-metric-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

@media (max-width: 575.98px) {
    .ph-metric-grid { grid-template-columns: 1fr; }
}

.ph-metric {
    /* Each tile gets a thin top border so the grid reads as discrete
       metrics without needing a heavier card-within-card treatment. */
    border-top: 1px solid var(--border);
    padding-top: 0.5rem;
}

.ph-metric-label {
    color: var(--muted);
    font-size: 0.78rem;
    text-transform: uppercase;
    letter-spacing: 0.03em;
    margin-bottom: 0.15rem;
}

.ph-metric-value {
    font-size: 1.1rem;
    font-weight: 600;
    color: var(--text);
    font-variant-numeric: tabular-nums;
}

.ph-insurance {
    display: flex;
    flex-direction: column;
    gap: 0.15rem;
    font-size: 0.95rem;
    font-weight: 500;
}

.ph-ins-line {
    display: inline-flex;
    align-items: center;
    gap: 0.35rem;
}

.ph-ins-valid   { color: #198754; }    /* bootstrap success green */
.ph-ins-expired { color: #b02a37; }    /* readable red in both themes */
.ph-ins-unknown { color: var(--muted); }

/* Dark-mode bumps for status colors so they stay readable on the
   darker glass surface without needing to override anything global. */
body.theme-dark .ph-ins-valid   { color: #4ade80; }
body.theme-dark .ph-ins-expired { color: #f87171; }

/* =================================================================
   Budget Viewer
   ================================================================= */

.budget-main {
    /* Match the breathing room used by .ph-main / workspace pages so the
       sidebar transitions don't shift the budget content sideways. */
    padding-left: 1rem;
    padding-right: 1rem;
}

.budget-breadcrumb .breadcrumb {
    background: transparent;
    padding: 0;
    font-size: 0.9rem;
}

/* ---------- Hierarchy selector cards (Customers, Projects, Models, Elevations) ---------- */
.budget-customer-card,
.budget-project-card,
.budget-model-card,
.budget-elev-card {
    transition: transform 0.08s ease, box-shadow 0.08s ease, border-color 0.08s ease;
}
.budget-customer-card:hover,
.budget-project-card:hover,
.budget-model-card:hover,
.budget-elev-card:hover {
    transform: translateY(-1px);
    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
}
body.theme-dark .budget-customer-card:hover,
body.theme-dark .budget-project-card:hover,
body.theme-dark .budget-model-card:hover,
body.theme-dark .budget-elev-card:hover {
    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.4);
}

.budget-customer-icon,
.budget-project-icon,
.budget-model-icon,
.budget-elev-icon {
    /* Use the portal's --muted token (which swaps in .theme-dark) instead
       of Bootstrap's --bs-secondary which doesn't follow the dark palette. */
    font-size: 1.5rem;
    color: var(--muted);
}

.budget-customer-name,
.budget-project-code,
.budget-model-name,
.budget-elev-name {
    /* Bootstrap's --bs-body-color isn't overridden in .theme-dark, so the
       bold card label was dark-on-dark in dark mode. --text swaps cleanly. */
    font-weight: 600;
    color: var(--text);
}

/* ---------- Budget view — search bar ---------- */
.budget-search {
    flex: 1 1 320px;
    min-width: 240px;
    max-width: 460px;
}

/* ---------- Task View groups ----------
   Bootstrap's .card-header bakes in its own light background, text colour
   and border-bottom colour that don't follow the portal palette. The
   button is also a .card-header so we override all three explicitly using
   theme tokens; the rule is identical in both themes since the tokens
   themselves swap, which fixes dark mode without altering light mode. */
.budget-task-head.card-header {
    background-color: var(--card-bg);
    color: var(--text);
    border: 0;
    border-bottom: 1px solid var(--border);
    cursor: pointer;
}
.budget-task-head.card-header:hover {
    background-color: var(--glass-surface, rgba(0, 0, 0, 0.025));
}
.budget-task-head .budget-task-totals,
.budget-task-head .text-muted {
    color: var(--muted) !important;
}
.budget-task-head:focus-visible {
    outline: 2px solid var(--link, #0d6efd);
    outline-offset: -2px;
}
.budget-task-head .budget-task-caret {
    transition: transform 0.15s ease;
}
.budget-task-head[aria-expanded="true"] .budget-task-caret {
    transform: rotate(90deg);
}
.budget-task-totals {
    white-space: nowrap;
}

/* ---------- Money cells ---------- */
.budget-money {
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
}

/* ---------- Summary footer ---------- */
.budget-summary-bar {
    border-top: 1px solid var(--border);
    padding-top: 0.75rem;
}
.budget-summary-amount {
    font-weight: 600;
    font-variant-numeric: tabular-nums;
}

/* ---------- Budget view responsive — desktop / tablet / mobile ----------

   Default rules below target the desktop (≥1200px) presentation. The
   tablet block (768-1199.98px) and mobile block (≤767.98px) override
   from there. Column show/hide piggybacks on `bv-col-*` classes the
   template sprinkles on individual <th>/<td>; label swaps inside one
   cell use paired <span> wrappers `bv-label-desktop` / `bv-label-tablet`.
   No markup duplicates per viewport — each variant just hides via
   `display: none`, which screen readers correctly skip.

   The Supervisor pricing gate is server-side (Jinja `{% if not
   is_super %}`), so a Super never receives the PRICING cells; CSS
   doesn't need to know about the role. */

/* Default — desktop labels visible, tablet labels hidden. */
.bv-label-tablet,
.bv-col-pricing-tablet {
    display: none;
}

/* ---------- Tablet (768-1199.98px) — Table View 9 cols, Task View 8 cols.
   Drops FALLBACK, collapses PRICE/TOTAL_COST/TOTAL_PRICE into a single
   stacked PRICING cell, and pairs UNIT with QTY inline. Tightens cell
   padding so the surviving columns breathe without horizontal scroll.
   Touch devices at xl+ (iPad Pro 12.9" landscape) get this layout too,
   as do narrow-desktop laptops (1200–1599px, non-touch) where the
   12-column desktop table overflowed its content column at 1366–1440px;
   wide desktops (≥1600px) keep the roomy auto layout. */
@media (min-width: 768px) and (max-width: 1199.98px),
       (min-width: 1200px) and (hover: none) and (pointer: coarse),
       (min-width: 1200px) and (max-width: 1599.98px) and (hover: hover) and (pointer: fine) {
    /* Swap inline labels: hide desktop variants, show tablet variants. */
    .bv-label-desktop { display: none; }
    .bv-label-tablet { display: inline; }

    /* Fixed layout pins the table to 100% of its content column so the
       9-column budget can't out-grow the viewport (the auto layout +
       vendor min-width floor overflowed at 768/1024). Free-text columns
       (VENDOR, DESCRIPTION) wrap within their share instead of pushing
       the table wide. */
    .budget-line-table {
        table-layout: fixed;
        width: 100%;
    }

    /* Column-width budget (% of table). Table View and Task View share the
       same column order, so nth-child targeting is stable across both.
       Hidden columns (FALLBACK + the desktop pricing trio, positions 9-12)
       contribute no column under fixed layout. The content-heavy columns
       (QTY "5.000 Hr", the Q qualify widget, the stacked PRICING cell) get
       enough width that their nowrap content fits at 768px; the wrappable
       text columns (TASK, COSTCODE, VENDOR, PART, DESCRIPTION) take the
       rest and wrap to extra lines instead of forcing horizontal scroll. */
    .budget-line-table thead th:nth-child(1) { width: 9%; }   /* TASK */
    .budget-line-table thead th:nth-child(2) { width: 9%; }   /* CC */
    .budget-line-table thead th:nth-child(3) { width: 10%; }  /* COSTCODE */
    .budget-line-table thead th:nth-child(4) { width: 10%; }  /* VENDOR (wraps) */
    .budget-line-table thead th:nth-child(5) { width: 9%; }   /* PART */
    .budget-line-table thead th:nth-child(6) { width: 10%; }  /* DESCRIPTION (wraps) */
    .budget-line-table thead th:nth-child(7) { width: 10%; }  /* QTY */
    .budget-line-table thead th:nth-child(8) { width: 15%; }  /* Q (qualify widget) */
    .budget-line-table thead th:nth-child(13) { width: 18%; } /* PRICING (stacked + info icon) */

    /* Header labels wrap/break within their fixed column instead of
       staying nowrap and overlapping the neighbor (the real-device bug:
       "COSTCODE" over "VENDOR", "DESCRIPTION" over "QTY"). Overrides the
       global .bv-th-sort / .workspace-sort-link nowrap. The sort link is
       normally `display: inline-flex`, whose anonymous text flex-item has
       `min-width: auto` and so won't break a long single word; switching it
       to `display: inline` + `overflow-wrap: anywhere` lets "COSTCODE" /
       "DESCRIPTION" break to a second line rather than spill. Consistent
       with the body cells, which already wrap at tablet. */
    .budget-line-table thead th {
        white-space: normal;
        overflow-wrap: anywhere;
    }
    .budget-line-table thead th .workspace-sort-link {
        display: inline;
        white-space: normal;
        overflow-wrap: anywhere;
    }

    /* Drop FALLBACK column entirely on tablet — 1-char low-glance value
       still available via cursor data + Edit Qty modal context. */
    .bv-col-fallback { display: none; }

    /* Collapse the desktop pricing trio (PRICE | TOTAL_COST | TOTAL_PRICE)
       into the single tablet PRICING cell. */
    .bv-col-price,
    .bv-col-total-cost,
    .bv-col-total-price { display: none; }
    .bv-col-pricing-tablet { display: table-cell; }

    /* Tighten cell padding so the 9-column Table View fits 768px without
       horizontal scroll. Matches the brief's px-2 → px-1 directive. */
    .budget-line-table > :not(caption) > * > * {
        padding: 0.25rem 0.4rem;
    }

    /* VENDOR — no truncation. Under table-layout:fixed the column takes
       its share of the table width and long names wrap to 2-3 lines via
       vertical space instead of horizontal overflow. min-width:0 removes
       the old 8.5rem floor, which (under fixed layout) would otherwise
       force the table wider than the viewport at 768/1024. No
       `text-overflow: ellipsis`, ever. */
    .bv-col-vendor {
        min-width: 0;
        white-space: normal;
        overflow-wrap: anywhere;
        word-break: break-word;
        line-height: 1.25;
    }

    /* PRICING stacked cell — TOTAL_PRICE prominent, TOTAL_COST + PRICE/u
       muted underneath. Each row carries an inline label ("Total",
       "Cost", "/unit") via .bv-pricing-label so every row is
       self-documenting; the column header's info icon (.bv-info)
       surfaces the same legend in popover form. */
    .bv-pricing-stack {
        display: flex;
        flex-direction: column;
        align-items: flex-end;
        gap: 0.05rem;
        line-height: 1.2;
    }
    .bv-pricing-total {
        font-weight: 700;
        font-variant-numeric: tabular-nums;
    }
    .bv-pricing-sub {
        color: var(--muted);
        font-size: 0.78rem;
        font-variant-numeric: tabular-nums;
        white-space: nowrap;
    }
    .bv-pricing-label {
        font-size: 0.72rem;
        color: var(--muted);
        font-weight: 500;
        margin-left: 0.25rem;
        font-variant-numeric: normal;
    }
}

/* ---------- Info-icon popover — pure-CSS hover / focus / tap reveal.
   Sits in the tablet PRICING column header (.bv-col-pricing-tablet).
   Hidden by default; visible when the host span is hovered, focused
   (keyboard), or has focus (tap, since `tabindex="0"` makes the span
   tabbable on touch devices). No JS — survives the budget view's
   XHR refresh after edits without re-binding. Light / dark colors
   come from theme tokens. */
.bv-info {
    position: relative;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    margin-left: 0.25rem;
    width: 1.3rem;
    height: 1.3rem;
    color: var(--muted);
    cursor: help;
    border-radius: 50%;
    line-height: 1;
    vertical-align: middle;
}
.bv-info:hover,
.bv-info:focus-visible {
    color: var(--link);
    outline: none;
}
.bv-info:focus-visible {
    box-shadow: 0 0 0 2px var(--link);
}
.bv-info > i {
    font-size: 0.95rem;
}
.bv-info-tip {
    position: absolute;
    top: calc(100% + 0.35rem);
    right: 0;
    z-index: 30;
    display: none;
    min-width: 16rem;
    max-width: 20rem;
    padding: 0.55rem 0.7rem;
    /* Solid floating card. `--card-bg` is intentionally semi-transparent
       elsewhere in the portal (~0.74 light / ~0.72 dark) so card
       surfaces show the glass background through them — useful for
       in-flow content but a legibility problem for a popover that
       floats over busy table rows. We layer the same `--card-bg` tint
       over a solid `--bg` floor: the `background-color` gives a
       fully-opaque base, the `background-image` gradient (uniform fill)
       paints the card tint on top, and the net surface reads as a
       solid theme-aware card with zero see-through. Border + shadow
       (below) supply the visual lift. */
    background-color: var(--bg);
    background-image: linear-gradient(var(--card-bg), var(--card-bg));
    background-repeat: no-repeat;
    color: var(--text);
    border: 1px solid var(--border);
    border-radius: 0.4rem;
    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.18);
    font-size: 0.8rem;
    font-weight: 400;
    line-height: 1.4;
    text-align: left;
    text-transform: none;
    letter-spacing: normal;
    white-space: normal;
}
.bv-info:hover .bv-info-tip,
.bv-info:focus .bv-info-tip,
.bv-info:focus-within .bv-info-tip,
.bv-info-tip:hover {
    display: block;
}

/* Column-aware popover anchoring. The default `.bv-info-tip` anchors at
   the icon's right edge and extends LEFT — correct for icons near the
   right side of a table (Budget View PRICING, payment processor
   DUE DATE). Icons near the LEFT side of a table (payment processor
   POX is column 2) need the inverse: anchor at the icon's left edge
   and extend RIGHT, so the popover stays within the table interior
   instead of overflowing the table's left edge. Apply `bv-info-tip--left`
   alongside `bv-info-tip` on those popovers. */
.bv-info-tip.bv-info-tip--left {
    left: 0;
    right: auto;
}
.bv-info-tip-title {
    display: block;
    font-weight: 600;
    font-size: 0.78rem;
    color: var(--muted);
    text-transform: uppercase;
    letter-spacing: 0.04em;
    margin-bottom: 0.3rem;
}
.bv-info-tip-row {
    display: block;
    padding: 0.1rem 0;
}
.bv-info-tip-key {
    display: inline-block;
    min-width: 3.2rem;
    font-weight: 600;
    color: var(--text);
    margin-right: 0.35rem;
}

/* ---------- Mobile line cards (Table View + Task View, ≤767.98px) ----------
   Option X layout — mirrors PO Workspace Detail's mobile card pattern:
       1. Title row: PART N + Q widget on the right
       2. Sub-line: description (PRT), muted
       3. Meta region (paired field rows)
       4. Tail: VENDOR_NAM (muted)
   Per-card and modal-backdrop transparency tokens unchanged. */
.budget-line-card .card-body {
    padding: 0.7rem 0.85rem;
}
.bv-card-title-row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 0.5rem;
}
.bv-card-title {
    font-weight: 700;
    font-size: 1rem;
    color: var(--text);
}
.bv-card-q {
    /* Q widget — the bv_q_cell macro renders a button or badge; let it
       size to its content, right-aligned. */
    display: flex;
    align-items: center;
    flex: 0 0 auto;
}
.bv-card-sub {
    margin-top: 0.2rem;
    font-size: 0.86rem;
    line-height: 1.3;
    overflow-wrap: anywhere;
    word-break: break-word;
}
/* Meta region — data-point grid. Each data point is a small uppercase
   muted label above its value (mirrors the .ordq-mobile-qty-label /
   .ordq-mobile-qty-value pattern PO Workspace uses in its Edit Qty
   card). Pairs share a 1fr 1fr grid; single data points (TASK)
   span the full row via .bv-data-full. The visual grouping is the
   spacing + label-above-value structure — no pipe separators. */
.bv-card-meta {
    margin-top: 0.55rem;
    padding-top: 0.5rem;
    border-top: 1px solid var(--border);
    display: flex;
    flex-direction: column;
    gap: 0.6rem;
}
.bv-data-pair {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 0.6rem;
}
.bv-data {
    display: flex;
    flex-direction: column;
    gap: 0.15rem;
    min-width: 0;
}
.bv-data-full {
    width: 100%;
}
.bv-data-label {
    font-size: 0.7rem;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: var(--muted);
    font-weight: 600;
    line-height: 1;
}
.bv-data-value {
    font-size: 0.95rem;
    font-weight: 500;
    color: var(--text);
    line-height: 1.2;
    word-break: break-word;
    overflow-wrap: anywhere;
}
.bv-data-value-money {
    font-variant-numeric: tabular-nums;
}

/* Tail — VENDX · VENDOR_NAM. VENDX is rendered as a small tabular-nums
   code prefix in muted color so it reads as an identifier without
   visually competing with the vendor name. The middle-dot is purely
   decorative (aria-hidden on the markup). */
.bv-card-tail {
    margin-top: 0.6rem;
    padding-top: 0.5rem;
    border-top: 1px solid var(--border);
    display: flex;
    align-items: baseline;
    gap: 0.4rem;
    flex-wrap: wrap;
    line-height: 1.25;
}
.bv-card-vendx {
    font-size: 0.78rem;
    color: var(--muted);
    font-weight: 600;
    font-variant-numeric: tabular-nums;
    letter-spacing: 0.02em;
}
.bv-card-vendsep {
    color: var(--muted);
    font-size: 0.85rem;
}
.bv-card-vendor {
    font-size: 0.88rem;
    color: var(--text);
    overflow-wrap: anywhere;
    word-break: break-word;
}

/* ---------- Budget landing — jump-to-project search ---------- */
.budget-jump-search {
    max-width: 720px;
    margin-left: auto;
    margin-right: auto;
}

.budget-jump-search-input .form-control {
    /* Leave room for the leading bi-search icon and trailing spinner. */
    padding-right: 2.5rem;
}

.budget-jump-search-icon {
    position: absolute;
    left: 0.85rem;
    top: 50%;
    transform: translateY(-50%);
    color: var(--muted);
    font-size: 1.05rem;
    pointer-events: none;
}

.budget-jump-search-spinner {
    /* Centre vertically with calc() instead of transform: translateY — the
       Bootstrap .spinner-border animation sets `transform: rotate(360deg)`
       in its keyframe, which fully replaces the static transform and would
       cause the spinner to jump out of position instead of rotating in
       place. Leaving `transform` unused at rest lets the animation own it. */
    position: absolute;
    right: 0.85rem;
    top: calc(50% - 0.5rem);
    width: 1rem;
    height: 1rem;
}

/* ---------- Budget input placeholders (dark mode) ----------
   The portal-wide .theme-dark .form-control rule darkens the input surface
   but leaves Bootstrap's default placeholder colour (a low-opacity dark
   tone) unreadable against the new dark background. Match the dashboard
   convention (.dashboard-search-input::placeholder uses var(--muted)).
   Scoping to .budget-main covers every input on every budget page (the
   landing search, the customer/filter input, the budget-view search and
   filter rows) without affecting any other module. Light mode is
   untouched because this rule is scoped under body.theme-dark. */
body.theme-dark .budget-main .form-control::placeholder {
    color: var(--muted);
    opacity: 1;
}

.budget-search-results .budget-search-list {
    border-radius: 0.5rem;
    overflow: hidden;
}

.budget-search-row {
    padding: 0.75rem 1rem;
}

.budget-search-row:hover {
    background-color: rgba(0, 0, 0, 0.025);
}

/* Bootstrap's .list-group-item bakes in light backgrounds via --bs-* tokens
   that don't follow our .theme-dark palette. Restate the surface, text,
   border, and hover colours using the portal theme tokens so dark mode
   reads correctly without affecting light mode (the rules above already
   set the light hover, and Bootstrap's defaults handle light surface). */
body.theme-dark .budget-search-list .list-group-item.budget-search-row,
body.theme-dark .budget-search-list .list-group-item-action.budget-search-row {
    background-color: var(--card-bg);
    color: var(--text);
    border-color: var(--border);
}

body.theme-dark .budget-search-row:hover,
body.theme-dark .budget-search-row:focus {
    background-color: rgba(255, 255, 255, 0.06);
    color: var(--text);
}

body.theme-dark .budget-search-row .text-muted {
    color: var(--muted) !important;
}

.budget-search-proj {
    font-size: 1rem;
    letter-spacing: 0.02em;
}

.budget-search-chip {
    text-decoration: none;
    font-weight: 500;
    /* --border swaps in dark mode; the previous --bs-border-color reference
       didn't follow the dark palette and rendered as a near-invisible line. */
    border: 1px solid var(--border);
    padding: 0.3rem 0.55rem;
    transition: background-color 0.08s ease, border-color 0.08s ease, color 0.08s ease;
}

.budget-search-chip:hover,
.budget-search-chip:focus {
    background-color: var(--link, #0d6efd) !important;
    color: #fff !important;
    border-color: var(--link, #0d6efd);
}

/* Dark-mode chip surface — Bootstrap's text-bg-light reads white-on-white
   against the dark search row, and on top of that the inherited body-color
   token isn't always our light-on-dark text colour. Restate using the
   portal theme tokens so the chip text is readable on the dark surface. */
body.theme-dark .budget-search-chip {
    background-color: rgba(255, 255, 255, 0.08) !important;
    color: var(--text) !important;
    border-color: var(--border);
}

body.theme-dark .budget-search-chip:hover,
body.theme-dark .budget-search-chip:focus {
    background-color: var(--link, #0d6efd) !important;
    color: #fff !important;
    border-color: var(--link, #0d6efd);
}

.budget-browse-divider {
    color: var(--muted);
    letter-spacing: 0.06em;
}

.budget-browse-divider hr {
    opacity: 0.25;
}

/* =================================================================
   Budget Viewer — dark-mode header hierarchy
   -----------------------------------------------------------------
   In dark mode the default Budget surfaces all collapse to a single
   near-black tone, so a user can't tell a clickable task header from
   a table column header from page-level body text. This block adds
   three tiers using existing portal tokens (no new vars). Light mode
   is intentionally untouched — every selector is scoped under
   body.theme-dark and to budget-specific classes so other portal
   tables/headers are not affected.

   Hierarchy (lightest -> darkest in dark mode):
     1. Collapsible task header   -> --glass-surface (subtle blue lift)
                                  -> 3px --link left border = clickable
     2. Table column headers      -> --table-head-bg (distinctly darker)
     3. Data rows                 -> --card-bg (base card surface)
     4. Page section headings     -> no background, divider underneath
   ================================================================= */

/* (1) Collapsible task group header — elevated, primary-color accent. */
body.theme-dark .budget-task-head.card-header {
    background-color: var(--glass-surface);
    color: var(--text);
    border-left: 3px solid var(--link);
    border-bottom: 1px solid var(--border);
}
body.theme-dark .budget-task-head.card-header:hover {
    background-color: var(--glass-surface-strong);
}
/* Expanded state — keep the stronger surface so the active group
   stays visually distinct from the collapsed peers below it. */
body.theme-dark .budget-task-head.card-header[aria-expanded="true"] {
    background-color: var(--glass-surface-strong);
    border-bottom-color: var(--link);
}
/* Caret + body text inside the header pop a touch brighter against the
   tinted surface. */
body.theme-dark .budget-task-head .budget-task-caret {
    color: var(--link);
}

/* (2) Table column headers — darker than the task header, not interactive.
   Scoped to .budget-line-table so we don't disturb the portal-wide
   .table thead rule used by PO Workspace, Payment Processor, etc. */
body.theme-dark .budget-line-table thead th {
    background-color: var(--table-head-bg);
    color: var(--text);
    border-bottom: 1px solid var(--border);
    /* No left-border accent — these aren't clickable. */
}

/* (3) Page section headings — text only, with a divider under the whole
   heading row to separate the title block from the cards/table below.
   `:has()` lets us target the heading wrapper without changing templates;
   browsers that don't support :has() simply skip the rule and the page
   still works (dark mode just loses the divider line on those engines). */
body.theme-dark .budget-main > .d-flex:has(> div > h1.h5) {
    padding-bottom: 0.6rem;
    border-bottom: 1px solid var(--border);
}
body.theme-dark .budget-main > .d-flex > div > h1.h5 {
    color: var(--text);
}

/* =================================================================
   Breadcrumb — dark mode readability
   -----------------------------------------------------------------
   Bootstrap's .breadcrumb-item.active inherits $breadcrumb-active-color,
   which doesn't follow the portal's dark palette and ends up as an
   unreadable dark grey on the dark page surface. The previous fix
   wasn't specific enough; restate explicitly with !important on the
   active item to beat Bootstrap's resolved value. Light mode is
   untouched — these rules are scoped under .theme-dark.
   ================================================================= */
.theme-dark .breadcrumb-item.active {
    color: rgba(255, 255, 255, 0.9) !important;
}

.theme-dark .breadcrumb-item a {
    color: rgba(255, 255, 255, 0.65);
}

.theme-dark .breadcrumb-item + .breadcrumb-item::before {
    color: rgba(255, 255, 255, 0.4);
}

/* =================================================================
   Budget — Add Item slide-in panel (.bai-*)
   -----------------------------------------------------------------
   Right-edge slide-in modeled on the notification center pattern.
   Uses portal theme tokens so light/dark swap automatically. The
   panel renders inside the page (after main) so the topbar stays
   visible behind it.
   ================================================================= */

.bai-backdrop {
    position: fixed;
    inset: 0;
    background: rgba(0, 0, 0, 0.32);
    z-index: 1080;
    opacity: 0;
    transition: opacity 0.18s ease;
}
.bai-backdrop.is-open {
    opacity: 1;
}
body.theme-dark .bai-backdrop {
    background: rgba(0, 0, 0, 0.55);
}

.bai-panel {
    position: fixed;
    top: 0;
    right: 0;
    height: 100vh;
    width: 480px;
    max-width: 100vw;
    background: var(--card-bg);
    color: var(--text);
    border-left: 1px solid var(--border);
    box-shadow: -4px 0 16px rgba(0, 0, 0, 0.18);
    z-index: 1090;
    transform: translateX(100%);
    transition: transform 0.22s ease;
    display: flex;
    flex-direction: column;
    backdrop-filter: blur(6px);
}
.bai-panel.is-open {
    transform: translateX(0);
}

@media (max-width: 1199px),
       (min-width: 1200px) and (hover: none) and (pointer: coarse) {
    .bai-panel { width: 380px; }
}
@media (max-width: 575px) {
    .bai-panel { width: 100vw; border-left: 0; }
}

/* ---- Mobile (≤767px): make the Add Item panel scrollable ----
   Desktop / tablet behavior is intentionally untouched. On phones the
   panel must:
     1. Sit BELOW the fixed top bar (otherwise its top is hidden behind
        the navbar and the user thinks the panel is taller than the
        viewport).
     2. Use a strict flex layout so the header and footer stay pinned
        and the body can scroll between them.
     3. Honour iOS momentum scrolling and contain overscroll so swipes
        don't bubble out to the locked page underneath.
   The 100dvh override is the iOS-Safari fix for collapsing-toolbar 100vh
   resolution; browsers without dvh fall back to 100vh from the line above. */
@media (max-width: 767px) {
    .bai-panel {
        top: var(--portal-navbar-h, 70px);
        height: calc(100vh - var(--portal-navbar-h, 70px));
        height: calc(100dvh - var(--portal-navbar-h, 70px));
        max-height: calc(100vh - var(--portal-navbar-h, 70px));
        overflow: hidden;
    }
    .bai-head,
    .bai-foot {
        flex-shrink: 0;
    }
    .bai-form,
    .bai-review-screen {
        flex: 1 1 auto;
        min-height: 0;
    }
    .bai-body {
        flex: 1 1 auto;
        min-height: 0;
        overflow-y: auto;
        -webkit-overflow-scrolling: touch;
        overscroll-behavior: contain;
    }
}

.bai-head {
    display: flex;
    align-items: flex-start;
    gap: 0.75rem;
    padding: 0.85rem 1rem;
    border-bottom: 1px solid var(--border);
}
.bai-head-titles { flex: 1 1 auto; min-width: 0; }
.bai-title {
    margin: 0;
    font-size: 1.05rem;
    font-weight: 600;
    color: var(--text);
}
.bai-subtitle {
    margin-top: 0.15rem;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.bai-close {
    background: transparent;
    border: 0;
    color: var(--muted);
    font-size: 1.05rem;
    padding: 0.15rem 0.4rem;
    border-radius: 0.25rem;
    cursor: pointer;
}
.bai-close:hover { color: var(--text); background: var(--glass-surface); }

.bai-form {
    display: flex;
    flex-direction: column;
    flex: 1 1 auto;
    min-height: 0;
}
.bai-body {
    padding: 1rem;
    overflow-y: auto;
    flex: 1 1 auto;
}
.bai-foot {
    display: flex;
    justify-content: flex-end;
    gap: 0.5rem;
    padding: 0.75rem 1rem;
    border-top: 1px solid var(--border);
    background: var(--card-bg);
}

/* Suggestion dropdown — shared by parts, costcode, and vendor pickers
   inside the Add Item panel. Frosted glass with an opaque-enough fill so
   the suggestions read cleanly over whatever sits behind the panel
   field. The previous --card-bg fill (rgba 0.72-0.74) was translucent
   enough that long form labels behind the dropdown bled through.
   z-index sits comfortably above sibling panel content; no other
   .bai-suggest dropdowns exist outside this panel so this rule's reach
   is bounded. */
.bai-suggest {
    position: absolute;
    left: 0;
    right: 0;
    top: 100%;
    margin-top: 0.25rem;
    background: rgba(255, 255, 255, 0.92);
    -webkit-backdrop-filter: blur(12px);
    backdrop-filter: blur(12px);
    border: 1px solid rgba(0, 0, 0, 0.1);
    border-radius: 0.35rem;
    max-height: 260px;
    overflow-y: auto;
    z-index: 50;
    box-shadow: 0 4px 16px rgba(0, 0, 0, 0.1);
    color: var(--text);
}
body.theme-dark .bai-suggest {
    background: rgba(30, 32, 40, 0.95);
    border-color: rgba(255, 255, 255, 0.08);
    box-shadow: 0 4px 16px rgba(0, 0, 0, 0.3);
}

.bai-suggest-item {
    padding: 0.45rem 0.7rem;
    cursor: pointer;
    border-bottom: 1px solid rgba(0, 0, 0, 0.06);
    color: var(--text);
}
body.theme-dark .bai-suggest-item {
    border-bottom-color: rgba(255, 255, 255, 0.06);
}
.bai-suggest-item:last-child { border-bottom: 0; }
.bai-suggest-item:hover,
.bai-suggest-item.is-active {
    /* Subtle blue tint reads cleanly on both the white-frosted and
       dark-frosted surfaces. */
    background: rgba(13, 110, 253, 0.10);
}
body.theme-dark .bai-suggest-item:hover,
body.theme-dark .bai-suggest-item.is-active {
    background: rgba(117, 182, 255, 0.16);
}
.bai-suggest-partx {
    font-weight: 600;
    color: var(--text);
}
.bai-suggest-meta {
    font-size: 0.85rem;
    color: var(--muted);
}
.bai-suggest-empty {
    padding: 0.6rem 0.7rem;
    color: var(--muted);
    font-size: 0.9rem;
    text-align: center;
}

.bai-totals {
    background: var(--glass-surface);
    border: 1px solid var(--border);
}
body.theme-dark .bai-totals {
    background: var(--glass-surface-strong);
}
.bai-total {
    font-weight: 600;
    font-variant-numeric: tabular-nums;
}

.bai-po-preview {
    border: 1px solid var(--border);
    border-radius: 0.35rem;
    padding: 0.5rem 0.7rem;
    background: var(--glass-surface);
}
.bai-po-list {
    max-height: 220px;
    overflow-y: auto;
}
.bai-po-row {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    padding: 0.25rem 0;
    border-bottom: 1px dashed var(--border);
}
.bai-po-row:last-child { border-bottom: 0; }
.bai-po-row label {
    flex: 1 1 auto;
    margin: 0;
    cursor: pointer;
    font-size: 0.9rem;
}
.bai-po-pox { font-weight: 600; }
.bai-po-meta { color: var(--muted); margin-left: 0.5rem; }

.bai-po-empty,
.bai-po-loading {
    color: var(--muted);
    font-size: 0.9rem;
    padding: 0.4rem 0;
}

/* =================================================================
   Budget Edit Qty modal (Round 5, costcode-scoped)
   -----------------------------------------------------------------
   Full-screen on mobile (<=767px), centered ~720px on desktop, with
   sticky header + scrollable body + sticky footer. Reuses Add Item's
   .bai-pogrp-* grouped card styling for the per-PO cascade cards
   inside the modal (see "Add Item — grouped PO preview" further
   down) so the cascade visual language stays consistent.
   ================================================================= */

/* Backdrop + container — backdrop dims the page, container hosts the
   modal as a positioned card. */
.bce-backdrop {
    position: fixed;
    inset: 0;
    background: rgba(0, 0, 0, 0.32);
    z-index: 1080;
    opacity: 0;
    transition: opacity 0.18s ease;
}
.bce-backdrop.is-open { opacity: 1; }
body.theme-dark .bce-backdrop { background: rgba(0, 0, 0, 0.55); }

.bce-modal {
    position: fixed;
    z-index: 1090;
    display: flex;
    flex-direction: column;
    background: var(--card-bg);
    color: var(--text);
    border: 1px solid var(--border);
    box-shadow: 0 12px 32px rgba(0, 0, 0, 0.22);
    backdrop-filter: blur(6px);
    /* Desktop default: centered, ~720px wide, max 90vh tall. */
    top: 50%;
    left: 50%;
    transform: translate(-50%, calc(-50% + 24px));
    width: min(720px, 92vw);
    max-height: min(85vh, 820px);
    border-radius: 0.6rem;
    opacity: 0;
    transition: transform 0.22s ease, opacity 0.18s ease;
}
.bce-modal.is-open {
    transform: translate(-50%, -50%);
    opacity: 1;
}

/* Full-screen on mobile. The portal navbar sits at the top so the
   modal starts below it; height fills the remaining viewport so the
   sticky header+footer pin correctly. */
@media (max-width: 767px) {
    .bce-modal {
        top: var(--portal-navbar-h, 70px);
        left: 0;
        transform: translateY(20px);
        width: 100vw;
        max-width: 100vw;
        max-height: calc(100vh - var(--portal-navbar-h, 70px));
        max-height: calc(100dvh - var(--portal-navbar-h, 70px));
        border-radius: 0;
        border-left: 0;
        border-right: 0;
    }
    .bce-modal.is-open { transform: translateY(0); }
}

/* Header — sticky at top, contains scope title + close button. */
.bce-head {
    display: flex;
    align-items: flex-start;
    gap: 0.75rem;
    padding: 0.85rem 1rem;
    border-bottom: 1px solid var(--border);
    flex-shrink: 0;
}
.bce-head-titles { flex: 1 1 auto; min-width: 0; }
.bce-title {
    margin: 0;
    font-size: 1.05rem;
    font-weight: 600;
    color: var(--text);
}
.bce-subtitle {
    margin-top: 0.15rem;
    overflow-wrap: anywhere;
    word-break: break-word;
}
.bce-close {
    background: transparent;
    border: 0;
    color: var(--muted);
    font-size: 1.05rem;
    padding: 0.15rem 0.4rem;
    border-radius: 0.25rem;
    cursor: pointer;
}
.bce-close:hover { color: var(--text); background: var(--glass-surface); }

/* Form takes the remaining vertical space; sticky regions inside. */
.bce-form {
    display: flex;
    flex-direction: column;
    flex: 1 1 auto;
    min-height: 0;
}
.bce-body {
    padding: 1rem;
    overflow-y: auto;
    flex: 1 1 auto;
    -webkit-overflow-scrolling: touch;
    overscroll-behavior: contain;
}
.bce-foot {
    border-top: 1px solid var(--border);
    background: var(--card-bg);
    padding: 0.75rem 1rem;
    flex-shrink: 0;
}
.bce-foot-row {
    display: flex;
    align-items: center;
    gap: 0.5rem;
}

.bce-inline-toast {
    padding: 0.5rem 0.7rem;
    background: var(--selected-row-bg);
    border: 1px solid var(--selected-row-accent);
    border-radius: 0.3rem;
    margin-bottom: 0.7rem;
}
.bce-loading {
    padding: 1.2rem 0;
    text-align: center;
}

/* Parts list — one row per PARTX (user-visible label: "PART N").
   The card pattern mirrors PO Workspace Detail's mobile Edit Qty
   surface (po_workspace.html renderOrdquantEdit) on every viewport:
       row 1: header — PART | Description | Unit
       row 2: qty panels — Current Qty | New Qty (equal-size boxes)
       row 3: delta line — colored, hide-on-equal, live-update on input
       row 4: qualify control (+ legacy badge)
       row 5: totals — Cost / Price live readout (desktop / tablet only)
   The only viewport-conditional rule is the mobile totals hide — every
   other rule is shared so there's a single source of truth for the
   card layout across phone / tablet / desktop. Per-card and
   modal-backdrop transparency tokens unchanged. */
.bce-parts-list {
    display: flex;
    flex-direction: column;
    gap: 0.4rem;
}
.bce-part-row {
    display: grid;
    grid-template-columns: 1fr;
    grid-template-areas:
        "header"
        "qty"
        "delta"
        "qual"
        "totals";
    gap: 0.55rem;
    padding: 0.7rem 0.75rem;
    border: 1px solid var(--border);
    border-radius: 0.45rem;
    /* `--glass-surface-strong` (light 0.14 / dark 0.16) instead of
       `--glass-surface` (light 0.08 / dark 0.11) — a few-percent bump
       for legibility against busy underlying content. Scope is the
       Edit Qty modal's per-PART cards (`.bce-*` is unique to this
       modal); the shared global tokens are unchanged so PO Workspace
       and every other glass surface keep their original treatment. */
    background: var(--glass-surface-strong);
    transition: background-color 0.15s ease, border-color 0.15s ease;
}
.bce-part-row.is-changed {
    border-color: var(--selected-row-accent);
    background: var(--selected-row-bg);
}

/* Header — PART | Description | Unit. The header sits inside its own
   glass surround so it reads as the card's anchor on busy viewports;
   the surround uses the same bumped-opacity token as the outer row. */
.bce-part-header {
    grid-area: header;
    min-width: 0;
    background: var(--glass-surface-strong);
    border: 1px solid var(--border);
    border-radius: 0.45rem;
    padding: 0.55rem 0.65rem;
    display: flex;
    flex-direction: column;
    gap: 0.3rem;
    line-height: 1.3;
}
.bce-part-partx {
    font-weight: 600;
}
.bce-part-prt {
    color: var(--text);
    font-size: 0.88rem;
    overflow-wrap: anywhere;
    word-break: break-word;
}
.bce-part-unit {
    display: flex;
    align-items: baseline;
    gap: 0.4rem;
}
.bce-part-unit-label {
    font-size: 0.7rem;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: var(--muted);
}
.bce-part-unit-value {
    font-size: 0.9rem;
    font-weight: 600;
    color: var(--text);
}

/* Qty panels — 2-col side-by-side. Each panel is a label-wrapper only;
   the boxed visual moves to the value/input themselves so Current Qty
   and New Qty render at identical dimensions. The 1fr 1fr grid keeps
   the two columns equal-width regardless of content. max-width keeps
   the boxes from stretching unhelpfully wide on desktop while still
   spanning the full row on mobile. */
.bce-qty-panels {
    grid-area: qty;
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 0.55rem;
    max-width: 28rem;
}
.bce-qty-panel {
    background: transparent;
    border: 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: 0.3rem;
    min-width: 0;
}
.bce-qty-label {
    font-size: 0.7rem;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: var(--muted);
}
/* Shared dimensional + typographic rules — both qty boxes render at
   identical size for a clean side-by-side rhythm:
     - same width (filled inside the panel's 1fr column),
     - same min-height,
     - same padding,
     - same border treatment,
     - same font-size + line-height,
     - same box-sizing.
   Element-specific overrides below carry only the active-edit (input)
   vs non-editable (read-only) affordance. font-size: 16px also keeps
   iOS Safari from auto-zooming the New Qty input on focus. */
.bce-qty-value,
.bce-qty-input {
    box-sizing: border-box;
    width: 100%;
    min-height: 44px;
    padding: 0.45rem 0.65rem;
    font-size: 16px;
    line-height: 1.2;
    border: 1px solid var(--border);
    border-radius: 0.35rem;
    display: flex;
    align-items: center;
}
/* Read-only Current Qty — glass-surface bg + slightly heavier weight
   cue that says "snapshot you started with". No caret, no focus ring. */
.bce-qty-value {
    background: var(--glass-surface);
    color: var(--text);
    font-weight: 600;
    cursor: default;
}
/* Editable New Qty — solid card bg signals "active input"; the
   Bootstrap form-control class on the element supplies the native
   focus ring for the edit affordance. */
.bce-qty-input {
    background: var(--card-bg);
    color: var(--text);
    font-weight: 400;
}

/* Delta line — colored per direction, hidden when New == Current via
   the .is-empty class. JS toggles is-empty / is-increase / is-decrease
   on input. Copy ("Increase of N from original" / "Decrease of N from
   original") and color tokens mirror PO Workspace's
   .ordq-mobile-delta-text exactly so the two surfaces feel identical. */
.bce-delta {
    grid-area: delta;
    display: inline-flex;
    align-items: center;
    gap: 0.35rem;
    font-size: 0.85rem;
    color: var(--muted);
}
.bce-delta.is-empty {
    display: none;
}
.bce-delta.is-increase { color: #198754; }
.bce-delta.is-decrease { color: #dc3545; }
body.theme-dark .bce-delta.is-increase { color: #58d39b; }
body.theme-dark .bce-delta.is-decrease { color: #ff7f8a; }
.bce-delta-icon {
    font-size: 1rem;
    font-weight: 700;
}

/* Qualify control — sits below the delta line on every viewport,
   mirroring PO Workspace's .ordq-mobile-qualify position. Legacy
   badge tracks alongside for the lockout case. */
.bce-q-wrap {
    grid-area: qual;
    display: flex;
    align-items: center;
    gap: 0.5rem;
    flex-wrap: wrap;
    justify-content: flex-start;
}

/* Totals — Cost / Price live readout. Desktop / tablet only; mobile
   hides this row to match PO Workspace's mobile edit card. */
.bce-part-totals {
    grid-area: totals;
    font-size: 0.78rem;
    color: var(--muted);
    display: flex;
    gap: 1rem;
    flex-wrap: wrap;
}

@media (max-width: 575px) {
    /* Mobile-only difference: drop the totals row to match PO
       Workspace's mobile edit card, which omits Cost / Price. Every
       other rule — header layout, qty panels, delta, qualify — is
       shared with desktop / tablet for a single source of truth. */
    .bce-part-totals {
        display: none;
    }
}

/* Disclosure sections (wider scope + cascade). Native <details>
   provides the open/closed state; we style the summary for parity
   with the rest of the modal. */
.bce-disclosure {
    border: 1px solid var(--border);
    border-radius: 0.4rem;
    background: var(--glass-surface);
}
.bce-disclosure-summary {
    cursor: pointer;
    list-style: none;
    padding: 0.6rem 0.75rem;
    display: flex;
    align-items: center;
    gap: 0.5rem;
}
.bce-disclosure-summary::marker { content: ''; }
.bce-disclosure-summary::-webkit-details-marker { display: none; }
.bce-disclosure-summary::before {
    content: '\25B8';   /* right-pointing triangle */
    display: inline-block;
    color: var(--muted);
    transition: transform 0.15s ease;
}
.bce-disclosure[open] > .bce-disclosure-summary::before {
    transform: rotate(90deg);
}
.bce-disclosure-body {
    padding: 0 0.75rem 0.75rem;
}

/* Wider-scope row — reuses the same selected-state spacing trick
   from prior rounds: 3px transparent left border on unselected rows,
   filled via :has() on selected so toggling never causes a jump. */
.bce-scope-row {
    display: flex;
    align-items: flex-start;
    gap: 0.5rem;
    padding: 0.3rem 0.4rem 0.3rem 0.6rem;
    border-left: 3px solid transparent;
    border-radius: 0.25rem;
    margin-bottom: 0.15rem;
}
.bce-scope-row .form-check-input {
    /* Reset Bootstrap's .form-check input -1.5em pull so the checkbox
       sits inside the row's content box (overflow containers used to
       clip the bare checkbox at the left edge). */
    margin-top: 0.15rem;
    margin-left: 0;
    flex: 0 0 auto;
}
.bce-scope-row .form-check-label {
    flex: 1 1 auto;
    margin: 0;
    cursor: pointer;
    font-size: 0.9rem;
    min-width: 0;
    white-space: normal;
    overflow-wrap: anywhere;
    word-break: break-word;
    line-height: 1.3;
}
.bce-scope-row:has(.bce-scope-include:checked) {
    border-left-color: var(--selected-row-accent);
    background: var(--selected-row-bg);
}
.bce-scope-legacy {
    font-size: 0.7rem;
    cursor: help;
}
@media (max-width: 575px) {
    .bce-scope-row {
        padding-left: 0.85rem;
    }
}

/* Cascade summary line — single-line blast-radius readout above the
   per-PO cards (Round 5 refinement #1). Uses the selected-row tokens
   so it reads as "informational state" not as a destructive alert. */
.bce-cascade-summary {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    padding: 0.45rem 0.7rem;
    background: var(--selected-row-bg);
    border: 1px solid var(--selected-row-accent);
    border-radius: 0.3rem;
    font-size: 0.9rem;
    color: var(--text);
    overflow-wrap: anywhere;
}
.bce-cascade-summary .bi {
    color: var(--link);
}
.bce-cascade-list {
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
}

/* Qualify-and-Update modal — read-only header strip.
   Mirrors Edit Qty's Current Qty box treatment for the qty value so
   the two modals feel like siblings: same glass-surface bg, same
   border, same tabular-nums weight. PART and DESCRIPTION sit beside
   it on desktop; the trio stacks on mobile. Theme-aware via existing
   tokens — no hardcoded colours. */
.bqu-readonly-row {
    display: grid;
    grid-template-columns: auto 1fr auto;
    align-items: end;
    gap: 0.75rem 1rem;
    margin-bottom: 1rem;
}
.bqu-readonly-meta {
    display: flex;
    flex-direction: column;
    gap: 0.3rem;
    min-width: 0;
}
.bqu-readonly-label {
    font-size: 0.7rem;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: var(--muted);
}
.bqu-readonly-value {
    font-size: 0.95rem;
    color: var(--text);
    font-weight: 500;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
/* The qty box renders at the same dimensions as Edit Qty's Current Qty
   panel so the eye reads "this is the locked-in qty". */
.bqu-readonly-meta-qty {
    min-width: 8rem;
}
.bqu-readonly-qty {
    box-sizing: border-box;
    width: 100%;
    min-height: 44px;
    padding: 0.45rem 0.65rem;
    font-size: 16px;
    line-height: 1.2;
    border: 1px solid var(--border);
    border-radius: 0.35rem;
    background: var(--glass-surface);
    color: var(--text);
    font-weight: 600;
    font-variant-numeric: tabular-nums;
    display: flex;
    align-items: center;
    justify-content: flex-end;
    white-space: nowrap;
}
@media (max-width: 599px) {
    .bqu-readonly-row {
        grid-template-columns: 1fr;
        gap: 0.5rem;
    }
    .bqu-readonly-meta-qty {
        min-width: 0;
    }
}

/* When a field has client-side validation error class. */
.bai-form .is-invalid + .invalid-feedback,
.bai-form .is-invalid ~ .invalid-feedback {
    display: block;
}

/* =================================================================
   Budget — Active Cursor row
   -----------------------------------------------------------------
   Reuses the existing --pp-row-selected-bg token defined alongside
   the Payment Processor selection palette so light/dark swap is
   already handled. Visual indicator is a tinted background plus a
   solid 3px left edge in the portal --link colour. The cursor row
   gets pointer cursor + slight hover lift so the click affordance
   is obvious. Scoped to .bv-row so other tables/cards aren't
   affected.
   ================================================================= */
.bv-row {
    cursor: pointer;
}
.bv-row.budget-cursor > * {
    background-color: var(--pp-row-selected-bg) !important;
}
.bv-row.budget-cursor {
    /* Coloured left edge — works on tr (collapses into the first cell)
       and on the mobile .budget-line-card. Use box-shadow inset so
       the border doesn't disturb table cell layout / column widths. */
    box-shadow: inset 3px 0 0 var(--link);
}
.bv-row.budget-cursor td:first-child,
.bv-row.budget-cursor.budget-line-card {
    position: relative;
}

/* Mobile-card cursor — tint the card body + add the same left edge. */
.budget-line-card.bv-row.budget-cursor {
    background-color: var(--pp-row-selected-bg) !important;
    border-left: 3px solid var(--link);
}

/* Slight hover hint so users notice rows are clickable. */
.bv-row:hover:not(.budget-cursor) > * {
    background-color: var(--glass-surface);
}

/* =====================================================================
   Budget Viewer — paint performance (Firefox optimization)
   ---------------------------------------------------------------------
   Diagnostic baseline (June 2026): Firefox content-process profile of
   the budget view showed ~57% of CPU samples in pure Paint operations
   during scroll/typing — paint-pipeline bound, not style-recalc or JS.
   Task View renders all rows unpaginated (median 73, p90 159, max 348
   per CUSTOMER/PROJ/MODEL/ELEV view), so paint cost scales with row
   count and Firefox amplifies the per-row repaint compared to Chrome.

   Strategy (no visual change — verify both themes after edits):
     1. CSS containment on row containers — paint/layout work inside a
        row cannot affect the page, so the hover bg swap above and the
        cursor box-shadow stay local to the row.
     2. content-visibility: auto on rows — off-screen rows skip rendering
        entirely. contain-intrinsic-size keeps the scrollbar stable
        while a row is virtualised.
     3. will-change: transform on open modal/panel containers — slide-in
        animations run on the compositor instead of repainting the page
        surface each frame. Scoped to .is-open so closed modals don't
        hold a GPU layer; the controller JS toggles the class on
        open/close.

   Keep this block adjacent to the cursor/hover rules above so future
   maintainers see the containment boundary alongside the rules that
   benefit from it.
   ===================================================================== */

/* tbody containment — supported on `display: table-row-group` per CSS
   Containment L1, isolates paint to each task group's tbody. */
.budget-line-table > tbody {
    contain: layout paint;
}

/* Row containers — .bv-row sits on both the desktop <tr> and the mobile
   <div>. layout+paint (skipping style) keeps theme-token inheritance
   intact. On older Firefox this is a no-op on table-row; mobile cards
   and modern Firefox see the full benefit. */
.bv-row,
.budget-line-card {
    contain: layout paint;
}

/* Off-screen row rendering — Task View doesn't paginate so a typical
   view carries 70–200 rows below the fold. content-visibility: auto
   skips rendering work for off-screen rows; contain-intrinsic-size
   is the placeholder used while a row is virtualised. Desktop rows
   ~32px tall, mobile cards ~160px. */
.budget-line-table > tbody > tr.bv-row {
    content-visibility: auto;
    contain-intrinsic-size: auto 32px;
}
.budget-line-card {
    content-visibility: auto;
    contain-intrinsic-size: auto 160px;
}

/* Layer promotion for open slide-in panel (.bai-panel — Add Item) and
   centered modals (.bce-modal — Edit Qty, Qualify, Conflict preview,
   Cascade preview). Scoped to .is-open so closed modals don't hold a
   GPU layer. */
.bai-panel.is-open,
.bce-modal.is-open {
    will-change: transform;
}

/* =====================================================================
   Quick row-level qualify cell — used in both Task View and Table View
   and on the mobile card. Inline button + badge palette mirrors the
   portal's existing success/secondary tokens so light/dark swap is
   automatic. The cell is scoped so the click handler in the Edit JS
   never collides with the row's cursor click.
   ===================================================================== */
.bv-q-cell {
    display: inline-flex;
    align-items: center;
    gap: 0.25rem;
    white-space: nowrap;
}
.bv-q-toggle {
    line-height: 1.1;
}
.bv-q-toggle:disabled {
    opacity: 0.6;
    cursor: not-allowed;
}
.bv-q-badge {
    font-size: 0.72rem;
    padding: 0.2em 0.45em;
}
.bv-q-other {
    font-size: 0.72rem;
    padding: 0.2em 0.45em;
    cursor: help;
}

/* =====================================================================
   Budget — Costcode focus filter
   ---------------------------------------------------------------------
   Two surfaces sharing the portal's colour tokens so both themes ride
   along without parallel rules:

   1. .bv-cc-sticky-head — mobile-only section header above each
      costcode group in the Table View cards. position: sticky with
      a solid background so the cards underneath don't bleed through
      while it pins to the viewport top. Tapping the header focuses
      the budget filter on that costcode (navigation aid only — see
      Round 5: the row-level funnel was removed in favour of the
      costcode-scoped Edit modal, sticky-header focus stayed).

   2. .bv-focus-banner — inline banner above the list, rendered only
      when COSTCODE=eq is the sole active filter. Shows the focused
      costcode and a "Show all" link that routes to the unfiltered
      view (same destination the Reset link produces in that case).
   ===================================================================== */

/* Sticky costcode headers — mobile-only (the Table View desktop
   table conveys grouping via column order, so a sticky bar there
   would be redundant). Scoped to .budget-line-cards-grouped so the
   flat (non-COSTCODE-sorted) layout doesn't get headers it can't
   honour. */
.budget-line-cards .bv-cc-sticky-head {
    position: sticky;
    /* Sit just under the portal's fixed top navbar on mobile so the
       header doesn't slide behind it. var(--portal-navbar-h) is the
       same token used elsewhere for mobile-pinned surfaces; falls
       back to 70px if the variable isn't set. */
    top: var(--portal-navbar-h, 70px);
    z-index: 3;
    margin: 0.5rem 0 0.25rem;
    padding: 0.35rem 0.65rem;
    background: var(--card-bg);
    border: 1px solid var(--border);
    border-left: 3px solid var(--link);
    border-radius: 0.3rem;
    font-size: 0.85rem;
    font-weight: 600;
    /* Backdrop blur lets translucent backgrounds soften the cards
       scrolling underneath instead of letting their full saturation
       bleed through the header. Falls back gracefully on browsers
       that don't support it. */
    backdrop-filter: blur(6px);
    -webkit-backdrop-filter: blur(6px);
}
.budget-line-cards .bv-cc-sticky-head-link {
    display: flex;
    align-items: center;
    gap: 0.25rem;
    color: var(--text);
    text-decoration: none;
}
.budget-line-cards .bv-cc-sticky-head-link:hover,
.budget-line-cards .bv-cc-sticky-head-link:focus-visible {
    color: var(--link);
}
.budget-line-cards .bv-cc-sticky-head-link-active {
    cursor: default;
    color: var(--link);
}
.budget-line-cards .bv-cc-sticky-head-label {
    flex: 0 1 auto;
}
.budget-line-cards .bv-cc-sticky-head-count {
    flex: 0 1 auto;
    font-weight: 400;
}

/* Focused-state banner — sole-filter case. Compact pill with the
   focused costcode and a Show all link. Uses --selected-row-bg
   (already theme-aware) so the banner reads as state, not a
   destructive alert. */
.bv-focus-banner {
    display: flex;
    align-items: center;
    gap: 0.6rem;
    padding: 0.45rem 0.7rem;
    background: var(--selected-row-bg);
    border: 1px solid var(--selected-row-accent);
    border-radius: 0.35rem;
    font-size: 0.9rem;
}
.bv-focus-banner-label {
    color: var(--text);
    font-weight: 500;
}
.bv-focus-banner-label .bi-funnel-fill {
    color: var(--link);
    margin-right: 0.25rem;
}
.bv-focus-banner-clear {
    margin-left: auto;
    color: var(--link);
    text-decoration: none;
    font-weight: 500;
}
.bv-focus-banner-clear:hover,
.bv-focus-banner-clear:focus-visible {
    text-decoration: underline;
}

/* Read-only context block at the top of the Add Item panel. */
.bai-context {
    border: 1px solid var(--border);
    border-radius: 0.35rem;
    padding: 0.5rem 0.65rem;
    background: var(--glass-surface);
}
.bai-ctx-val {
    font-weight: 600;
    color: var(--text);
    word-break: break-word;
}
.bai-cc-echo {
    font-style: italic;
}
.bai-price-source {
    font-size: 0.75rem;
    color: var(--muted);
    background: transparent;
    border-left: 0;
    max-width: 12rem;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* ---- Add Item panel — display-only price + inline toast + counter ---- */
.bai-price-display {
    display: flex;
    align-items: baseline;
    gap: 0.5rem;
    padding: 0.5rem 0.65rem;
    border: 1px solid var(--border);
    border-radius: 0.35rem;
    background: var(--glass-surface);
}
.bai-price-label {
    font-weight: 500;
}
.bai-price-value {
    font-size: 1.05rem;
    font-weight: 600;
    color: var(--text);
}
.bai-price-display .bai-price-source {
    margin-left: auto;
    font-size: 0.75rem;
}

/* Hint text under the price label ("Pick a part and a vendor to fetch
   the unit price.", "Fetching price…", etc.). Bootstrap's .form-text
   defaults to --bs-secondary-color which the portal does NOT override
   in .theme-dark — so the text stayed dim grey on the dark panel and
   was unreadable. var(--muted) swaps automatically: #6c757d in light,
   #a0a3ad in dark — readable contrast in both modes. ID-scoped so no
   other .form-text elements are affected. */
#bai-price-helper {
    color: var(--muted);
}

.bai-inline-toast {
    background: var(--glass-surface-strong);
    border: 1px solid var(--link);
    color: var(--text);
    padding: 0.45rem 0.7rem;
    border-radius: 0.35rem;
    font-size: 0.9rem;
    margin-bottom: 0.75rem;
    transition: opacity 0.18s ease;
}
.bai-inline-toast.is-fading {
    opacity: 0;
}

.bai-session-count {
    font-size: 0.75rem;
    font-weight: 400;
    margin-left: 0.4rem;
}

/* ---- Add Item — pending list + review step + restore banner ---- */
.bai-restore-banner {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    flex-wrap: wrap;
    padding: 0.55rem 0.7rem;
    margin-bottom: 0.75rem;
    border: 1px solid var(--link);
    background: var(--glass-surface);
    border-radius: 0.35rem;
    color: var(--text);
    font-size: 0.9rem;
}
.bai-restore-msg { flex: 1 1 auto; }

.bai-pending-wrap {
    border: 1px solid var(--border);
    border-radius: 0.4rem;
    padding: 0.55rem 0.7rem;
    background: var(--card-bg);
}
.bai-pending-head {
    margin-bottom: 0.4rem;
}
.bai-pending-list { display: flex; flex-direction: column; gap: 0.35rem; }
.bai-pending-empty {
    color: var(--muted);
    font-size: 0.9rem;
    padding: 0.4rem 0;
}

.bai-pending-row {
    display: grid;
    grid-template-columns: 1fr auto;
    gap: 0.35rem 0.55rem;
    align-items: start;
    padding: 0.45rem 0.55rem;
    border: 1px solid var(--border);
    border-radius: 0.35rem;
    background: var(--glass-surface);
}
.bai-pending-main { font-weight: 500; color: var(--text); }
.bai-pending-meta {
    grid-column: 1 / -1;
    color: var(--muted);
    font-size: 0.82rem;
    line-height: 1.35;
}
.bai-pending-actions {
    display: flex;
    gap: 0.2rem;
    align-self: start;
}
.bai-pending-actions button {
    background: transparent;
    border: 0;
    color: var(--muted);
    padding: 0.15rem 0.35rem;
    border-radius: 0.25rem;
    cursor: pointer;
    font-size: 0.95rem;
}
.bai-pending-actions button:hover { color: var(--text); background: var(--glass-surface-strong); }
.bai-pending-actions .bai-pending-remove:hover { color: #d9534f; }

/* Review screen — replaces .bai-body when visible. */
.bai-review-screen {
    display: flex;
    flex-direction: column;
    flex: 1 1 auto;
    min-height: 0;
}
/* Review screen now uses the same .bai-pcard layout as the editable
   pending list. Stack the cards with the same vertical gap as the
   pending list so the two screens read identically. */
.bai-review-list {
    display: flex;
    flex-direction: column;
    gap: 0.6rem;
}
/* Hide any leftover actions area on review-mode cards as a defense
   in depth — the JS already omits it, but if a future caller forgets,
   the read-only contract still holds. */
.bai-pcard--review .bai-pcard-actions { display: none; }

.bai-foot-row {
    display: flex;
    justify-content: flex-end;
    gap: 0.5rem;
    width: 100%;
}

/* ---- Budget Task View — client-side sortable headers ---- */
.bv-th-sort {
    cursor: pointer;
    user-select: none;
    white-space: nowrap;
}
.bv-th-sort:hover {
    color: var(--link);
}
.bv-sort-indicator {
    display: inline-block;
    margin-left: 0.3rem;
    width: 0.55rem;
    text-align: center;
    color: var(--muted);
    font-size: 0.75rem;
    line-height: 1;
}
.bv-th-sort.bv-sort-asc .bv-sort-indicator::before {
    content: '\25B2';   /* ▲ */
    color: var(--link);
}
.bv-th-sort.bv-sort-desc .bv-sort-indicator::before {
    content: '\25BC';   /* ▼ */
    color: var(--link);
}

/* ---- Add Item — selected-part chip ---- */
.bai-part-chip {
    display: inline-flex;
    align-items: center;
    gap: 0.45rem;
    padding: 0.4rem 0.7rem;
    border-radius: 999px;
    background: var(--glass-surface-strong);
    border: 1px solid var(--link);
    color: var(--text);
    max-width: 100%;
    font-size: 0.95rem;
    line-height: 1.2;
}
.bai-part-chip-partx {
    font-weight: 700;
    color: var(--link);
    flex: 0 0 auto;
}
.bai-part-chip-sep {
    color: var(--muted);
    flex: 0 0 auto;
}
.bai-part-chip-prt {
    flex: 1 1 auto;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.bai-part-chip-clear {
    flex: 0 0 auto;
    background: transparent;
    border: 0;
    color: var(--muted);
    font-size: 1.1rem;
    line-height: 1;
    padding: 0 0.15rem;
    cursor: pointer;
    border-radius: 0.2rem;
}
.bai-part-chip-clear:hover {
    color: var(--text);
    background: var(--glass-surface);
}

/* ---- Add Item — pending parts as card grid ---- */
.bai-pending-list {
    display: flex;
    flex-direction: column;
    gap: 0.6rem;
}
.bai-pcard {
    background: var(--card-bg);
    border: 1px solid var(--border);
    box-shadow: 0 1px 3px rgba(0, 0, 0, 0.06);
    border-radius: 0.4rem;
    transition: box-shadow 0.08s ease, border-color 0.08s ease;
}
.bai-pcard:hover {
    box-shadow: 0 3px 8px rgba(0, 0, 0, 0.10);
    border-color: var(--link);
}
body.theme-dark .bai-pcard {
    box-shadow: 0 1px 3px rgba(0, 0, 0, 0.35);
}
body.theme-dark .bai-pcard:hover {
    box-shadow: 0 3px 8px rgba(0, 0, 0, 0.45);
}
.bai-pcard .card-body {
    padding: 0.7rem 0.85rem;
}

.bai-pcard-top {
    display: flex;
    justify-content: space-between;
    align-items: flex-start;
    gap: 0.5rem;
    margin-bottom: 0.5rem;
}
.bai-pcard-costcode {
    font-size: 1.1rem;
    font-weight: 700;
    color: var(--text);
    line-height: 1.1;
}
.bai-pcard-cc {
    margin-top: 0.1rem;
    line-height: 1.2;
    /* Wrap long descriptions naturally; the card grows vertically rather
       than clipping the text. */
    white-space: normal;
    overflow-wrap: anywhere;
}
.bai-pcard-partx-wrap { min-width: 0; max-width: 60%; }
.bai-pcard-partx {
    font-weight: 600;
    color: var(--link);
    font-size: 0.9rem;
}
.bai-pcard-prt {
    line-height: 1.2;
    /* Wrap on multiple lines when the description is long. The previous
       overflow:hidden + ellipsis + nowrap combo was clipping descriptions
       like "Yardage Charge 32 Meter — extended..." mid-word. */
    white-space: normal;
    overflow-wrap: anywhere;
}

.bai-pcard-mid {
    display: flex;
    gap: 1rem;
    margin-bottom: 0.45rem;
    padding-top: 0.4rem;
    border-top: 1px dashed var(--border);
}
.bai-pcard-cell {
    font-size: 0.92rem;
    color: var(--text);
}
.bai-pcard-lbl {
    color: var(--muted);
    font-size: 0.78rem;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    margin-right: 0.2rem;
}
.bai-pcard-val { font-weight: 600; }
.bai-pcard-q-on {
    color: var(--link);
    font-weight: 700;
}
.bai-pcard-q-off {
    color: var(--muted);
}

.bai-pcard-bottom {
    display: flex;
    flex-direction: column;
    gap: 0.15rem;
    padding-top: 0.4rem;
    border-top: 1px dashed var(--border);
}
.bai-pcard-no-price {
    color: var(--muted);
    font-style: italic;
}

.bai-pcard-actions {
    display: flex;
    justify-content: flex-end;
    gap: 0.4rem;
    margin-top: 0.6rem;
}

/* ---- Add Item — vendor auto-suggestion hint ---- */
.bai-vendor-hint {
    margin-top: 0.25rem;
    padding: 0.3rem 0.5rem;
    border-radius: 0.3rem;
    background: var(--glass-surface);
    border: 1px solid var(--border);
    color: var(--muted);
    font-size: 0.8rem;
    line-height: 1.3;
}
.bai-vendor-hint .bi {
    color: var(--link);
    margin-right: 0.25rem;
}

/* =================================================================
   Add Item — Propagation + Update POs (independent parallel sections)
   -----------------------------------------------------------------
   The two sections are visually distinct cards stacked one above the
   other. Each carries its own header label and a 2px left accent so
   the user can scan them as separate concerns. Propagation is always
   visible and works without Update POs being checked; Update POs
   gates only its own preview below the master checkbox.
   ================================================================= */
.bai-update-section {
    padding: 0.55rem 0.75rem;
    border: 1px solid var(--border);
    border-left-width: 2px;
    background: var(--glass-surface);
    border-radius: 0.35rem;
}
.bai-update-section + .bai-update-section {
    /* Stacked sections get extra space between them so they don't
       read as a single grouped block. */
    margin-top: 0.75rem;
}
.bai-update-section-title {
    font-size: 0.78rem;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    color: var(--muted);
    margin-bottom: 0.4rem;
}
.bai-propagate {
    /* No special divider needed — the section card border + label
       already do the visual grouping. */
}
.bai-prop-preview {
    background: var(--card-bg);
    border: 1px solid var(--border);
    border-radius: 0.4rem;
    padding: 0.5rem 0.7rem;
    max-height: 240px;
    overflow-y: auto;
}
.bai-prop-list {
    display: flex;
    flex-direction: column;
    gap: 0.25rem;
}
.bai-prop-row {
    display: grid;
    grid-template-columns: auto 1fr auto;
    align-items: center;
    gap: 0.5rem;
    padding: 0.25rem 0.4rem;
    border-radius: 0.3rem;
    background: var(--glass-surface);
    font-size: 0.9rem;
}
.bai-prop-row .bai-prop-label {
    color: var(--text);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.bai-prop-pill {
    font-size: 0.75rem;
    font-weight: 600;
    padding: 0.1rem 0.45rem;
    border-radius: 999px;
    border: 1px solid transparent;
    white-space: nowrap;
}
.bai-prop-pill-new {
    background: rgba(13, 110, 253, 0.10);
    color: var(--link);
    border-color: var(--link);
}
.bai-prop-pill-conflict {
    background: rgba(220, 53, 69, 0.10);
    color: #b02a37;
    border-color: #b02a37;
}
body.theme-dark .bai-prop-pill-conflict {
    color: #f87171;
    border-color: #f87171;
    background: rgba(248, 113, 113, 0.12);
}
.bai-prop-empty {
    color: var(--muted);
    font-size: 0.85rem;
    padding: 0.4rem 0;
}

/* ---- Conflict modal ---- */
.bai-conflict-modal {
    position: fixed;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    width: 92vw;
    max-width: 720px;
    max-height: 86vh;
    background: var(--card-bg);
    color: var(--text);
    border: 1px solid var(--border);
    border-radius: 0.5rem;
    box-shadow: 0 10px 32px rgba(0, 0, 0, 0.28);
    z-index: 1100;
    display: flex;
    flex-direction: column;
}
.bai-conflict-head {
    padding: 0.75rem 1rem;
    border-bottom: 1px solid var(--border);
    display: flex;
    justify-content: space-between;
    align-items: flex-start;
    gap: 0.5rem;
}
.bai-conflict-title {
    margin: 0;
    font-size: 1.05rem;
    font-weight: 600;
    color: var(--text);
}
.bai-conflict-body {
    padding: 1rem;
    overflow-y: auto;
    flex: 1 1 auto;
}
.bai-conflict-card {
    border: 1px solid var(--border);
    border-radius: 0.4rem;
    padding: 0.75rem;
    background: var(--glass-surface);
}
.bai-conflict-target {
    margin-bottom: 0.5rem;
    text-transform: uppercase;
    letter-spacing: 0.04em;
}
.bai-conflict-cols {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 0.75rem;
}
@media (max-width: 575px) {
    .bai-conflict-cols { grid-template-columns: 1fr; }
}
.bai-conflict-col {
    border: 1px solid var(--border);
    border-radius: 0.35rem;
    padding: 0.55rem 0.7rem;
    background: var(--card-bg);
}
.bai-conflict-col-title {
    text-transform: uppercase;
    letter-spacing: 0.04em;
    margin-bottom: 0.35rem;
}
.bai-conflict-record {
    font-size: 0.9rem;
    line-height: 1.5;
    word-break: break-word;
}
.bai-conflict-record .bai-conflict-line {
    display: flex;
    justify-content: space-between;
    gap: 0.5rem;
}
.bai-conflict-record .bai-conflict-key {
    color: var(--muted);
    font-size: 0.78rem;
    text-transform: uppercase;
    letter-spacing: 0.04em;
}
.bai-conflict-record .bai-conflict-val {
    color: var(--text);
    font-weight: 500;
    text-align: right;
    overflow-wrap: anywhere;
}
.bai-conflict-choice {
    display: flex;
    gap: 0.5rem;
    flex-wrap: wrap;
}
.bai-conflict-choice .btn.is-chosen {
    background: var(--link);
    color: #fff;
    border-color: var(--link);
}
.bai-conflict-foot {
    padding: 0.65rem 1rem;
    border-top: 1px solid var(--border);
    display: flex;
    flex-wrap: wrap;
    gap: 0.5rem 0.75rem;
    justify-content: space-between;
    background: var(--card-bg);
}
.bai-conflict-nav,
.bai-conflict-bulk,
.bai-conflict-final {
    display: flex;
    gap: 0.4rem;
    align-items: center;
}
.bai-conflict-final {
    margin-left: auto;
}

/* ---- Add Item — review-step propagation summary ---- */
.bai-review-prop {
    border: 1px solid var(--border);
    border-radius: 0.4rem;
    padding: 0.55rem 0.7rem;
    background: var(--card-bg);
}
.bai-review-prop-head {
    margin-bottom: 0.45rem;
    padding-bottom: 0.35rem;
    border-bottom: 1px dashed var(--border);
}
.bai-review-prop-title {
    font-weight: 600;
    color: var(--text);
    font-size: 0.95rem;
}
.bai-review-prop-list {
    display: flex;
    flex-direction: column;
    gap: 0.3rem;
}
.bai-review-prop-row {
    display: grid;
    grid-template-columns: auto 1fr auto;
    align-items: center;
    gap: 0.5rem;
    padding: 0.3rem 0.45rem;
    border-radius: 0.3rem;
    background: var(--glass-surface);
    font-size: 0.9rem;
}
.bai-review-prop-row.is-excluded {
    opacity: 0.55;
}
.bai-review-prop-state {
    font-size: 0.85rem;
    color: var(--muted);
    width: 1.4rem;
    text-align: center;
}
.bai-review-prop-state-included {
    color: var(--link);
}
.bai-review-prop-state-excluded {
    color: var(--muted);
}
.bai-review-prop-label {
    color: var(--text);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.bai-review-prop-pills {
    display: flex;
    gap: 0.35rem;
    align-items: center;
    flex-wrap: wrap;
    justify-content: flex-end;
}
.bai-review-prop-decision {
    font-size: 0.78rem;
    color: var(--muted);
    font-style: italic;
    margin-top: 0.15rem;
    grid-column: 1 / -1;
}
.bai-review-prop-po-counts {
    grid-column: 1 / -1;
    font-size: 0.78rem;
    color: var(--muted);
    margin-top: 0.15rem;
}

/* =================================================================
   Add Item — grouped PO preview (Fixes 2, 3, 4)
   ================================================================= */
.bai-pogrp {
    border: 1px solid var(--border);
    /* Primary-coloured left edge for clear at-a-glance section
       separation. Combined with the per-section background wash and
       the increased margin between adjacent sections (rule below),
       group boundaries are immediately visible while scanning. */
    border-left: 3px solid var(--link);
    border-radius: 0.4rem;
    margin-top: 0.5rem;
    /* Whole-section subtle wash — extends the head's tint feel across
       the body so each section reads as one cohesive tinted block. */
    background: var(--glass-surface);
    overflow: hidden;
}
/* 16px gap between adjacent sections (the first one keeps its
   small top margin so it doesn't crowd the "Update matching POs"
   checkbox above). */
.bai-pogrp + .bai-pogrp {
    margin-top: 1rem;
}
/* Section header — full-width 3-column grid: title (left, bold, wraps),
   counts (center, muted), Select all / Deselect all (right). MODEL +
   ELEV never truncate; long names wrap to additional lines of the
   title cell while the counts and controls stay aligned to their
   columns. Subtle background tint via --glass-surface separates the
   bar from the rows below. */
.bai-pogrp-head {
    display: grid;
    grid-template-columns: minmax(0, 1fr) auto auto;
    align-items: center;
    column-gap: 0.75rem;
    row-gap: 0.25rem;
    padding: 0.55rem 0.75rem;
    /* Header sits a touch stronger than the section wash so the bar
       reads as the section's anchor without being a heavy stripe. */
    background: var(--glass-surface-strong);
    border-bottom: 2px solid var(--border);
    font-size: 0.9rem;
}
.bai-pogrp-title {
    font-weight: 600;
    color: var(--text);
    /* Wrap full MODEL/ELEV — never ellipsis. */
    overflow-wrap: anywhere;
    word-break: break-word;
    line-height: 1.25;
}
.bai-pogrp-current {
    color: var(--link);
    font-weight: 500;
    font-size: 0.72rem;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    padding: 0.05rem 0.4rem;
    border-radius: 999px;
    border: 1px solid var(--link);
    background: var(--glass-surface-strong);
    margin-left: 0.4rem;
    white-space: nowrap;
}
.bai-pogrp-counts {
    color: var(--muted);
    font-size: 0.78rem;
    white-space: nowrap;
}
.bai-pogrp-selectall {
    display: flex;
    justify-content: flex-end;
}
.bai-pogrp-sa-toggle {
    padding: 0.05rem 0.4rem;
    font-size: 0.78rem;
    /* Reserve enough width for the longer of the two labels so the
       header columns don't shift when the toggle flips between
       "Select all" and "Deselect all". */
    min-width: 5.4rem;
    text-align: right;
}
/* On phone widths the header keeps Select all / Deselect all on the
   right of the title (parity with Add Item's section header) — the
   muted counts drop to a second row so they don't squeeze the title
   or push the toggle off the right edge. */
@media (max-width: 575px) {
    .bai-pogrp-head {
        grid-template-columns: minmax(0, 1fr) auto;
        grid-template-areas:
            "title    selectall"
            "counts   counts";
        row-gap: 0.2rem;
    }
    .bai-pogrp-title    { grid-area: title; }
    .bai-pogrp-counts   { grid-area: counts; justify-self: start; }
    .bai-pogrp-selectall { grid-area: selectall; justify-self: end; }
}

/* Body — no inner scroll. The list flows naturally inside the panel
   so the user scrolls the whole panel rather than nested regions. */
.bai-pogrp-body {
    padding: 0.25rem 0.75rem;
}
.bai-pogrp-empty {
    color: var(--muted);
    font-size: 0.85rem;
    padding: 0.5rem 0;
}

/* PO row — two-line layout with breathing room. Line 1: checkbox +
   prominent PO number (or Lot). Line 2: muted secondary details.
   Solid bottom border per row to clearly separate each lot. */
.bai-pogrp-row {
    display: grid;
    grid-template-columns: auto minmax(0, 1fr);
    align-items: start;
    column-gap: 0.6rem;
    padding: 0.55rem 0;
    border-bottom: 1px solid var(--border);
    font-size: 0.9rem;
}
.bai-pogrp-row:last-child { border-bottom: 0; }
.bai-pogrp-row > .form-check-input {
    margin-top: 0.2rem;
}
.bai-pogrp-label {
    margin: 0;
    cursor: pointer;
    min-width: 0;
}
.bai-pogrp-line1 {
    color: var(--text);
    font-weight: 500;
    line-height: 1.3;
    overflow-wrap: anywhere;
    word-break: break-word;
}
/* Existing-PO rows (i.e. NOT .is-create) — gentle glassy wash that
   sits IN the section rather than ON it. Both modes use a low alpha
   so the parent section's --glass-surface tint subtly bleeds through,
   matching the rest of the panel's translucent aesthetic (--card-bg
   itself sits around 0.72-0.74 alpha; row backgrounds should be
   lighter still so they don't punch through). New-PO rows keep their
   translucent blue stacking with the parent. Text stays var(--text). */
.bai-pogrp-row:not(.is-create) {
    background: rgba(248, 249, 250, 0.35);
    padding-left: 0.6rem;
    padding-right: 0.4rem;
    border-radius: 0.2rem;
}
body.theme-dark .bai-pogrp-row:not(.is-create) {
    /* --card-bg base (#1b1e26 / rgb(27, 30, 38)) at low alpha so most
       of the section wash shows through — integrates the row with the
       surrounding dark UI without forming a hard rectangle. */
    background: rgba(27, 30, 38, 0.25);
}
.bai-pogrp-pox {
    font-weight: 600;
}
.bai-pogrp-line1-aux {
    color: var(--muted);
    font-weight: 400;
    margin-left: 0.15rem;
}
.bai-pogrp-line2 {
    color: var(--muted);
    font-size: 0.82rem;
    line-height: 1.3;
    margin-top: 0.15rem;
    overflow-wrap: anywhere;
    word-break: break-word;
}

/* "Will be created" row — info-blue tint + left edge so the user
   immediately recognises that checking this triggers a NEW rr3_PO.
   Same two-line breathing room as the existing-PO rows. */
.bai-pogrp-row.is-create {
    background: rgba(13, 110, 253, 0.06);
    border-left: 3px solid var(--link);
    padding-left: 0.6rem;
    padding-right: 0.4rem;
}
body.theme-dark .bai-pogrp-row.is-create {
    background: rgba(117, 182, 255, 0.10);
}
.bai-pogrp-row.is-create .bai-pogrp-create-icon {
    color: var(--link);
    margin-right: 0.15rem;
}
.bai-pogrp-line2-create {
    color: var(--link);
    font-style: italic;
}
body.theme-dark .bai-pogrp-line2-create {
    color: #75b6ff;
}

/* ====================================================================
   ACH Management
   ====================================================================
   Sensitive fields (account / routing numbers) are masked by default
   and reveal on hover (desktop) or tap (mobile). The full value is
   present in the DOM because the page is gated to AP/Admin only —
   masking is a visual safeguard against shoulder-surfing, not a
   server-side secret. */

.sensitive-field {
    display: inline-block;
    font-family: var(--bs-font-monospace, ui-monospace, Menlo, monospace);
    font-variant-numeric: tabular-nums;
    cursor: pointer;
    user-select: text;
    border-radius: 3px;
    padding: 0 0.25rem;
    transition: background-color 0.12s ease;
}
.sensitive-field:hover,
.sensitive-field:focus-visible {
    background-color: rgba(255, 193, 7, 0.18); /* subtle amber wash */
    outline: 2px solid transparent;
    outline-offset: 2px;
}
.sensitive-field:focus-visible {
    outline-color: var(--link, #0d6efd);
}
/* Default state: masked visible, revealed hidden. Explicit `display:inline`
   on the masked child defends against parent-display quirks. */
.sensitive-masked {
    display: inline;
}
.sensitive-revealed {
    display: none;
}
.sensitive-field:hover .sensitive-masked,
.sensitive-field:focus-visible .sensitive-masked,
.sensitive-field.is-revealed .sensitive-masked {
    display: none;
}
.sensitive-field:hover .sensitive-revealed,
.sensitive-field:focus-visible .sensitive-revealed,
.sensitive-field.is-revealed .sensitive-revealed {
    display: inline;
}

/* ACH list — clickable rows. Click anywhere on a tbody row (except inside
   an interactive control) to select the record; the header "View History"
   button then targets that record's audit-trail page. */
.ach-record-row {
    cursor: pointer;
}
.ach-record-row.ach-row-selected > * {
    background-color: var(--pp-row-selected-bg) !important;
}

/* Action button border contrast on the hovered / selected row.
   .btn-outline-secondary's border-color resolves to a medium gray that
   blends into Bootstrap's table-hover background — the Edit button's
   outline vanishes against a hovered row. Force the border to use the
   portal's body-text token (var(--text)), which contrasts strongly in
   both light and dark themes regardless of the row's background.
   Applies to every action button in .ach-row-actions so the Delete
   button keeps the same border treatment as Edit. */
.ach-record-row:hover .ach-row-actions .btn,
.ach-record-row.ach-row-selected .ach-row-actions .btn {
    border-color: var(--text);
}

/* Mobile action row for ACH list cards (Edit / History / Delete). */
.ach-card-actions {
    display: flex;
    gap: 0.5rem;
    margin-top: 0.6rem;
    padding-top: 0.5rem;
    border-top: 1px solid var(--border);
}
.ach-card-actions .btn {
    flex: 1 1 auto;
    min-height: 40px;
}

/* ACH mobile card uses pp-row-card-pox slot for the vendor name rather
   than a real POX, so let it wrap to two lines if the name is long. */
@media (max-width: 767.98px) {
    .ach-row-card .pp-row-card-pox {
        white-space: normal;
        line-height: 1.25;
        font-family: var(--bs-body-font-family);
        text-transform: none;
        letter-spacing: 0;
        flex: 1 1 auto;
        min-width: 0;
    }
}

/* ====================================================================
   Open in Budget button (PO Workspace + PO Detail)
   ==================================================================== */

/* Bootstrap's default .btn:disabled drops pointer-events to none, which
   also suppresses the native `title` tooltip on hover. The Open in
   Budget button uses `title` to explain *why* it's disabled
   ("Select a PO first", "PO is missing Elevation", etc.), so we
   restore pointer-events on the disabled state. cursor:not-allowed
   keeps the visual signal that the button isn't actionable. */
#po-open-budget-btn:disabled,
#po-open-budget-btn.disabled {
    pointer-events: auto;
    cursor: not-allowed;
}

/* ====================================================================
   Reports view + Run modals — dark mode adjustments
   ====================================================================

   Bootstrap's default .modal-content is white-on-light-text, which is
   unreadable in dark mode. The Add Parts modal solves this with its
   own scoped block (.add-parts-modal …); the rest of the portal's
   modals fall back to defaults. Scoping these rules to .reports-modal
   keeps them additive — existing modal styling (e.g. Add Parts) is
   untouched. */

/* ----- Reports run modals -------------------------------------------- */
.theme-dark .reports-modal .modal-content {
    background-color: var(--card-bg);
    color: var(--text);
    border: 1px solid var(--glass-border);
}
.theme-dark .reports-modal .modal-header,
.theme-dark .reports-modal .modal-footer {
    border-color: var(--glass-border);
}
.theme-dark .reports-modal .modal-title {
    color: var(--text);
}
.theme-dark .reports-modal .btn-close {
    /* Bootstrap's btn-close is a dark SVG; invert + grayscale to make
       it visible against a dark backdrop. Mirrors the Add Parts modal
       pattern. */
    filter: invert(1) grayscale(1);
}
.theme-dark .reports-modal .form-check-label,
.theme-dark .reports-modal legend.form-label,
.theme-dark .reports-modal label.form-label {
    color: var(--text);
}
.theme-dark .reports-modal code {
    color: var(--text);
    background-color: rgba(var(--glass-accent-rgb), 0.18);
    padding: 0 4px;
    border-radius: 3px;
}

/* The Run Budget Report modal opts into the Add Parts modal "family"
   look by carrying both .reports-modal and .add-parts-modal. In dark
   mode the .reports-modal content-background rule above ties the Add
   Parts gradient on specificity, so re-assert the gradient here (after
   it in source order) to keep the two modals visually identical. The
   themed --add-parts-modal-bg token already covers light + dark. */
.reports-modal.add-parts-modal .modal-content {
    background: var(--add-parts-modal-bg);
}

/* Footer Prices toggle (left) stacks its radios row over a short
   caption; the action buttons sit right via .justify-content-between. */
.reports-prices-footer {
    display: flex;
    flex-direction: column;
    gap: 0.1rem;
    min-width: 0;
}
.reports-prices-footer .reports-prices-caption {
    margin: 0;
}

/* Cascade fields: parent-disabled state + inline hint copy. The hint
   text auto-hides via the JS controller when a hint isn't applicable;
   we keep the color readable in both themes so a future hint string
   ("No models for this project") doesn't fade into the modal chrome. */
.reports-modal .reports-cascade-hint {
    font-size: 0.78rem;
    color: var(--muted, #6c757d);
    margin-top: 0.15rem;
}
.theme-dark .reports-modal .reports-cascade-hint {
    color: var(--muted);
}

/* Cascade error message slot. .invalid-feedback only shows when
   .is-invalid is set on the input, which is the standard Bootstrap
   contract — repeated here for dark-mode contrast. */
.reports-modal .reports-cascade-error {
    font-size: 0.78rem;
}
.theme-dark .reports-modal .form-control.is-invalid {
    border-color: #e57b7b;
    background-image: none;
}
.theme-dark .reports-modal .invalid-feedback {
    color: #ffb4b4;
}

/* ----- Reports landing page ----------------------------------------- */

/* The Recent Reports filter form is wrapped in .bg-light, which is
   white in dark mode and clashes with the dark card it sits inside.
   Re-skin to a translucent dark surface so it reads as a sub-panel. */
.theme-dark .reports-main .reports-filter-form {
    background-color: var(--glass-surface) !important;
    border-color: var(--glass-border) !important;
    color: var(--text);
}

/* Available Reports cards (.report-available-card) sit inside .card,
   which is themed; the inner .border.rounded panel needs a touch of
   contrast in dark mode so the cards visually separate. */
.theme-dark .reports-main .report-available-card {
    background-color: var(--glass-surface);
    border-color: var(--glass-border) !important;
}

/* The highlighted "just-generated" row uses Bootstrap's table-success
   (pale green) which is hard to read on a dark table. Re-skin to a
   glass accent that matches the rest of the portal's selected states. */
.theme-dark .reports-main .table-success,
.theme-dark .reports-main .table-success > * {
    background-color: var(--selected-row-bg) !important;
    color: var(--text) !important;
    --bs-table-bg-state: transparent;
}

/* Pagination + filter-form labels — explicit dark color so they pick
   up the page theme rather than inheriting Bootstrap's defaults. */
.theme-dark .reports-main .form-label,
.theme-dark .reports-main .form-check-label {
    color: var(--text);
}

/* ----- Recent Reports — mobile card layout (<768px) ------------------
   The desktop table forces horizontal scrolling at narrow widths;
   the card layout below mirrors the portal's mobile-card convention
   (PO Workspace, Budget View) — theme-token surfaces, tap-friendly
   buttons, no overflow. Visibility is driven by Bootstrap's d-md-*
   utility classes on the wrapping divs, so only the styling lives
   here. */

.reports-mobile-list {
    display: flex;
    flex-direction: column;
    gap: 0.6rem;
}
.reports-mobile-card {
    background-color: var(--card-bg);
    color: var(--text);
    border: 1px solid var(--border);
    border-radius: 8px;
    padding: 0.75rem 0.85rem;
    box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
}
.reports-mobile-card-highlight {
    /* Green accent for the just-generated report. Matches the
       table-success row's role on desktop without using the
       Bootstrap green that goes unreadable in dark mode. */
    border-color: var(--selected-row-accent, #6ee7b7);
    background-color: var(--selected-row-bg, rgba(110, 231, 183, 0.12));
}
.reports-mobile-card-head {
    display: flex;
    align-items: baseline;
    flex-wrap: wrap;
    gap: 0.4rem 0.5rem;
    margin-bottom: 0.25rem;
}
.reports-mobile-type-chip {
    font-size: 0.7rem;
    font-weight: 700;
    letter-spacing: 0.04em;
    padding: 0.15rem 0.45rem;
    border-radius: 4px;
    border: 1px solid var(--border);
    background-color: rgba(0, 0, 0, 0.04);
    color: var(--text);
    white-space: nowrap;
}
.theme-dark .reports-mobile-type-chip {
    background-color: rgba(255, 255, 255, 0.06);
    border-color: var(--glass-border);
}
.reports-mobile-type-chip.reports-mobile-type-budget {
    /* Subtle blue tint — same accent as the Reports view's available
       cards in dark mode, applied here in both themes for parity. */
    background-color: rgba(74, 144, 226, 0.12);
    border-color: rgba(74, 144, 226, 0.32);
}
.reports-mobile-type-chip.reports-mobile-type-po-status {
    background-color: rgba(99, 102, 241, 0.12);
    border-color: rgba(99, 102, 241, 0.32);
}
.reports-mobile-type-chip.reports-mobile-type-lot-billing {
    /* Teal tint — distinct from Budget (blue) and PO Status (indigo). */
    background-color: rgba(20, 160, 140, 0.14);
    border-color: rgba(20, 160, 140, 0.34);
}
.reports-mobile-scope {
    font-size: 0.95rem;
    line-height: 1.25;
    word-break: break-word;
    flex: 1 1 auto;
    min-width: 0;
}
.reports-mobile-scope-sep {
    color: var(--muted, #6c757d);
}
.reports-mobile-meta {
    font-size: 0.78rem;
    color: var(--muted, #6c757d);
    line-height: 1.3;
    margin-bottom: 0.6rem;
    word-break: break-word;
}
.theme-dark .reports-mobile-meta {
    color: var(--muted);
}
.reports-mobile-actions {
    display: flex;
    gap: 0.5rem;
    align-items: stretch;
}
.reports-mobile-delete-form {
    margin: 0;
    /* The delete form wraps the button — make it act as a flex item
       inside .reports-mobile-actions so the button can size itself
       to match View. */
    flex: 0 0 auto;
}
.reports-mobile-btn {
    /* 44px minimum tap target per the brief; padding tuned so the
       button reads as a button, not a tag. */
    min-height: 44px;
    padding: 0.5rem 1rem;
    font-size: 0.95rem;
    font-weight: 500;
    border-radius: 6px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
}
.reports-mobile-btn-view {
    /* Primary action — filled, takes available width on narrow screens
       so the tap target stays large. */
    flex: 1 1 auto;
}
.reports-mobile-btn-delete {
    /* Secondary action — outlined danger, fixed width on the right. */
    flex: 0 0 auto;
}
.reports-empty-state {
    padding: 0.5rem 0;
    font-size: 0.95rem;
}
@media (max-width: 575.98px) {
    /* Extra-small screens: stack chip + scope vertically so the
       scope line can use the full width. */
    .reports-mobile-card-head {
        flex-direction: column;
        align-items: flex-start;
    }
}

/* ============================================================
   Safety Toolbox
   ============================================================
   List view (.safety-toolbox-table / .safety-toolbox-mobile), submit
   form (.safety-toolbox-form), and the image gallery on the detail
   view. All colors come from theme tokens so the same rules render
   in both light and dark mode without duplication. */

.safety-row-deleted td,
.safety-card-deleted .card-body {
    opacity: 0.55;
}
.safety-row-deleted td:first-child,
.safety-card-deleted .card-body {
    /* Keep the DELETED badge readable even when the row is muted. */
    opacity: 1;
}

/* Submissions list — row action icon buttons.
   Same visual vocabulary as ACH's row actions: a small horizontal
   cluster of outline btn-sm icon-only buttons on desktop, and an
   icon-plus-label flex row on mobile cards. .safety-row-action is
   width-pinned so the icon buttons line up evenly even when the
   action set varies (View only, +Edit, +Delete, +Restore). */
.safety-row-action {
    /* btn-sm collapses to a tight pill for icon-only content; pin
       a 32px square so the cluster reads as a consistent control
       bar across rows that carry different numbers of actions. */
    min-width: 32px;
    padding-left: 0.4rem;
    padding-right: 0.4rem;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    line-height: 1;
}
.safety-row-action i {
    line-height: 1;
}

/* Mobile card action row — icon-plus-label buttons, gap-wrapped,
   each pinned to a 44px tap target per the WCAG mobile guideline. */
.safety-card-action {
    min-height: 44px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 0.4rem;
    padding-left: 0.85rem;
    padding-right: 0.85rem;
}

.safety-comment-cell {
    max-width: 32ch;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.safety-comment-text {
    white-space: pre-wrap;
}

/* Submissions table — header background.
   The portal-wide --table-head-bg (#f1f3f5 light / #1f2028 dark)
   sits flush enough against the page bg (#f0f2f8 / #111217) that
   in light mode the header row visually merges into the body
   rows. PO Workspace / Payment Processor avoid this because their
   tables sit inside translucent --card-bg surfaces; the Safety
   Toolbox table sits directly on the page bg. Scoped override
   here uses --border as a fill — semantically a divider colour,
   but it gives the strongest readable contrast against --bg
   without introducing a new token. Dark mode also bumps a notch
   lighter, which keeps the header distinct from --bg there too. */
.safety-toolbox-table thead th {
    background-color: var(--border);
    color: var(--text);
    font-weight: 600;
}

/* Submissions table — tablet compactness (md / lg only).
   The desktop table renders at every viewport ≥768px now, so
   between 768-1199px we need it to fit without the .table-
   responsive parent kicking in a horizontal scroll. The rebalance:
     - cell padding tightened to ~5px / ~6px
     - font size dropped to 0.82rem so the same row fits in less
       horizontal space
     - thumbnail shrinks to 40px (it bumps back to 56px at xl+)
     - comment cap drops to 14ch + ellipsis
     - PROJ / VENDOR name suffixes hidden in template at md/lg
       (` · NAME` only renders at xl+), so the rules below just
       cover sizing / spacing.
   Restored to desktop defaults at xl+ on non-touch devices. Touch
   devices at xl+ (iPad Pro 12.9" landscape) keep this tablet sizing via
   the pointer/hover clause; the matching Bootstrap d-xl-* utility
   override block follows directly after this rule. */
@media (min-width: 768px) and (max-width: 1199.98px),
       (min-width: 1200px) and (hover: none) and (pointer: coarse) {
    .safety-toolbox-table {
        font-size: 0.82rem;
    }
    .safety-toolbox-table > :not(caption) > * > * {
        padding: 0.3rem 0.4rem;
    }
    .safety-toolbox-table .safety-list-thumb-trigger {
        width: 40px;
        height: 40px;
    }
    .safety-toolbox-table .safety-comment-cell {
        max-width: 14ch;
    }
    .safety-toolbox-table .safety-cell-projects,
    .safety-toolbox-table .safety-cell-vendors {
        /* Codes only at md/lg — keeps the cell narrow even on a
           submission with three badges. */
        max-width: 12ch;
        overflow: hidden;
        white-space: nowrap;
        text-overflow: ellipsis;
    }
    .safety-toolbox-table .safety-row-action {
        /* The action icon cluster pinned to a smaller 28px square so
           three icons + gaps don't squeeze the rest of the row. */
        min-width: 28px;
        padding-left: 0.3rem;
        padding-right: 0.3rem;
    }
    .safety-toolbox-table .safety-submitted-date {
        line-height: 1.2;
    }
    .safety-toolbox-table .safety-submitted-super {
        line-height: 1.2;
        margin-top: 0.1rem;
    }
}

/* Bootstrap d-xl-* utility override for touch devices at xl+ widths.
   The submissions table uses width-only Bootstrap responsive utilities
   (d-xl-table-cell / d-xl-inline / d-xl-none) to fold the "Submitted by"
   column into the date cell and swap full names for codes at md/lg.
   Those utilities don't know about pointer/hover, so on a touch device
   at xl+ (iPad Pro 12.9" landscape) they'd still show the dense desktop
   variant even though the sizing block above has switched to tablet.
   These rules force the md/lg (tablet) variant on touch-at-xl. !important
   is required because Bootstrap's utilities use !important themselves. */
@media (min-width: 1200px) and (hover: none) and (pointer: coarse) {
    /* Hide the standalone "Submitted by" column header + cell — it folds
       into the date cell's second line (.safety-submitted-super) below. */
    .safety-toolbox-table .d-xl-table-cell { display: none !important; }

    /* Date cell: show the compact date, hide the full date+time, and
       reveal the SUPER name as the cell's second line. */
    .safety-toolbox-table .safety-submitted-date .d-xl-inline { display: none !important; }
    .safety-toolbox-table .safety-submitted-date .d-xl-none { display: inline !important; }
    .safety-toolbox-table .safety-submitted-super.d-xl-none { display: block !important; }

    /* Projects: drop the ` · NAME` suffix, keep the bare code badge. */
    .safety-toolbox-table .safety-cell-projects .d-xl-inline { display: none !important; }

    /* Vendors: show the short VENDX code, hide the full vendor name. */
    .safety-toolbox-table .safety-cell-vendors .d-xl-inline { display: none !important; }
    .safety-toolbox-table .safety-cell-vendors .d-xl-none { display: inline !important; }
}

/* ------------------------------------------------------------------
   Submissions list — inline thumbnail preview + lightbox.
   Drives the new "Images" column on desktop and the horizontal
   thumbnail strip on mobile cards. The lightbox modal lives in
   submissions.html; this block styles the trigger surfaces and the
   modal chrome. All colours come from existing portal theme tokens
   so light + dark both work without selector branching.
   ------------------------------------------------------------------ */

.safety-image-cell {
    /* Keep the cell snug — the actual control is the 48-60px thumb
       inside, not the cell itself. */
    width: 1%;
    white-space: nowrap;
    vertical-align: middle;
}

.safety-list-thumb-trigger {
    /* Reset native button styling so this reads as an image, not a
       form control. The relative positioning anchors the +N count
       badge. */
    position: relative;
    display: inline-block;
    width: 56px;
    height: 56px;
    padding: 0;
    border: 1px solid var(--border);
    border-radius: 0.4rem;
    background: var(--card-bg);
    overflow: hidden;
    cursor: pointer;
    line-height: 0;
}
.safety-list-thumb-trigger:hover,
.safety-list-thumb-trigger:focus-visible {
    border-color: var(--glass-border);
    box-shadow: 0 0 0 2px var(--btn-hover-bg);
    outline: none;
}
.safety-list-thumb-img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
}
.safety-list-thumb-count {
    /* Float a small "+N" pill over the bottom-right of the
       thumbnail so the user sees there are more images without
       another column. Token-driven colours so the badge tracks the
       portal's primary action accent in both themes. */
    position: absolute;
    right: 2px;
    bottom: 2px;
    padding: 0 0.35rem;
    font-size: 0.66rem;
    font-weight: 600;
    line-height: 1.45;
    color: #fff;
    background-color: rgba(var(--glass-accent-rgb), 0.92);
    border-radius: 999px;
    pointer-events: none;
}

/* Mobile card thumbnail strip — horizontal, scrollable, with the
   scrollbar suppressed so the strip looks clean on iOS / Android.
   Each cell is 72px square (tap target sized via 1.5x of the image
   visual size, comfortable enough to avoid mis-taps on the wider
   cards). */
.safety-image-strip {
    display: flex;
    gap: 0.4rem;
    overflow-x: auto;
    overflow-y: hidden;
    padding-bottom: 2px;
    scrollbar-width: none;       /* Firefox */
    -ms-overflow-style: none;    /* legacy IE/Edge */
}
.safety-image-strip::-webkit-scrollbar {
    display: none;               /* WebKit / Blink */
}
.safety-image-strip-cell {
    flex: 0 0 auto;
    width: 72px;
    height: 72px;
    padding: 0;
    border: 1px solid var(--border);
    border-radius: 0.4rem;
    background: var(--card-bg);
    overflow: hidden;
    cursor: pointer;
    line-height: 0;
    text-decoration: none;
}
.safety-image-strip-cell:hover,
.safety-image-strip-cell:focus-visible {
    border-color: var(--glass-border);
    box-shadow: 0 0 0 2px var(--btn-hover-bg);
    outline: none;
}
.safety-image-strip-cell img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
}

/* ------------------------------------------------------------------
   Lightbox modal.
   Bootstrap modal handles backdrop + focus-trap + ESC. We just
   restyle the body to be a dark photo viewer with prev/next
   buttons and a counter chip. The body fills available space; the
   image is letterboxed inside with object-fit: contain.
   ------------------------------------------------------------------ */

.safety-lightbox .safety-lightbox-content {
    background-color: rgba(8, 10, 14, 0.96);
    border: 1px solid var(--glass-border);
    color: #f2f4f8;
}
.safety-lightbox .safety-lightbox-header {
    background-color: transparent;
    border-bottom: 1px solid var(--glass-border);
    padding: 0.4rem 0.75rem;
}
.safety-lightbox-counter {
    color: #f2f4f8;
    font-size: 0.82rem;
    font-weight: 600;
    letter-spacing: 0.02em;
}
.safety-lightbox-body {
    position: relative;
    padding: 0;
    min-height: 50vh;
    display: flex;
    align-items: stretch;
    justify-content: stretch;
}
.safety-lightbox-stage {
    flex: 1 1 auto;
    display: flex;
    align-items: center;
    justify-content: center;
    /* The stage is the touchstart/touchend target; padding gives a
       comfortable swipe area on mobile without bumping the image
       smaller than necessary. */
    padding: 0.5rem 0;
    min-width: 0;
}
.safety-lightbox-image {
    max-width: 100%;
    max-height: 80vh;
    object-fit: contain;
    display: block;
}

.safety-lightbox-nav {
    position: absolute;
    top: 50%;
    transform: translateY(-50%);
    z-index: 5;
    width: 44px;
    height: 44px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    background-color: rgba(255, 255, 255, 0.08);
    border: 1px solid rgba(255, 255, 255, 0.18);
    color: #f2f4f8;
    border-radius: 999px;
    font-size: 1.4rem;
    line-height: 1;
}
.safety-lightbox-nav:hover,
.safety-lightbox-nav:focus-visible {
    background-color: rgba(255, 255, 255, 0.16);
    color: #fff;
    outline: none;
}
.safety-lightbox-prev { left: 0.75rem; }
.safety-lightbox-next { right: 0.75rem; }

@media (max-width: 575.98px) {
    /* Pull the nav buttons in slightly on tiny phones so they don't
       sit on top of the image's edges. */
    .safety-lightbox-prev { left: 0.4rem; }
    .safety-lightbox-next { right: 0.4rem; }
    .safety-lightbox-image { max-height: 75vh; }
}

/* Submit form — multi-row picker + image preview. */

.safety-multi-row {
    /* Inputs already inherit form-control styling; the row is purely a
       flex wrapper, no padding/border of its own. */
}

.safety-image-preview {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
    gap: 0.5rem;
    margin-top: 0.5rem;
}
.safety-image-thumb {
    position: relative;
    border: 1px solid var(--bs-border-color);
    border-radius: 0.5rem;
    overflow: hidden;
    background: var(--bs-tertiary-bg);
}
.safety-image-thumb img {
    width: 100%;
    height: 120px;
    object-fit: cover;
    display: block;
}
.safety-image-thumb-remove {
    position: absolute;
    top: 4px;
    right: 4px;
    width: 24px;
    height: 24px;
    border: 0;
    border-radius: 50%;
    background: rgba(0, 0, 0, 0.65);
    color: #fff;
    font-size: 1rem;
    line-height: 1;
    display: flex;
    align-items: center;
    justify-content: center;
    cursor: pointer;
}
.safety-image-thumb-caption {
    padding: 0.25rem 0.4rem;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* Detail view — project / vendor badge stack inside the
   po-detail-block. Wraps cleanly and keeps consistent spacing
   between badges. */
.safety-detail-tag-list {
    display: flex;
    flex-wrap: wrap;
    gap: 0.35rem;
}

/* Detail view — image gallery. Thumbnails open the original in a new
   tab on click; no lightbox library. */

.safety-image-gallery {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
    gap: 0.5rem;
}
.safety-image-card {
    display: block;
    color: inherit;
    text-decoration: none;
    border: 1px solid var(--bs-border-color);
    border-radius: 0.5rem;
    overflow: hidden;
    background: var(--bs-tertiary-bg);
}
.safety-image-card:hover {
    border-color: var(--bs-primary);
}
.safety-image-card img {
    width: 100%;
    height: 160px;
    object-fit: cover;
    display: block;
}
.safety-image-card-caption {
    padding: 0.25rem 0.4rem;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* Mobile tweaks. */
@media (max-width: 575.98px) {
    .safety-image-preview,
    .safety-image-gallery {
        grid-template-columns: repeat(2, 1fr);
    }
}

/* Landing page — primary action rendered as a substantial card
   with an icon chip stacked above the label, secondary as a
   right-aligned sibling that sits in normal flow underneath with a
   visible vertical gap (no overlap with the card).

   Color strategy: every surface, border, and text color on the
   primary card maps to an existing portal theme token, so the
   landing flows through the standard light/dark switch without
   any selector branching:

     Card background  → --card-bg       (portal's raised-surface
                                          token — white-ish in light,
                                          elevated dark in dark; same
                                          one .card uses)
     Card border      → --border        (same 1px divider used by
                                          .card and .table-bordered)
     Card text        → --text          (default body color)
     Card shadow      → --glass-shadow  (canonical ambient card lift)
     Icon chip bg     → --bg            (page background — chip
                                          renders as a subtle cutout
                                          within the raised card,
                                          mirroring how a notification
                                          dot sits inside a card
                                          elsewhere in the portal)
     Icon color       → --muted         (the chip is the muted page
                                          background, so the "+" uses
                                          a muted text token to read
                                          against it without
                                          out-shouting the bolder
                                          --text label)
     Hover bg         → --btn-hover-bg  (row-hover convention used by
                                          .pp-table, .ph-table,
                                          .btn-outline-*:hover)
     Hover border     → --glass-border  (matches .btn-outline-* hover)
     Active bg        → --btn-active-bg (matches .btn-outline-* active)

   .btn-secondary / .btn-primary are intentionally NOT used — they
   bind Bootstrap's --bs-btn-bg / --bs-btn-color and would override
   the portal tokens above.

   Secondary action keeps the vanilla portal
   ``.btn-outline-secondary`` surface (text + border from
   ``--text`` and ``--border``, hover from ``--btn-hover-bg``).
   Only the stage's flex layout + a 44px tap target are overridden
   here. */

.safety-toolbox-landing-main {
    /* base.html's <main> applies py-4; we need to clear that so the
       landing's own vertical-centering flex has the full content
       height to work with. */
    padding-top: 0;
    padding-bottom: 0;
}
.safety-landing {
    display: flex;
    align-items: center;
    justify-content: center;
    /* Fill the visible viewport minus the fixed topbar (~64px) and
       a comfortable allowance for the tab bar + body padding so the
       primary card sits visually centered on first paint without
       requiring a scroll. The min() guard avoids huge gaps on
       very tall monitors. */
    min-height: min(80vh, calc(100vh - 160px));
    padding: 3rem 1rem;
    text-align: center;
}
.safety-landing-stage {
    /* Flex column: the primary card stretches to the stage width,
       the secondary aligns to the cross-axis end (right edge of
       the card) on its own row below. The gap controls the visual
       breathing room between the two. */
    display: flex;
    flex-direction: column;
    align-items: stretch;
    gap: 0.875rem;
    max-width: min(420px, 100%);
}

/* ----- Primary action card -----
   .btn provides the link reset (display, padding baseline,
   transition); every visible surface attribute below is portal-
   token driven. */
.safety-landing-primary {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 1.25rem;
    width: 100%;
    min-width: 280px;
    padding: 3rem 2rem;
    border: 1px solid var(--border);
    border-radius: 1rem;
    line-height: 1.25;
    background-color: var(--card-bg);
    color: var(--text);
    box-shadow: var(--glass-shadow);
    transition:
        background-color 120ms ease-out,
        border-color 120ms ease-out,
        transform 120ms ease-out,
        box-shadow 120ms ease-out;
}
.safety-landing-primary:hover,
.safety-landing-primary:focus-visible {
    /* Resting state matches the page so we lean on a surface +
       border shift (the portal's row-hover convention) plus a
       small lift to keep the affordance discoverable. */
    background-color: var(--btn-hover-bg);
    border-color: var(--glass-border);
    color: var(--text);
    transform: translateY(-2px);
    box-shadow: 0 12px 28px rgba(0, 0, 0, 0.24);
}
.safety-landing-primary:active {
    background-color: var(--btn-active-bg);
    border-color: var(--glass-border);
    transform: translateY(0);
    box-shadow: 0 6px 18px rgba(0, 0, 0, 0.2);
}

/* Icon chip inside the primary card.
   The chip uses the page-bg token so it reads as a recessed cutout
   within the raised --card-bg surface — the inverse of the previous
   iteration, intentionally. Icon color uses --muted so the "+"
   stays visible against the muted page bg without competing with
   the bolder --text label below. */
.safety-landing-primary-icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 64px;
    height: 64px;
    border-radius: 0.75rem;
    background-color: var(--bg);
    color: var(--muted);
    flex-shrink: 0;
}
.safety-landing-primary-icon i {
    font-size: 2rem;
    line-height: 1;
}
.safety-landing-primary-text {
    font-size: 1.25rem;
    font-weight: 600;
    /* Inherits --text from .safety-landing-primary. */
}

/* ----- Right-aligned secondary -----
   Keep the standard portal btn-outline-secondary look — text color
   from --text, border from --border, hover from --btn-hover-bg.
   Layout is the only deliberate override; we don't restate font
   size, weight, or radius so this button matches every other
   secondary action in the portal. */
.safety-landing-secondary {
    /* Sit in normal flow below the card; align-self overrides the
       parent's align-items: stretch so the button keeps its
       intrinsic width and snaps to the right edge of the card. */
    align-self: flex-end;
    /* 44px guideline for a tap target regardless of viewport — the
       btn-sm base is below that, this lifts it back up. */
    min-height: 44px;
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    white-space: nowrap;
}

/* Mobile — primary card shrinks proportionally; secondary stays
   right-aligned below it down to ~360px, then centers under the
   card on the very smallest viewports we support so the spatial
   anchor still reads even when the card spans most of the screen. */
@media (max-width: 575.98px) {
    .safety-landing {
        /* Top-align on mobile so the card sits high in the viewport
           with comfortable ~28px top padding, not floating mid-
           screen on a tall phone. Desktop centering (the rules in
           the base block above) is unchanged. The min-height is
           kept so the page still fills the viewport — only the flex
           alignment shifts. */
        align-items: flex-start;
        min-height: min(75vh, calc(100vh - 140px));
        padding: 1.75rem 1rem;
    }
    .safety-landing-stage {
        max-width: 100%;
        width: 100%;
    }
    .safety-landing-primary {
        /* Larger top/bottom padding + min-height so the card reads
           as a substantial surface on mobile rather than a squat
           rectangle. The vertical stack (chip → label) breathes
           with the bumped gap. */
        padding: 3.25rem 1.5rem;
        gap: 1.5rem;
        min-width: 0;
        min-height: 240px;
    }
    .safety-landing-primary-icon {
        width: 56px;
        height: 56px;
    }
    .safety-landing-primary-icon i {
        font-size: 1.75rem;
    }
    .safety-landing-primary-text {
        font-size: 1.1rem;
    }
}
@media (max-width: 359.98px) {
    /* Center the secondary under the card so it can't visually
       hug the screen edge on tiny phones. */
    .safety-landing-secondary {
        align-self: center;
    }
}

/* Submit form (and admin edit form) — width caps at ALL viewports.
   Inputs used to span the full container width, which on a wide
   desktop monitor produces 800-1200px-wide pickers that read as
   sloppy. The constraints below are tuned to the kind of data each
   field carries:

     - PROJ rows: ~14rem (224px) — codes are 1-5 chars, no need to
       sprawl past that
     - VENDOR rows: ~26rem (416px) — names can run long
     - COMMENT textarea: ~32rem (512px) — multi-line prose
     - Mobile: tighten to fit inside a 320-414px phone with side
       padding so the form doesn't hug the viewport edges

   The picker rows key off .safety-multi-list[data-name] so PROJ
   and VENDOR get different caps without per-row CSS classes. */
.safety-toolbox-form .safety-multi-list[data-name="proj"] .safety-multi-row {
    max-width: 14rem;
}
.safety-toolbox-form .safety-multi-list[data-name="vendx"] .safety-multi-row {
    max-width: 26rem;
}
.safety-toolbox-form #safety-comment,
.safety-toolbox-form #safety-edit-comment {
    max-width: 32rem;
}
@media (max-width: 575.98px) {
    .safety-toolbox-form {
        /* Side padding so contents don't run flush against the
           viewport edge inside .container's already-tight padding. */
        padding-left: 1rem;
        padding-right: 1rem;
    }
    .safety-toolbox-form .safety-multi-list[data-name="proj"] .safety-multi-row {
        max-width: 14rem;
    }
    .safety-toolbox-form .safety-multi-list[data-name="vendx"] .safety-multi-row {
        max-width: 22rem;
    }
    .safety-toolbox-form #safety-comment,
    .safety-toolbox-form #safety-edit-comment {
        max-width: 26rem;
    }
}

/* ----- Typeahead component (replaces <datalist>) -----
   The component is a wrapper div (.safety-typeahead) containing an
   <input> and an absolutely-positioned dropdown panel
   (.safety-typeahead-panel). All colours come from portal theme
   tokens so the dropdown adapts to light / dark without selector
   branching. */

.safety-typeahead {
    position: relative;
}

/* Remove (X) button on multi-row pickers.
   The default .btn-link.p-0 sizing depends on the icon glyph's
   line-height to centre, which drifts because .bi-x-lg's box has
   asymmetric internal padding. Pin a fixed square and centre the
   glyph with flex so the X reads as visually-centred in both rows
   of the picker and matches across viewports. */
.safety-multi-remove {
    width: 32px;
    height: 32px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    line-height: 1;
    flex-shrink: 0;
}
.safety-multi-remove i {
    /* Strip any inherited offset; the flex parent does the centring. */
    display: block;
    line-height: 1;
    margin: 0;
}
.safety-typeahead-input.is-invalid {
    /* Bootstrap's is-invalid already paints the red border; this
       just bumps the border-color so it survives our other
       form-control overrides cleanly. */
    border-color: var(--bs-danger, #dc3545);
}
.safety-typeahead-panel {
    position: absolute;
    top: calc(100% + 4px);
    left: 0;
    /* The input is intentionally narrower than full names (the PROJ
       row caps at 14rem, VENDOR at 26rem), but the dropdown rows
       need to read in full. Let the panel match the input width as
       a floor (min-width: 100%) but grow up to a sensible cap when
       option text demands it, then scroll horizontally if anything
       still overruns. Capped at 36rem (~576px) so it can't fly off
       a 414px phone — see also the mobile guard below. */
    min-width: 100%;
    width: max-content;
    max-width: min(36rem, calc(100vw - 2rem));
    z-index: 20;
    max-height: 16rem;
    overflow-y: auto;
    /* Momentum scrolling on iOS so a long option list flicks naturally;
       the JS only selects on a non-moving tap, so scrolling is free. */
    -webkit-overflow-scrolling: touch;
    background-color: var(--card-bg);
    color: var(--text);
    border: 1px solid var(--border);
    border-radius: 0.5rem;
    box-shadow: var(--glass-shadow);
    backdrop-filter: blur(8px);
    -webkit-backdrop-filter: blur(8px);
}
.safety-typeahead-panel[hidden] {
    display: none;
}
.safety-typeahead-option {
    /* 44px guideline for tap targets — important on mobile. */
    min-height: 44px;
    display: flex;
    align-items: center;
    padding: 0.5rem 0.85rem;
    line-height: 1.3;
    cursor: pointer;
    color: var(--text);
    border-bottom: 1px solid var(--border);
    /* Names can run long — wrap inside the row rather than truncate,
       so the user can read the full vendor / project name. The
       max-width on the panel keeps any single row from exploding
       past the viewport. */
    white-space: normal;
    overflow-wrap: anywhere;
    user-select: none;
    -webkit-user-select: none;
}
.safety-typeahead-option:last-child {
    border-bottom: 0;
}
.safety-typeahead-option:hover,
.safety-typeahead-option-active {
    background-color: var(--btn-hover-bg);
}

