/* ── Self-hosted fonts ─────────────────────────────────────────────────────
   Variable woff2 files served from ../fonts/, latin subset only (US-only
   app, no need for Vietnamese / Cyrillic / Greek). font-display:swap keeps
   text readable in the system fallback until the font loads; because they
   are same-origin + preloaded in the HTML, the swap window is near-zero
   on anything but the first visit. One @font-face per family covers every
   weight the app uses via the variable-weight axis. */
@font-face {
    font-family: 'Fraunces';
    font-style: normal;
    font-weight: 100 900;
    font-display: swap;
    src: url('../fonts/fraunces.woff2') format('woff2');
}
@font-face {
    font-family: 'Inter';
    font-style: normal;
    font-weight: 100 900;
    font-display: swap;
    src: url('../fonts/inter.woff2') format('woff2');
}
@font-face {
    font-family: 'JetBrains Mono';
    font-style: normal;
    font-weight: 100 900;
    font-display: swap;
    src: url('../fonts/jetbrains-mono.woff2') format('woff2');
}

/* Make the HTML `hidden` attribute reliably hide elements even when author
   CSS gives them an explicit display (e.g. .pdf-chosen uses display:flex,
   which otherwise ties the UA stylesheet on specificity and wins by source
   order, leaving a `hidden` element visible). */
[hidden] { display: none !important; }

:root {
    --bg:           #eef2f7;
    --surface:      #f7faff;
    --ink:          #1a1a1a;
    --ink-soft:     #4a4a4a;
    --ink-muted:    #7a8590;
    --rule:         #c8d4e2;
    --rule-soft:    #dde6f0;
    --accent:       #1f4e8b;
    --db-bg:        #dbe4ed;
    --db-bg-alt:    #cfd9e6;
    --db-ink:       #485866;
    --row-alt:      #eef3f9;
    --focus:        #2766b8;
    --header-bg:    #1a1a1a;
    --header-ink:   #eef2f7;
}

* { box-sizing: border-box; margin: 0; padding: 0; }
html, body { height: 100%; }

body {
    background: var(--bg);
    color: var(--ink);
    font-family: 'Inter', -apple-system, sans-serif;
    font-size: 13px;
    line-height: 1.4;
    padding: 20px 20px 0;
    display: flex;
    flex-direction: column;
}

/* ── Header ─────────────────────────────────────────────────────── */
header {
    /* Title block on the left, user-strip on the right. The filter metrics
       live in the view-tabs now, not here. */
    display: flex;
    justify-content: space-between;
    align-items: center;
    gap: 20px;
    padding-bottom: 14px;
    border-bottom: 2px solid var(--ink);
    flex-shrink: 0;
}

.title-block { position: relative; }
.title-block .eyebrow {
    font-family: 'JetBrains Mono', monospace;
    font-size: 10px;
    letter-spacing: 0.18em;
    text-transform: uppercase;
    color: var(--ink-muted);
    margin-bottom: 4px;
    user-select: none;   /* shift-click to open settings — don't start a selection */
}

/* Hidden settings menu — revealed by shift-click on the eyebrow */
.settings-menu {
    position: absolute;
    top: 100%;
    left: 0;
    z-index: 50;
    min-width: 340px;
    margin-top: 4px;
    padding: 14px 16px;
    background: var(--surface);
    border: 1px solid var(--ink);
    box-shadow: 0 12px 28px -12px rgba(0,0,0,0.3);
}
.settings-menu label + label { margin-top: 10px; }
.settings-menu h3 {
    font-family: 'JetBrains Mono', monospace;
    font-size: 10px;
    letter-spacing: 0.14em;
    text-transform: uppercase;
    color: var(--ink-muted);
    margin-bottom: 10px;
}
.settings-menu label {
    display: flex;
    flex-direction: column;
    gap: 4px;
    font-size: 11px;
    font-family: 'JetBrains Mono', monospace;
    letter-spacing: 0.04em;
}
.settings-menu select {
    font-family: 'Inter', sans-serif;
    font-size: 13px;
    padding: 6px 8px;
    border: 1px solid var(--rule);
    background: var(--bg);
    color: var(--ink);
}
/* Full-width action button inside the settings menu (for links like
   "Open Activity Log" that aren't a select). Sits between the last
   select and the shift-click hint, visually a muted secondary button. */
.settings-menu .settings-action {
    margin-top: 12px;
    width: 100%;
    font-family: 'Inter', sans-serif;
    font-size: 13px;
    padding: 7px 10px;
    border: 1px solid var(--ink);
    background: var(--surface);
    color: var(--ink);
    cursor: pointer;
}
.settings-menu .settings-action:hover { background: var(--ink); color: var(--surface); }

.settings-hint {
    margin-top: 10px;
    font-size: 10px;
    color: var(--ink-muted);
    font-style: italic;
}

.title-block h1 {
    font-family: 'Fraunces', serif;
    font-weight: 600;
    font-size: 28px;
    letter-spacing: -0.02em;
    line-height: 1;
    font-style: italic;
}

/* Legacy .meta-row is no longer used in the header, but keep the typographic
   defaults in case stray usages remain. */
.meta-row {
    font-family: 'JetBrains Mono', monospace;
    font-size: 10px;
    color: var(--ink-muted);
    letter-spacing: 0.05em;
}

.legend {
    display: inline-flex;
    align-items: center;
    gap: 6px;
}

.legend-swatch {
    display: inline-block;
    width: 10px;
    height: 10px;
    border: 1px solid var(--rule);
}

.legend-swatch.db    { background: var(--db-bg); }
.legend-swatch.input { background: var(--surface); }

.row-count {
    color: var(--ink-soft);
    font-weight: 500;
}

/* Signed-in strip — right-side item of the header flex row. */
header .user-strip {
    font-family: 'JetBrains Mono', monospace;
    font-size: 10px;
    letter-spacing: 0.08em;
    color: var(--ink-muted);
    white-space: nowrap;
    display: inline-flex;
    align-items: center;
    gap: 8px;
}
.user-strip strong { color: var(--ink); font-weight: 500; letter-spacing: 0.02em; }
.user-strip .sep   { color: var(--rule); }
/* Deterministic hue per user — PHP sets --avatar-hue on the element.
   Saturation + lightness are fixed so colors land in the same "pleasant
   pastel" range regardless of hue, and the dark text stays readable. */
.user-avatar {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 24px;
    height: 24px;
    border-radius: 50%;
    background: hsl(var(--avatar-hue, 210), 55%, 68%);
    color: #1a1a1a;
    font-family: 'Inter', sans-serif;
    font-size: 10px;
    font-weight: 600;
    letter-spacing: 0.02em;
    text-transform: uppercase;
    user-select: none;
    cursor: default;
}
.user-strip .sign-out-link {
    color: var(--focus);
    text-decoration: none;
    font-weight: 500;
}
.user-strip .sign-out-link:hover { text-decoration: underline; }

/* Live search count — sits inline next to the search input in the toolbar. */
.toolbar .search { display: flex; align-items: center; gap: 10px; }

/* ── Service-line pills ────────────────────────────────────────────────────
   Four pill-shaped links in the toolbar: All / Court / Process / Records.
   Server-rendered (bookmarkable, no JS flash), each preserving the current
   view + filter so staff can combine (Billed + Court) via a single click. */
.svc-pills {
    display: inline-flex;
    gap: 2px;
    align-items: center;
    padding: 2px;
    background: var(--surface);
    border: 1px solid var(--rule);
    border-radius: 999px;
}
.svc-pill {
    font-family: 'Inter', sans-serif;
    font-size: 12px;
    font-weight: 500;
    letter-spacing: 0.02em;
    padding: 5px 14px;
    color: var(--ink-soft);
    text-decoration: none;
    border: none;
    background: transparent;      /* <button> reset for the scorecard variant */
    cursor: pointer;
    border-radius: 999px;
    white-space: nowrap;
    transition: background 0.12s ease, color 0.12s ease;
}
.svc-pill:hover { color: var(--ink); background: var(--bg); }
.svc-pill.active {
    color: var(--surface);
    background: var(--ink);
}
.svc-pill .svc-count {
    color: var(--ink-muted);
    font-size: 11px;
    margin-left: 3px;
}
.svc-pill.active .svc-count {
    color: var(--surface);
    opacity: 0.7;
}

/* ── Invoice-type pills ────────────────────────────────────────────────────
   Standard / Prepay narrowing — sits beside the department (svc) pills in
   the toolbar. Visually distinct from .svc-pill (tinted border + warmer
   active color on the Prepay pill) so staff can tell at a glance which
   filter is engaged. */
.type-pills {
    display: inline-flex;
    gap: 2px;
    align-items: center;
    padding: 2px;
    background: var(--surface);
    border: 1px solid var(--rule);
    border-radius: 999px;
}
.type-pill {
    font-family: 'Inter', sans-serif;
    font-size: 12px;
    font-weight: 500;
    letter-spacing: 0.02em;
    padding: 5px 14px;
    color: var(--ink-soft);
    text-decoration: none;
    border: none;
    background: transparent;
    cursor: pointer;
    border-radius: 999px;
    white-space: nowrap;
    transition: background 0.12s ease, color 0.12s ease;
}
.type-pill:hover { color: var(--ink); background: var(--bg); }
.type-pill.active {
    color: var(--surface);
    background: var(--ink);
}
/* Prepay pill: amber tint when active, so the Prepay-only view is
   recognizable at a glance. Inactive state matches the others. */
.type-pill-prepay.active {
    background: #b45309;   /* amber-700 */
}
.type-pill .type-count {
    color: var(--ink-muted);
    font-size: 11px;
    margin-left: 3px;
}
.type-pill.active .type-count {
    color: var(--surface);
    opacity: 0.7;
}

/* ── Prepay row indicator & dialog type picker ─────────────────────────────
   Visual marker for prepay rows in both the main grid and the batch review
   grid, plus the Standard/Prepay radio-group at the top of the Add Invoice
   dialog. */
.prepay-badge {
    display: inline-block;
    font-family: 'JetBrains Mono', monospace;
    font-size: 9px;
    font-weight: 700;
    letter-spacing: 0.08em;
    padding: 1px 4px;
    margin-left: 4px;
    color: #92400e;        /* amber-800 */
    background: #fef3c7;   /* amber-100 */
    border: 1px solid #fcd34d;  /* amber-300 */
    border-radius: 3px;
    vertical-align: middle;
}
tr.row-prepay > td {
    /* Faint amber tint so a prepay row reads as different without clashing
       with the ratio-low / shared-qc highlights, which stay on their own cells. */
    background: #fffbeb;   /* amber-50 */
}
.dialog-type-picker {
    display: inline-flex;
    gap: 10px;
    margin-left: 16px;
    padding: 3px 8px;
    background: var(--bg);
    border: 1px solid var(--rule);
    border-radius: 6px;
    font-size: 12px;
}
.dialog-type-picker label {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    cursor: pointer;
    user-select: none;
}
.dialog-type-picker input[type="radio"] { margin: 0; }
/* Prepay-mode cue on the dialog header: the picker gets an amber border so
   the user can see at a glance that this is a prepay invoice. */
.add-dialog.prepay-mode .dialog-type-picker {
    background: #fef3c7;
    border-color: #fcd34d;
}
/* Batch review grid: prepay cell and prepay-tinted row (same amber as the
   main grid for consistency). */
.batch-review-grid td.prepay-cell { text-align: center; }
.batch-review-grid tr.row-prepay > td { background: #fffbeb; }

/* ── Reviewed (prepay closeout) ────────────────────────────────────────────
   Prepay rows render the Reviewed checkbox in the Acct Approved column slot
   (standards keep their original Acct Approved checkbox there). The amber
   accent on the checked state matches the Prepay badge language. The
   .reviewed-na dash is used in the Reconciled view for the Ready-to-Export
   cell on prepay rows (prepays never export). */
.prepay-reviewed-cell input[type="checkbox"]:checked {
    accent-color: #b45309;   /* amber-700 */
}
.reviewed-na {
    color: var(--ink-muted);
    opacity: 0.6;
    cursor: default;
}
/* Dialog fields: Reviewed shows only in prepay mode; Acct Approved hides
   in prepay mode (mirrors the grid — Acct Approved doesn't apply to
   prepays). Both hinges off the .prepay-mode class on the dialog shell. */
.add-dialog .dialog-reviewed { display: none; }
.add-dialog.prepay-mode .dialog-reviewed {
    display: flex;
    background: #fffbeb;        /* amber-50 */
    border: 1px solid #fcd34d;  /* amber-300 */
    border-radius: 4px;
    padding: 4px 8px;
}
.add-dialog.prepay-mode .dialog-acct-approved { display: none; }

/* Scorecard-dialog variant: the pills sit under a tiny label the way
   the other filter controls (Range / From / To) do. */
.scorecard-dialog .scorecard-svc {
    display: flex;
    flex-direction: column;
    gap: 3px;
}
.scorecard-dialog .scorecard-svc-label {
    font-family: 'JetBrains Mono', monospace;
    font-size: 10px;
    letter-spacing: 0.08em;
    color: var(--ink-muted);
}
/* The .scorecard-dialog .scorecard-filters button rule above (which gives
   every filter-bar <button> the primary-dark look) is (0,2,1) in
   specificity because of the trailing element selector. To override, the
   pill rules below carry the .scorecard-filters scope too, landing at
   (0,3,0) which beats (0,2,1) and lets the active-vs-inactive contrast
   come through. */
.scorecard-dialog .scorecard-filters .svc-pill {
    border: none;
    background: transparent;
    color: var(--ink-soft);
    padding: 5px 14px;
}
.scorecard-dialog .scorecard-filters .svc-pill:hover {
    color: var(--ink);
    background: var(--bg);
    border-color: transparent;
}
.scorecard-dialog .scorecard-filters .svc-pill.active {
    color: var(--surface);
    background: var(--ink);
    border-color: var(--ink);
}
/* Scorecard Type pills (P4) — same specificity trick as the svc-pill rules
   above so the generic .scorecard-filters button styling doesn't win. The
   Prepay pill keeps its amber-active variant inherited from the toolbar's
   .type-pill-prepay rule. */
.scorecard-dialog .scorecard-filters .type-pill {
    border: none;
    background: transparent;
    color: var(--ink-soft);
    padding: 5px 14px;
}
.scorecard-dialog .scorecard-filters .type-pill:hover {
    color: var(--ink);
    background: var(--bg);
    border-color: transparent;
}
.scorecard-dialog .scorecard-filters .type-pill.active {
    color: var(--surface);
    background: var(--ink);
    border-color: var(--ink);
}
.scorecard-dialog .scorecard-filters .type-pill-prepay.active {
    background: #b45309;      /* amber-700 — matches toolbar's Prepay pill */
    border-color: #b45309;
}
.toolbar .search-count {
    font-family: 'JetBrains Mono', monospace;
    font-size: 10px;
    letter-spacing: 0.08em;
    color: var(--ink-muted);
    white-space: nowrap;
}

/* Dashboard — three metric links in the header that filter the Open view.
   Replaces the old DB/Input legend. Count under label, both centered. */
.dashboard {
    display: flex;
    gap: 6px;
}
.dashboard .metric {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 2px;
    min-width: 72px;
    padding: 6px 14px 7px;
    text-decoration: none;
    color: var(--ink-muted);
    background: var(--surface);
    border: 1px solid var(--rule);
    transition: border-color 0.15s ease, color 0.15s ease, background 0.15s ease;
}
.dashboard .metric:hover {
    border-color: var(--focus);
    color: var(--ink);
}
.dashboard .metric.active {
    background: var(--ink);
    border-color: var(--ink);
    color: var(--surface);
    cursor: default;
}
.metric-label {
    font-family: 'JetBrains Mono', monospace;
    font-size: 9px;
    letter-spacing: 0.14em;
    text-transform: uppercase;
    line-height: 1;
}
.metric-value {
    font-family: 'Fraunces', serif;
    font-weight: 600;
    font-size: 20px;
    font-style: italic;
    line-height: 1;
    font-variant-numeric: tabular-nums;
}

/* ── View switcher tabs ─────────────────────────────────────────── */
/* Header row — view tabs on the left, action buttons on the right. The row
   carries the single bottom rule so tabs and buttons share one baseline.
   The status banner lives one row below (inside .toolbar) instead of here. */
.view-tabs-row {
    display: flex;
    align-items: flex-end;
    justify-content: space-between;
    gap: 16px;
    margin-top: 8px;
    border-bottom: 1px solid var(--rule);
}
.view-tabs-row .view-tabs {
    flex: 0 0 auto;
    margin: 0;
    border-bottom: 0;   /* parent carries the rule now */
}
.view-tabs-row .btn-group {
    flex: 0 0 auto;
    align-self: center;
    padding-bottom: 4px;
}

/* Status banner sits inside .toolbar, between the search box (left) and
   the right edge. Flex-grow so it fills the space whether or not a
   banner is present; empty-but-flex-grow keeps the toolbar height
   stable. Single-line ellipsis for the rare long error. */
.toolbar .banner-slot {
    flex: 1 1 auto;
    display: flex;
    align-items: center;
    min-width: 0;
    padding: 0 12px;
}
.toolbar .banner-slot .banner {
    margin: 0;
    width: 100%;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.view-tabs {
    display: flex;
    gap: 2px;
    margin: 8px 0 0;
    border-bottom: 1px solid var(--rule);
    flex-shrink: 0;
}
.view-tab {
    font-family: 'Inter', sans-serif;
    font-size: 12px;
    font-weight: 500;
    letter-spacing: 0.02em;
    padding: 8px 18px;
    color: var(--ink-soft);
    text-decoration: none;
    border: 1px solid transparent;
    border-bottom: none;
    position: relative;
    bottom: -1px;
    transition: color 0.15s ease, background 0.15s ease;
}
.view-tab:hover { color: var(--ink); background: var(--surface); }
/* Active tab stands out via accent-colored text, heavier weight, and a
   top accent bar drawn with an inset box-shadow (doesn't disturb layout
   the way an extra border would). The surface background + rule border
   together still carry the "tab lifts off the row" effect. */
.view-tab.active {
    color: var(--accent);
    font-weight: 600;
    background: var(--surface);
    border-color: var(--rule);
    box-shadow: inset 0 3px 0 var(--accent);
}
.view-count {
    color: var(--ink-muted);
    font-weight: 500;
    font-size: 13px;
    margin-left: 4px;
}
/* Match the ratio-low red from the main grid so the Low Ratio tab's count
   signals the margin-review threshold at a glance. */
.view-tab[href*="filter=lowratio"] .view-count { color: #b91c1c; }
/* Prepay tab carries the same amber the Prepay rows and PP badge use, so
   the tab reads as "prepay territory" at a glance. */
.view-tab-prepay .view-count { color: #b45309; }    /* amber-700 */
.view-tab-prepay.active {
    color: #b45309;
    box-shadow: inset 0 3px 0 #b45309;
}

/* ── Toolbar ────────────────────────────────────────────────────── */
.toolbar {
    display: flex;
    justify-content: space-between;
    align-items: center;
    padding: 10px 0;
    gap: 16px;
    flex-shrink: 0;
}

.search {
    flex: 1;
    max-width: 320px;
    position: relative;
}

.search input {
    width: 100%;
    padding: 7px 12px 7px 30px;
    font-family: 'Inter', sans-serif;
    font-size: 12px;
    background: var(--surface);
    border: 1px solid var(--rule);
    outline: none;
    transition: border-color 0.15s ease;
}

.search input:focus { border-color: var(--focus); }

.search::before {
    content: '⌕';
    position: absolute;
    left: 10px;
    top: 50%;
    transform: translateY(-50%);
    color: var(--ink-muted);
    font-size: 14px;
}

.btn-group { display: flex; gap: 8px; }

button {
    font-family: 'Inter', sans-serif;
    font-size: 12px;
    font-weight: 500;
    letter-spacing: 0.02em;
    padding: 7px 16px;
    border: 1px solid var(--ink);
    cursor: pointer;
    transition: all 0.15s ease;
    background: transparent;
    color: var(--ink);
}

button.primary {
    background: var(--ink);
    color: var(--surface);
}

button:hover { background: var(--accent); border-color: var(--accent); color: var(--surface); }

/* Disabled buttons shouldn't react to hover; semi-transparent and
   not-allowed cursor make the state obvious. */
button:disabled,
button:disabled:hover {
    opacity: 0.4;
    cursor: not-allowed;
    background: transparent;
    border-color: var(--rule);
    color: var(--ink);
}
button.primary:disabled,
button.primary:disabled:hover {
    background: var(--ink);
    border-color: var(--ink);
    color: var(--surface);
    opacity: 0.4;
}

/* ── Table Wrapper ──────────────────────────────────────────────── */
.table-wrap {
    flex: 1;
    overflow: auto;
    border: 1px solid var(--rule);
    background: var(--surface);
    box-shadow: 0 1px 0 rgba(0,0,0,0.02), 0 16px 32px -20px rgba(0,0,0,0.08);
    margin-bottom: 16px;
}

table {
    border-collapse: separate;
    border-spacing: 0;
    font-size: 13px;
    width: max-content;
    min-width: 100%;
    /* table-layout: fixed is required for resizable columns — in auto
       layout, the browser treats width values as suggestions and lets
       content push cells wider. Fixed makes <th> widths authoritative,
       and tbody cells inherit from them. */
    table-layout: fixed;
}

/* ── Default column widths ──────────────────────────────────────────────────
   Set on <th data-col=…> only; <td> cells inherit via table-layout:fixed.
   JS overrides these inline when the user drags a gripper, and restores
   from localStorage on page load. "Reset widths" in the Columns panel
   clears the inline overrides so these defaults take over again. */
thead th[data-col="edit"]            { width: 36px;  }
thead th[data-col="qc"]              { width: 100px; }
thead th[data-col="accountc"]        { width: 110px; }
thead th[data-col="vendor_inv"]      { width: 150px; }
thead th[data-col="inv_date"]        { width: 100px; }
thead th[data-col="affiliate"]       { width: 180px; }
thead th[data-col="aff_cost"]        { width: 100px; }
thead th[data-col="aff_advance"]     { width: 110px; }
thead th[data-col="crt_advance"]     { width: 100px; }
thead th[data-col="total_billed"]    { width: 110px; }
thead th[data-col="buid"]            { width: 75px;  }
thead th[data-col="client_inv"]      { width: 110px; }
thead th[data-col="ratio"]           { width: 80px;  }
thead th[data-col="billed"]          { width: 85px;  }
thead th[data-col="acct_approved"]   { width: 90px;  }
thead th[data-col="ready_to_export"] { width: 100px; }
thead th[data-col="notes"]           { width: 240px; }

/* Truncation — with table-layout:fixed, text that overflows the cell
   width needs explicit handling. Pure-text cells get ellipsis; inputs
   fill their cell naturally via width:100% so they truncate on their
   own as the user squeezes the column. */
.table-wrap tbody td { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.table-wrap tbody td input,
.table-wrap tbody td select {
    width: 100%;
    box-sizing: border-box;
    min-width: 0;
}

/* ── Column resize grippers ─────────────────────────────────────────────────
   Each resizable header gets a thin drag strip at its right edge via
   ::after. Nearly invisible by default, accent-blue on hover so staff
   can find them without noise during normal grid scanning. */
thead th[data-col] {
    position: sticky;
    top: 0;
}
thead th[data-col]:not([data-col="edit"])::after {
    content: '';
    position: absolute;
    right: 0;
    top: 0;
    width: 6px;
    height: 100%;
    cursor: col-resize;
    background: transparent;
    z-index: 4;
}
thead th[data-col]:not([data-col="edit"]):hover::after {
    /* Brighter sky-blue stripe at full opacity so staff can see it clearly
       against the dark header background. */
    background: #7fb8ff;
    opacity: 1;
}
/* While a drag is in progress, suppress text selection site-wide so the
   cursor doesn't accidentally highlight headers / row content. */
body.column-resizing,
body.column-resizing * {
    cursor: col-resize !important;
    user-select: none !important;
}

/* ── Columns popover (Columns toolbar button) ───────────────────────────── */
.columns-wrap { position: relative; display: inline-block; }
.columns-panel {
    position: absolute;
    top: calc(100% + 4px);
    right: 0;
    background: var(--surface);
    border: 1px solid var(--rule);
    box-shadow: 0 10px 28px -8px rgba(0,0,0,0.25);
    padding: 14px 16px;
    min-width: 300px;
    z-index: 100;
}
.columns-panel h4 {
    font-family: 'JetBrains Mono', monospace;
    font-size: 10px;
    letter-spacing: 0.12em;
    text-transform: uppercase;
    color: var(--ink-muted);
    margin: 0 0 10px;
}
.columns-list {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 6px 16px;
    margin-bottom: 12px;
    max-height: 360px;
    overflow-y: auto;
}
.columns-list label {
    display: flex;
    align-items: center;
    gap: 6px;
    font-size: 12px;
    cursor: pointer;
    user-select: none;
    color: var(--ink);
}
.columns-list input[type="checkbox"] { cursor: pointer; }
.columns-list label.col-essential {
    color: var(--ink-muted);
    cursor: not-allowed;
}
.columns-foot {
    display: flex;
    justify-content: flex-end;
    gap: 8px;
    padding-top: 10px;
    border-top: 1px solid var(--rule-soft);
}
.columns-foot button {
    font-family: 'Inter', sans-serif;
    font-size: 11px;
    padding: 5px 12px;
    border: 1px solid var(--rule);
    background: var(--bg);
    color: var(--ink);
    cursor: pointer;
}
.columns-foot button:hover { border-color: var(--focus); color: var(--focus); }

/* ── Header row ─────────────────────────────────────────────────── */
thead th {
    position: sticky;
    top: 0;
    z-index: 3;
    background: var(--header-bg);
    color: var(--header-ink);
    font-family: 'JetBrains Mono', monospace;
    font-size: 10px;
    font-weight: 500;
    letter-spacing: 0.12em;
    text-transform: uppercase;
    padding: 10px 10px;
    text-align: left;
    white-space: nowrap;          /* ← key fix: headers always one line */
    border-right: 1px solid #2c2c2c;
    user-select: none;
}

thead th:last-child { border-right: none; }

/* Sortable column headers */
thead th { cursor: pointer; }
thead th.sort-asc::after  { content: ' ▲'; opacity: 0.85; font-size: 9px; margin-left: 2px; }
thead th.sort-desc::after { content: ' ▼'; opacity: 0.85; font-size: 9px; margin-left: 2px; }

thead th.db-col {
    background: #1c2630;
    color: #bccad9;
}

thead th .marker {
    display: inline-block;
    width: 4px;
    height: 4px;
    background: var(--accent);
    border-radius: 50%;
    margin-right: 6px;
    vertical-align: middle;
    opacity: 0.8;
}

/* Sticky first column (QC Number) for horizontal scroll context */
thead th.sticky-col,
tbody td.sticky-col {
    position: sticky;
    left: 0;
    z-index: 2;
}

thead th.sticky-col { z-index: 4; }

tbody td.sticky-col {
    background: var(--db-bg);
    box-shadow: 1px 0 0 var(--rule);
}

tbody tr:nth-child(even) td.sticky-col {
    background: var(--db-bg-alt);
}

/* ── Reconciled view: batch grouping ────────────────────────────── */
tr.batch-header > td {
    background: #1c2630;
    color: #bccad9;
    padding: 10px 14px;
    font-family: 'JetBrains Mono', monospace;
    font-size: 11px;
    letter-spacing: 0.08em;
    cursor: pointer;
    user-select: none;
    position: sticky;
    left: 0;
    border-bottom: 1px solid var(--rule);
}
tr.batch-header:hover > td { background: #243040; }
.batch-chevron {
    display: inline-block;
    margin-right: 10px;
    color: var(--focus);
    transition: transform 0.15s ease;
    width: 10px;
    text-align: center;
}
tbody.batch.collapsed .batch-chevron { transform: rotate(-90deg); }
tbody.batch.collapsed > tr.data-row { display: none; }

.batch-label { font-weight: 500; }
.batch-count {
    margin-left: 16px;
    color: #8a95a3;
    text-transform: uppercase;
    font-size: 10px;
    letter-spacing: 0.12em;
}
.batch-actions {
    float: right;
    display: inline-flex;
    gap: 8px;
}
.batch-actions button {
    font-family: 'Inter', sans-serif;
    font-size: 11px;
    padding: 3px 10px;
    border: 1px solid #4a5a66;
    background: transparent;
    color: #d6dde5;
    cursor: pointer;
    transition: background 0.15s, color 0.15s, border-color 0.15s;
}
.batch-actions button:hover {
    background: var(--focus);
    border-color: var(--focus);
    color: var(--surface);
}

/* Right-align money columns in reconciled read-only view */
tbody tr.data-row td.num {
    text-align: right;
    font-variant-numeric: tabular-nums;
    padding: 8px 10px;
}
tbody tr.data-row td { padding: 8px 10px; }

/* ── Body cells ─────────────────────────────────────────────────── */
/* content-visibility lets the browser skip rendering rows that aren't
   on screen — huge initial-paint win for ~950-row tables. The intrinsic
   size hint keeps scrollbar geometry sensible until each row paints. */
tbody tr {
    content-visibility: auto;
    contain-intrinsic-size: auto 33px;
}
tbody tr:nth-child(even) { background: var(--row-alt); }
tbody tr:hover { background: #dde7f0; }
tbody tr:hover td.sticky-col { background: #c2cfdb; }

tbody td {
    padding: 0;
    border-right: 1px solid var(--rule-soft);
    border-bottom: 1px solid var(--rule-soft);
    white-space: nowrap;
    vertical-align: middle;
}

tbody td.db-col {
    background: var(--db-bg);
    color: var(--db-ink);
    font-family: 'JetBrains Mono', monospace;
    font-size: 13px;
    padding: 8px 10px;
}

tbody tr:nth-child(even) td.db-col {
    background: var(--db-bg-alt);
}

/* Inputs inside cells */
tbody td input[type="text"],
tbody td input[type="date"],
tbody td input[type="number"],
tbody td select {
    font-family: 'Inter', sans-serif;
    font-size: 13px;
    color: var(--ink);
    background: transparent;
    border: none;
    outline: none;
    padding: 8px 10px;
    width: 100%;
    min-width: 90px;
}

tbody td input[type="number"] { text-align: right; font-variant-numeric: tabular-nums; }
tbody td input[type="date"]   { min-width: 130px; }

tbody td input:focus,
tbody td select:focus {
    background: #fff;
    box-shadow: inset 0 0 0 1.5px var(--focus);
}

tbody td select {
    appearance: none;
    background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='8' height='5' viewBox='0 0 8 5'><path d='M1 1l3 3 3-3' stroke='%238a8580' stroke-width='1.2' fill='none'/></svg>");
    background-repeat: no-repeat;
    background-position: right 8px center;
    padding-right: 22px;
    cursor: pointer;
}

tbody td select.yesno { min-width: 70px; }
tbody td input.notes { min-width: 180px; }

/* Column-specific widths */
.col-edit      { min-width: 32px; width: 32px; max-width: 32px; }
.col-qc        { min-width: 90px; }
.col-acct      { min-width: 100px; }
.col-vendor    { min-width: 110px; }
.col-date      { min-width: 130px; }
.col-aff       { min-width: 130px; }
.col-money     { min-width: 100px; }
.col-buid      { min-width: 80px; }
.col-inv       { min-width: 100px; }
.col-ratio     { min-width: 70px; }
.col-yesno     { min-width: 80px; }
.col-notes     { min-width: 200px; }

/* ── Add-Invoice dialog ─────────────────────────────────────────── */
dialog.add-dialog {
    border: 1px solid var(--ink);
    background: var(--bg);
    color: var(--ink);
    padding: 0;
    width: min(1012px, 95vw);
    max-height: 90vh;
    box-shadow: 0 24px 60px -20px rgba(0,0,0,0.35);
}
/* Pin New Invoice / Edit Invoice, Manage Affiliates, and Export History
   to a 40px top margin — matches the Batch Add dialog so the full set of
   dialogs hangs from the same visual rail just below the "VENDOR COST
   TRACKER" eyebrow. Left/right stay auto so UA horizontal centering holds.
   Nested dialogs (quick-affiliate) and Batch Add set their own margins. */
dialog#addDialog,
dialog#historyDialog,
dialog#affiliatesDialog {
    margin: 40px auto auto auto;
    max-height: calc(100vh - 60px);   /* leave a little room below so the dialog doesn't clip on short viewports */
}
/* When a PDF is attached, dialog widens and lays out as [form | viewer]
   with the viewer getting most of the width so staff can actually read
   the invoice while verifying auto-filled fields. Form is fixed to 740px
   — enough breathing room for the 3- and 4-column fieldsets at the
   trimmed input widths (~110px). */
dialog.add-dialog.with-viewer {
    width: min(1620px, 97vw);
    display: grid;
    grid-template-columns: 740px minmax(0, 1fr);
    grid-template-rows: calc(100vh - 60px);   /* match the dialog's max-height so the grid row fills (not overflows) the dialog */
}
dialog.add-dialog.with-viewer > form {
    min-width: 0;
    display: flex;
    flex-direction: column;
}
dialog.add-dialog.with-viewer > form > .dialog-body {
    flex: 1 1 auto;
    max-height: none;   /* grid row cap handles height instead */
}

/* PDF viewer panel */
.pdf-viewer-panel {
    display: flex;
    flex-direction: column;
    border-left: 1px solid var(--rule);
    background: var(--surface);
    min-height: 0;
}
.pdf-viewer-head {
    display: flex;
    justify-content: space-between;
    align-items: center;
    gap: 10px;
    padding: 14px 16px;
    border-bottom: 1px solid var(--rule);
    background: var(--bg);
}
.pdf-viewer-head h3 {
    font-family: 'JetBrains Mono', monospace;
    font-size: 10px;
    letter-spacing: 0.14em;
    text-transform: uppercase;
    color: var(--ink-muted);
    margin: 0;
}
.pdf-viewer-filename {
    font-family: 'JetBrains Mono', monospace;
    font-size: 10px;
    color: var(--ink-muted);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.pdf-viewer-body { flex: 1 1 auto; overflow: hidden; min-height: 0; }
.pdf-viewer-body iframe { width: 100%; height: 100%; border: 0; display: block; }

/* Narrow screens — stack the viewer below the form instead of beside it.
   With a 740px form column, side-by-side needs at least ~1240px of width
   before the viewer gets too cramped to be useful. */
@media (max-width: 1240px) {
    dialog.add-dialog.with-viewer {
        grid-template-columns: 1fr;
        grid-template-rows: auto 50vh;
        width: min(1012px, 95vw);
    }
    .pdf-viewer-panel { border-left: 0; border-top: 1px solid var(--rule); }
}
dialog.add-dialog::backdrop { background: rgba(20, 18, 14, 0.45); }

.dialog-head, .dialog-foot {
    display: flex;
    justify-content: space-between;
    align-items: center;
    padding: 14px 20px;
    background: var(--surface);
}
.dialog-head { border-bottom: 1px solid var(--rule); }
.dialog-foot { border-top: 1px solid var(--rule); gap: 8px; }
.dialog-foot { justify-content: flex-end; }
.dialog-foot button { font-size: 13px; }   /* match the grid + dialog bodies; overrides generic 12px */

.dialog-head h2 {
    font-family: 'Fraunces', serif;
    font-weight: 600;
    font-style: italic;
    font-size: 22px;
    letter-spacing: -0.01em;
}
.dialog-close {
    border: none;
    background: transparent;
    font-size: 22px;
    line-height: 1;
    cursor: pointer;
    padding: 0 6px;
    color: var(--ink-muted);
}
.dialog-close:hover { color: var(--accent); background: transparent; }

.dialog-body {
    padding: 16px 20px;
    overflow-y: auto;
    max-height: calc(90vh - 120px);
}

.dialog-body fieldset {
    border: 1px solid var(--rule);
    padding: 12px 14px 14px;
    margin-bottom: 14px;
    display: grid;
    /* minmax(0, 1fr) — otherwise 1fr won't shrink below the inputs'
       intrinsic width (default size=20 ≈ 180px) and the narrow form
       side gets a horizontal scrollbar when the PDF viewer is open. */
    grid-template-columns: repeat(3, minmax(0, 1fr));
    gap: 10px 14px;
}
.dialog-body fieldset.cols-4 { grid-template-columns: repeat(4, minmax(0, 1fr)); }
.dialog-body legend {
    font-family: 'JetBrains Mono', monospace;
    font-size: 10px;
    letter-spacing: 0.14em;
    text-transform: uppercase;
    color: var(--ink-muted);
    padding: 0 6px;
}
.dialog-body label {
    display: flex;
    flex-direction: column;
    gap: 4px;
    font-size: 11px;
    color: var(--ink-soft);
    font-family: 'JetBrains Mono', monospace;
    letter-spacing: 0.04em;
    min-width: 0;   /* let the grid cell shrink below its content size */
}
.dialog-body label.span2 { grid-column: span 2; }
.dialog-body label.span3 { grid-column: span 3; }
.dialog-body label.row-start { grid-column-start: 1; }  /* force this label onto a new row */

.dialog-body input,
.dialog-body select {
    font-family: 'Inter', sans-serif;
    font-size: 13px;
    color: var(--ink);
    background: var(--surface);
    border: 1px solid var(--rule);
    outline: none;
    padding: 7px 10px;
    transition: border-color 0.15s ease;
    /* Short-value fields (identifiers, money, dates, Yes/No) are capped
       at 150px so they fill most of their grid cell without blowing out
       the narrow form side. The 4-column CRT fieldset's cells are ~157px
       at the current form width, so 150px fits snugly in all layouts. */
    width: 100%;
    max-width: 150px;
    min-width: 0;
    box-sizing: border-box;
}
/* Full-column width for the free-text fields that benefit from room:
   Affiliate (long business names), Notes (span-3 label), and the
   multi-match QC picker (long "branch | date | code | …" option labels). */
.dialog-body input[list="affiliate-list"],
.dialog-body label.span2 input,
.dialog-body label.span2 select,
.dialog-body label.span3 input,
.dialog-body label.span3 select,
.dialog-body #qcMatchPicker {
    max-width: 100%;
}
.dialog-body input:focus,
.dialog-body select:focus { border-color: var(--focus); }

/* Edit column — first column of every row, opens the full dialog. */
tbody td.edit-cell {
    text-align: center;
    padding: 2px;
    background: var(--surface);
}
tbody td.edit-cell .row-edit-btn {
    border: none;
    background: transparent;
    cursor: pointer;
    font-size: 13px;
    padding: 2px 4px;
    opacity: 0.55;
    line-height: 1;
}
tbody td.edit-cell .row-edit-btn:hover { opacity: 1; }

/* Yes/No columns — checkboxes centered in each cell. */
tbody td.yesno-cell {
    text-align: center;
    padding: 0;
}
tbody td.yesno-cell input[type="checkbox"] {
    cursor: pointer;
    width: 16px;
    height: 16px;
    margin: 6px;
    accent-color: var(--accent);
}

/* Edit mode on the dialog — hide the PDF drop zone only when a PDF
   is already attached. If the row has no pdf_path, we show the dropzone
   so staff can attach one after the fact. (Replacing an existing PDF
   is deliberately not supported here — would need a confirm-and-replace
   flow to avoid silent overwrite.) */
dialog.add-dialog.edit-mode.has-existing-pdf .pdf-dropzone { display: none; }

/* Refresh CRT Data button — only shown in Edit mode (new-row flow uses
   the automatic lookup on QC change). */
.refresh-crt-btn {
    display: none;
    margin-top: 6px;
    padding: 4px 10px;
    font-family: 'Inter', sans-serif;
    font-size: 11px;
    font-weight: 500;
    border: 1px solid var(--rule);
    background: var(--surface);
    color: var(--ink);
    cursor: pointer;
    align-self: flex-start;
    transition: background 0.15s ease, border-color 0.15s ease;
}
.refresh-crt-btn:hover { background: var(--focus); border-color: var(--focus); color: var(--surface); }
dialog.add-dialog.edit-mode .refresh-crt-btn { display: inline-block; }

/* Vendor inv # cell with attached-PDF link */
tbody td.vendor-inv-cell {
    display: flex;
    align-items: center;
    padding: 0;
}
tbody td.vendor-inv-cell input { flex: 1; min-width: 70px; }
tbody td.vendor-inv-cell .pdf-link {
    display: inline-block;
    padding: 0 8px;
    text-decoration: none;
    font-size: 13px;
    opacity: 0.7;
    transition: opacity 0.15s ease, transform 0.1s ease;
}
tbody td.vendor-inv-cell .pdf-link:hover { opacity: 1; transform: scale(1.15); }

/* Manage Affiliates dialog */
.affiliate-new {
    display: flex;
    flex-direction: column;
    gap: 10px;
    margin-bottom: 16px;
    padding: 12px;
    background: var(--surface);
    border: 1px solid var(--rule);
}
.affiliate-new .row-top    { display: grid; grid-template-columns: 1.5fr 2fr 1fr;              gap: 10px; }
.affiliate-new .row-bottom { display: grid; grid-template-columns: 0.8fr 0.8fr 0.8fr auto;     gap: 10px; }
.affiliate-new input,
.affiliate-new select {
    font-family: 'Inter', sans-serif;
    font-size: 13px;
    padding: 7px 10px;
    border: 1px solid var(--rule);
    background: #fff;
    outline: none;
    transition: border-color 0.15s ease;
}
.affiliate-new input:focus,
.affiliate-new select:focus { border-color: var(--focus); }
.affiliate-new button {
    font-family: 'Inter', sans-serif;
    font-size: 13px;
    font-weight: 500;
    padding: 7px 16px;
    border: 1px solid var(--ink);
    background: var(--ink);
    color: var(--surface);
    cursor: pointer;
    transition: background 0.15s, border-color 0.15s;
}
.affiliate-new button:hover { background: var(--accent); border-color: var(--accent); }

.affiliates-table {
    width: 100%;
    border-collapse: collapse;
    font-size: 13px;
    margin-bottom: 14px;
}
.affiliates-table th {
    background: var(--surface);
    padding: 8px 10px;
    text-align: left;
    font-family: 'JetBrains Mono', monospace;
    font-size: 10px;
    text-transform: uppercase;
    letter-spacing: 0.12em;
    color: var(--ink-muted);
    border-bottom: 1px solid var(--rule);
}
.affiliates-table th.num     { text-align: right; }
.affiliates-table th.actions { text-align: right; }
.affiliates-table td {
    padding: 4px 6px;
    border-bottom: 1px solid var(--rule-soft);
    vertical-align: middle;
}
.affiliates-table td input,
.affiliates-table td select {
    width: 100%;
    font-family: 'Inter', sans-serif;
    font-size: 13px;
    padding: 5px 8px;
    border: 1px solid transparent;
    background: transparent;
    outline: none;
    transition: border-color 0.15s, background 0.15s;
}
.affiliates-table td input:focus,
.affiliates-table td select:focus { border-color: var(--focus); background: #fff; }
.affiliates-table td input.dirty,
.affiliates-table td select.dirty { background: #fff8e1; }

/* Dim rows marked Inactive */
.affiliates-table tr.inactive-row td input,
.affiliates-table tr.inactive-row td select { color: var(--ink-muted); }
.affiliates-table tr.inactive-row td.num    { color: var(--ink-muted); }
.affiliates-table td.num {
    text-align: right;
    font-variant-numeric: tabular-nums;
    color: var(--ink-soft);
    padding-right: 14px;
}
.affiliates-table td.actions { white-space: nowrap; text-align: right; }
.affiliates-table .affiliate-save,
.affiliates-table .affiliate-delete {
    font-family: 'Inter', sans-serif;
    font-size: 11px;
    padding: 3px 10px;
    border: 1px solid var(--ink);
    background: transparent;
    cursor: pointer;
    margin-left: 4px;
    transition: background 0.15s, color 0.15s, border-color 0.15s;
}
.affiliates-table .affiliate-save:hover,
.affiliates-table .affiliate-delete:hover:not(:disabled) {
    background: var(--accent);
    border-color: var(--accent);
    color: var(--surface);
}
.affiliates-table .affiliate-delete:disabled {
    opacity: 0.35;
    cursor: not-allowed;
}
.affiliates-loading { text-align: center; padding: 24px; color: var(--ink-muted); font-style: italic; }

.affiliates-note {
    font-family: 'Inter', sans-serif;
    font-size: 11px;
    color: var(--ink-muted);
    line-height: 1.5;
    padding: 10px 12px;
    border-left: 3px solid var(--rule);
    background: var(--bg);
}
.affiliates-note code {
    font-family: 'JetBrains Mono', monospace;
    font-size: 10px;
    background: var(--surface);
    padding: 0 4px;
}

/* Export History dialog */
.history-table {
    width: 100%;
    border-collapse: collapse;
    font-family: 'JetBrains Mono', monospace;
    font-size: 13px;
    margin-bottom: 14px;
}
.history-table th,
.history-table td {
    padding: 8px 12px;
    border-bottom: 1px solid var(--rule-soft);
    text-align: left;
}
.history-table th {
    font-size: 10px;
    letter-spacing: 0.12em;
    text-transform: uppercase;
    color: var(--ink-muted);
    border-bottom: 1px solid var(--rule);
}
.history-table td.history-count { text-align: right; color: var(--ink-soft); }
.history-table td.history-actions { text-align: right; white-space: nowrap; }
.history-table .reexport-batch,
.history-table .undo-batch {
    font-family: 'Inter', sans-serif;
    font-size: 11px;
    padding: 4px 12px;
    border: 1px solid var(--ink);
    background: transparent;
    cursor: pointer;
    margin-left: 6px;
}
.history-table .reexport-batch:hover,
.history-table .undo-batch:hover { background: var(--accent); color: var(--surface); border-color: var(--accent); }
.history-table .reexport-batch[disabled] { opacity: 0.5; cursor: not-allowed; }
.history-note {
    font-family: 'Inter', sans-serif;
    font-size: 11px;
    color: var(--ink-muted);
    line-height: 1.5;
    padding: 10px 12px;
    border-left: 3px solid var(--rule);
    background: var(--bg);
}
.history-note code {
    font-family: 'JetBrains Mono', monospace;
    font-size: 10px;
    background: var(--surface);
    padding: 0 4px;
}

/* PDF drop zone in the AFFILIATE section */
.pdf-dropzone {
    margin-top: 6px;
    padding: 18px 14px;
    border: 1.5px dashed var(--rule);
    background: var(--surface);
    text-align: center;
    cursor: pointer;
    transition: border-color 0.15s ease, background 0.15s ease;
    font-family: 'JetBrains Mono', monospace;
    font-size: 11px;
    color: var(--ink-muted);
    letter-spacing: 0.04em;
}
.pdf-dropzone:hover,
.pdf-dropzone.drag-over {
    border-color: var(--focus);
    background: #e6eef9;
    color: var(--ink);
}
.pdf-dropzone .pdf-icon { font-size: 16px; margin-right: 8px; vertical-align: -2px; }
.pdf-chosen {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 10px;
    color: var(--ink);
    font-size: 12px;
}
.pdf-chosen .pdf-size { color: var(--ink-muted); font-size: 10px; }
.pdf-remove {
    border: 1px solid var(--rule);
    background: var(--bg);
    color: var(--ink-muted);
    font-size: 14px;
    line-height: 1;
    cursor: pointer;
    padding: 1px 7px 3px;
    margin-left: 4px;
}
.pdf-remove:hover { background: var(--accent); color: var(--surface); border-color: var(--accent); }

/* Auto-fill button (on-demand mode only) */
.pdf-autofill-btn {
    border: 1px solid var(--focus);
    background: var(--focus);
    color: var(--surface);
    font-family: 'Inter', sans-serif;
    font-size: 12px;
    padding: 4px 10px;
    cursor: pointer;
    letter-spacing: 0.02em;
}
.pdf-autofill-btn:hover { background: var(--ink); border-color: var(--ink); }

/* Parsing state on the dropzone — a left-to-right scanning sweep over
   the dropzone background makes it obvious that work is happening
   (vs. the earlier subtle opacity pulse, which was easy to miss). The
   dropzone text ("Parsing filename.pdf with Haiku 5.5…") also pulses
   its color between the focus blue and a brighter accent so the word
   draws the eye. position: relative + overflow: hidden lets the ::after
   scanning gradient sit inside the dropzone bounds. */
.pdf-dropzone.parsing {
    position: relative;
    overflow: hidden;
    border-color: var(--focus);
    background: #eef3fb;
    color: var(--focus);
}
.pdf-dropzone.parsing::after {
    content: '';
    position: absolute;
    top: 0;
    left: -40%;
    width: 40%;
    height: 100%;
    background: linear-gradient(90deg,
        rgba(39, 102, 184, 0) 0%,
        rgba(39, 102, 184, 0.18) 50%,
        rgba(39, 102, 184, 0) 100%);
    animation: parsingScan 1.4s ease-in-out infinite;
    pointer-events: none;
}
@keyframes parsingScan {
    0%   { left: -40%; }
    100% { left: 100%; }
}
/* The visible text sits above the ::after sweep, pulsing between the
   focus blue and a brighter accent so the "Parsing…" word draws the
   eye independently of the background scan. */
.pdf-dropzone.parsing .pdf-chosen,
.pdf-dropzone.parsing .pdf-prompt {
    position: relative;
    z-index: 1;
    animation: parsingTextPulse 1.4s ease-in-out infinite;
    font-weight: 600;
}
@keyframes parsingTextPulse {
    0%, 100% { color: var(--focus); }
    50%      { color: #0f4ea3; }
}

/* Toast-style confidence summary under the Affiliate fieldset — the Affiliate
   fieldset is a 3-column grid, so span the full row instead of sitting in
   the first column and pushing the layout. */
.parse-summary {
    grid-column: 1 / -1;
    margin: 6px 0 10px;
    padding: 8px 12px;
    border-left: 3px solid var(--focus);
    background: #eef3fb;
    font-size: 11px;
    color: var(--ink);
    line-height: 1.4;
    font-family: 'JetBrains Mono', monospace;
    letter-spacing: 0.02em;
}
.parse-summary.warn   { border-left-color: #c88a2a; background: #fbf3e3; }
.parse-summary.err    { border-left-color: var(--accent); background: #fbe9e4; }
.parse-summary .notes { color: var(--ink-muted); display: block; margin-top: 4px; font-style: italic; }

/* Shared-QC badge in the Ratio cell — flags rows whose ratio has been
   pro-rated across sibling invoices on the same QC. Subtle but visible. */
tbody td.ratio-cell { white-space: nowrap; }

/* Red Ratio values below 3:1 — accounting threshold for margin review. */
tbody td.ratio-cell.ratio-low { color: #b91c1c; font-weight: 600; }

/* Dialog delete button — destructive, pushed to the far left so it's
   visually separated from Cancel / Save on the right. */
.dialog-foot button.danger {
    background: #b91c1c;
    border: 1px solid #b91c1c;
    color: var(--surface);
    margin-right: auto;    /* flex-end default on .dialog-foot */
}
.dialog-foot button.danger:hover {
    background: #991818;
    border-color: #991818;
}
.dialog-foot button.danger:disabled {
    opacity: 0.5;
    cursor: not-allowed;
}

/* Affiliate input + "+" button for quick-create flow. Wraps the input and
   the add-button in a flex row inside the label without disturbing the
   fieldset's grid cell. */
.input-with-add {
    display: flex;
    gap: 4px;
    align-items: stretch;
    min-width: 0;
}
.input-with-add input {
    flex: 1 1 auto;
    min-width: 0;
    max-width: none !important;  /* beat the generic .dialog-body input max-width: 150 */
}
.affiliate-add-btn {
    flex: 0 0 auto;
    padding: 0 10px;
    background: var(--surface);
    border: 1px solid var(--rule);
    color: var(--ink);
    font-family: 'Inter', sans-serif;
    font-size: 13px;
    font-weight: 600;
    line-height: 1;
    cursor: pointer;
    white-space: nowrap;
}
.affiliate-add-btn:hover {
    background: var(--focus);
    color: var(--surface);
    border-color: var(--focus);
}

/* Narrower sub-dialog for the quick-create form. */
dialog.quick-affiliate-dialog { width: min(560px, 95vw); }
.quick-affiliate-dialog .banner.error { margin: 0 0 10px; }

/* ── Affiliate Scorecard dialog ─────────────────────────────────────────── */
dialog.scorecard-dialog {
    /* Near-full-viewport, same inset as Batch Add / Activity Log so dialogs
       feel consistent. See the comment on dialog.batch-dialog for why we
       skip `display:` on the base rule and only override under [open]. */
    width: calc(100vw - 80px);
    max-width: calc(100vw - 80px);
    height: calc(100vh - 40px);
    max-height: calc(100vh - 40px);
    margin: 40px auto 0 auto;
    padding: 0;
}
dialog.scorecard-dialog[open] {
    display: flex;
    flex-direction: column;
}
.scorecard-dialog > header.dialog-head,
.scorecard-dialog > footer.scorecard-foot,
.scorecard-dialog > .scorecard-filters { flex: 0 0 auto; }
.scorecard-dialog > .scorecard-body    { flex: 1 1 auto; min-height: 0; overflow: auto; padding: 16px 20px; }

/* Filter bar — date range dropdown + reconciled toggle. Change on any
   control triggers a refetch via the JS module. */
.scorecard-dialog .scorecard-filters {
    display: flex;
    flex-wrap: wrap;
    align-items: flex-end;
    gap: 12px 20px;
    padding: 12px 20px;
    background: var(--surface);
    border-bottom: 1px solid var(--rule);
}
.scorecard-dialog .scorecard-filters label {
    display: flex;
    flex-direction: column;
    gap: 3px;
    font-family: 'JetBrains Mono', monospace;
    font-size: 10px;
    letter-spacing: 0.08em;
    color: var(--ink-muted);
}
.scorecard-dialog .scorecard-filters select,
.scorecard-dialog .scorecard-filters input[type="date"] {
    font-family: 'Inter', sans-serif;
    font-size: 13px;
    padding: 6px 10px;
    border: 1px solid var(--rule);
    background: var(--bg);
    color: var(--ink);
    outline: none;
    min-width: 160px;
}
.scorecard-dialog .scorecard-filters .scorecard-toggle {
    flex-direction: row;
    align-items: center;
    gap: 6px;
    font-family: 'Inter', sans-serif;
    font-size: 13px;
    color: var(--ink);
    text-transform: none;
    letter-spacing: 0;
    padding-bottom: 7px;
}
.scorecard-dialog .scorecard-filters button {
    font-family: 'Inter', sans-serif;
    font-size: 13px;
    padding: 7px 16px;
    border: 1px solid var(--ink);
    background: var(--ink);
    color: var(--surface);
    cursor: pointer;
}

/* Summary tiles — four stat cards above the table. */
.scorecard-dialog .scorecard-tiles {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
    gap: 14px;
    margin-bottom: 20px;
}
.scorecard-dialog .tile {
    background: var(--surface);
    border: 1px solid var(--rule);
    padding: 14px 18px;
}
.scorecard-dialog .tile-label {
    font-family: 'JetBrains Mono', monospace;
    font-size: 10px;
    letter-spacing: 0.12em;
    text-transform: uppercase;
    color: var(--ink-muted);
    margin-bottom: 6px;
}
.scorecard-dialog .tile-value {
    font-family: 'Fraunces', serif;
    font-size: 28px;
    font-weight: 600;
    line-height: 1;
    color: var(--ink);
    margin-bottom: 6px;
}
.scorecard-dialog .tile-sub {
    font-family: 'JetBrains Mono', monospace;
    font-size: 10px;
    color: var(--ink-muted);
}

/* Scorecard table — same aesthetic as the main grid but simpler. */
.scorecard-dialog .scorecard-table-wrap {
    background: var(--surface);
    border: 1px solid var(--rule);
    overflow-x: auto;
}
.scorecard-dialog .scorecard-table {
    width: 100%;
    border-collapse: collapse;
    font-size: 13px;
}
.scorecard-dialog .scorecard-table thead th {
    position: sticky;
    top: 0;
    background: var(--header-bg);
    color: var(--header-ink);
    text-align: left;
    padding: 10px 12px;
    font-family: 'JetBrains Mono', monospace;
    font-size: 10px;
    letter-spacing: 0.12em;
    text-transform: uppercase;
    font-weight: 500;
    cursor: pointer;
    user-select: none;
    white-space: nowrap;
}
.scorecard-dialog .scorecard-table thead th.num  { text-align: right; }
.scorecard-dialog .scorecard-table thead th:hover { background: #333; }
.scorecard-dialog .scorecard-table thead th.sort-asc::after  { content: ' ▲'; opacity: 0.6; }
.scorecard-dialog .scorecard-table thead th.sort-desc::after { content: ' ▼'; opacity: 0.6; }
.scorecard-dialog .scorecard-table tbody td {
    padding: 9px 12px;
    border-bottom: 1px solid var(--rule-soft);
    vertical-align: middle;
}
.scorecard-dialog .scorecard-table tbody tr:hover td { background: var(--row-alt); }
.scorecard-dialog .scorecard-table tbody td.num  { text-align: right; font-variant-numeric: tabular-nums; }
.scorecard-dialog .scorecard-table tbody td.date { white-space: nowrap; color: var(--ink-soft); }
.scorecard-dialog .scorecard-table tbody td a {
    color: var(--focus);
    text-decoration: none;
    font-weight: 500;
}
.scorecard-dialog .scorecard-table tbody td a:hover { text-decoration: underline; }
.scorecard-dialog .scorecard-table tbody td.low-ratio { color: #b91c1c; font-weight: 600; }

.scorecard-dialog .scorecard-status {
    padding: 30px;
    text-align: center;
    color: var(--ink-muted);
    font-family: 'JetBrains Mono', monospace;
    font-size: 11px;
}

.scorecard-dialog .scorecard-foot {
    display: flex;
    justify-content: flex-end;
    padding: 10px 20px;
    border-top: 1px solid var(--rule);
    background: var(--surface);
}
.scorecard-dialog .scorecard-foot button {
    font-family: 'Inter', sans-serif;
    font-size: 13px;
    padding: 7px 16px;
    border: 1px solid var(--ink);
    background: var(--surface);
    color: var(--ink);
    cursor: pointer;
}
/* Primary (Download CSV) sits next to the Close button on the right of the
   prepay-report footer; left margin gives it a visible gap from Close. */
.scorecard-dialog .scorecard-foot button.primary {
    background: var(--ink);
    color: var(--surface);
    margin-right: 10px;
}

/* ── Prepay Report dialog (P3) ─────────────────────────────────────────────
   Inherits the Scorecard dialog shell (filters, tiles, body). Adds a
   grouped-sections layout where each month renders its own header strip
   plus a table of rows that closed out in that month. The Pre-tracking
   bucket (NULL reviewed_at) renders last with a dimmed label. */
.prepay-report-dialog .prepay-report-groups {
    display: flex;
    flex-direction: column;
    gap: 18px;
    margin-top: 14px;
}
.prepay-report-dialog .prepay-group {
    background: var(--surface);
    border: 1px solid var(--rule);
}
.prepay-report-dialog .prepay-group-header {
    display: flex;
    align-items: center;
    gap: 14px;
    padding: 10px 14px;
    background: var(--header-bg);
    color: var(--header-ink);
    font-family: 'JetBrains Mono', monospace;
    font-size: 11px;
    letter-spacing: 0.08em;
}
.prepay-report-dialog .prepay-group-label {
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.14em;
}
.prepay-report-dialog .prepay-group-count {
    opacity: 0.8;
}
.prepay-report-dialog .prepay-group-totals {
    margin-left: auto;
    display: flex;
    gap: 18px;
    font-variant-numeric: tabular-nums;
}
.prepay-report-dialog .prepay-group-totals span strong { color: var(--surface); }
/* Pre-tracking bucket — same shell, dimmed header so staff can tell it's a
   "closed out before P3" bucket at a glance. */
.prepay-report-dialog .prepay-group.pre-tracking .prepay-group-header {
    background: #52525b;    /* zinc-600 */
}
.prepay-report-dialog .prepay-group table {
    width: 100%;
    border-collapse: collapse;
    font-size: 12px;
}
.prepay-report-dialog .prepay-group thead th {
    background: var(--row-alt);
    color: var(--ink-soft);
    text-align: left;
    padding: 7px 10px;
    font-family: 'JetBrains Mono', monospace;
    font-size: 10px;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    font-weight: 500;
    border-bottom: 1px solid var(--rule);
}
.prepay-report-dialog .prepay-group thead th.num { text-align: right; }
.prepay-report-dialog .prepay-group tbody td {
    padding: 7px 10px;
    border-bottom: 1px solid var(--rule-soft);
    vertical-align: middle;
}
.prepay-report-dialog .prepay-group tbody tr:last-child td { border-bottom: none; }
.prepay-report-dialog .prepay-group tbody td.num {
    text-align: right;
    font-variant-numeric: tabular-nums;
}
.prepay-report-dialog .prepay-group tbody td.ratio-low {
    color: #b91c1c;
    font-weight: 600;
}
.prepay-report-dialog .prepay-group tbody td.notes {
    color: var(--ink-soft);
    max-width: 320px;
}
.prepay-report-dialog .prepay-report-empty {
    padding: 30px;
    text-align: center;
    color: var(--ink-muted);
    font-family: 'JetBrains Mono', monospace;
    font-size: 11px;
    background: var(--surface);
    border: 1px dashed var(--rule);
}

/* ── Activity Log dialog ────────────────────────────────────────────────── */
dialog.activity-dialog {
    /* Near-full-viewport, same inset as Batch Add so dialogs feel consistent.
       See the comment on dialog.batch-dialog below for why we skip `display:`
       on this base rule and only override under [open]. */
    width: calc(100vw - 80px);
    max-width: calc(100vw - 80px);
    height: calc(100vh - 40px);
    max-height: calc(100vh - 40px);
    margin: 40px auto 0 auto;
    padding: 0;
}
dialog.activity-dialog[open] {
    display: flex;
    flex-direction: column;
}
.activity-dialog > header.dialog-head,
.activity-dialog > footer.activity-foot,
.activity-dialog > .activity-filters { flex: 0 0 auto; }
.activity-dialog > .activity-body    { flex: 1 1 auto; min-height: 0; }

.activity-filters {
    display: flex;
    flex-wrap: wrap;
    align-items: flex-end;
    gap: 12px 14px;
    padding: 14px 20px;
    border-bottom: 1px solid var(--rule);
    background: var(--surface);
}
.activity-filters label {
    display: flex;
    flex-direction: column;
    gap: 3px;
    font-family: 'JetBrains Mono', monospace;
    font-size: 10px;
    letter-spacing: 0.08em;
    color: var(--ink-muted);
}
.activity-filters input,
.activity-filters select {
    font-family: 'Inter', sans-serif;
    font-size: 13px;
    padding: 6px 8px;
    border: 1px solid var(--rule);
    background: var(--bg);
    color: var(--ink);
    outline: none;
    min-width: 140px;
}
.activity-filters input:focus,
.activity-filters select:focus { border-color: var(--focus); }
.activity-filters button {
    font-family: 'Inter', sans-serif;
    font-size: 13px;
    padding: 7px 14px;
    border: 1px solid var(--ink);
    background: var(--surface);
    color: var(--ink);
    cursor: pointer;
}
.activity-filters button.primary { background: var(--ink); color: var(--surface); }
.activity-filters button:hover    { background: var(--focus); border-color: var(--focus); color: var(--surface); }

.activity-body {
    overflow: hidden;
    padding: 14px 20px;
    display: flex;
    flex-direction: column;
}
.activity-table-wrap {
    overflow: auto;
    border: 1px solid var(--rule);
    background: var(--bg);
    flex: 1 1 auto;
    min-height: 0;
}
.activity-table {
    width: 100%;
    border-collapse: collapse;
    font-family: 'JetBrains Mono', monospace;
    font-size: 12px;
}
.activity-table thead th {
    position: sticky;
    top: 0;
    background: var(--header-bg);
    color: var(--header-ink);
    text-align: left;
    padding: 8px 10px;
    font-size: 10px;
    letter-spacing: 0.12em;
    text-transform: uppercase;
    font-weight: 500;
}
.activity-table tbody td {
    padding: 7px 10px;
    border-bottom: 1px solid var(--rule-soft);
    vertical-align: top;
}
.activity-table tbody tr:hover td { background: var(--row-alt); }
.activity-table .col-time    { white-space: nowrap; width: 155px; color: var(--ink-soft); }
.activity-table .col-user    { white-space: nowrap; width: 180px; }
.activity-table .col-action  { white-space: nowrap; width: 140px; }
.activity-table .col-target  { white-space: nowrap; width: 100px; }
.activity-table .col-details { min-width: 240px; color: var(--ink); font-family: 'Inter', sans-serif; }
.activity-table .col-ip      { white-space: nowrap; width: 120px; color: var(--ink-muted); font-size: 11px; }

/* Action badges — a different muted hue per action family so the eye
   can scan a long log and pick out the login vs edit vs delete clusters. */
.activity-table .action-badge {
    display: inline-block;
    padding: 2px 7px;
    border-radius: 10px;
    font-size: 10px;
    font-weight: 600;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    background: var(--rule-soft);
    color: var(--ink);
}
.activity-table .action-badge.auth-login        { background: #d7e9d3; color: #2f5a1f; }
.activity-table .action-badge.auth-logout       { background: #e2e8e0; color: #4a5a32; }
.activity-table .action-badge.auth-login_failed { background: #fbe4e3; color: #8b2d1f; }
.activity-table .action-badge.action-add        { background: #d4e4f8; color: #184e8b; }
.activity-table .action-badge.action-update     { background: #faf0d4; color: #7a5a0e; }
.activity-table .action-badge.action-delete     { background: #fbe4e3; color: #8b2d1f; }
.activity-table .action-badge.action-export     { background: #e4e0f5; color: #4a3a7a; }
.activity-table .action-badge.action-view       { background: #e8ecef; color: #4a5a6a; }

.activity-table .target-link {
    color: var(--focus);
    text-decoration: none;
    font-weight: 600;
    cursor: pointer;
}
.activity-table .target-link:hover { text-decoration: underline; }

/* Mini avatar in the User column — same crc32-hue scheme as the top-bar one. */
.activity-table .mini-avatar {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 18px;
    height: 18px;
    border-radius: 50%;
    background: hsl(var(--avatar-hue, 210), 55%, 68%);
    color: #1a1a1a;
    font-family: 'Inter', sans-serif;
    font-size: 9px;
    font-weight: 600;
    letter-spacing: 0.02em;
    text-transform: uppercase;
    margin-right: 6px;
    vertical-align: -4px;
}

.activity-status {
    padding: 20px;
    text-align: center;
    color: var(--ink-muted);
    font-family: 'JetBrains Mono', monospace;
    font-size: 11px;
}

.activity-foot {
    display: flex;
    justify-content: space-between;
    align-items: center;
    padding: 10px 20px;
    border-top: 1px solid var(--rule);
    background: var(--surface);
}
.activity-pagination {
    display: flex;
    align-items: center;
    gap: 12px;
    font-family: 'JetBrains Mono', monospace;
    font-size: 11px;
    color: var(--ink-muted);
}
.activity-pagination button {
    font-family: 'Inter', sans-serif;
    font-size: 12px;
    padding: 5px 12px;
    border: 1px solid var(--rule);
    background: var(--bg);
    color: var(--ink);
    cursor: pointer;
}
.activity-pagination button:disabled { opacity: 0.4; cursor: not-allowed; }
.activity-pagination button:not(:disabled):hover { border-color: var(--focus); color: var(--focus); }

/* ── Batch Add dialog ───────────────────────────────────────────────────── */
dialog.batch-dialog {
    /* Near-full-viewport dialog, inset 40px on top/left/right so it visually
       hangs off the parent page's "VENDOR COST TRACKING" eyebrow and has
       matching breathing room on both sides. Bottom runs to the viewport
       edge so the footer button row sits at the very bottom.
       NOTE: no `display:` rule on the base selector — the UA stylesheet
       hides a closed dialog via `dialog:not([open]) { display: none }`;
       setting display here would clobber that. We only override on [open]. */
    width: calc(100vw - 80px);
    max-width: calc(100vw - 80px);
    height: calc(100vh - 40px);
    max-height: calc(100vh - 40px);
    margin: 40px auto 0 auto;
    padding: 0;
}
/* When open, the dialog is a vertical flex container so the header stays
   pinned at the top, footer at the bottom, and the active content section
   fills the middle and manages its own scroll. */
dialog.batch-dialog[open] {
    display: flex;
    flex-direction: column;
}
.batch-dialog > header.dialog-head,
.batch-dialog > footer.batch-foot { flex: 0 0 auto; }

/* Content sections are hidden by default; the state machine turns the
   active one on with the right display mode AND flex-growth so it fills
   the space between header and footer. */
.batch-dialog .batch-setup,
.batch-dialog .batch-parsing,
.batch-dialog .batch-review { display: none; }
.batch-dialog .batch-setup {
    display: block;
    flex: 1 1 auto;
    min-height: 0;
    overflow-y: auto;
    padding: 20px 24px;
}
.batch-dialog.parsing .batch-setup { display: none; }
.batch-dialog.parsing .batch-parsing {
    display: block;
    flex: 1 1 auto;
    min-height: 0;
    overflow-y: auto;
    padding: 20px 24px;
}
.batch-dialog.reviewing .batch-setup { display: none; }
.batch-dialog.reviewing .batch-review {
    display: grid;
    grid-template-columns: minmax(0, 1fr) 520px;   /* table-first; PDF trimmed to give the review grid breathing room */
    grid-template-rows: 1fr;                       /* force the single row to fill the parent height */
    gap: 16px;
    padding: 16px 20px;
    flex: 1 1 auto;
    min-height: 0;
    overflow: hidden;   /* grid-wrap and the iframe handle their own scroll */
}
.batch-dialog.parsing .batch-foot .primary,
.batch-dialog.parsing .batch-foot #batchCancelBtn { visibility: hidden; }

/* Step 1 — vendor + drop zone */
.batch-vendor-row {
    display: flex;
    flex-direction: column;
    gap: 6px;
    margin-bottom: 14px;
}
.batch-vendor-row > span {
    font-family: 'JetBrains Mono', monospace;
    font-size: 11px;
    letter-spacing: 0.04em;
    color: var(--ink-soft);
}
.batch-vendor-row > span small { color: var(--ink-muted); font-size: 10px; }
.batch-vendor-row input {
    font-family: 'Inter', sans-serif;
    font-size: 14px;
    padding: 9px 12px;
    border: 1px solid var(--rule);
    background: var(--surface);
    color: var(--ink);
    outline: none;
}
.batch-vendor-row input:focus { border-color: var(--focus); }
/* Keep the vendor input a sensible fixed width — the dialog is near-full-
   viewport, so a stretched input looks oversized. Overrides the generic
   .input-with-add { flex: 1 1 auto } above for this row only. */
.batch-vendor-row .input-with-add input { flex: 0 0 420px; width: 420px; }

.batch-dropzone {
    padding: 36px 20px;
    border: 2px dashed var(--rule);
    background: var(--surface);
    text-align: center;
    cursor: pointer;
    font-family: 'JetBrains Mono', monospace;
    font-size: 12px;
    color: var(--ink-muted);
    letter-spacing: 0.04em;
    transition: border-color 0.15s ease, background 0.15s ease;
}
.batch-dropzone:hover,
.batch-dropzone.drag-over {
    border-color: var(--focus);
    background: #eef3fb;
    color: var(--ink);
}
.batch-dropzone .pdf-icon { font-size: 22px; margin-right: 10px; vertical-align: -3px; }

/* Step 2 — parsing progress */
.batch-progress-row {
    display: flex;
    justify-content: space-between;
    align-items: baseline;
    font-family: 'JetBrains Mono', monospace;
    font-size: 12px;
    color: var(--ink);
    margin-bottom: 8px;
}
.batch-cost { color: var(--ink-muted); font-size: 11px; }
.batch-progress-track {
    height: 8px;
    background: var(--surface);
    border: 1px solid var(--rule);
    overflow: hidden;
}
.batch-progress-bar {
    height: 100%;
    background: var(--focus);
    width: 0%;
    transition: width 0.2s ease;
}
.batch-progress-note {
    margin-top: 10px;
    font-size: 10px;
    color: var(--ink-muted);
    font-style: italic;
}

/* Step 3 — review grid + side PDF viewer */
.batch-review-grid-wrap { overflow: auto; border: 1px solid var(--rule); min-width: 0; }
.batch-review-grid { width: 100%; border-collapse: collapse; font-size: 13px; }
.batch-review-grid thead th {
    position: sticky;
    top: 0;
    z-index: 1;
    background: var(--header-bg);
    color: var(--surface);
    text-align: left;
    padding: 8px 6px;
    font-family: 'JetBrains Mono', monospace;
    font-size: 9px;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    white-space: nowrap;
}
.batch-review-grid tbody tr { border-top: 1px solid var(--rule); }
.batch-review-grid tbody tr.selected { background: #eef3fb; }
.batch-review-grid tbody tr.excluded { opacity: 0.45; }
.batch-review-grid tbody tr.duplicate td.flags { color: #b91c1c; font-weight: 600; }
.batch-review-grid td { padding: 4px 6px; vertical-align: middle; }
.batch-review-grid td.file {
    font-family: 'JetBrains Mono', monospace;
    font-size: 10px;
    color: var(--ink-soft);
    max-width: 170px;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    cursor: pointer;
}
.batch-review-grid td.file:hover { color: var(--focus); text-decoration: underline; }
.batch-review-grid td input[type="text"],
.batch-review-grid td input[type="date"],
.batch-review-grid td input[type="number"] {
    width: 100%;
    min-width: 60px;
    font-size: 13px;
    padding: 3px 5px;
    border: 1px solid transparent;
    background: transparent;
    color: var(--ink);
    outline: none;
    box-sizing: border-box;
}
.batch-review-grid td input:focus { border-color: var(--focus); background: var(--surface); }
.batch-review-grid td.readonly input { color: var(--ink-muted); cursor: not-allowed; }
.batch-review-grid td.ratio { font-variant-numeric: tabular-nums; }
.batch-review-grid td.ratio.ratio-low { color: #b91c1c; font-weight: 600; }
.batch-review-grid td.flags { font-size: 10px; }
.batch-review-grid td.billed-cell { text-align: center; }
.batch-review-grid th.billed-head {
    white-space: nowrap;
    text-align: center;
}
.batch-review-grid th.billed-head input { margin-right: 4px; vertical-align: -1px; }

/* Notes column — wider by default; header carries a replicate input + button. */
.batch-review-grid th.notes-head { min-width: 220px; }
.batch-review-grid td.notes-cell input { min-width: 180px; }

/* Left column of the review step: toolbar strip above a scrollable table.
   Lives inside the two-column grid in place of the bare grid-wrap. */
.batch-review-main {
    display: flex;
    flex-direction: column;
    min-width: 0;
    gap: 8px;
    overflow: hidden;
}
.batch-review-main .batch-review-grid-wrap { flex: 1 1 auto; }

/* "Note to apply to all rows" toolbar — moved out of the Notes column
   header so the input has real width. Stays above the grid as a strip. */
.batch-notes-toolbar {
    flex: 0 0 auto;
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 8px 10px;
    background: color-mix(in oklch, var(--surface), var(--ink) 3%);
    border: 1px solid var(--rule);
    border-radius: 6px;
}
.batch-notes-toolbar-label {
    font-family: 'JetBrains Mono', monospace;
    font-size: 10px;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--ink-muted);
    flex: 0 0 auto;
    /* Pushes the label + input + button group to the right so the label's
       left edge sits roughly under the Advance column. Empty space on the
       left of the toolbar is intentional. */
    margin-left: auto;
}
.batch-notes-toolbar input {
    flex: 0 0 320px;
    min-width: 0;
    font-family: 'Inter', sans-serif;
    font-size: 13px;
    padding: 6px 10px;
    border: 1px solid var(--rule);
    background: var(--bg);
    color: var(--ink);
    outline: none;
}
.batch-notes-toolbar input:focus { border-color: var(--focus); outline: none; }
.batch-notes-toolbar button {
    flex: 0 0 auto;
    font-family: 'Inter', sans-serif;
    font-size: 12px;
    font-weight: 500;
    padding: 6px 14px;
    background: var(--bg);
    border: 1px solid var(--rule);
    color: var(--ink);
    cursor: pointer;
    white-space: nowrap;
}
.batch-notes-toolbar button:hover {
    background: var(--focus);
    border-color: var(--focus);
    color: var(--surface);
    cursor: pointer;
}

.batch-review-grid td.flags .f-dup { color: #b91c1c; }
.batch-review-grid td.flags .f-conf-low { color: #b91c1c; }
.batch-review-grid td.flags .f-conf-med { color: #c88a2a; }
.batch-review-grid td.flags .f-no-crt { color: #c88a2a; }
.batch-review-grid td.flags .f-records {
    color: #0f766e;         /* teal — different hue from red/amber so it reads as "informational, not a problem" */
    font-weight: 600;
}
.batch-review-grid td.include { text-align: center; }

.batch-pdf-side {
    display: flex;
    flex-direction: column;
    border: 1px solid var(--rule);
    background: var(--surface);
    min-height: 0;
}
.batch-pdf-head {
    display: flex;
    justify-content: space-between;
    align-items: center;
    gap: 10px;
    padding: 10px 14px;
    border-bottom: 1px solid var(--rule);
    background: var(--bg);
    font-family: 'JetBrains Mono', monospace;
    font-size: 10px;
    letter-spacing: 0.1em;
    text-transform: uppercase;
}
.batch-pdf-head-label { color: var(--ink-muted); }
.batch-pdf-head-hint {
    text-transform: none;
    letter-spacing: 0;
    font-size: 10px;
    color: var(--ink-soft);
    margin-left: 4px;
    font-weight: 400;
}
.batch-pdf-head-name {
    text-transform: none;
    letter-spacing: 0;
    color: var(--ink-soft);
    font-size: 10px;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.batch-pdf-side iframe { flex: 1 1 auto; width: 100%; border: 0; }

.batch-foot { padding: 12px 20px; display: flex; align-items: center; gap: 10px; }
.batch-foot .spacer { flex: 1; }
.batch-foot-summary {
    font-family: 'JetBrains Mono', monospace;
    font-size: 11px;
    color: var(--ink-muted);
}
.shared-qc-badge {
    display: inline-block;
    margin-left: 4px;
    font-size: 11px;
    cursor: help;
    opacity: 0.75;
    vertical-align: 1px;
}
.shared-qc-badge:hover { opacity: 1; }

/* Field-level confidence hints — subtle left-tint on auto-filled inputs */
.dialog-body input.conf-high   { box-shadow: inset 3px 0 0 #4a7a3f; }
.dialog-body input.conf-medium { box-shadow: inset 3px 0 0 #c88a2a; }
.dialog-body input.conf-low    { box-shadow: inset 3px 0 0 var(--accent); }
/* Clear hint as soon as the user edits the field — they've taken ownership. */
.dialog-body input.conf-dirty  { box-shadow: none; }

/* Readonly fields in the dialog (locked until QC lookup runs) */
.dialog-body input[readonly] {
    background: #e6ebf2;
    color: var(--ink-muted);
    cursor: not-allowed;
}
.dialog-body input[readonly]:focus { border-color: var(--rule); }

/* QC lookup status row — spans all four CRT columns so Records / multi-
   match / no-match messages are visible under the whole row of related
   fields, not just hidden under the QC column. Lives as a direct grid
   child of the CRT fieldset. min-height keeps the row's height stable
   even when the status is empty, so the layout doesn't jump as it
   fills + clears. */
.lookup-status {
    grid-column: 1 / -1;
    margin-top: -4px;        /* visually hug the row above instead of the one below */
    padding: 2px 2px 4px;
    font-size: 11px;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    min-height: 14px;
    text-align: center;
}
.lookup-status.ok   { color: #4a5a32; }
.lookup-status.warn { color: #8b2d1f; font-weight: 600; }

/* Multi-match picker shown under the QC input when more than one order
   matches the entered QC Number. */
.qc-match-picker {
    margin-top: 4px;
    width: 100%;
    font-family: 'JetBrains Mono', monospace;
    font-size: 11px;
    padding: 5px 8px;
    border: 1px solid var(--focus);
    background: #fff8e1;
    color: var(--ink);
    cursor: pointer;
}
.qc-match-picker:focus { outline: 1px solid var(--focus); }

/* Just-added row flash */
@keyframes flash-row {
    0%   { background: #fff6c4; }
    100% { background: transparent; }
}
tbody tr.flash > td { animation: flash-row 2.5s ease-out; }

/* Dirty row indicator — thin accent stripe on the sticky first column */
tbody tr[data-dirty] td.sticky-col {
    box-shadow: inset 3px 0 0 var(--accent), 1px 0 0 var(--rule);
}

/* Status banner */
.banner {
    margin: 10px 0 0;
    padding: 8px 14px;
    font-family: 'JetBrains Mono', monospace;
    font-size: 11px;
    letter-spacing: 0.05em;
    border: 1px solid var(--rule);
    background: var(--surface);
}
.banner.ok    { border-color: #6a7a4a; color: #4a5a32; background: #eef0e0; }
.banner.error { border-color: #b53d2f; color: #8b2d1f;        background: #fbe4e3; }

/* Custom scrollbar */
.table-wrap::-webkit-scrollbar { height: 12px; width: 12px; }
.table-wrap::-webkit-scrollbar-track { background: var(--bg); }
.table-wrap::-webkit-scrollbar-thumb {
    background: var(--rule);
    border: 2px solid var(--bg);
    border-radius: 6px;
}
.table-wrap::-webkit-scrollbar-thumb:hover { background: var(--ink-muted); }

/* ── Re-attach PDFs dialog ─────────────────────────────────────────────
   Shares the batch-dialog chrome (same shell, same dropzone, same review
   grid skeleton), with its own status/match-row accents and a lightweight
   "missing invoices" note that swaps color based on the lookup result. */
.reattach-missing-note {
    margin: 12px 0;
    padding: 10px 14px;
    border: 1px solid var(--rule);
    border-radius: 8px;
    background: color-mix(in oklch, var(--bg), var(--ink) 2%);
    color: var(--ink-muted);
    font-size: 14px;
}
.reattach-missing-note.reattach-none { color: #4a5a32; border-color: #6a7a4a; background: #eef0e0; }
.reattach-missing-note.reattach-some { color: var(--ink); }

#reattachGrid .reattach-fname {
    font-family: 'JetBrains Mono', ui-monospace, Consolas, monospace;
    font-size: 12.5px;
    word-break: break-all;
    max-width: 320px;
}
#reattachGrid .reattach-override { max-width: 320px; width: 100%; }
#reattachGrid .reattach-nomatch  { color: var(--ink-muted); }
#reattachGrid .reattach-ok       { color: #4a5a32; font-weight: 600; }
#reattachGrid .reattach-skip     { color: #8b2d1f; font-weight: 600; }
#reattachGrid .reattach-remove {
    margin-left: 8px;
    border: 1px solid var(--rule);
    background: transparent;
    color: var(--ink-muted);
    width: 22px; height: 22px;
    border-radius: 4px;
    cursor: pointer;
    line-height: 1;
}
#reattachGrid .reattach-remove:hover {
    color: var(--ink);
    border-color: var(--ink-muted);
}

/* ── Toolbar menu dropdowns (+ Add, Affiliates, Export) ───────────────
   Follows the same shape as .columns-wrap/.columns-panel but with a
   vertical list of actions instead of a checkbox grid. Each menu item
   is a plain button that fires its onclick / id-bound handler, and the
   generic JS closes the panel on item click and on outside click. */
.menu-wrap { position: relative; display: inline-block; }
.menu-trigger .menu-caret {
    font-size: 10px;
    margin-left: 2px;
    opacity: 0.65;
    display: inline-block;
    transform: translateY(-1px);
}
.menu-panel {
    position: absolute;
    top: calc(100% + 4px);
    left: 0;
    background: var(--surface);
    border: 1px solid var(--rule);
    box-shadow: 0 10px 28px -8px rgba(0,0,0,0.25);
    padding: 6px;
    min-width: 200px;
    z-index: 100;
    display: flex;
    flex-direction: column;
    gap: 2px;
}
.menu-panel button {
    text-align: left;
    background: transparent;
    border: 1px solid transparent;
    padding: 8px 12px;
    font-size: 13px;
    color: var(--ink);
    cursor: pointer;
    border-radius: 4px;
}
.menu-panel button:hover,
.menu-panel button:focus-visible {
    background: color-mix(in oklch, var(--surface), var(--ink) 6%);
    border-color: var(--rule);
    outline: none;
}

/* Hide the PDF side pane and reclaim its grid column when toggled off.
   Persisted per browser via localStorage so the choice sticks across saves. */
.batch-dialog.pdf-hidden.reviewing .batch-review {
    grid-template-columns: minmax(0, 1fr);
}
.batch-dialog.pdf-hidden .batch-pdf-side { display: none; }

/* ── Batch Update dialog ─────────────────────────────────────────────────
   Matches dialog.batch-dialog's near-full-viewport sizing and position
   (inset 40px on top/left/right, bottom to the viewport edge) so staff
   switching between Batch Add and Batch Update don't get a visual jump.
   Simpler contents than Batch Add — no PDF side pane and no state
   machine — so the body is a single flex column that fills the middle
   and the review table scrolls inside its own wrapper.
   NOTE: no `display:` rule on the base selector — same reason as
   dialog.batch-dialog above: the UA stylesheet hides a closed dialog via
   `dialog:not([open]) { display: none }`. Override only on [open]. */
dialog.batch-update-dialog {
    width:      calc(100vw - 80px);
    max-width:  calc(100vw - 80px);
    height:     calc(100vh - 40px);
    max-height: calc(100vh - 40px);
    margin: 40px auto 0 auto;
    padding: 0;
}
dialog.batch-update-dialog[open] {
    display: flex;
    flex-direction: column;
}
.batch-update-dialog > header.dialog-head,
.batch-update-dialog > footer.dialog-foot { flex: 0 0 auto; }
/* The dialog body: single flex column that fills the middle of the
   dialog. MUST beat `.dialog-body { max-height: calc(90vh - 120px);
   overflow-y: auto }` from the shared add-dialog chrome — otherwise
   the body gets capped shorter than the dialog's own height, the grid-
   wrap child collapses, and the sticky thead scrolls out of view the
   moment the table renders. We also kill overflow-y on the body itself
   so the grid-wrap is the ONE scroll container, which is what the
   thead's `position: sticky; top: 0` is anchored to. */
.batch-update-body {
    padding: 16px 20px;
    display: flex;
    flex-direction: column;
    gap: 12px;
    flex: 1 1 auto;
    min-width: 0;
    min-height: 0;
    max-height: none;
    overflow: hidden;
}
.batch-update-intro {
    margin: 0;
    color: var(--ink-muted);
    font-size: 13px;
    line-height: 1.5;
}
.batch-update-status {
    font-size: 13px;
    color: var(--ink-soft);
    padding: 6px 8px;
    border: 1px solid var(--rule);
    border-radius: 4px;
    background: #fafafa;
    flex: 0 0 auto;
}
.batch-update-status.warn {
    color: #b45309;
    border-color: #f59e0b;
    background: #fffbeb;
}
.batch-update-grid-wrap {
    overflow: auto;
    border: 1px solid var(--rule);
    min-width: 0;
    min-height: 0;
    flex: 1 1 auto;
}
.batch-update-grid {
    /* MUST set table-layout and width here — the global `table { table-
       layout: fixed; width: max-content; min-width: 100% }` rule for the
       main grid's draggable columns otherwise forces every column in
       this dialog to the same width and content overflows into its
       neighbor. `auto` lets the columns size to content (combined with
       the `white-space: nowrap` on th/td below, long Affiliate names
       push the Affiliate column wider and overflow becomes a horizontal
       scroll inside .batch-update-grid-wrap, not into the next cell). */
    width: max-content;
    min-width: 100%;
    table-layout: auto;
    border-collapse: collapse;
    font-size: 13px;
}
/* Match the Batch Add grid's dark-chrome header so the two dialogs
   feel like siblings and the sticky thead has strong contrast against
   the scrolled rows below it. white-space:nowrap on BOTH th and td
   prevents the Affiliate / Aff. Inv. # columns from wrapping and
   visually overlapping — long values become a horizontal scroll inside
   the grid-wrap instead. */
.batch-update-grid thead th {
    position: sticky;
    top: 0;
    z-index: 1;
    background: var(--header-bg);
    color: var(--surface);
    text-align: left;
    padding: 8px 10px;
    font-family: 'JetBrains Mono', monospace;
    font-size: 9px;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    white-space: nowrap;
}
.batch-update-grid th.num,
.batch-update-grid td.num { text-align: right; }
.batch-update-grid tbody tr { border-top: 1px solid var(--rule); }
.batch-update-grid td {
    padding: 4px 10px;
    vertical-align: middle;
    white-space: nowrap;
}
.batch-update-grid td.flags { font-size: 11px; }

/* CRT Inv # — this is the whole headline value the dialog is fetching,
   so give it a quiet accent so staff's eye lands on it first. Header
   gets a deeper emerald over the dark chrome; body cells carry a soft
   mint tint with semibold tabular-numeric text. */
.batch-update-grid thead th[data-col="crt_inv"] {
    background: #065f46;
    color: #ecfdf5;
}
.batch-update-grid tbody td[data-col="crt_inv"] {
    background: #ecfdf5;
    color: #065f46;
    font-weight: 600;
    font-variant-numeric: tabular-nums;
}

/* Sortable header click affordance + direction arrow. ::after inherits
   the th's text color, so it reads whether the th is on the dark chrome
   or the CRT-column emerald. */
.batch-update-grid thead th[data-sort] {
    cursor: pointer;
    user-select: none;
}
.batch-update-grid thead th[data-sort]:hover { background: #333; }
.batch-update-grid thead th[data-col="crt_inv"][data-sort]:hover { background: #047857; }
.batch-update-grid thead th[data-sort].sort-asc::after  { content: ' ▲'; opacity: 0.7; }
.batch-update-grid thead th[data-sort].sort-desc::after { content: ' ▼'; opacity: 0.7; }
.batch-update-grid td.flags .f-conf-med {
    color: #b45309;
    background: #fef3c7;
    padding: 1px 6px;
    border-radius: 3px;
    font-weight: 600;
}
.batch-update-grid td.flags .f-no-crt {
    color: #4b5563;
    background: #f3f4f6;
    padding: 1px 6px;
    border-radius: 3px;
}
.batch-update-summary {
    font-size: 12px;
    color: var(--ink-muted);
}
