/* PTI Procurement */

/* Subtle global scrollbar — applies to the whole page and any other
   default-styled scroll containers. Thin grey thumb on transparent
   track; deepens on hover. Matches the per-pane scrollbar styling on
   the invoice detail page (PDF preview + draft row table). Added
   2026-05-14. */
html {
  scrollbar-width: thin;
  scrollbar-color: rgba(0,0,0,0.15) transparent;
}
::-webkit-scrollbar {
  width: 10px;
  height: 10px;
}
::-webkit-scrollbar-track {
  background: transparent;
}
::-webkit-scrollbar-thumb {
  background: rgba(0,0,0,0.13);
  border-radius: 5px;
  border: 2px solid transparent;
  background-clip: padding-box;
}
::-webkit-scrollbar-thumb:hover {
  background: rgba(0,0,0,0.28);
  background-clip: padding-box;
}

:root {
  --bg:           #FAF8F3;        /* warm near-white (was cool #FAFAF9) */
  --surface:      #FFFFFF;
  --surface-2:    #F4F0E8;        /* warm light surface (was cool #F4F4F2) */
  --border:       #EAE1D2;        /* warm border (was cool #E5E5E3) */
  --border-2:     #DBCEB8;        /* warm stronger border (was cool #D0D0CC) */

  --text:         #1F1610;        /* warm near-black (was neutral #111111) */
  --text-mute:    #6B5E4E;        /* warm mute (was cool #5A5A55) */
  --text-soft:    #8C7B66;        /* warm soft (was cool #8E8E88) */

  --accent:       #A85A1C;        /* PTI Orange — active states, primary actions */
  --accent-deep:  #8A4814;        /* deeper PTI Orange for hover/pressed */
  --link:         #9E4E16;        /* warm copper-brown for hyperlinks (was cool blue #2C5282) */

  --good:         #1D6F3F;
  --good-bg:      #E7F2EC;
  --warn:         #8A5A1F;
  --warn-bg:      #F5EBDA;
  --bad:          #9C2F2F;
  --bad-bg:       #F5E0DD;

  --radius:       4px;
  --radius-lg:    6px;

  --font-sans:    "Inter", -apple-system, "Segoe UI", system-ui, sans-serif;
  --font-mono:    "SF Mono", "Cascadia Mono", Consolas, monospace;
}

* { box-sizing: border-box; }
html, body { margin: 0; padding: 0; }
body {
  font-family: var(--font-sans);
  /* Lit cream, not flat paper — a soft copper glow eases off the top into a
     gentle warm-cream gradient down the canvas. Painted on <body> (not
     main.content) so it covers the FULL viewport. main.content is capped at
     1400px and left-aligned, so when the gradient lived there it stopped dead
     at the cap and the flat --bg showed beyond it — a hard seam on wide
     displays ("fix the side slop, the glossy stops", Josh 2026-06-10). The
     sidebar paints its own gradient on top, so the canvas reads as one
     continuous lit surface. background-attachment:fixed pins the glow to the
     viewport (and the glow was already viewport-relative when it lived on
     main.content, so this only un-clips it — it doesn't move). (2026-06-10) */
  background:
    radial-gradient(720px 380px at 16% -6%, rgba(214,138,68,.09), transparent 60%),
    linear-gradient(180deg, #FCFAF4 0%, #F7F1E6 54%, #F2EADB 100%);
  background-attachment: fixed;
  color: var(--text);
  font-size: 13px;
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
  font-feature-settings: "cv11";
}
a { color: var(--link); text-decoration: none; }
a:hover { text-decoration: underline; }
h1, h2, h3, h4 { margin: 0; font-weight: 600; letter-spacing: -0.011em; }

/* Layout */
/* ─── App shell + sidebar — v5 light cream (2026-05-22) ────────────
   The cold dark #14130F sidebar was disconnected from the warm cream
   palette of the rest of the app. Rebuilt: light cream background,
   muted warm-brown text, brand orange for active state. One palette
   across the whole UI. Per Josh: collapsible. Slides off entirely
   (transform: translateX) with a chevron toggle inside the sidebar
   and a reveal button pinned to top-left of main when hidden. */
.app {
  display: grid;
  grid-template-columns: 220px minmax(0, 1fr);
  min-height: 100vh;
  /* 2026-06-10 (Josh) — the grid-template-columns transition is REMOVED.
     Animating grid tracks (220px↔0 with a minmax(0,1fr) sibling) is
     unreliable: in testing the collapse simply didn't take — the track
     stuck at 220px while the rest of the collapsed styling applied, so the
     sidebar stayed on screen overlapping centred content ("shit gets
     fucked up when you close the sidebar"). An instant track swap is the
     robust behaviour; the sidebar snaps cleanly open/closed every time. */
}
main.content {
  min-width: 0;  /* prevents wide table from blowing out the grid */
  /* Clip stray horizontal overflow at the content box so the PAGE never grows
     a sideways scrollbar. The sidebar is position:sticky with only top:0 — it
     pins vertically but NOT horizontally, so any body-level horizontal scroll
     drags it out of view and exposes bare cream beside it ("the sidebar stops
     short and there's empty space", Josh 2026-07-08). Every legitimately-wide
     surface (Open Orders .oo-scroll, Purchase Log .pl-scroll, invoices
     .table-scroll, the doc viewer capped at 800px) already scrolls inside its
     OWN container, so nothing here relies on page-level horizontal scroll.
     `clip` (not hidden/auto) contains only the horizontal axis without creating
     a scroll container, so vertical page scroll, sticky table headers, and the
     fixed pop-out lightbox are all untouched. (2026-07-08) */
  overflow-x: clip;
  /* Transparent — the lit-cream canvas now lives on <body> (full-bleed) so
     the gradient no longer clips to this 1400px-capped box. Content cards
     (white) still float on the body surface, so data reads clean and never
     sits on copper. (2026-06-10) */
  background: transparent;
}

/* Sidebar is always rendered — the hamburger collapse/expand feature was
   removed 2026-06-23 (Josh). Restore from git (the .nav-toggle +
   .nav-collapsed rules and the base.html toggle script) if a collapsible
   nav is ever wanted again. */
aside.nav {
  /* Lit cream, not flat cream: a soft copper glow warms the brand area at
     the top, easing into a gentle vertical gradient down the rail. Energy
     from light + depth, no flooding. (2026-06-09) */
  background:
    radial-gradient(440px 240px at 28% 0%, rgba(214,138,68,.17), transparent 62%),
    linear-gradient(180deg, #FBF8F2 0%, #F4ECDD 60%, #EEE4D2 100%);
  color: #6B5E4E;
  padding: 22px 0 16px;
  display: flex;
  flex-direction: column;
  position: sticky;
  top: 0;
  height: 100vh;
  border-right: 1px solid #EFE6D2;
  overflow: hidden;
}

aside.nav .brand {
  position: relative;
  padding: 20px 22px 20px;
  border-bottom: 1px solid #EFE6D2;
}
/* PTI wordmark — bumped to 168px once the "Procurement"
   subtitle (v23) and the collapse chevron (v24) were both retired
   and the wordmark owned the brand area on its own. Previous caps
   were 128 (shared with subtitle) and 150 (fought the chevron). */
/* The wordmark links home. The anchor hugs the logo (inline-block + negative
   margin cancels its own padding, so the wordmark sits in exactly the same
   spot as before) and fades in a soft warm backdrop on hover — our wordmark
   answer to X's circular icon highlight. Kept faint and slow so it reads as a
   quiet "this is clickable," not a tinted box. */
aside.nav .brand .brand-link {
  display: inline-block;
  padding: 8px 12px;
  margin: -8px -12px;
  border-radius: 14px;
  text-decoration: none;
  transition: background 0.18s ease;
}
/* `.is-hover` is the JS-driven twin of :hover. The logo is data-turbo-permanent
   (same node across navigations), so a class set on pointerenter rides through
   every Turbo swap and only clears on pointerleave — unlike :hover, which the
   browser drops for a frame mid-swap and flashed the backdrop off. See the
   pointer-binding script in base.html. */
aside.nav .brand .brand-link:hover,
aside.nav .brand .brand-link.is-hover {
  /* A warm sand, kept a few shades deeper than the copper glow the brand
     area sits on (~#F5E5D4) so the backdrop actually reads — a lighter tint
     just disappeared into the glow. This is our answer to X's grey circle:
     clearly present on hover, still soft and on-palette. */
  background: #EBD0AC;
}
aside.nav .brand .brand-logo {
  display: block;
  width: 168px;
  max-width: 100%;
  height: auto;
}
/* Legacy text label — no longer used but kept in case any page renders it. */
aside.nav .brand .pti {
  font-size: 10px; font-weight: 600; letter-spacing: 1.6px;
  text-transform: uppercase; color: #8C7B66;
}
/* "Procurement" subtitle — natural case, muted, no tracking.
   Was uppercase 11px tracked — classic admin-panel tell. (v6 bumped
   the top margin from 12 → 16 so the wordmark and subtitle each get
   their own beat.) */
aside.nav .brand .title {
  margin-top: 16px;
  font-size: 13px;
  font-weight: 500;
  letter-spacing: 0;
  text-transform: none;
  color: #8C7B66;
}
/* v24 (2026-05-23) — `.nav-toggle`, `.nav-reveal`, and the
   `.app.nav-collapsed` companion rules all retired with the
   sidebar collapse/reveal feature. Restore from git (any commit
   before 2026-05-23) if responsive constraints change. */

aside.nav ul {
  list-style: none;
  margin: 16px 0 0;
  padding: 6px 0;
  flex: 1;
  overflow-y: auto;
}
aside.nav li { position: relative; margin: 1px 0; }
aside.nav li a {
  display: flex;
  align-items: center;
  gap: 11px;
  padding: 11px 22px;
  color: #6E5C46;
  /* 2026-06-14 — bumped 13.5 → 15px per Josh (X-style, a touch larger
     and more confident). Icon size below tracks this. */
  font-size: 15px;
  font-weight: 500;
  border-left: 3px solid transparent;
  text-decoration: none;
}
aside.nav li a:hover {
  color: #3D2817;
  background: #F5EFE0;
  text-decoration: none;
}
aside.nav li.active a {
  color: #8A4A16;
  background: #FFE6CF;
  font-weight: 600;
}
/* .nav-icon (reinstated 2026-06-14, scoped) — icons were retired in v7
   as over-design across all 10 items. Brought back for the two ACTION
   items only (PO Generation, Upload) — the things you *do* rather than
   browse — so they read as buttons in the list. Inherits currentColor,
   so it tints copper on the active item and warms on hover with the
   label. flex-shrink:0 keeps it from squashing on the narrow layout. */
.nav-icon {
  flex-shrink: 0;
  width: 18px;
  height: 18px;
  opacity: 0.85;
}
aside.nav li.active a .nav-icon { opacity: 1; }

/* "(Testing Phase)" suffix on Accuracy — was parenthetical body text.
   Now a small muted tag. The parens are stripped in base.html v5. */
.nav-suffix {
  font-size: 10px;
  font-weight: 500;
  color: #B8AEA0;
  letter-spacing: 0.02em;
  margin-left: 4px;
}
aside.nav li.active a .nav-suffix {
  color: #8A4814;
  opacity: 0.7;
}

/* Pending count chip — soft warm pill, auto-pushed right inside the
   nav-item flex. */
.nav-count {
  display: inline-flex;
  align-items: center;
  margin-left: auto;
  min-width: 22px;
  height: 18px;
  padding: 0 7px;
  justify-content: center;
  background: #FFE6CF;
  color: #8A4A16;
  font-size: 11px;
  font-weight: 600;
  border-radius: 999px;
}
aside.nav li.active a .nav-count {
  background: rgba(255, 255, 255, 0.6);
  color: #8A4A16;
}

/* Footer status — replaces the "LAST UPLOAD / INVOICES / AUTO-CLASSIFIED"
   K/V triplet with a quiet two-line summary. Less admin-panel; more
   ambient context. (v5.) */
.sys {
  padding: 14px 24px 0;
  margin-top: 12px;
  border-top: 1px solid #EFE6D2;
  font-size: 12px;
  color: #8C7B66;
  line-height: 1.55;
}
.sys .row { display: block; margin-bottom: 0; }
.sys .row .k { color: #8C7B66; font-weight: 400; }
.sys .row .v { color: #3D2817; font-weight: 500; font-variant-numeric: tabular-nums; }

.sys .scrape-btn {
  display: block; width: 100%; margin-top: 10px;
  background: transparent; color: #6B5E4E;
  border: 1px solid #EFE6D2;
  padding: 6px; border-radius: var(--radius);
  font-size: 11px; cursor: pointer; font-family: inherit;
  letter-spacing: 0;
}
.sys .scrape-btn:hover { background: #FFF7EC; color: #8A4A16; border-color: #F5D7B8; }

/* Auth placeholder — was inline-styled in base.html (which read as
   form-fill clutter). Now a class. Renders cleaner muted text without
   the "label / strong" pairing. */
.auth-block {
  padding: 14px 24px 4px;
  margin-top: 8px;
  border-top: 1px solid #EFE6D2;
  font-size: 11.5px;
  color: #8C7B66;
  line-height: 1.55;
}
.auth-block .auth-title {
  display: block;
  color: #3D2817;
  font-weight: 500;
  margin-bottom: 2px;
}
.auth-block a {
  color: #8A4A16;
  text-decoration: underline;
}
.auth-block a:hover { color: #8A4814; }

main.content { padding: 28px 40px 60px; max-width: 1400px; }

/* Page header
   2026-05-22 — dropped the grey 1px rule + the 48px orange tick at
   the start. That treatment reads as starter-template (default AI
   landing-page formula: thin rule, brand-color accent at the left
   edge). Whitespace + the page title's type weight do the
   separation work now. */
.page-header {
  display: flex; justify-content: space-between; align-items: flex-end;
  margin-bottom: 22px; padding-bottom: 14px;
}
.page-header h1 { font-size: 20px; font-weight: 600; }
/* Shared "← Back" link — quiet by default, warms to the accent on hover,
   never underlines. Used across every document viewer (invoice, PO, contract,
   goods-receipt) so they read identically; pair it with [data-smart-back] for
   the history.back() behaviour wired in base.html. */
.page-back-link {
  font-size: 13px; color: var(--text-mute); text-decoration: none;
  white-space: nowrap;
}
.page-back-link:hover { color: var(--accent); text-decoration: none; }
/* Inside a .page-header it parks top-right, opposite the title. */
.page-header .page-back-link {
  align-self: flex-start; margin-top: 6px; flex: 0 0 auto;
}
.page-header h1 .dim { color: var(--text-soft); font-weight: 400; margin-left: 4px; }
.page-header .meta { font-size: 12px; color: var(--text-mute); }

/* Card — flat, no shadow */
.card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
}
.card > .ms-scroll, .card > .pl-scroll { border-bottom-left-radius: var(--radius-lg); border-bottom-right-radius: var(--radius-lg); }
.card .card-h {
  padding: 12px 18px; border-bottom: 1px solid var(--border);
  display: flex; justify-content: space-between; align-items: baseline;
}
.card .card-h h3 {
  font-size: 12px; font-weight: 600;
  text-transform: uppercase; letter-spacing: 0.7px; color: var(--text);
}
.card .card-h .more { font-size: 11px; color: var(--text-soft); }
.card .card-h .more a { color: var(--text-mute); }

/* KPI row (overview only) */
.kpi-row {
  display: grid; grid-template-columns: repeat(5, 1fr);
  gap: 0;
  background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--radius-lg); overflow: hidden;
  margin-bottom: 22px;
}
.kpi { padding: 14px 16px; border-right: 1px solid var(--border); }
.kpi:last-child { border-right: none; }
.kpi .label { font-size: 10px; color: var(--text-mute);
  text-transform: uppercase; letter-spacing: 0.7px; font-weight: 600; }
.kpi .value { font-size: 22px; font-weight: 600; margin-top: 4px;
  font-variant-numeric: tabular-nums; letter-spacing: -0.015em; }
.kpi .value .unit { font-size: 12px; color: var(--text-mute); font-weight: 400; margin-left: 2px; }
.kpi .sub { margin-top: 3px; font-size: 11px; color: var(--text-mute); font-variant-numeric: tabular-nums; }
.d.up   { color: var(--bad); }
.d.down { color: var(--good); }
.d.nil  { color: var(--text-soft); }
@media (max-width: 1100px) { .kpi-row { grid-template-columns: repeat(2, 1fr); } }

/* ─── In-site confirmation modal (2026-05-22 v8) ───────────────────
   Generic modal vocabulary — used right now by the PO delete flow
   (po_list.html) but the class names are app-agnostic so other
   destructive actions can reuse them. Backdrop dims with a warm
   dark tint (rgba(31,22,16,…)) to match the cream palette rather
   than cold black. Card has a soft lift-shadow and gentle pop
   animation so it reads as a deliberate moment, not a flash. */
.modal-backdrop {
  position: fixed;
  inset: 0;
  background: rgba(31, 22, 16, 0.42);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 1000;
  animation: modal-fade-in 0.14s ease-out;
}
/* v13 — renamed from `.modal-card` → `.confirm-card`. A pre-
   existing `.modal-card` rule lower in this file (line ~3404,
   the inbox shortcuts dialog) was overriding mine with
   `width: 440px; padding: 0`, which is why my dimensions never
   took effect across v9 → v12 no matter what I tried. Class is
   now distinct so the cascade can't bite us. */
.confirm-card {
  background: #FFFFFF;
  border-radius: 14px;
  /* Roomier top/bottom so the title and buttons breathe against the box
     edges (sides unchanged). */
  padding: 36px 20px 32px;
  width: 100%;
  max-width: 300px;
  box-shadow: 0 12px 36px rgba(31, 22, 16, 0.20),
              0 2px 6px rgba(31, 22, 16, 0.08);
  animation: modal-pop 0.18s ease-out;
}
.modal-title {
  font-size: 18px;
  font-weight: 600;
  color: #1F1610;
  margin: 0 0 22px 0;
  letter-spacing: -0.008em;
  text-align: center;
}
.modal-body {
  font-size: 14px;
  color: #5A4D3A;
  line-height: 1.55;
  margin-bottom: 22px;
}
.modal-body p {
  margin: 0 0 10px 0;
}
.modal-body p:last-child {
  margin-bottom: 0;
}
.modal-warning {
  color: #9C2F2F;
  font-weight: 500;
  margin-top: 12px;
}
.modal-actions {
  display: flex;
  gap: 12px;
  /* v10 — centered (was flex-end). Pairs with the centered title
     so the modal reads as a balanced symmetric dialog. */
  justify-content: center;
}
/* Modal buttons — paired set, identical dimensions, color is the
   only role signal (canon principle #5). Cancel is the safe pill;
   Delete is the destructive pink. */
.modal-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 7px;
  padding: 9px 18px;
  min-width: 90px;
  border-radius: 8px;
  font-size: 13.5px;
  font-weight: 500;
  border: 0;
  cursor: pointer;
  font-family: inherit;
  transition: background 0.12s, color 0.12s;
}
.modal-btn:focus-visible {
  outline: 2px solid #C0712F;
  outline-offset: 2px;
}
.modal-btn-cancel {
  background: #F5EFE0;
  color: #5A4D3A;
}
.modal-btn-cancel:hover {
  background: #ECE3D0;
  color: #1F1610;
}
.modal-btn-destructive {
  background: #FCE8E8;
  color: #9C2F2F;
  font-weight: 600;
}
.modal-btn-destructive:hover {
  background: #F8D5D5;
  color: #7A1F1F;
}
/* A hair down so the trash reads as vertically centered against "Delete". */
.modal-btn-destructive svg { position: relative; top: 0.5px; }

@keyframes modal-fade-in {
  from { opacity: 0; }
  to   { opacity: 1; }
}
@keyframes modal-pop {
  from { transform: scale(0.96); opacity: 0; }
  to   { transform: scale(1);    opacity: 1; }
}

/* Save-confirmation toast (PO created / saved to Drafts) — a quiet
   bottom-right card rather than a tinted full-width banner. White surface,
   copper check, slides up on appear and fades on dismiss. */
.po-toast {
  position: fixed;
  right: 24px;
  bottom: 24px;
  z-index: 1100;
  display: flex;
  align-items: center;
  gap: 11px;
  max-width: 380px;
  padding: 13px 14px 13px 15px;
  background: #FFFFFF;
  border: 1px solid #EFE3D0;
  border-radius: 12px;
  box-shadow: 0 10px 30px rgba(31, 22, 16, 0.16),
              0 2px 6px rgba(31, 22, 16, 0.07);
  animation: po-toast-in 0.22s ease-out;
}
.po-toast.is-leaving {
  opacity: 0;
  transform: translateY(8px);
  transition: opacity 0.25s ease, transform 0.25s ease;
}
.po-toast-check {
  flex: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 26px;
  height: 26px;
  border-radius: 50%;
  background: #F1E2CF;
  color: #A85A1C;
}
/* Delete toast — soft red so it reads as a removal, not a success. */
.po-toast-check--danger { background: #FCE8E8; color: #9C2F2F; }
.po-toast-text {
  font-size: 13.5px;
  color: #3A2E22;
  white-space: nowrap;
}
.po-toast-text strong { color: #1F1610; font-weight: 600; }
.po-toast-link {
  flex: none;
  font-size: 12.5px;
  font-weight: 500;
  color: #6B5E4E;
  text-decoration: none;
  white-space: nowrap;
}
.po-toast-link:hover { color: #8A4A16; text-decoration: underline; }
.po-toast-close {
  flex: none;
  margin-left: 2px;
  border: 0;
  background: transparent;
  color: #B8AC99;
  font-size: 14px;
  line-height: 1;
  cursor: pointer;
  padding: 4px;
  border-radius: 5px;
}
.po-toast-close:hover { color: #6B5E4E; background: #F5EFE3; }
@keyframes po-toast-in {
  from { opacity: 0; transform: translateY(10px); }
  to   { opacity: 1; transform: translateY(0); }
}

/* "Editing draft" pill — shown next to a PO form's H1 when the page was
   opened via /po/{id}/edit, so it's clear you're revising an existing
   draft rather than starting a new PO. Copper-tinted, quiet. */
.edit-badge {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  vertical-align: middle;
  margin-left: 12px;
  padding: 4px 10px;
  border-radius: 999px;
  background: #F1E2CF;
  color: #8A4A16;
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.02em;
  white-space: nowrap;
}
.edit-badge::before {
  content: '';
  width: 6px; height: 6px;
  border-radius: 50%;
  background: #C0712F;
}

/* Tables */
/* 2026-05-22 v3 — Airbnb-tier rebuild. Earlier attempts shipped two
   conflicting thead rules (one principled at 12.5/500/warm-brown,
   one override at 13/600/dark-text on a grey-cream shelf). The
   override won and the page read as admin-panel. This block is now
   the single source of truth; the override below was deleted.
   Canon principle #9:
     Body data: 14px / weight 500 / #1F1610 (almost-black)
     Headers:   12.5px / weight 500 / #8C7B66 muted warm-brown,
                0.02em letter-spacing, hairline #EFE6D2 underline
     Row pad:   17px vertical / 16px horizontal (breathing room)
     Dividers:  1px #F7F1E3 (warm, almost invisible)
     Hover:     #FFFBF4 (subtle warm tint)
   Headers whisper; body speaks. */
table.data {
  width: 100%;
  border-collapse: collapse;
  font-size: 14px;
}
table.data thead { background: transparent; }
table.data th {
  text-align: left;
  padding: 16px 20px 12px;
  font-weight: 500;
  color: #8C7B66;
  font-size: 12.5px;
  text-transform: none;
  letter-spacing: 0.02em;
  white-space: nowrap;
  border-bottom: 1px solid #EFE6D2;
}
/* Sticky table headers (2026-06-05, Josh — NHL.com pattern) — the column
   labels stay pinned to the top of the viewport while the rows scroll
   under them, so you never lose the column mapping on long tables
   (invoices, POs, copper log…). An opaque fill hides the rows passing
   beneath; the divider is drawn with box-shadow rather than the cell's
   own border because border-collapse drops a border once the cell is
   stuck. z-index keeps the header above body rows but below the fixed
   top-corner chrome (hamburger / history-pager live at z 60). */
table.data thead th {
  position: sticky;
  top: 0;
  z-index: 5;
  /* Warm light-orange wash so the header band reads as distinct from the
     white rows scrolling beneath it (Josh, 2026-06-05). Opaque so rows
     don't bleed through; divider warmed to match the tint. */
  background: #FFF4E9;
  box-shadow: 0 1px 0 #F2DCC2;
  color: #8A5A33;
}
table.data td {
  /* 22px vertical / 20px horizontal — Brian Chesky energy.
     The canon's 16-18px was a baseline; Airbnb-tier wants 22+
     so each row reads as a discrete card-like band rather than
     a packed admin row. */
  padding: 22px 20px;
  border-bottom: 1px solid #F7F1E3;
  vertical-align: middle;
  font-size: 14px;
  font-weight: 500;
  color: #1F1610;
}
table.data tbody tr:last-child td { border-bottom: none; }
table.data tbody tr { cursor: pointer; }
table.data tbody tr:hover { background: #FFFBF4; }
table.data .num { text-align: right; }
/* Numeric cells — sans, tabular-nums for column alignment, slightly
   lighter weight so they don't shout. Monthly Summary (.ms) tables
   are dense and have their own size overrides further down. */
table.data tbody td.num {
  font-variant-numeric: tabular-nums;
  font-family: var(--font-sans);
  font-weight: 400;
}
table.data.ms tbody td.num {
  font-size: 13px;
  font-weight: 400;
  font-variant-numeric: tabular-nums;
  font-family: var(--font-sans);
}
table.data.ms tbody td:first-child {
  font-family: var(--font-sans);
  font-size: 13px;
}
table.data .invoice-num { font-weight: 500; }
/* Date cell — secondary info, slightly muted so it supports the
   primary identifier columns (PO Number, Supplier) rather than
   competing with them. Airbnb pattern: hierarchy through color
   modulation, not size. */
table.data td.cell-date {
  /* 2026-05-23 evening: Josh wanted the dates darker (was muted
     #6B5E4E, too faded against the rest of the table). Bumped to
     primary dark #1F1610 so the column reads as confident
     metadata, not background text. tabular-nums kept so the dates
     align in a clean column on the right. */
  color: #1F1610;
  font-weight: 400;
  font-variant-numeric: tabular-nums;
  /* A date split across two lines ("Jun 8," / "2026") reads broken at
     any width — the column owns its width instead. (2026-06-09) */
  white-space: nowrap;
}
/* Month divider bar — light orange instead of grey so the breaks
   between months are easy to see at a glance when the table is
   long. Same palette as the Uploaded / supplier-orange pills. */
table.data tr.month-row {
  background: #FFF1E5;
  border-top: 1px solid #F5D7B8;
  border-bottom: 1px solid #F5D7B8;
}
table.data tr.month-row td {
  padding: 6px 14px;
  font-size: 10.5px;
  font-weight: 700;
  color: #8A4A16;
  text-transform: uppercase;
  letter-spacing: 0.6px;
}

/* Status indicators — text + dot, not pills */
.tag {
  display: inline-flex; align-items: center; gap: 5px;
  font-size: 11px; color: var(--text-mute); padding: 0;
  background: none;
}
.tag::before {
  content: ""; width: 6px; height: 6px; border-radius: 50%;
  background: var(--text-soft); display: inline-block;
}
.tag.high::before, .tag.approved::before { background: var(--good); }
.tag.medium::before, .tag.pending::before { background: var(--warn); }
.tag.low::before, .tag.flag::before, .tag.rejected::before { background: var(--bad); }
/* 2026-05-22 — `.no-dot` suppresses the leading status dot entirely.
   Used on the "Uploaded" tag in the Purchase Orders table where the
   neutral grey dot read as a leftover artifact rather than a status
   indicator. */
.tag.no-dot::before { display: none; }
.tag.no-dot { gap: 0; }
.conf { display: inline-block; width: 6px; height: 6px; border-radius: 50%; background: var(--text-soft); }
.conf.high { background: var(--good); } .conf.medium { background: var(--warn); }
.conf.low, .conf.flag { background: var(--bad); }

/* Settlement-chain badges (inbox + purchase log). Added 2026-05-13.
   Forward = this Adjustment settles N Provisionals.
   Reverse = this Provisional is settled by M Adjustments. */
.chain-badge {
  display: inline-block; padding: 2px 6px; margin-right: 4px;
  border-radius: var(--radius); font-size: 11px; font-weight: 600;
  border: 1px solid var(--border-2); white-space: nowrap;
}
.chain-badge.forward { color: var(--accent); border-color: var(--accent); }
.chain-badge.reverse { color: var(--text-mute); border-color: var(--border-2); }

/* Chips / year picker — minimal */
.chips { display: flex; gap: 4px; margin-bottom: 14px; flex-wrap: wrap; }
.chip {
  padding: 4px 10px; border-radius: var(--radius);
  border: 1px solid var(--border-2);
  font-size: 11.5px; color: var(--text-mute); cursor: pointer;
  background: var(--surface);
}
.chip:hover { border-color: var(--text); color: var(--text); text-decoration: none; }
.chip.active { background: var(--accent); color: white; border-color: var(--accent); }
.year-picker { display: flex; gap: 3px; }
.year-picker .chip { padding: 3px 9px; font-size: 11.5px; }

/* Buttons */
.btn {
  padding: 6px 12px; border-radius: var(--radius); font-size: 12px;
  border: 1px solid var(--border-2); background: var(--surface);
  color: var(--text); cursor: pointer; font-family: inherit;
  /* Keep button labels on a single line — the "← Back to list" link
     in narrow .page-header .meta cells was wrapping into 3 lines
     ("← Back to" / "list") when the parent flex item shrunk. */
  white-space: nowrap;
}
.btn:hover { border-color: var(--text); }
.btn.primary { background: var(--accent); color: white; border-color: var(--accent); }
.btn.primary:hover { background: #000; border-color: #000; }
.btn.danger { background: var(--surface); color: var(--bad); border-color: var(--bad); }

/* In-row action buttons (2026-05-22) — refined text-style links
   for use inside table cells. The standard .btn is too loud when
   stacked three-wide in every row; .btn-text is borderless until
   hover, when it picks up a soft warm tint. Used for the
   View / Download / Delete column on the Purchase Orders page. */
.btn-text {
  display: inline-block;
  padding: 5px 10px;
  margin: 0 1px;
  font-size: 12px;
  font-weight: 500;
  color: #5A4A38;
  background: transparent;
  border: 1px solid transparent;
  border-radius: 6px;
  text-decoration: none;
  cursor: pointer;
  font-family: inherit;
  white-space: nowrap;
  transition: background 0.12s, color 0.12s;
}
.btn-text:hover {
  background: #FFF1E5;
  color: #8A4A16;
  /* Re-assert over the global `a:hover { text-decoration: underline }`,
     which out-specifies .btn-text's `none` on anchor-based actions
     (Edit / Download) — button-based ones were already immune. */
  text-decoration: none;
}
.btn-text.danger {
  color: #9C2F2F;
}
.btn-text.danger:hover {
  background: #FCE8E8;
  color: #7A1414;
}
/* Detail sidecar delete — a quiet, self-sized outlined danger action so it
   reads as a deliberate button rather than a stray link in an empty card. */
.doc-delete-btn {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 4px 9px;
  font-size: 12px;
  border-color: rgba(156, 47, 47, 0.28);
}
.doc-delete-btn svg { flex: none; width: 13px; height: 13px; }

/* Actions column — right-aligned (2026-06-09). The trio of inline
   buttons became a single "⋯" kebab (see .row-actions), so the column
   now hugs the right edge the way action menus conventionally do, with
   the popover anchored under it. Was centered when it held three buttons. */
table.data th.actions-col,
table.data td.actions-col { text-align: right; }

/* 2026-05-22 — Service label (PO Library Service column).
   Replaces the previous .svc-tag pill. Pills were overused; the
   Service column is metadata and only needs to read as a quiet
   category, not a badge. Typography does the work: uppercase,
   tracked, warm-brown muted. Same vocabulary Linear/Stripe use
   for tags-as-text-only. */
/* 2026-05-22 v4 — Service column label. Was uppercase tracked
   warm-brown (badge-y, even after the .svc-tag pill retirement).
   Now matches the body-text vocabulary: natural case, 14px, the
   same near-black as the supplier column but weight 400 so the
   eye registers it as supporting metadata, not a primary
   identifier. Reads as one of the row, not a label-on-data. */
.svc-label {
  font-size: 14px;
  font-weight: 400;
  color: #1F1610;
  letter-spacing: 0;
  text-transform: none;
}

/* Material column — same vocabulary as the Service column above.
   Plain text in primary-dark, no chip. The broad category
   (Copper / Nylon / Reels) is metadata supporting the row,
   not a primary identifier — typography carries the signal.
   Added 2026-05-23 alongside the Material column in po_list.html. */
.mat-label {
  font-size: 14px;
  font-weight: 400;
  color: #1F1610;
  letter-spacing: 0;
  text-transform: none;
}

/* Ship-To column — same plain-text vocabulary as Material/Service.
   The destination (Sark / IWG / Owl / Nexans / PTI) is a supporting
   attribute of the order, not a primary identifier or status, so it
   reads as typography, no chip. Added 2026-06-10 with the Ship-To
   column in po_list.html. */
.ship-to-label {
  font-size: 14px;
  font-weight: 400;
  color: #1F1610;
  letter-spacing: 0;
  text-transform: none;
}

/* Invoice Type badge (2026-06-03) — Provisional vs Final (= Adjustment).
   The distinction matters because a Final invoice re-prices an already-
   delivered load, so its weight must not be counted as new copper. */
.inv-type {
  display: inline-block;
  font-size: 11.5px;
  font-weight: 600;
  letter-spacing: 0.02em;
  padding: 2px 9px;
  border-radius: 999px;
  white-space: nowrap;
  background: #F3EFE7;
  color: #6B5E4E;
}
.inv-type.prov {
  background: #EAF3ED;
  color: #1A5A30;
}
.inv-type.final {
  background: #FCE9D8;
  color: #8A4A16;
}
.inv-type.tolling {
  background: #ECEEF4;
  color: #3B4663;
}

/* ─── Upload page redesign (2026-05-22) ─────────────────────────
   The dropzone is now the page's hero. Numbered instructions and
   the surrounding card wrapper retired — the affordance IS the
   instruction. Document-type override demoted to a chip in the
   top-right of the dropzone (default = Auto-detect for the 99% case).
   Visual language: warm cream gradient idle, brand-orange dragover,
   88px circular icon centerpiece, friendlier copy.
   References: Airbnb upload, Instagram new-post, Linear file picker. */

.upload-page {
  /* 2026-05-22 — bumped from 780 → 1080. At 780 the empty state
     felt intimate but the file-staged state wasted huge horizontal
     space on the right when 20+ files were queued. 1080 lets the
     file list breathe while still keeping the layout contained. */
  /* Left-aligned (not margin:0 auto) so the page title sits at the same
     content-left edge as every other tab — Josh's "consistent title
     placement" pass, 2026-06-23. The dropzone keeps its 1080px cap. */
  max-width: 1080px;
}
/* Upload uses a bespoke hero (H1 + routing chip on one row) rather than
   .page-header, so its title is sized/placed to match the canonical page
   title here: same 32px, and no top offset so it starts at the content's
   28px top like every other tab. 2026-06-23. */
.upload-hero { margin: 0 0 28px 0; }
.upload-hero h1 {
  font-size: 32px;
  font-weight: 600;
  letter-spacing: -0.025em;
  color: #1F1610;
  margin: 0;
  line-height: 1.1;
}
.upload-subtitle {
  margin: 10px 0 0 0;
  font-size: 14.5px;
  color: #6B5E4E;
  line-height: 1.55;
  max-width: 560px;
}
.upload-subtitle-link {
  color: #8A4A16;
  text-decoration: none;
  font-weight: 500;
  border-bottom: 1px dotted #E0A878;
}
.upload-subtitle-link:hover {
  border-bottom-style: solid;
  color: #6B2B07;
}

/* Hero row — the subtitle (left) and routing override (right) on
   the same baseline. 2026-05-22 — gives the dropdown a visual
   anchor: it reads as "the subtitle states the default; the chip
   lets you override it." */
.upload-hero-row {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 24px;
  margin-top: 0;
}
.upload-hero-row .upload-subtitle { margin: 0; }
.upload-hero-row h1 { flex: 0 1 auto; }

/* Routing override — "Route as [Detect automatically ▾]". Custom dropdown
   (button + popover) so the open list is themed; a native <select>'s list is
   OS-rendered (serif + blue highlight). Re-introduced 2026-06-18 so the user
   can force a destination (esp. Contract) when auto-detection can't tell. */
.route-select {
  position: relative;
  display: inline-flex;
  align-items: center;
  flex: 0 0 auto;
}
.route-select-btn {
  display: inline-flex;
  align-items: center;
  box-sizing: border-box;
  background: #FBF6F0;
  border: 1px solid #E7DACB;
  border-radius: 9px;
  padding: 8px 12px 8px 14px;
  font: inherit;
  cursor: pointer;
  transition: border-color .12s ease, background-color .12s ease, box-shadow .12s ease;
}
.route-select-btn:hover {
  border-color: #D9B38F;
  background: #FFFFFF;
}
/* "Route as" folds into the control as a muted prefix, set off from the
   chosen value by a hairline divider so the whole pill reads as one
   labelled field rather than a stray label beside a button. */
.route-select-prefix {
  font-size: 13px;
  font-weight: 500;
  color: #1F1610;
  white-space: nowrap;
}
.route-select-sep {
  width: 1px;
  height: 15px;
  margin: 0 11px;
  flex: 0 0 auto;
  background: #E7DACB;
}
.route-select-value {
  /* Column width is set in JS to the exact width of the longest option, so the
     pill keeps one size as the choice changes while the longest value still
     sits flush (no floating/centred look). This is the no-JS fallback. */
  min-width: 96px;
  font-size: 13px;
  font-weight: 500;
  color: #1F1610;
  text-align: left;
  white-space: nowrap;
}
.route-select.is-open .route-select-btn,
.route-select-btn:focus-visible {
  outline: none;
  border-color: #C0712F;
  box-shadow: 0 0 0 3px rgba(192, 113, 47, 0.13);
}
.route-select-caret {
  color: #A8744A;
  flex: 0 0 auto;
  margin-left: auto;
  padding-left: 12px;
  transition: transform .15s ease;
}
.route-select.is-open .route-select-caret { transform: rotate(180deg); }

/* Open menu — matches the breadcrumb order-type switcher (_po_crumb_switcher):
   a clean CREAM panel with dark option rows and the current row lifted in
   light orange (no check mark). Kept in sync with .crumb-switch-menu /
   .crumb-switch-item. Right-aligned since the control sits at the page edge. */
.route-select-menu {
  position: absolute;
  top: calc(100% + 6px);
  right: 0;
  z-index: 40;
  min-width: 188px;
  margin: 0;
  padding: 6px;
  list-style: none;
  background: #FFFCF7;
  border: 1px solid #ECE3D5;
  border-radius: 14px;
  box-shadow: 0 12px 32px rgba(31, 22, 16, 0.14);
}
.route-select-opt {
  display: block;
  padding: 8px 11px;
  border-radius: 9px;
  font-size: 13px;
  line-height: 1.2;
  font-weight: 400;
  color: var(--text);
  white-space: nowrap;
  cursor: pointer;
  transition: background .1s ease, color .1s ease;
}
.route-select-opt:hover { background: #FFF3E6; color: var(--text); }
/* Current selection = the highlighted row: light-orange fill + copper text,
   exactly like the breadcrumb switcher's current row. */
.route-select-opt.is-selected {
  background: #FFF0E0;
  color: #8A4A16;
  font-weight: 600;
}
.route-select-opt.is-selected:hover {
  background: #FFE6CF;
  color: #8A4A16;
}

/* Dropzone — base shape overridden from the earlier admin-y design.
   The earlier .drop-zone rules higher up still apply; these later
   rules win the cascade for the upload-page surface. */
.upload-form .drop-zone {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 14px;
  padding: 64px 32px;
  min-height: 340px;
  background: linear-gradient(180deg, #FFFBF6 0%, #FFFFFF 100%);
  border: 1.5px solid #E5D5BA;
  border-radius: 16px;
  cursor: pointer;
  transition: background 0.18s ease, border-color 0.18s ease,
              box-shadow 0.18s ease, transform 0.18s ease;
}
.upload-form .drop-zone:hover {
  border-color: #C0712F;
  background: linear-gradient(180deg, #FFF7EC 0%, #FFFCF7 100%);
}
.upload-form .drop-zone.drag-active {
  border-color: #C0712F;
  border-style: solid;
  background: linear-gradient(180deg, #FFF1E5 0%, #FFE6CF 100%);
  box-shadow: 0 0 0 4px rgba(233, 123, 47, 0.12);
  transform: scale(1.004);
}
.upload-form .drop-zone.has-file {
  cursor: default;
  padding: 22px 22px 26px;
  min-height: 0;
  background: #FFFFFF;
  border-style: solid;
  border-color: #ECE3D5;
  align-items: stretch;
  justify-content: flex-start;
}

/* dz-options — the Auto-detect chip wrapper, now used inside the
   .upload-options-bar above the dropzone. (Previously: corner chip
   in dropzone → dz-header row → moved out 2026-05-22.) */
.dz-options {
  display: inline-flex;
  align-items: center;
  gap: 8px;
}
.dz-options-label {
  font-size: 10px;
  font-weight: 700;
  color: #8C7B66;
  letter-spacing: 0.6px;
  text-transform: uppercase;
}
/* 2026-05-22 — refined the dropdown: more substantial padding,
   warmer cream fill (FFFBF6 instead of stark white), bolder chevron,
   slightly bigger type. Now reads as a confident configuration
   control rather than a generic filter chip. */
.dz-options-select {
  appearance: none;
  -webkit-appearance: none;
  -moz-appearance: none;
  font-family: inherit;
  font-size: 13px;
  font-weight: 500;
  color: #3D2817;
  background-color: #FFFBF6;
  border: 1px solid #E5DCC9;
  border-radius: 8px;
  padding: 8px 34px 8px 14px;
  cursor: pointer;
  min-width: 170px;
  letter-spacing: -0.005em;
  background-image:
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8'%3E%3Cpath fill='none' stroke='%23E97B2F' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round' d='M1 1.5l5 5 5-5'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 12px center;
  background-size: 12px 8px;
  transition: border-color 0.14s ease, background-color 0.14s ease,
              color 0.14s ease, box-shadow 0.14s ease;
}
.dz-options-select:hover {
  border-color: #C0712F;
  background-color: #FFF7EC;
  color: #8A4A16;
}
.dz-options-select:focus {
  outline: none;
  border-color: #C0712F;
  box-shadow: 0 0 0 3px rgba(233, 123, 47, 0.16);
}

/* Empty-state prompt — 88px circular icon as the visual anchor,
   bigger headline, friendlier subline with an inline "browse" link. */
.upload-form .drop-zone .dz-prompt {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 10px;
  pointer-events: none;   /* clicks pass through to the parent <label> */
}
.upload-form .drop-zone .dz-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 88px;
  height: 88px;
  background: #FFF1E5;
  border-radius: 50%;
  color: #C0712F;
  margin-bottom: 6px;
  transition: transform 0.18s ease, background 0.18s ease;
}
.upload-form .drop-zone:hover .dz-icon {
  transform: translateY(-2px);
  background: #FFE6CF;
}
.upload-form .drop-zone.drag-active .dz-icon {
  transform: translateY(-4px) scale(1.04);
  background: #FFD6AC;
}
.upload-form .drop-zone .dz-headline {
  font-size: 20px;
  font-weight: 600;
  color: #1F1610;
  letter-spacing: -0.015em;
  margin: 0;
  text-align: center;
}
.upload-form .drop-zone.drag-active .dz-headline { color: #8A4A16; }
.upload-form .drop-zone .dz-subline {
  font-size: 14px;
  color: #6B5E4E;
  text-align: center;
}
.upload-form .drop-zone .dz-browse-link {
  /* Re-enable pointer events on this one element so the inline
     "browse" reads as a tappable link, while the rest of the prompt
     stays click-through to the parent <label>. */
  pointer-events: auto;
  color: #8A4A16;
  font-weight: 600;
  border-bottom: 1.5px solid #E0A878;
  cursor: pointer;
  transition: color 0.12s, border-color 0.12s;
}
.upload-form .drop-zone .dz-browse-link:hover {
  color: #6B2B07;
  border-bottom-color: #8A4A16;
}
.upload-form .drop-zone .dz-hint {
  font-size: 12px;
  color: #8C7B66;
  margin-top: 4px;
  letter-spacing: 0.1px;
}

/* Action bar — sits below the dropzone without a wrapper card.
   Picks up the same gradient-lift primary button language as the
   PO Library page header. */
.upload-form .action-bar {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-top: 20px;
  padding: 0;
}
/* 2026-05-22 — softened the primary CTA from the heavy orange
   gradient to a calm pill: soft warm background, deep orange
   text, no shadows, no gradient. Same visual language as the
   Delete-on-hover pill from the PO Library table actions. Josh:
   "calm and casual, clean … easy simple beautiful". The action's
   importance now comes from position + color + typography, not
   from chrome. */
.upload-form .action-bar .btn.primary {
  background: #FFE6CF;
  border: none;
  color: #8A4A16;
  font-weight: 600;
  padding: 8px 18px;
  font-size: 13.5px;
  border-radius: 8px;
  box-shadow: none;
  min-width: 130px;
  text-align: center;
  transition: background 0.14s ease, color 0.14s ease;
}
.upload-form .action-bar .btn.primary:hover {
  background: #FFD6AC;
  color: #6B2B07;
  box-shadow: none;
}
.upload-form .action-bar .btn.primary:active {
  background: #F5C394;
  transform: none;
}

/* File-staged state — restyle of the existing .dz-chip / .dz-filelist
   from pill-style to clean rows. The earlier .drop-zone .dz-chip
   rules higher up in this file still apply; these later rules win
   for the upload-page surface. (2026-05-22.) */
.upload-form .drop-zone .dz-filelist {
  /* 2026-05-22 — CSS grid (was single-column flex) so the file list
     fills horizontal space when many files are queued. Each tile
     wraps onto the next column at ~300px minimum, giving 2–3
     columns at the 1080px container width. Borders dropped in
     favor of subtle hover tints because rectangular borders look
     awkward when tiles wrap mid-row. */
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
  gap: 2px 8px;
  margin: 0;
  padding: 0;
  width: 100%;
}
.upload-form .drop-zone .dz-chip {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 9px 10px;
  background: transparent;
  border: none;
  border-radius: 8px;
  box-shadow: none;
  font-family: inherit;
  font-size: 13.5px;
  font-weight: 500;
  color: #1F1610;
  transition: background 0.12s ease;
  /* width:100% + justify-self:stretch makes each tile fill its
     grid cell horizontally. Without this the tile sized to its
     content and grid centered it in the cell — icons in column 1
     wouldn't line up with column 2 etc. min-width:0 lets the
     filename ellipsize without forcing the tile wider than the
     cell. (Fixed 2026-05-22.) */
  width: 100%;
  min-width: 0;
  justify-self: stretch;
}
.upload-form .drop-zone .dz-chip:hover {
  background: #FFFBF6;
}
.upload-form .drop-zone .dz-chip-icon {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 28px;
  height: 28px;
  color: #C0712F;
  background: #FFF1E5;
  border-radius: 6px;
}
.upload-form .drop-zone .dz-chip-name {
  flex: 1 1 auto;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  /* 2026-05-22 — switched from mono → sans. Mono made the file
     list read as a directory listing/log output instead of a
     polite list of attachments. Real products (Linear, Stripe,
     Airbnb) use sans for filenames. */
  font-family: inherit;
  font-size: 13.5px;
  font-weight: 500;
  color: #1F1610;
  letter-spacing: -0.005em;
}
.upload-form .drop-zone .dz-chip-size {
  flex: 0 0 auto;
  font-family: inherit;
  font-size: 12px;
  color: #8C7B66;
  font-weight: 400;
  margin-left: 4px;
}
.upload-form .drop-zone .dz-chip-remove {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 24px;
  height: 24px;
  padding: 0;
  border: none;
  background: transparent;
  border-radius: 6px;
  color: #B8AEA0;
  font-size: 18px;
  line-height: 1;
  cursor: pointer;
  transition: background 0.12s, color 0.12s;
}
.upload-form .drop-zone .dz-chip-remove:hover {
  background: #FCE8E8;
  color: #9C2F2F;
}

/* "+ Add more files" — sits at the bottom-right of the dropzone
   when files are queued, neighbour to the Upload & save button
   in the sticky action bar below. align-self overrides the
   dropzone's align-items so this button right-aligns instead of
   stretching. JS toggles display to inline-flex. 2026-05-22. */
.dz-addmore-link {
  display: none;        /* JS sets inline-flex when files queued */
  align-self: flex-end;
  align-items: center;
  gap: 6px;
  margin-top: 12px;
  padding: 5px 10px;
  font-family: inherit;
  font-size: 13px;
  font-weight: 500;
  color: #8A4A16;
  background: transparent;
  border: none;
  cursor: pointer;
  border-radius: 6px;
  transition: color 0.12s, background 0.12s;
}
.dz-addmore-link:hover {
  color: #6B2B07;
  background: #FFF7EC;
}
.dz-addmore-link svg { color: currentColor; }

/* "Clear queue" — permanent soft-pink pill, matching the
   dimensions and visual weight of Upload & save next to it.
   2026-05-22: was a text link with pill-on-hover; Josh wanted
   both buttons same size + Clear queue to carry its tint at
   rest. The soft pink signals "destructive" through palette
   alone, no hover-state required. */
.dz-clear-link {
  display: inline-block;
  padding: 8px 18px;
  font-family: inherit;
  font-size: 13.5px;
  font-weight: 600;
  color: #9C2F2F;
  background: #FCE8E8;
  border: none;
  border-radius: 8px;
  cursor: pointer;
  text-decoration: none;
  min-width: 130px;
  text-align: center;
  transition: background 0.14s ease, color 0.14s ease;
}
.dz-clear-link:hover {
  background: #F5C2C2;
  color: #7A1414;
}
.dz-clear-link:active {
  background: #E89A9A;
}

/* ─── Post-upload result panel (2026-05-22 redesign) ───────────
   Replaces the .detail-card + "RESULT" microlabel + K/V dump.
   Friendly hero header with status icon, tinted stat chips,
   typography-first table, soft-pill action buttons.
   Same 1080px max-width as .upload-page so the result feels
   like a continuation of the same page when it replaces the
   pre-upload form. */
.upload-result {
  max-width: 1080px;
  padding: 0;
  background: transparent;
  border: none;
}

.upload-result-header {
  display: flex;
  align-items: center;
  gap: 14px;
  margin-bottom: 16px;
}
.upload-result-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 44px;
  height: 44px;
  border-radius: 50%;
  flex: 0 0 auto;
}
/* Per-status palettes — soft tinted circle, deeper-color icon. */
.upload-result-icon.success { background: #E2F2E7; color: #1D7A3D; }
.upload-result-icon.error   { background: #FCE8E8; color: #9C2F2F; }
.upload-result-icon.running { background: #FFE6CF; color: #8A4A16; }
.upload-result-icon.stopped { background: #F5EFE3; color: #8C7B66; }
/* While a batch is running the icon is a circular refresh arrow that
   spins — a live "still working" signal, never a checkmark. */
.upload-result-icon.running svg {
  animation: ingest-spin 1s linear infinite;
  transform-origin: 50% 50%;
}

.upload-result-text {
  display: flex;
  flex-direction: column;
  gap: 1px;
}
.upload-result-title {
  font-size: 22px;
  font-weight: 600;
  letter-spacing: -0.02em;
  color: #1F1610;
  line-height: 1.15;
}
.upload-result-subtitle {
  font-size: 14px;
  color: #6B5E4E;
}

.upload-result-stats {
  display: flex;
  flex-wrap: wrap;
  gap: 16px 22px;
  margin-bottom: 22px;
  padding: 14px 0;
  border-top: 1px solid #F5EFE3;
  border-bottom: 1px solid #F5EFE3;
}
.upload-stat {
  font-size: 13.5px;
  color: #8C7B66;
  letter-spacing: -0.005em;
}
.upload-stat strong {
  color: #6B5E4E;
  font-weight: 600;
  margin-right: 4px;
  font-size: 14px;
}
.upload-stat.good strong   { color: #1D7A3D; }
.upload-stat.medium strong { color: #B85C00; }
.upload-stat.bad strong    { color: #B91C1C; }

/* Result sections — Invoices, Purchase Orders, Errors. Each has
   its own table with column structure that fits its content,
   rendered only when it has rows. 2026-05-22 — replaces the single
   mixed table where the "Invoice" header was wrong for PO rows. */
.upload-result-section {
  margin-bottom: 28px;
}
.upload-result-section:last-of-type {
  margin-bottom: 24px;
}
.upload-result-section-title {
  font-size: 17px;
  font-weight: 600;
  color: #1F1610;
  letter-spacing: -0.018em;
  margin: 0 0 14px 0;
  line-height: 1.2;
}
.upload-result-section-count {
  font-weight: 400;
  font-size: 14px;
  color: #B8AEA0;
  margin-left: 8px;
  letter-spacing: 0;
}

/* Result table — typography-first headers, same vocabulary as
   the PO Library table redesign. Plain rows, warm divider, hover
   tint. 2026-05-22 polish — header weight bumped (550 → confident
   but not loud), body text bumped to 14px for legibility,
   filename column rendered in sans (was mono → felt like a
   directory listing). Invoice numbers stay mono because they're
   IDs. */
.upload-result-table {
  width: 100%;
  border-collapse: collapse;
  margin-bottom: 8px;
}
.upload-result-table thead { background: transparent; }
.upload-result-table thead th {
  /* 2026-05-22 polish — headers used to compete with the body text
     (same dark bold) with a loud brand-orange underline below them.
     Now muted warm-brown, lighter weight, with a hairline soft
     divider. Same vocabulary Linear/Notion/Airbnb use: headers
     SUPPORT the data, they don't shout above it. */
  text-align: left;
  padding: 12px 16px 10px;
  font-weight: 500;
  font-size: 12.5px;
  color: #8C7B66;
  letter-spacing: 0.02em;
  text-transform: none;
  border-bottom: 1px solid #EFE6D2;
}
.upload-result-table tbody td {
  /* Generous padding — Airbnb-tier breathing room. Was 14px,
     now 18px vertical / 16px horizontal. The whole table reads
     more like a "list of moments" and less like a CSV. */
  padding: 18px 16px;
  font-size: 14px;
  color: #1F1610;
  border-bottom: 1px solid #F7F1E3;
  vertical-align: middle;
}
.upload-result-table tbody tr {
  transition: background 0.12s ease;
}
.upload-result-table tbody tr:hover { background: #FFFBF4; }
.upload-result-table tbody tr:last-child td { border-bottom: none; }
/* Cell classes — each column gets its own treatment so the row
   reads as a coherent line of information rather than a mix of
   fonts/sizes. (2026-05-22.) */

/* Filename — scoped to the upload result table (the .dz- prefix
   means it's queue-related). */
.upload-result-table .dz-file-name-cell {
  font-family: inherit;
  font-size: 14px;
  font-weight: 500;
  color: #1F1610;
  letter-spacing: -0.005em;
}

/* 2026-05-22 — `.cell-invoice` + `.cell-method` generalized (were
   scoped to .upload-result-table only). Reusable across the site
   for the typography-only "this text is the tap target" treatment
   that replaced the teal invoice-pill on the PO Library. Brand-
   orange text + dotted underline (solid on hover). One click
   affordance pattern shared across Upload result + PO Library. */
.cell-invoice,
.cell-method {
  font-family: inherit;
  font-size: 14px;
  font-weight: 500;
  color: #1F1610;
  letter-spacing: -0.005em;
}
.cell-invoice {
  color: #8A4A16;
  text-decoration: none;
  border-bottom: 1px dotted #E0A878;
}
.cell-invoice:hover {
  border-bottom-style: solid;
}
/* 2026-06-29 — Inside the Library data tables (PO list, Invoices,
   Credits, Activity) drop the dotted-underline affordance and match
   Open Orders' `.oo-po`: plain brand-orange text, underline on hover
   only. Scoped to `table.data` so the Upload result table
   (`.upload-result-table`) keeps the shared dotted treatment. */
table.data .cell-invoice {
  border-bottom: 0;
}
table.data .cell-invoice:hover {
  border-bottom: 0;
  text-decoration: none;
}

/* Type — secondary classification, slightly muted. */
.upload-result-table .cell-type {
  font-size: 14px;
  color: #6B5E4E;
  font-weight: 400;
}

/* Outcome — was a dot+text pill. 2026-05-22: dot dropped, the
   colored word itself carries the signal. */
.upload-result-table .tag {
  display: inline;
  font-size: 14px;
  font-weight: 500;
  padding: 0;
  background: none;
  letter-spacing: -0.005em;
}
.upload-result-table .tag::before { display: none; }
.upload-result-table .tag.high,
.upload-result-table .tag.approved { color: #1D7A3D; }
.upload-result-table .tag.medium,
.upload-result-table .tag.pending  { color: #B85C00; }
.upload-result-table .tag.low,
.upload-result-table .tag.flag,
.upload-result-table .tag.rejected { color: #B91C1C; }

/* Confidence — text-only, color by level. Replaces the
   floating colored dot. */
.upload-result-table .conf-text {
  font-size: 14px;
  font-weight: 500;
  letter-spacing: -0.005em;
}
.upload-result-table .conf-text.high   { color: #1D7A3D; }
.upload-result-table .conf-text.medium { color: #B85C00; }
.upload-result-table .conf-text.low,
.upload-result-table .conf-text.flag   { color: #B91C1C; }

/* Legacy classes still in use elsewhere in this table (mono on
   error rows, small on inline labels). */
.upload-result-table .mono { font-size: 13.5px; }
.upload-result-table .small { font-size: 13px; color: #6B5E4E; }

/* Result action buttons — same soft-pill vocabulary as the
   Clear queue + Upload & save pair. "Upload another file" sits
   on the left (back-to-start action), View accuracy + Go to
   Pending on the right (forward navigation). Spacer separates. */
.upload-result-actions {
  display: flex;
  align-items: center;
  gap: 10px;
}
.upload-result-actions-spacer { flex: 1; }
.upload-result-btn {
  display: inline-block;
  padding: 8px 18px;
  font-family: inherit;
  font-size: 13.5px;
  font-weight: 600;
  color: #3D2817;
  background: #FFFBF6;
  border: 1px solid #E5DCC9;
  border-radius: 8px;
  cursor: pointer;
  text-decoration: none;
  min-width: 130px;
  text-align: center;
  transition: background 0.14s ease, color 0.14s ease,
              border-color 0.14s ease;
}
.upload-result-btn:hover {
  background: #FFF7EC;
  border-color: #C0712F;
  color: #8A4A16;
}
.upload-result-btn.primary {
  background: #FFE6CF;
  border-color: transparent;
  color: #8A4A16;
}
.upload-result-btn.primary:hover {
  background: #FFD6AC;
  border-color: transparent;
  color: #6B2B07;
}

/* Action bar — just two buttons. No chrome (was a styled "bar"
   with background tint, border, shadow, backdrop-blur; Josh
   2026-05-22 — "why does it need to be a bar?"). Right-aligned
   below the dropzone, the gradient on Upload & save and the
   typography of Clear queue carry their own weight. */
.upload-form .action-bar.upload-action-bar {
  display: flex;
  align-items: center;
  gap: 14px;
  margin-top: 14px;
  padding: 0;
  background: transparent;
  border: none;
  box-shadow: none;
}
.upload-action-spacer { flex: 1; }

/* ─── Page-header action cluster (2026-05-22) ───────────────────
   Tighter, calmer presentation for the top-right button row on
   /po and similar pages. The previous treatment crammed 4–5 same-
   weight buttons in a line; now we keep just the two primary
   actions visible (Upload PO + New PO) and tuck maintenance
   actions behind a kebab "⋯" menu. */
.page-header-actions {
  display: flex;
  gap: 8px;
  align-items: center;
}
.page-header-actions .btn {
  padding: 8px 16px;
  font-size: 13px;
  font-weight: 550;            /* between medium (500) and semibold (600) */
  letter-spacing: -0.005em;    /* slight optical tightening at this size */
  border-radius: 8px;
  border-color: #E5DCC9;
  color: #3D2817;
  background: #FFFFFF;
  /* Soft inner highlight + crisp baseline shadow — gives the secondary
     button a hint of depth without competing with the primary. */
  box-shadow: 0 1px 0 rgba(61, 40, 23, 0.04),
              inset 0 1px 0 rgba(255, 255, 255, 0.9);
  transition: background 0.14s ease, border-color 0.14s ease,
              color 0.14s ease, box-shadow 0.14s ease,
              transform 0.06s ease;
}
.page-header-actions .btn:hover {
  background: #FFF7EC;
  border-color: #C0712F;
  color: #8A4A16;
  box-shadow: 0 2px 4px rgba(233, 123, 47, 0.10),
              inset 0 1px 0 rgba(255, 255, 255, 0.9);
}
.page-header-actions .btn:active {
  transform: translateY(0.5px);
  box-shadow: 0 0 0 rgba(0,0,0,0);
}
.page-header-actions .btn.primary {
  background: linear-gradient(180deg, #C77F3C 0%, #C0712F 100%);
  border-color: #AC5514;
  color: #FFFFFF;
  font-weight: 600;
  /* Lift-shadow + inner highlight — the primary now reads as a
     deliberate hero CTA rather than a flat colored rectangle. */
  box-shadow: 0 1px 2px rgba(201, 82, 10, 0.30),
              0 4px 10px rgba(233, 123, 47, 0.22),
              inset 0 1px 0 rgba(255, 255, 255, 0.20);
}
.page-header-actions .btn.primary:hover {
  background: linear-gradient(180deg, #C0712F 0%, #BC6516 100%);
  border-color: #B04708;
  color: #FFFFFF;
  box-shadow: 0 2px 4px rgba(176, 71, 8, 0.32),
              0 6px 14px rgba(233, 123, 47, 0.30),
              inset 0 1px 0 rgba(255, 255, 255, 0.25);
}
.page-header-actions .btn.primary:active {
  background: #AC5514;
  box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.15);
}

/* Leading icon inside a header button. The svg uses stroke:currentColor
   so it picks up the button's text color (warm brown on secondary,
   white on primary) and shifts with the hover state for free. */
.btn-with-icon {
  display: inline-flex;
  align-items: center;
  gap: 7px;
}
.btn-icon-lead {
  display: inline-block;
  flex: 0 0 auto;
  /* Slight optical lift — SVG strokes look heavy when centered on the
     text baseline; nudging up half a pixel feels more balanced. */
  margin-top: -1px;
}

/* Kebab "⋯" overflow menu — uses native <details> so open/close
   needs no JS. The <summary> is the kebab pill; the popover sits
   absolutely under it, right-aligned to the page header. */
.admin-menu { position: relative; }
.admin-menu summary {
  list-style: none;            /* hide default disclosure triangle */
  cursor: pointer;
  padding: 7px 12px;
  font-size: 16px;
  line-height: 1;
  letter-spacing: 1px;
  color: #5A4A38;
}
.admin-menu summary::-webkit-details-marker { display: none; }
.admin-menu[open] summary {
  background: #FFF1E5;
  border-color: #F5D7B8;
  color: #8A4A16;
}
.admin-menu-list {
  position: absolute;
  top: calc(100% + 6px);
  right: 0;
  min-width: 280px;
  padding: 6px;
  background: #FFFFFF;
  border: 1px solid #ECE3D5;
  border-radius: 10px;
  box-shadow: 0 8px 24px rgba(61, 40, 23, 0.10),
              0 2px 6px rgba(61, 40, 23, 0.06);
  z-index: 50;
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.admin-menu-item {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  padding: 9px 11px;
  border: none;
  background: transparent;
  border-radius: 7px;
  cursor: pointer;
  text-align: left;
  font-family: inherit;
  color: #3D2817;
  transition: background 0.10s;
}
.admin-menu-item:hover { background: #FFF7EC; }
.admin-menu-icon {
  flex: 0 0 auto;
  width: 22px;
  height: 22px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: 50%;
  background: #FFF1E5;
  color: #8A4A16;
  font-size: 12px;
  margin-top: 1px;
}
.admin-menu-text {
  display: flex;
  flex-direction: column;
  gap: 1px;
}
.admin-menu-text strong {
  font-size: 12.5px;
  font-weight: 600;
  color: #3D2817;
}
.admin-menu-sub {
  font-size: 11px;
  color: #8C7B66;
  font-weight: 400;
}

/* Detail view */
.detail-grid {
  display: grid;
  grid-template-columns: 1fr 460px;
  gap: 18px;
  align-items: start;
}
/* 2026-05-24 — Sticky-top removed. Per Josh: "why is the left side
   scrolling but right side stays fixed. i hate that." The previous
   sticky+internal-scroll behavior locked both columns to the viewport
   and required scrolling INSIDE each column; in practice the PDF
   preview ate the left column's scroll while the right rail just sat
   there. Now both columns are normal block elements that scroll with
   the page. If we ever want a sticky right rail again, restore from
   git before 2026-05-24. */
.detail-grid > div:nth-child(1),
.detail-grid > div:nth-child(2) {
  /* normal flow — no sticky, no internal scroll */
}
/* Hide the inner card's own bottom margin so it doesn't add scrollable
   dead space below the table — the column's own padding is enough. */
.detail-grid > div > .detail-card:last-child { margin-bottom: 0; }
.detail-card {
  background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--radius-lg); padding: 16px 18px; margin-bottom: 14px;
}
.detail-card h3 {
  margin: 0 0 12px; font-size: 11px; font-weight: 600;
  color: var(--text-mute); text-transform: uppercase; letter-spacing: 0.7px;
}
.kv { display: grid; grid-template-columns: 150px 1fr; gap: 6px 14px; font-size: 12.5px; }
.kv .k { color: var(--text-mute); }
.kv .v { font-variant-numeric: tabular-nums; }

.reasoning { padding-left: 16px; }
.reasoning li { margin-bottom: 4px; font-size: 12.5px; }
.flags-list { padding-left: 16px; color: var(--warn); }
.flags-list li { margin-bottom: 4px; }

/* ===== Classification trace card (on invoice detail page) =====
   Visually prominent because for email-ingested invoices this is the
   audit trail Eric uses to trust the classification. */
.trace-card { border-left: 4px solid var(--border-2); }
.trace-card.high-trace   { border-left-color: var(--good); }
.trace-card.medium-trace { border-left-color: var(--text); }
.trace-card.low-trace    { border-left-color: var(--warn); }
.trace-card.flag-trace   { border-left-color: var(--bad); }

.trace-source { margin-bottom: 14px; }
.source-badge {
  display: inline-flex; align-items: center; gap: 8px;
  padding: 4px 12px;
  border-radius: 99px;
  font-size: 10.5px; font-weight: 600; letter-spacing: 0.8px;
  text-transform: uppercase;
}
.source-badge.email  { background: #E6F0FA; color: #1F3552; }
.source-badge.sage   { background: #F0E6FA; color: #4A2D5F; }
.source-badge.legacy { background: var(--surface-2); color: var(--text-mute); }
.source-badge .src-label { opacity: 0.7; font-weight: 500; }
.source-badge .src-value { font-weight: 700; letter-spacing: 1px; }

.email-envelope {
  margin-top: 10px;
  padding: 10px 14px;
  background: var(--surface-2);
  border-radius: var(--radius);
  font-size: 12.5px;
}
.email-envelope .env-row { display: flex; gap: 8px; padding: 2px 0; }
.email-envelope .env-label {
  color: var(--text-mute);
  min-width: 60px;
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.5px;
  font-weight: 600;
  padding-top: 1px;
}
.email-envelope .env-value { color: var(--text); flex: 1; word-break: break-word; }

.trace-summary {
  display: flex; align-items: center; gap: 12px;
  padding: 12px 14px;
  background: var(--surface-2);
  border-radius: var(--radius);
  margin-bottom: 14px;
}
.trace-conf-pill {
  display: inline-flex; align-items: center;
  padding: 4px 10px;
  border-radius: 4px;
  font-size: 11px; font-weight: 700; letter-spacing: 1px;
  color: #fff;
  flex-shrink: 0;
}
.trace-conf-pill.high   { background: var(--good); }
.trace-conf-pill.medium { background: var(--text); }
.trace-conf-pill.low    { background: var(--warn); }
.trace-conf-pill.flag   { background: var(--bad); }
.trace-conf-explainer { font-size: 12.5px; color: var(--text); line-height: 1.4; }

.trace-section { margin-top: 16px; }
.trace-section-h {
  font-size: 10.5px; font-weight: 600; letter-spacing: 0.8px;
  text-transform: uppercase; color: var(--text-mute);
  margin-bottom: 8px;
}
.trace-section-h.flags { color: var(--bad); }

.trace-reasoning { list-style: none; padding: 0; margin: 0; }
.trace-reasoning li {
  position: relative;
  padding: 7px 0 7px 26px;
  font-size: 13px;
  line-height: 1.5;
  border-bottom: 1px solid var(--border);
}
.trace-reasoning li:last-child { border-bottom: none; }
.trace-reasoning li::before {
  content: "✓";
  position: absolute; left: 4px; top: 7px;
  color: var(--good); font-weight: 700;
}

.trace-flags { list-style: none; padding: 0; margin: 0; }
.trace-flags li {
  position: relative;
  padding: 7px 0 7px 26px;
  font-size: 13px;
  line-height: 1.5;
  color: var(--text);
  border-bottom: 1px solid var(--border);
}
.trace-flags li:last-child { border-bottom: none; }
.trace-flags li::before {
  content: "⚠";
  position: absolute; left: 2px; top: 5px;
  color: var(--warn); font-weight: 700;
  font-size: 15px;
}

.trace-empty {
  padding: 12px;
  text-align: center;
  color: var(--text-mute);
  font-style: italic;
  font-size: 13px;
}

/* Raw OCR text display — collapsed inside the PDF extraction card.
   Eric expands this when he wants to see exactly what the parser read. */
.ocr-text { margin-top: 14px; border-top: 1px solid var(--border); padding-top: 10px; }
.ocr-text > summary {
  cursor: pointer;
  font-size: 12.5px; font-weight: 600;
  letter-spacing: normal; text-transform: none;
  color: var(--text);
  list-style: none;
  padding: 2px 0;
}
.ocr-text > summary::before {
  content: "▸ ";
  color: var(--text);
}
.ocr-text[open] > summary::before { content: "▾ "; }
.ocr-text > summary:hover { color: var(--text); }
.ocr-text .ocr-body {
  margin: 10px 0 0;
  padding: 12px 14px;
  background: var(--surface-2);
  border-radius: var(--radius);
  font-family: var(--font-mono);
  font-size: 11.5px;
  line-height: 1.5;
  color: var(--text);
  max-height: 360px;
  overflow-y: auto;
  white-space: pre-wrap;
  word-break: break-word;
}

textarea, input[type="text"] {
  width: 100%; padding: 7px 9px;
  border: 1px solid var(--border-2); border-radius: var(--radius);
  font-family: inherit; font-size: 12.5px; resize: vertical;
  background: var(--surface);
}
textarea:focus, input:focus {
  outline: none; border-color: var(--text);
}

.action-bar { display: flex; gap: 8px; justify-content: flex-end; margin-top: 14px; }

/* Activity / latest tables on overview */
.feed { padding: 0; }
.feed .item {
  display: grid; grid-template-columns: 14px 1fr auto; gap: 12px;
  padding: 9px 18px; align-items: baseline;
  border-bottom: 1px solid var(--border); font-size: 12.5px;
}
.feed .item:last-child { border-bottom: none; }
.feed .dot { width: 6px; height: 6px; border-radius: 50%;
  background: var(--good); margin-top: 5px; }
.feed .dot.warn { background: var(--warn); }
.feed .item .desc .num { color: var(--text); font-weight: 500; }
.feed .item .desc .vendor { color: var(--text-mute); margin-left: 6px; }
.feed .item .when { font-size: 11.5px; color: var(--text-soft); font-variant-numeric: tabular-nums; }

/* Empty state */
.empty { padding: 48px 20px; text-align: center; color: var(--text-mute); }
.empty h4 { font-size: 13px; margin-bottom: 4px; font-weight: 600; }
.empty p { margin: 3px 0; font-size: 12.5px; }

/* Purchase Log wide table — bounded height, both scrollbars always reachable */
.pl-scroll {
  overflow-x: scroll;
  overflow-y: auto;
  max-width: 100%;
  max-height: calc(100vh - 240px);
}
.pl-scroll thead th { position: sticky; top: 0; z-index: 4; background: var(--surface-2); }
.pl-scroll { scrollbar-width: thin; scrollbar-color: rgba(0,0,0,0.15) transparent; }
.pl-scroll::-webkit-scrollbar { height: 8px; width: 8px; }
.pl-scroll::-webkit-scrollbar-track { background: transparent; }
.pl-scroll::-webkit-scrollbar-thumb { background: rgba(0,0,0,0.13); border-radius: 4px; }
.pl-scroll::-webkit-scrollbar-thumb:hover { background: rgba(0,0,0,0.28); }
/* Purchase Log table — wide horizontal scroll, tight column padding so
   you can fit as much across the screen as possible. First column
   (Method) anchors the row identity on horizontal scroll; the other
   workbook-manual columns are no longer pinned so they don't eat space.
   Updated 2026-05-13: unfroze cols 2 and 3 per Joshua's request. */
.pl-table {
  font-size: 11.5px;
  /* No fixed min-width — let columns auto-size to content. The previous
     3800px floor forced unnecessary horizontal scroll even when content
     was short. */
  table-layout: auto;
  font-variant-numeric: tabular-nums;
}
.pl-table th, .pl-table td {
  white-space: nowrap;
  padding: 4px 8px;            /* tightened from 5/9 to 4/8 */
}
.pl-table th {
  font-size: 10px;
  font-weight: 600;
  letter-spacing: 0.7px;
  /* Subtle PTI accent on the column-header row: thin orange top edge.
     Matches the page-header underline pattern, lighter than the heavy
     orange chart-header we tried. Added 2026-05-13. */
  border-top: 2px solid var(--accent);
  border-bottom: 1.5px solid var(--border-2);
  background: var(--surface);
  color: var(--text);
  padding-top: 7px;
  padding-bottom: 6px;
}
.pl-table .num { font-size: 11.5px; padding: 4px 8px; }
.pl-table .trunc {
  max-width: 130px; overflow: hidden; text-overflow: ellipsis;
}
.pl-table th.g-end, .pl-table td.g-end { border-right: 1px solid var(--border-2); }
.pl-table tr.group-row th {
  text-align: left; font-size: 9.5px; letter-spacing: 0.8px;
  font-weight: 600; text-transform: uppercase; color: var(--text-soft);
  background: var(--surface); padding: 6px 9px 3px;
  border-bottom: 1px solid var(--border);
}

/* Method column (col B) — no longer sticky per Josh 2026-05-13.
   Kept the width caps so the column doesn't blow out on long method
   names like "Glencore / Africa (Cathode)". */
.pl-table .sticky-1 {
  min-width: 170px; max-width: 200px;
  overflow: hidden; text-overflow: ellipsis;
}

/* Cols 2 and 3 are no longer sticky — they scroll with the rest. Keep
   their max-width modest so they don't dominate. */
.pl-table .sticky-2 { max-width: 140px; overflow: hidden; text-overflow: ellipsis; }
.pl-table .sticky-3 { max-width: 170px; overflow: hidden; text-overflow: ellipsis; }

/* Month-divider label sticks to the left edge while the row spans full width */
.pl-table tr.month-row td { padding-left: 0; }
.pl-table .month-label {
  display: inline-block; padding: 5px 14px;
  position: sticky; left: 0;
}

.subtotal-bar {
  display: flex; gap: 28px;
  padding: 9px 18px; margin-bottom: 12px;
  background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--radius);
  font-size: 12.5px; font-variant-numeric: tabular-nums;
}
.subtotal-bar span strong { color: var(--text-mute); font-weight: 600;
  text-transform: uppercase; letter-spacing: 0.5px; font-size: 10.5px; margin-right: 6px; }

/* ===== Purchase Log: KPI summary tiles ===== */
.pl-kpis {
  display: grid; grid-template-columns: repeat(4, 1fr); gap: 12px;
  margin-bottom: 16px;
}
.pl-kpi {
  background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--radius-lg); padding: 14px 18px;
}
.pl-kpi-label {
  font-size: 10.5px; font-weight: 600; letter-spacing: 0.8px;
  text-transform: uppercase; color: var(--text-mute);
  margin-bottom: 6px;
}
.pl-kpi-value {
  font-size: 24px; font-weight: 600; line-height: 1.05;
  font-variant-numeric: tabular-nums; letter-spacing: -0.01em;
  color: var(--text);
}
.pl-kpi-sub {
  font-size: 11.5px; color: var(--text-soft);
  margin-top: 4px; font-variant-numeric: tabular-nums;
}

/* ===== Purchase Log: filter row with labelled inputs ===== */
.pl-filters {
  display: flex; gap: 14px; align-items: flex-end;
  flex-wrap: wrap; margin-bottom: 16px;
  padding: 12px 16px;
  background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--radius);
}
.pl-filter { display: flex; flex-direction: column; gap: 4px; min-width: 140px; }
.pl-filter-grow { flex: 1; min-width: 220px; }
.pl-filter label {
  font-size: 10.5px; font-weight: 600; letter-spacing: 0.6px;
  text-transform: uppercase; color: var(--text-mute);
}
.pl-filter select, .pl-filter input[type="text"] {
  padding: 7px 10px; border: 1px solid var(--border-2);
  border-radius: var(--radius); font-family: inherit; font-size: 13px;
  background: var(--surface);
}
.pl-filter input[type="text"]:focus, .pl-filter select:focus {
  outline: none; border-color: var(--text);
}
.pl-filter-actions { display: flex; gap: 6px; align-items: center; }

/* Polish the month-divider row in the table */
.pl-table .month-label {
  font-size: 11px; font-weight: 700; letter-spacing: 1.5px;
  text-transform: uppercase; color: var(--text-mute);
  padding: 6px 14px;
}

/* Pointer cursor on clickable rows so the UX is obvious */
.pl-table tbody tr { cursor: pointer; }

/* Empty-state block — actionable copy, not just "no data" */
.pl-scroll .empty { padding: 56px 24px; }
.pl-scroll .empty h4 { font-size: 15px; margin-bottom: 6px; color: var(--text); }
.pl-scroll .empty p { font-size: 13px; color: var(--text-mute); }
.pl-scroll .empty p a { color: var(--text); text-decoration: underline; }

.notes-cell {
  max-width: 220px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  color: var(--text-mute); font-size: 11.5px;
}

/* Monthly Summary — dense tables, horizontal scroll, sticky Month column */
.ms-scroll {
  overflow-x: auto;
  /* Firefox: thin, soft-grey thumb on transparent track. */
  scrollbar-width: thin;
  scrollbar-color: rgba(0,0,0,0.15) transparent;
}
.ms-scroll::-webkit-scrollbar { height: 8px; }
.ms-scroll::-webkit-scrollbar-track { background: transparent; }
.ms-scroll::-webkit-scrollbar-thumb {
  background: rgba(0,0,0,0.13);
  border-radius: 4px;
}
.ms-scroll::-webkit-scrollbar-thumb:hover { background: rgba(0,0,0,0.28); }

table.data.ms { font-size: 11.5px; min-width: 100%; }
table.data.ms th {
  padding: 7px 9px; font-size: 10px;
  font-weight: 600; white-space: normal;     /* allow wrap */
  vertical-align: bottom; line-height: 1.25;
  min-width: 90px;
}
table.data.ms th.month-col { min-width: 100px; }
/* Force year cols to a fixed width — total exceeds card width, so .ms-scroll
   shows ~4 years and the rest are reachable via horizontal scroll. */
table.data.ms th.num, table.data.ms td.num { min-width: 130px; }
table.data.ms td { padding: 6px 9px; white-space: nowrap; }
table.data.ms .num { font-size: 12px; }
table.data.ms .rate { display: block; font-size: 9.5px; color: var(--text-soft);
  font-weight: 400; margin-top: 1px; }
table.data.ms tr.total-row { background: var(--surface-2); font-weight: 600; }
table.data.ms tr.total-row td { border-top: 1px solid var(--border-2); }
table.data.ms tr.pct-row td { color: var(--text-soft); font-style: normal; font-size: 11px; }

/* Sticky Month column for Monthly Summary blocks */
table.data.ms th.sticky-month,
table.data.ms td.sticky-month {
  position: sticky; left: 0; background: var(--surface); z-index: 2;
  border-right: 1px solid var(--border-2);
}
table.data.ms thead th.sticky-month { background: var(--surface-2); z-index: 3; }
table.data.ms tr.total-row td.sticky-month,
table.data.ms tr.pct-row td.sticky-month { background: var(--surface-2); }

/* Comments thread on invoice detail */
ul.comments { list-style: none; margin: 0; padding: 0; }
ul.comments li { padding: 9px 0; border-top: 1px solid var(--border); font-size: 13px; }
ul.comments li:first-child { border-top: none; padding-top: 0; }
ul.comments .who strong { color: var(--text); }
ul.comments .when { font-size: 11px; color: var(--text-soft); margin-left: 6px; }
ul.comments .body { margin-top: 3px; color: var(--text); }
ul.comments .edited { font-size: 11px; color: var(--text-soft); margin-left: 6px; cursor: help; }
ul.comments .edit-link { font-size: 11px; color: var(--text-mute); margin-left: 8px; }
ul.comments .edit-link:hover { color: var(--text); text-decoration: underline; }
.reactions { margin-top: 6px; display: flex; gap: 4px; flex-wrap: wrap; align-items: center; }

/* Existing-reaction pills (only shown when count >= 1) */
.reactions .rx {
  display: inline-flex; align-items: center; gap: 4px;
  padding: 2px 8px; font-size: 12.5px;
  background: var(--surface-2); border: 1px solid var(--border);
  border-radius: 999px; cursor: pointer; font-family: inherit;
}
.reactions .rx:hover { border-color: var(--text); }
.reactions .rx.mine { background: var(--copper-soft); border-color: var(--copper); }
.reactions .rx .ct { font-size: 11px; color: var(--text-mute); font-variant-numeric: tabular-nums; }
.reactions .rx.mine .ct { color: var(--copper-dark); font-weight: 600; }

/* Picker — Teams-style smiley + that expands to show all 5 options */
.rx-picker { position: relative; display: inline-block; }
.rx-picker summary {
  list-style: none; cursor: pointer;
  display: inline-flex; align-items: center; gap: 2px;
  padding: 2px 8px; font-size: 12.5px;
  background: var(--surface); border: 1px solid var(--border);
  border-radius: 999px; color: var(--text-mute);
}
.rx-picker summary::-webkit-details-marker { display: none; }
.rx-picker summary:hover { border-color: var(--text); color: var(--text); }
.rx-picker summary .plus { font-weight: 600; }
.rx-pop {
  position: absolute; bottom: calc(100% + 4px); left: 0; z-index: 10;
  display: flex; gap: 2px; padding: 4px;
  background: var(--surface); border: 1px solid var(--border);
  border-radius: 999px; box-shadow: 0 4px 12px rgba(0,0,0,0.08);
  white-space: nowrap;
}
.rx-opt {
  background: none; border: none; padding: 4px 6px;
  font-size: 16px; cursor: pointer; border-radius: 999px;
  line-height: 1; font-family: inherit;
}
.rx-opt:hover { background: var(--surface-2); transform: scale(1.15); }
.at-mention {
  display: inline-block; background: var(--copper-soft);
  color: var(--copper-dark); padding: 0 4px; border-radius: 3px;
  font-weight: 500;
}

/* Decision history timeline in invoice detail */
ul.history { list-style: none; margin: 0; padding: 0; }
ul.history li { padding: 8px 0; border-top: 1px solid var(--border); }
ul.history li:first-child { border-top: none; padding-top: 0; }
ul.history .ts { font-size: 11px; color: var(--text-soft); }
ul.history .row { display: flex; gap: 10px; align-items: center; margin-top: 3px; }
ul.history .who { font-size: 12px; color: var(--text-mute); }
ul.history .cm { font-size: 12px; color: var(--text); margin-top: 3px; }

/* Embedded PDF preview in invoice detail view — legacy iframe version,
   kept for any consumer still using it. New default is .pdf-preview-pages
   (server-rendered PNG pages) — see below. */
.pdf-preview {
  width: 100%; height: 480px;
  border: 1px solid var(--border); border-radius: var(--radius);
  margin-top: 12px; background: var(--surface-2);
}

/* Server-rendered PDF preview: pages stacked vertically as <img> tags.
   Scrolls if the PDF is longer than ~720px. Each image gets a subtle
   shadow so the page boundaries read like Sage's preview pane. Added
   2026-05-14 to bypass the per-user Chrome "Download PDFs vs Open in
   Chrome" setting that breaks inline <iframe> rendering on machines
   where the user has it set to download. */
.pdf-preview-pages {
  max-height: 820px; overflow-y: auto;
  border: 1px solid var(--border); border-radius: var(--radius);
  margin-top: 12px; background: var(--surface-2);
  padding: 12px;
  display: flex; flex-direction: column; gap: 12px;
}

/* Webkit scrollbar styling — Chrome/Edge/Safari for the PDF preview
   pane (subtle scroll). */
.pdf-preview-pages::-webkit-scrollbar {
  width: 8px;
}
.pdf-preview-pages::-webkit-scrollbar-track {
  background: transparent;
}
.pdf-preview-pages::-webkit-scrollbar-thumb {
  background: rgba(0,0,0,0.15);
  border-radius: 4px;
}
.pdf-preview-pages::-webkit-scrollbar-thumb:hover {
  background: rgba(0,0,0,0.30);
}
/* Same softening for the PDF preview pane (Firefox). */
.pdf-preview-pages {
  scrollbar-width: thin;
  scrollbar-color: rgba(0,0,0,0.18) transparent;
}
.pdf-preview-pages .pdf-preview-img {
  width: 100%; max-width: 800px; height: auto;
  align-self: center; background: white;
  box-shadow: 0 1px 6px rgba(0, 0, 0, 0.08);
  border-radius: 2px;
}
.pdf-preview-loading,
.pdf-preview-error {
  font-size: 13px; color: var(--text-mute);
  text-align: center; padding: 24px 12px;
}
.pdf-preview-error a {
  color: var(--accent); text-decoration: underline;
}

/* Compact Print + Download buttons on the PDF preview card header. */
.pdf-preview-actions { display: inline-flex; align-items: center; gap: 8px; }
/* Preview toolbar buttons — soft copper-outlined pills that warm on hover,
   matching the Goods-Receipt / Contract viewer buttons (was a pair of flat
   little 4px rectangles). The leading icon keeps its inline margin-right, so
   no flex gap here. */
.btn.btn-download,
.btn.btn-print {
  display: inline-flex; align-items: center;
  padding: 7px 14px;
  font-size: 12.5px; font-weight: 600; letter-spacing: 0.01em;
  border-radius: 999px;
  color: #8A4714; text-decoration: none;
  /* Copper-tinted glass: a more translucent warm fill so the page tint shows
     through, a brighter inset top sheen + faint inner base line for the glass
     edge, and a stronger backdrop blur. No drop-shadow — it sits flat, not
     floating. */
  background: linear-gradient(180deg, rgba(255,241,228,0.62), rgba(245,222,201,0.48));
  border: 1px solid rgba(168, 90, 28, 0.45);
  box-shadow: inset 0 1px 0 rgba(255,255,255,0.75),
              inset 0 -1px 0 rgba(168,90,28,0.10);
  backdrop-filter: blur(14px) saturate(1.5);
  -webkit-backdrop-filter: blur(14px) saturate(1.5);
  transition: background .15s, color .15s, border-color .15s;
}
.btn.btn-download:hover,
.btn.btn-print:hover {
  background: linear-gradient(180deg, rgba(252,231,214,0.82), rgba(238,210,186,0.7));
  border-color: rgba(168, 90, 28, 0.7);
  color: #7C3D0E; text-decoration: none;
}
/* Print's "Preparing…" busy state while the render warms. */
.btn.btn-print:disabled { opacity: 0.55; cursor: default; }
.btn.btn-print:disabled:hover {
  background: linear-gradient(180deg, rgba(255,241,228,0.62), rgba(245,222,201,0.48));
  border-color: rgba(168, 90, 28, 0.45); color: #8A4714;
}

/* PDF preview card: title on the left, Download PDF on the right. */
.pdf-preview-header {
  display: flex; align-items: center; justify-content: space-between;
  margin-bottom: 6px;
}
/* Preview card title — natural case, not the tiny uppercase-tracked eyebrow
   the other detail cards use (which reads as an admin-panel tell). Scoped
   under .detail-card so it reliably overrides the shared `.detail-card h3`
   rule. The eye glyph stays as a quiet leading mark. Consistent across the
   invoice + PO viewers (both just say "Preview" — the page H1 already names
   the document and number). */
.detail-card .pdf-preview-header h3 {
  margin: 0;
  font-size: 16px; font-weight: 600; letter-spacing: -0.01em;
  text-transform: none; color: var(--text);
}

/* Ingest action-bar status pill. Three colored states:
   loading (with spinner), success (with check), error (with bang).
   Replaces the plain "Saved." text. Added 2026-05-14. */
.ingest-status {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 4px 12px; border-radius: 999px;
  font-size: 12px; font-weight: 500;
  line-height: 1.4;
}
.ingest-status.loading {
  background: var(--surface-2);
  color: var(--text-mute);
}
.ingest-status.success {
  background: var(--good-bg, #E6F0E6);
  color: var(--good, #2F5F2F);
}
.ingest-status.error {
  background: var(--bad-bg);
  color: var(--bad);
}
.ingest-status-spinner {
  width: 12px; height: 12px;
  border: 2px solid currentColor; border-top-color: transparent;
  border-radius: 50%;
  animation: ingest-spin 0.8s linear infinite;
  flex-shrink: 0;
}
.ingest-status-icon {
  flex-shrink: 0;
  display: inline-block;
}
@keyframes ingest-spin {
  to { transform: rotate(360deg); }
}

/* Sub-tab navigation inside the Copper Pricing page */
.tabs {
  display: flex; gap: 4px; margin-bottom: 18px;
  border-bottom: 1px solid var(--border);
}
.tabs .tab {
  padding: 9px 16px; font-size: 13px; font-weight: 500;
  color: var(--text-mute); text-decoration: none;
  border-bottom: 2px solid transparent;
  margin-bottom: -1px;            /* overlap the underline border on the parent */
  transition: color 0.12s, border-color 0.12s;
}
.tabs .tab:hover { color: var(--text); }
.tabs .tab.active {
  color: var(--text); border-bottom-color: var(--text);
  font-weight: 600;
}

/* Live hero pair: COMEX on the left, LME on the right.
   Stacks vertically on narrow screens. */
.live-hero-grid {
  display: grid; grid-template-columns: 1fr 1fr; gap: 14px;
  margin-bottom: 22px;
}

/* COMEX/LME live-price hero banner */
.live-hero {
  display: grid; grid-template-columns: 1fr auto; gap: 24px;
  align-items: start; padding: 16px 22px; margin: 0;
  background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--radius-lg);
}
/* Keep the right-column meta (Last fetched / Open / Prev) anchored to the
   bottom while the main label/price content top-aligns on both cards. */
.live-hero .lh-right { align-self: end; }
.live-hero.lme { background: var(--surface-2); }
/* Standalone live banner above the COMEX Spot table — match the data
   region's max width so it visually spans both the table and stats card. */
.live-hero.comex-spot-hero { max-width: var(--comex-data-w); }

/* "15-MIN DELAYED" badge next to the COMEX label. Yahoo's free HG=F feed
   is delayed per their TOS, so the dashboard cannot claim to be live —
   we surface that honestly in a muted amber pill so the disclaimer is
   impossible to miss at a glance. */
.delay-badge {
  display: inline-flex; align-items: center;
  margin-left: 10px;
  padding: 2px 8px;
  font-size: 10px; font-weight: 700;
  letter-spacing: 0.8px;
  color: #8B5A00;
  background: rgba(245, 158, 11, 0.10);
  border: 1px solid rgba(245, 158, 11, 0.40);
  border-radius: 4px;
  vertical-align: 2px;
  cursor: help;
}

/* Price flash — briefly highlights the price cell when it changes, so the
   reader sees the tick visually instead of just a silent digit swap. */
.live-hero .lh-price.flash-up   { animation: flash-up   600ms ease-out; }
.live-hero .lh-price.flash-down { animation: flash-down 600ms ease-out; }
@keyframes flash-up {
  0%   { background: rgba(26, 127, 62, 0.22); }
  100% { background: transparent; }
}
@keyframes flash-down {
  0%   { background: rgba(196, 48, 43, 0.22); }
  100% { background: transparent; }
}
.live-hero .lh-price {
  border-radius: 6px;
  padding: 0 4px;
  margin-left: -4px;
  transition: background 300ms ease-out;
}

/* Data-source callout under data tables — the entire card is a clickable
   link to the upstream feed, so anyone can verify any value in one tap.
   Styled as a real CTA (tinted background, prominent link, arrow) so it's
   clearly inviting interaction, not just a footnote. */
.data-source {
  display: block;
  max-width: var(--comex-data-w);
  margin: 20px 0 8px;
  padding: 14px 18px;
  background: rgba(168, 90, 28, 0.06);  /* faint PTI-Orange tint */
  border: 1px solid rgba(168, 90, 28, 0.25);
  border-left: 3px solid var(--accent);
  border-radius: var(--radius);
  text-decoration: none;
  color: inherit;
  transition: background 120ms ease, border-color 120ms ease, transform 120ms ease;
}
.data-source:hover {
  background: rgba(168, 90, 28, 0.10);
  border-color: rgba(168, 90, 28, 0.45);
}
.data-source:hover .data-source-arrow { transform: translate(2px, -2px); }
.data-source-label {
  font-size: 10.5px; font-weight: 700;
  letter-spacing: 0.6px; text-transform: uppercase;
  color: var(--accent);
  margin-bottom: 5px;
}
.data-source-link {
  font-size: 15px; font-weight: 600;
  color: var(--text);
  display: flex; align-items: center; gap: 8px;
  margin-bottom: 4px;
}
.data-source-arrow {
  display: inline-block;
  font-size: 14px; font-weight: 700;
  color: var(--accent);
  transition: transform 120ms ease;
}
.data-source-help {
  font-size: 12.5px; color: var(--text-soft);
  line-height: 1.4;
  max-width: 80ch;
}
.live-hero .lh-converted {
  font-size: 14px; color: var(--text-soft); margin-top: 6px;
  font-variant-numeric: tabular-nums;
}
.live-hero .lh-spread {
  font-size: 13px; font-weight: 500; margin-top: 4px;
  font-variant-numeric: tabular-nums;
}
.live-hero .lh-spread.up   { color: var(--good); }
.live-hero .lh-spread.down { color: var(--bad); }
.live-hero .lh-price-empty {
  font-size: 20px; color: var(--text-mute); font-weight: 500;
}

/* Arb comparison panel — COMEX vs LME in both units */
.arb-compare table.data.arb th, .arb-compare table.data.arb td {
  font-variant-numeric: tabular-nums;
}
.arb-compare table.data.arb td .src {
  font-size: 10.5px; color: var(--text-mute);
  text-transform: uppercase; letter-spacing: 0.6px;
  margin-left: 6px; font-weight: 500;
}
.arb-compare table.data.arb td.num.up   { color: var(--good); font-weight: 500; }
.arb-compare table.data.arb td.num.down { color: var(--bad);  font-weight: 500; }

/* Hero spread display — big magnitude + direction at top of the arb card. */
.arb-hero {
  padding: 22px 22px 20px;
  text-align: center;
  border-bottom: 1px solid var(--border);
  background: var(--surface-2);
}
.arb-hero .arb-direction {
  font-size: 11px; font-weight: 600; letter-spacing: 1.5px;
  text-transform: uppercase; color: var(--text-mute);
  margin-bottom: 8px;
}
.arb-hero.up .arb-direction { color: var(--good); }
.arb-hero.down .arb-direction { color: var(--bad); }
.arb-hero .arb-magnitude {
  font-size: 44px; font-weight: 700; line-height: 1;
  font-variant-numeric: tabular-nums; letter-spacing: -0.02em;
  color: var(--text);
}
.arb-hero.up .arb-magnitude { color: var(--good); }
.arb-hero.down .arb-magnitude { color: var(--bad); }
.arb-hero .arb-breakdown {
  font-size: 13px; color: var(--text-soft);
  margin-top: 8px; font-variant-numeric: tabular-nums;
}
.arb-hero .arb-breakdown .sep { color: var(--border-2); padding: 0 6px; }
/* Actionable read for buyers — what the spread means in practice. */
.arb-hero .arb-implication {
  margin: 14px auto 0;
  padding: 10px 18px;
  display: inline-block;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  font-size: 13px; color: var(--text);
  font-variant-numeric: tabular-nums;
}
.arb-hero .arb-implication strong { font-weight: 700; }

/* Manual LME entry + Refresh button, inline below the comparison table. */
.arb-actions {
  display: flex; align-items: center; gap: 14px;
  flex-wrap: wrap;
  padding: 14px 22px;
  border-top: 1px solid var(--border);
}
.lme-form-inline { display: flex; align-items: center; gap: 8px; margin: 0; }
.lme-form-inline label {
  font-size: 11px; color: var(--text-mute);
  text-transform: uppercase; letter-spacing: 0.8px; font-weight: 600;
}
.lme-form-inline input {
  padding: 6px 10px; border: 1px solid var(--border-2);
  border-radius: var(--radius); font-family: inherit; font-size: 13px;
  font-variant-numeric: tabular-nums; width: 130px;
}
.arb-footnote {
  font-size: 11.5px; color: var(--text-soft); font-style: italic;
  flex: 1; min-width: 220px; text-align: right;
}

/* Clickable current-month cell in the Projected Average grid.
   Visually distinguished from historical cells by warm-tone background. */
table.data.ms td.current-cell {
  background: #FFF4E8;  /* warm pale tint matching PTI Orange family */
  position: relative;
}
table.data.ms td.current-cell .cell-link {
  color: var(--text); text-decoration: none; font-weight: 700;
  display: inline-block; padding: 2px 6px; margin: -2px -6px;
  border-bottom: 1.5px dotted var(--accent);
  transition: background 0.12s;
}
table.data.ms td.current-cell .cell-link:hover {
  background: #FFE7CD;
  border-bottom-color: var(--text);
}
/* Historical cells (closed months) — muted so the eye lands on the
   current-month projection first. They're real, finalized data, not the
   guess; the visual hierarchy reflects that. */
table.data.ms td.historical-cell {
  color: var(--text-soft);
  font-weight: 400;
}

/* ===== Projections hero — current-month projection in big at top ===== */
.proj-hero {
  background: linear-gradient(to right, var(--surface) 0%, #FFF7EE 100%);
  border: 1px solid var(--border);
  max-width: var(--comex-data-w);
}

/* Plain-English explainer banner — sits above the price/formula grid so the
   reader gets the mental model in one sentence before they see the math. */
.proj-hero-explainer {
  padding: 18px 26px 16px;
  border-bottom: 1px solid var(--border);
  background: rgba(255, 255, 255, 0.55);
}
.proj-hero-explainer-label {
  font-size: 11px; font-weight: 700; letter-spacing: 1.2px;
  text-transform: uppercase; color: var(--accent);
  margin-bottom: 6px;
}
.proj-hero-explainer-body {
  font-size: 13.5px; line-height: 1.55;
  color: var(--text);
  max-width: 82ch;
}
.proj-hero-explainer-body strong {
  color: var(--text); font-weight: 700;
  font-variant-numeric: tabular-nums;
}
/* Algebraic-formula explainer: a single-line fraction + "= Result".
   Variables on the fraction are quieter (regular weight, dark gray) so the
   formula visually recedes; the RESULT after the equals stays bold orange
   so the answer is what your eye lands on. Same uppercase + letter-spacing
   as the section label keeps the typography cohesive. */
.proj-hero-explainer-row {
  display: flex;
  align-items: center;
  justify-content: flex-start;
  gap: 14px;
  margin: 10px 0 2px;
  white-space: nowrap;
}
.proj-hero-explainer-fraction {
  display: inline-flex; flex-direction: column;
  align-items: stretch;
  flex-shrink: 1;
}
.pheh-num,
.pheh-den {
  display: flex; align-items: center;
  gap: 10px;
  padding: 5px 0;
  white-space: nowrap;
}
.pheh-num { justify-content: flex-start; }  /* "S" in Sum aligns with "H" in How */
.pheh-den { justify-content: center;     }  /* denominator stays centered under the bar */
.pheh-bar {
  height: 1px;
  background: var(--text);
  margin: 2px 0;
  opacity: 0.55;
}
.proj-hero-explainer-eq {
  display: inline-flex; align-items: center;
  gap: 10px;
  flex-shrink: 0;
}
.pheh-eq-sign {
  font-size: 14px;
  font-weight: 600;
  color: var(--text-soft);
}
/* Fraction variables: bold dark text. Matches the section label's weight
   for typographic consistency, but stays dark (not orange) so the visual
   focus is reserved for the RESULT on the right. */
.proj-hero-explainer-fraction .phf-var {
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.8px;
  text-transform: uppercase;
  color: var(--text);
  white-space: nowrap;
}
/* The RESULT after the equals — bold + orange, this is what the formula
   produces and where the eye should land. */
.proj-hero-explainer-eq .phf-var.phf-var-result {
  font-size: 11.5px;
  font-weight: 700;
  letter-spacing: 1.0px;
  text-transform: uppercase;
  color: var(--accent);
  white-space: nowrap;
}
.proj-hero-explainer-fraction .phf-divop {
  color: var(--text-soft);
  font-weight: 500;
  font-size: 13px;
}
.proj-hero-explainer-fraction .phf-paren {
  color: var(--text-soft);
  font-weight: 700;
  font-size: 13px;
}
/* Parenthesized sub-expression: tight 2px between parens and the wrapped
   words, instead of the 10px gap the parent flex applies between top-level
   children. Added 2026-05-13. */
.proj-hero-explainer-fraction .phf-group {
  display: inline-flex;
  align-items: center;
  gap: 2px;
}

.proj-hero-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 0;
}
.proj-hero-main { padding: 22px 26px; border-right: 1px solid var(--border); }
.proj-hero-formula { padding: 22px 26px; }
/* Lay the fraction and the "= result" side by side. Reads like a textbook
   math line: numerator-over-denominator equals answer. */
.proj-hero-formula-row {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 22px;
}
.proj-hero-formula-row .proj-hero-formula-body { flex-shrink: 0; }
.proj-hero-label {
  font-size: 11px; font-weight: 600; letter-spacing: 1.2px;
  text-transform: uppercase; color: var(--text-mute);
  margin-bottom: 10px;
}
.proj-hero-price {
  display: flex; align-items: baseline; gap: 6px;
  font-size: 44px; font-weight: 700; line-height: 1;
  font-variant-numeric: tabular-nums; letter-spacing: -0.02em;
  color: var(--text);
}
.proj-hero-unit {
  font-size: 18px; font-weight: 400; color: var(--text-mute);
}
.proj-hero-sub {
  margin-top: 12px; font-size: 13px; color: var(--text-soft);
  font-variant-numeric: tabular-nums;
}
.proj-hero-formula-label {
  font-size: 11px; font-weight: 600; letter-spacing: 1.2px;
  text-transform: uppercase; color: var(--text-mute);
  margin-bottom: 10px;
}

/* Classic numerator-over-denominator fraction — math reads at a glance.
   Numerator terms: label ABOVE value (header → number).
   Denominator term: value ABOVE label (number → caption).
   Width sized to content so the bar naturally spans the widest row. */
.proj-hero-fraction {
  display: flex; flex-direction: column;
  width: fit-content; max-width: 100%;
  margin: 16px auto 18px;
}
.phf-num,
.phf-den {
  display: flex;
  justify-content: center;
  gap: 14px;
  padding: 4px 14px;
}
.phf-num   { align-items: flex-end;   }  /* values sit at the BOTTOM of each term */
.phf-den   { align-items: flex-start; }  /* value sits at the TOP, label below   */
/* Flat numerator: values + operator on a single line, no per-term labels.
   The explainer paragraph above the fraction already tells the reader what
   each piece means; keeping labels in here just made it cluttered. */
.phf-num.phf-num-flat {
  align-items: baseline;
  gap: 10px;
}
.phf-num.phf-num-flat .phf-val { font-size: 19px; }
.phf-num.phf-num-flat .phf-op  { padding-bottom: 0; font-size: 22px; }
.phf-term {
  display: flex; flex-direction: column;
  align-items: center; gap: 5px;
  text-align: center;
  min-width: 0;
}
.phf-val {
  font-size: 19px; font-weight: 700;
  color: var(--text);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
  line-height: 1.1;
}
.phf-lbl {
  font-size: 10.5px; font-weight: 600;
  text-transform: uppercase; letter-spacing: 0.5px;
  color: var(--text-mute);
  line-height: 1.25;
  max-width: 26ch;
}
/* The `+` between numerator terms — align with the value row (bottom of the term). */
.phf-op {
  font-size: 26px; font-weight: 400;
  color: var(--text-soft);
  line-height: 1;
  align-self: flex-end;
  padding-bottom: 4px;
}
.phf-paren {
  color: var(--text-mute); font-weight: 500;
}
.phf-bar {
  height: 2px;
  background: var(--text);
  border-radius: 2px;
  margin: 8px 0;
}
/* Inline denominator: "31 calendar days in May" on one line, instead of
   stacked. Saves a row of vertical space which shrinks the fraction's
   overall height. */
.phf-den.phf-den-inline {
  display: flex;
  align-items: baseline;
  justify-content: center;
  gap: 8px;
  padding: 4px 14px;
}
.phf-den.phf-den-inline .phf-val {
  font-size: 19px; font-weight: 700;
  color: var(--text);
  font-variant-numeric: tabular-nums;
}
.phf-den.phf-den-inline .phf-lbl {
  font-size: 11px; font-weight: 600;
  text-transform: uppercase; letter-spacing: 0.5px;
  color: var(--text-mute);
}

.proj-hero-formula-body {
  font-size: 13px; color: var(--text);
  line-height: 1.7; font-variant-numeric: tabular-nums;
}
.proj-hero-formula-body .eq {
  font-size: 15px;
  white-space: nowrap;
  /* Override the .mono class from the template — the result reads more
     professional in the sans face, matching the big price up top.
     Refined 2026-05-13 per Josh's feedback. */
  font-family: var(--font-sans);
}
.proj-hero-formula-body strong {
  color: var(--accent);
  font-size: 22px;
  font-weight: 700;
  font-family: var(--font-sans);
  letter-spacing: -0.015em;
  font-variant-numeric: tabular-nums;
}
.proj-hero-formula-body .eq-unit {
  font-size: 12px; color: var(--text-mute); font-weight: 400;
  margin-left: 2px;
  font-family: var(--font-sans);
}
.proj-hero-link {
  display: inline-block; margin-top: 14px;
  font-size: 12px; color: var(--accent); font-weight: 600;
}

/* ===== Projection grid legend — explains what cells mean ===== */
.proj-legend {
  padding: 14px 22px 18px;
  border-top: 1px solid var(--border);
  font-size: 12.5px;
  line-height: 1.5;
}
.proj-legend-row {
  display: flex; gap: 10px; align-items: flex-start;
  padding: 4px 0;
  color: var(--text);
}
.proj-legend-row strong { color: var(--text); }
.proj-legend-row a { color: var(--accent); font-weight: 600; }
.proj-legend-swatch {
  display: inline-block; width: 18px; height: 18px;
  border-radius: 3px; flex-shrink: 0; margin-top: 2px;
  border: 1px solid var(--border-2);
}
.proj-legend-swatch.historical { background: var(--surface); }
.proj-legend-swatch.current    { background: #FFF4E8; border-color: var(--accent); }
.proj-legend-swatch.future     { background: transparent; }

/* ===== Projection calculation breakdown panel ===== */
.projection-detail .card-h { display: flex; justify-content: space-between; align-items: center; }
.projection-detail .pd-status {
  padding: 12px 22px; border-bottom: 1px solid var(--border);
  display: flex; align-items: center; gap: 12px;
}
.pd-pill {
  display: inline-flex; align-items: center;
  padding: 3px 10px; border-radius: 99px;
  font-size: 10.5px; font-weight: 700; letter-spacing: 0.8px;
  text-transform: uppercase;
}
.pd-pill.in-progress { background: var(--warn-bg, #FEF4E6); color: var(--warn, #B8651E); }
.pd-pill.complete    { background: #E6F4EA; color: #1F7A3A; }
.pd-status-text { font-size: 13px; color: var(--text-soft); }

.pd-grid {
  display: grid; grid-template-columns: 1fr 1fr; gap: 1px;
  background: var(--border);
}
.pd-section { background: var(--surface); padding: 16px 22px; }
.pd-section-h {
  font-size: 11px; font-weight: 600; letter-spacing: 1.2px;
  text-transform: uppercase; color: var(--text-mute);
  margin-bottom: 10px;
}

/* Daily-closes table */
.pd-rows-scroll { max-height: 340px; overflow-y: auto; border: 1px solid var(--border); border-radius: var(--radius); }
table.data.pd-rows { width: 100%; font-size: 12.5px; }
table.data.pd-rows th, table.data.pd-rows td { padding: 6px 12px; }
table.data.pd-rows thead th {
  position: sticky; top: 0; background: var(--surface-2); z-index: 1;
  box-shadow: inset 0 -1px 0 var(--border);
}
table.data.pd-rows tr.pd-rows-sum td,
table.data.pd-rows tr.pd-rows-avg td { border-top: 1px solid var(--border); }
table.data.pd-rows tr.pd-rows-sum td { padding-top: 8px; }

/* Inputs side */
table.data.pd-inputs { width: 100%; font-size: 13px; margin-bottom: 16px; }
table.data.pd-inputs td { padding: 6px 0; border-bottom: 1px solid var(--border); }
table.data.pd-inputs tr:last-child td { border-bottom: none; }

.pd-formula {
  background: var(--surface-2); padding: 12px 14px;
  border-radius: var(--radius); border: 1px solid var(--border);
  font-size: 13px; color: var(--text);
  margin-bottom: 16px;
}
.pd-formula-line { padding: 2px 0; }
.pd-formula-result {
  border-top: 1px solid var(--border); margin-top: 6px; padding-top: 8px;
  font-weight: 700;
}

.pd-result {
  background: var(--surface-2); padding: 14px 16px;
  border-radius: var(--radius); border: 1px solid var(--border);
  text-align: center;
}
.pd-result-label {
  font-size: 11px; font-weight: 600; letter-spacing: 1.2px;
  text-transform: uppercase; color: var(--text-mute);
}
.pd-result-value {
  font-size: 32px; font-weight: 700; line-height: 1;
  margin-top: 6px; font-variant-numeric: tabular-nums;
  letter-spacing: -0.02em;
}
.pd-result-precise { font-size: 11.5px; color: var(--text-soft); margin-top: 4px; }
.lme-form { display: flex; flex-direction: column; gap: 6px; min-width: 160px; }
.lme-form label { font-size: 11px; color: var(--text-mute); }
.lme-form input {
  padding: 6px 9px; border: 1px solid var(--border-2);
  border-radius: var(--radius); font-family: inherit; font-size: 13px;
  font-variant-numeric: tabular-nums;
}
.lme-form .btn { padding: 6px 12px; }
.live-hero .lh-label {
  display: flex; align-items: center; gap: 10px;
  font-size: 11px; font-weight: 500; letter-spacing: 1.2px;
  text-transform: uppercase; color: var(--text-mute);
  margin-bottom: 10px;
}
.live-hero .lh-label .exchange {
  font-size: 13px; font-weight: 700; letter-spacing: 1.5px;
  color: var(--text);
}
/* Exchange logo floats at top-right of each live-hero card.
   Nominative use on an internal tool — clearly identifies the data source
   without being decorative. Hides gracefully if the file isn't present. */
.live-hero { position: relative; }
.lh-logo {
  position: absolute;
  top: 18px; right: 22px;
  height: 40px;
  max-width: 180px;
  width: auto;
  object-fit: contain;
  opacity: 0.95;
}
.live-hero .lh-change.placeholder { color: var(--text-mute); font-weight: 400; }
/* Flex layout keeps "$x.xxxx" and "/ lb" or "/ tonne" on the same line. */
.live-hero .lh-price {
  display: flex; align-items: baseline; gap: 4px;
  font-size: 38px; font-weight: 600; line-height: 1;
  font-variant-numeric: tabular-nums; letter-spacing: -0.02em;
  white-space: nowrap;
}
.live-hero .lh-unit {
  font-size: 16px; font-weight: 400; color: var(--text-mute);
  letter-spacing: 0; white-space: nowrap;
}
/* LME price has more digits ($12,571.00) — shrink slightly so it fits. */
.live-hero.lme .lh-price { font-size: 32px; }
.live-hero .lh-source {
  font-size: 11.5px; color: var(--text-soft); margin-top: 8px;
}
.live-hero .lh-change {
  font-size: 16px; font-weight: 500; margin-top: 6px;
  font-variant-numeric: tabular-nums;
}
.live-hero .lh-change.up   { color: var(--good); }
.live-hero .lh-change.down { color: var(--bad); }
.live-hero .lh-right { text-align: right; }
.live-hero .lh-asof {
  font-size: 12px; color: var(--text-mute); margin-bottom: 4px;
}
.live-hero .lh-asof.stale { color: var(--warn); }
.live-hero .lh-open { font-size: 12px; color: var(--text-mute); }
.live-hero .lh-open .sep { color: var(--border-2); margin: 0 6px; }

/* Width budget for COMEX data blocks. Fixed at a tight Excel-like value
   so columns hug their content. Projected Avg has more years than fit at
   this width → horizontal scroll inside .ms-scroll. */
:root { --comex-data-w: 940px; }

/* Wraps every block on the COMEX page so header, live hero, and data
   all share the same left/right edges and the page reads as one column.
   Centered horizontally so empty viewport space becomes balanced margin
   rather than a lopsided gap on the right. */
.comex-page { max-width: var(--comex-data-w); }

.card.comex-data { max-width: var(--comex-data-w); }

/* COMEX page: 2-column split (Spot Table | Stats), capped to match Projected Avg */
.comex-split { display: grid; grid-template-columns: 1fr 280px; gap: 18px; max-width: var(--comex-data-w); align-items: start; }
.comex-split > .card:first-child { display: flex; flex-direction: column; }
.comex-split > .card:first-child > .card-h,
.comex-split > .card:first-child > .filter-bar { flex: 0 0 auto; }
/* height is set inline by JS to match the stats card; overflow does the scroll */
.comex-split .spot-scroll { overflow-y: auto; overflow-x: auto; }
/* Keep ISO dates on one line — they're short and shouldn't wrap. */
.comex-split .spot-scroll table.data td:first-child,
.comex-split .spot-scroll table.data th:first-child { white-space: nowrap; }
.comex-split .spot-scroll table.data { margin: 0; }
.comex-split .spot-scroll thead th {
  position: sticky; top: 0; z-index: 2;
  background: var(--surface-2); /* keep header readable when scrolling */
  box-shadow: inset 0 -1px 0 var(--border);
}
.comex-split .stat-row {
  padding: 10px 0; border-top: 1px solid var(--border);
}
.comex-split .stat-row:first-child { border-top: none; padding-top: 0; }
.comex-split .lbl {
  font-size: 11px; color: var(--text-mute);
  text-transform: uppercase; letter-spacing: 0.6px; font-weight: 600;
}
.comex-split .val {
  font-size: 18px; font-weight: 500; margin-top: 4px;
  font-variant-numeric: tabular-nums; letter-spacing: -0.01em;
}
.comex-split .sub { font-size: 11.5px; color: var(--text-soft); margin-top: 2px; }
.comex-split .sub.hl { color: var(--text-mute); margin-top: 1px; }
.comex-split .sub.hl .sep { color: var(--border-2); padding: 0 4px; }
.comex-split .freshness { color: var(--good); margin-top: 3px; }
.comex-split .freshness.stale { color: var(--warn); font-weight: 500; }

/* Historical state — clearly distinct from "current price" view */
.card.historical { border-color: var(--warn); border-width: 2px; }
.card.historical .card-h { background: var(--warn-bg); }
.hist-tag {
  display: inline-block; padding: 2px 8px;
  background: var(--warn); color: white;
  font-size: 10px; font-weight: 700; letter-spacing: 1px;
  border-radius: 3px;
}
.hist-banner {
  padding: 10px 18px; background: var(--warn-bg);
  border-bottom: 1px solid var(--border);
  font-size: 12px; color: var(--text);
}
.hist-banner a { color: var(--warn); font-weight: 500; }
@media (max-width: 1100px) { .comex-split { grid-template-columns: 1fr; } }

/* Filter row on Purchase Log */
.filter-row {
  display: flex; gap: 8px; align-items: center; margin-bottom: 14px;
  flex-wrap: wrap;
}
.filter-row select, .filter-row input[type="text"] {
  padding: 5px 10px; font-size: 12px; height: 28px;
  border: 1px solid var(--border-2); border-radius: var(--radius);
  background: var(--surface); font-family: inherit;
}
.filter-row input[type="text"] { min-width: 260px; }

/* Bloomberg-ish monospace metadata strip */
.meta.mono { font-family: var(--font-mono); font-size: 11px;
  color: var(--text-mute); letter-spacing: 0.2px; }
.meta.mono .sep { color: var(--border-2); margin: 0 6px; }

/* Duplicate .pl-scroll scrollbar rules left over from an earlier
   iteration. Kept as a no-op safety in case some browser misses the
   earlier block; harmonized 2026-05-14. */
.pl-scroll::-webkit-scrollbar { height: 8px; width: 8px; }
.pl-scroll::-webkit-scrollbar-track { background: transparent; }
.pl-scroll::-webkit-scrollbar-thumb { background: rgba(0,0,0,0.13); border-radius: 4px; }
.pl-scroll::-webkit-scrollbar-thumb:hover { background: rgba(0,0,0,0.28); }

/* Prose on Methods page */
.prose { margin: 0; padding: 14px 22px; line-height: 1.65; font-size: 12.5px; }
.prose li { margin-bottom: 6px; }
.prose ol, .prose ul { padding-left: 20px; }
.prose p { margin: 6px 0; }

/* ───────────────────────────────────────────────────────────────────
   Design refinement pass — 2026-05-13
   Subtle depth + smoother interactions + cleaner spacing.
   Don't change brand colors here; the palette in :root is canon.
   ─────────────────────────────────────────────────────────────────── */

/* Cards get a barely-perceptible elevation so they feel like surfaces,
   not pasted rectangles. The shadow is so subtle it reads more as a
   "soft separation from the background" than as a 3D effect. */
.card, .detail-card, .kpi-row {
  box-shadow:
    0 1px 1px rgba(10, 10, 10, 0.025),
    0 1px 3px rgba(10, 10, 10, 0.03);
}

/* Smooth hover/focus transitions everywhere interactive. The 120ms is
   short enough to feel snappy, long enough to read as "deliberate". */
.btn, .chip, .chain-badge, .tag,
table.data tbody tr,
.pl-table tbody tr,
aside.nav li a {
  transition: background-color 120ms ease,
              color 120ms ease,
              border-color 120ms ease,
              box-shadow 120ms ease;
}

/* Focus rings — PTI Orange. Replaces the previous "border darkens on
   focus" which was easy to miss. Visible enough for accessibility,
   subtle enough not to feel cartoonish. */
.btn:focus-visible,
input[type="text"]:focus,
input[type="file"]:focus,
textarea:focus,
select:focus {
  outline: 2px solid var(--accent);
  outline-offset: 1px;
  border-color: var(--accent);
}

/* Buttons — slight refinement. Keep the existing palette mapping. */
.btn {
  font-weight: 500;
  letter-spacing: 0.1px;
  line-height: 1.4;
  padding: 6px 14px;
}
.btn:hover { box-shadow: 0 1px 2px rgba(10,10,10,0.06); }
.btn.primary { font-weight: 600; }
.btn.primary:hover { background: var(--accent-deep); border-color: var(--accent-deep); }

/* Page header — slightly more breathing room, sharper hierarchy. */
/* 2026-05-22 v4 — header→content gap pushed from 26 → 40px so the
   page title gets its own moment of air before the data starts.
   Padding-bottom kept slim (the underline tick was retired). */
.page-header { margin-bottom: 40px; padding-bottom: 0; }
/* Canonical page title — one size/weight/placement for every page so the
   title sits in the same spot on every tab (matched to Open Orders, which
   Josh picked as the reference). Pages that still scope their own
   `… h1 { font-size: 32px … }` now just restate this default; the older
   tabs that relied on the base size (Directory, Copper Pricing, Methods,
   Catalogue, Purchasing Report, the Library sub-pages) inherit it here.
   2026-06-23. */
.page-header h1 {
  font-size: 32px;
  letter-spacing: -0.025em;
  line-height: 1.1;
}
.page-header h1 .dim {
  font-size: 18px;
  font-weight: 400;
  color: var(--text-soft);
  margin-left: 6px;
}
.page-header .meta { font-size: 12px; color: var(--text-mute); }

/* 2026-05-22 v5 — old dark-sidebar active-state override deleted.
   Was setting `color: #FFFFFF` which is invisible on the new light
   cream sidebar. Active-state vocabulary now lives in the single
   sidebar block at the top of the stylesheet. */

/* 2026-05-22 v3 — old hover + thead-th override deleted. They were
   forcing weight 600 / dark text / grey-cream shelf on top of the
   principled block in the Tables section above, and the override
   was winning (admin-panel look). Canon-compliant styles now live
   in the single Tables block. Kept the global tabular-nums for
   consistent column alignment in any numeric body cells that don't
   explicitly set it. */
table.data tbody td { font-variant-numeric: tabular-nums; }

/* ─── Settlement chain card on invoice detail ─── */
.settles-list, .settled-by-list {
  list-style: none; padding: 0; margin: 0;
}
.settles-list li, .settled-by-list li {
  padding: 4px 0;
  font-size: 13px;
}
.settles-list .muted, .settled-by-list .muted {
  color: var(--text-mute); font-size: 12px;
}

/* ─── Purchase Log row table — used by the /test-ingest debug page ─── */
.purchase-log-row-tbl {
  width: 100%;
  border-collapse: collapse;
  font-size: 13px;
  font-variant-numeric: tabular-nums;
}
.purchase-log-row-tbl thead th {
  text-align: left; padding: 6px 10px;
  font-size: 10.5px; font-weight: 600;
  text-transform: uppercase; letter-spacing: 0.6px;
  color: var(--text-mute);
  background: var(--surface-2);
  border-bottom: 1px solid var(--border);
}
.purchase-log-row-tbl tbody td {
  padding: 4px 10px;
  border-bottom: 1px solid var(--border);
}
.purchase-log-row-tbl tbody tr.manual {
  background: rgba(168, 90, 28, 0.04);  /* PTI Orange tinted 4% */
}
.purchase-log-row-tbl tbody tr.manual td:nth-child(2) {
  color: var(--accent-deep);
  font-weight: 500;
}
.purchase-log-row-tbl .num { text-align: right; }

/* ─── Status page cards ─── */
.status-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
  gap: 18px;
}
.status-kpi-label {
  font-size: 10.5px; text-transform: uppercase;
  letter-spacing: 0.7px; color: var(--text-mute); font-weight: 600;
}
.status-kpi-value {
  font-size: 30px; font-weight: 600;
  letter-spacing: -0.02em; line-height: 1.1;
  margin-top: 4px;
}

/* ─── Test ingest form ─── */
.test-ingest-form .kv {
  grid-template-columns: 180px 1fr;
  gap: 10px 12px;
  align-items: center;
}
.test-ingest-form input[type="text"],
.test-ingest-form input[type="file"] {
  max-width: 480px;
}
.test-ingest-result {
  margin-top: 16px;
}
.test-ingest-result pre {
  overflow-x: auto;
  font-size: 12px;
  background: var(--surface-2);
  padding: 12px;
  border-radius: var(--radius);
}

/* ─── Bulk action bar on inbox ─── */
.bulk-bar {
  padding: 10px 14px; margin-bottom: 10px;
  background: rgba(168, 90, 28, 0.08);
  border: 1px solid var(--accent);
  border-radius: var(--radius);
  display: flex; gap: 10px; align-items: center;
}
.bulk-bar .count {
  color: var(--text-mute);
  margin-right: 8px;
  font-variant-numeric: tabular-nums;
}

/* ─── Activity-counter strip on /audit ─── */
.activity-counters {
  padding: 12px 16px; margin-bottom: 16px;
  display: flex; gap: 28px; flex-wrap: wrap;
  font-size: 13px;
}
.activity-counters .muted { color: var(--text-mute); margin-right: 4px; }
.activity-counters strong {
  font-variant-numeric: tabular-nums;
  letter-spacing: -0.01em;
}

/* ─── KV layouts everywhere get consistent spacing ─── */
.kv { gap: 8px 16px; }
.kv .k { font-size: 12px; }
.kv .v { font-size: 13px; }

/* ─── Selection highlight — PTI Orange ─── */
::selection { background: rgba(168, 90, 28, 0.20); color: var(--text); }

/* ─── Print: keep it clean if Eric ever prints a page ─── */
@media print {
  aside.nav, .filter-row, .action-bar, .bulk-bar { display: none; }
  .card, .detail-card { box-shadow: none; border: 1px solid #ccc; }
  main.content { padding: 16px; max-width: 100%; }
}

/* ─── Status page additional utilities (added 2026-05-13) ─── */
.subsection-h {
  margin: 22px 0 10px 0;
  font-size: 11.5px; font-weight: 600;
  letter-spacing: 0.6px; text-transform: uppercase;
  color: var(--text-mute);
}
.hint-text {
  margin: 10px 0 0 0;
  color: var(--text-mute); font-size: 12px;
}
table.data tbody tr.drift-warn { background: var(--bad-bg); }
table.data tbody tr.drift-warn:hover { background: #F2D5CF; }

/* ─── No-activity warning card ─── */
.warn-card {
  padding: 16px 20px;
  background: var(--warn-bg);
  border: 1px solid var(--warn);
  border-radius: var(--radius-lg);
  margin-top: 8px;
}
.warn-card h3 { margin-top: 0; color: var(--warn); }
.warn-card ol { padding-left: 22px; }
.warn-card code {
  font-family: var(--font-mono); background: rgba(0,0,0,0.05);
  padding: 1px 5px; border-radius: 3px; font-size: 11.5px;
}

/* ─── Test ingest page polish ─── */
.test-ingest-result h4 {
  margin: 18px 0 8px 0;
  font-size: 11.5px; font-weight: 600;
  letter-spacing: 0.6px; text-transform: uppercase;
  color: var(--text-mute);
}
.test-ingest-result .kv {
  grid-template-columns: 200px 1fr;
}

/* ─── Section headers (h4 inside cards) ─── */
.detail-card h4 {
  margin: 22px 0 10px 0;
  font-size: 11.5px; font-weight: 600;
  letter-spacing: 0.6px; text-transform: uppercase;
  color: var(--text-mute);
}

/* ─── Page subtitle (the explanatory paragraph under a page h1) ─── */
.page-subtitle {
  margin: 6px 0 0 0;
  font-size: 13px;
  color: var(--text-mute);
  max-width: 720px;
}

/* Export CSV button — slight left margin to separate from year picker */
.export-csv-btn { margin-left: 10px; }

/* 2026-05-22 v5 — old dark-sidebar nav refinements deleted. They
   were forcing white text, translucent-white suffixes, uppercase
   tracked sys keys, and tighter padding on top of the principled
   light-cream sidebar block at the top of the stylesheet. With the
   sidebar palette flipped to cream, every white-on-anything rule
   went invisible. Active-state, .nav-count, .nav-suffix, .sys, and
   nav-link styles all live in the single sidebar block now. */
.sys { margin-top: auto; }   /* keep sys footer pinned to bottom */

/* ─── Methods Reference page layout ─── */
.stack-card { margin-bottom: 16px; }

/* Two-column reference grid for related small tables */
.ref-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 16px;
  margin-bottom: 16px;
}
.ref-grid > .card { margin-bottom: 0; }
@media (max-width: 900px) {
  .ref-grid { grid-template-columns: 1fr; }
}

/* ─── Final polish pass (2026-05-13) ─── */

/* Empty-state typography — make these moments feel intentional, not "page error". */
.empty {
  padding: 60px 24px;
}
.empty h4 {
  font-size: 17px;
  margin-bottom: 8px;
  font-weight: 600;
  color: var(--text);
  letter-spacing: -0.011em;
}
.empty p {
  margin: 6px auto;
  max-width: 520px;
  font-size: 13.5px;
  line-height: 1.55;
  color: var(--text-mute);
}
.empty p.muted { color: var(--text-soft); font-size: 12.5px; }
.empty code {
  font-family: var(--font-mono);
  background: var(--surface-2);
  padding: 1px 6px;
  border-radius: 3px;
  font-size: 12px;
  color: var(--text);
}
.empty a { color: var(--accent-deep); }
.empty a:hover { color: var(--accent); }

/* Section divider — used between large content blocks for breathing room */
.section-divider {
  height: 1px;
  background: var(--border);
  margin: 32px 0 28px;
  border: none;
}

/* Page header subtitle paragraph already styled via .page-subtitle. Keep
   it consistent: limit width so long copy doesn't run edge-to-edge. */

/* 2026-05-22 — the page-header underline + orange tick was retired.
   See the .page-header block higher up for the rationale. Keeping
   the relative-positioning hook in case future markup needs it,
   but the ::after accent is gone. */
.page-header { position: relative; }

/* Inline mono text — small refinement so addresses, IDs, dates feel like data */
.mono {
  font-family: var(--font-mono);
  font-size: 0.92em;
  letter-spacing: -0.01em;
}

/* Card transitions on hover — tactile feel without being pushy */
.card:hover {
  border-color: var(--border-2);
}

/* Detail-card h3 (small caps section header) — slightly tighter */
.detail-card h3 {
  font-size: 11px;
  letter-spacing: 0.85px;
  margin-bottom: 14px;
}

/* Generous detail-card padding when it stands alone */
.detail-card { padding: 18px 20px; }

/* ─── Prominent error banner (test ingest + future use) ─── */
.error-banner {
  display: flex; gap: 12px;
  padding: 14px 16px;
  margin: 0 0 18px 0;
  background: var(--bad-bg);
  border: 1px solid var(--bad);
  border-radius: var(--radius-lg);
  align-items: flex-start;
}
.error-banner-icon {
  font-size: 18px; line-height: 1;
  color: var(--bad);
  flex-shrink: 0;
  padding-top: 2px;
}
.error-banner-body { flex: 1; }
.error-banner-body strong {
  display: block;
  font-size: 14px;
  color: var(--bad);
  margin-bottom: 4px;
  letter-spacing: -0.01em;
}
.error-banner-body p {
  margin: 4px 0 0 0;
  font-size: 13px;
  line-height: 1.5;
  color: var(--text);
}
.error-banner-body p.muted {
  color: var(--text-mute);
  font-size: 12.5px;
}
.error-banner-body code {
  font-family: var(--font-mono);
  background: rgba(0,0,0,0.05);
  padding: 1px 5px;
  border-radius: 3px;
  font-size: 11.5px;
  color: var(--text);
}
.error-banner-body a {
  color: var(--bad);
  text-decoration: underline;
}

/* ─── Quick-filter pills (inbox) ─────────────────────────────────────
   One-tap shortcuts for the most common triage views, sitting above
   the full filter form. Added 2026-05-13. */
.quick-pills {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  align-items: center;
  margin: 0 0 12px 0;
}
.quick-pills .pill {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 5px 11px;
  font-size: 12.5px;
  font-weight: 500;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 999px;
  color: var(--text-mute);
  text-decoration: none;
  cursor: pointer;
  transition: background 120ms ease, border-color 120ms ease, color 120ms ease;
  font-family: inherit;
  line-height: 1.4;
}
.quick-pills .pill:hover {
  background: var(--surface-2);
  border-color: var(--border-2);
  color: var(--text);
}
.quick-pills .pill.active {
  background: var(--accent);
  border-color: var(--accent);
  color: #FFFFFF;
  font-weight: 600;
}
.quick-pills .pill.active:hover {
  background: var(--accent-deep);
  border-color: var(--accent-deep);
  color: #FFFFFF;
}
.quick-pills .pill.ghost {
  background: transparent;
  font-size: 12px;
  color: var(--text-soft);
}
.quick-pills .pill.ghost:hover {
  background: var(--surface-2);
  color: var(--text);
}
.quick-pills .pill-spacer { flex: 1; }

/* Vertical divider between pill groups (e.g. "confidence filters" vs
   "supplier filters" on the inbox). Added 2026-05-13. */
.quick-pills .pill-divider {
  display: inline-block;
  width: 1px;
  height: 18px;
  background: var(--border-2);
  margin: 0 4px;
  align-self: center;
}

/* ─── Keyboard shortcuts modal ───────────────────────────────────────
   Hidden until ? pressed or shortcuts pill clicked. Added 2026-05-13. */
.modal-overlay {
  position: fixed;
  inset: 0;
  background: rgba(10,10,10,0.45);
  z-index: 1000;
  display: none;
  align-items: center;
  justify-content: center;
  animation: modal-fade-in 120ms ease;
}
@keyframes modal-fade-in {
  from { opacity: 0; }
  to   { opacity: 1; }
}
.modal-card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  box-shadow: 0 12px 40px rgba(0,0,0,0.25);
  width: 440px;
  max-width: calc(100vw - 32px);
  max-height: calc(100vh - 64px);
  overflow: auto;
  padding: 0;
}
.modal-card .modal-h {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 16px 20px 12px;
  border-bottom: 1px solid var(--border);
}
.modal-card .modal-h h3 {
  margin: 0;
  font-size: 14px;
  font-weight: 600;
  letter-spacing: -0.01em;
  text-transform: none;
  color: var(--text);
}
.btn.small { font-size: 11.5px; padding: 4px 10px; }

.shortcuts-table {
  margin: 0;
  width: 100%;
  border: none;
}
.shortcuts-table td {
  padding: 9px 16px;
  font-size: 13px;
  border-bottom: 1px solid var(--border);
  color: var(--text);
}
.shortcuts-table tr:last-child td { border-bottom: none; }
.shortcuts-table td:first-child {
  width: 130px;
  white-space: nowrap;
}
kbd {
  display: inline-block;
  padding: 2px 7px;
  font-family: var(--font-mono);
  font-size: 11.5px;
  font-weight: 600;
  background: var(--surface-2);
  border: 1px solid var(--border-2);
  border-radius: 4px;
  box-shadow: 0 1px 0 var(--border-2);
  color: var(--text);
  line-height: 1.2;
  vertical-align: middle;
  margin: 0 1px;
}

/* ─── Keyboard-driven row highlight (inbox) ─────────────────────── */
tbody tr.kbd-highlight,
tbody tr.kbd-highlight td {
  background: rgba(168, 90, 28,0.06) !important;
}
tbody tr.kbd-highlight {
  box-shadow: inset 3px 0 0 var(--accent);
}

/* ─── Test Ingest — drop zone ───────────────────────────────────────
   Replaces the native <input type="file"> which looks like Windows 95.
   Click anywhere in the zone (it's a <label for>) or drag a PDF onto it.
   Big prominent Upload button inside, modeled on the myCourses-style
   drop modal. Added 2026-05-13, refined again per Josh's feedback. */
.drop-zone {
  display: flex;
  flex-direction: column;
  align-items: center;
  /* Top-aligned content so the headline sits near the top of the box
     instead of vertically centered. Matches the myCourses upload modal
     where "Drop files here, or click below!" hugs the top. */
  justify-content: flex-start;
  padding: 28px 24px 48px;
  border: 2px dashed var(--border-2);
  border-radius: var(--radius-lg, 10px);
  background: var(--surface-soft);
  cursor: pointer;
  transition: background 120ms ease, border-color 120ms ease;
  text-align: center;
  margin-bottom: 14px;
  min-height: 200px;
}
.drop-zone:hover {
  background: var(--surface);
  border-color: var(--accent);
}
.drop-zone.drag-active {
  background: rgba(168, 90, 28, 0.06);
  border-color: var(--accent);
  border-style: solid;
}
.drop-zone.has-file {
  background: var(--surface);
  border-color: var(--good, #2C8C5F);
  border-style: solid;
}

.drop-zone .dz-prompt {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 14px;
}
.drop-zone .dz-headline {
  font-size: 15px;
  font-weight: 500;
  color: var(--text-mute);
  letter-spacing: -0.005em;
}
.drop-zone:hover .dz-headline { color: var(--text); }

/* The big prominent Upload button — looks tappable, has the PTI orange
   on hover. This is the affordance the user wanted to be more obvious. */
.drop-zone .dz-upload-btn {
  display: inline-flex;
  align-items: center;
  gap: 9px;
  padding: 12px 24px;
  font-size: 15px;
  font-weight: 600;
  color: var(--text);
  background: var(--surface-2);
  border: 1px solid var(--border-2);
  border-radius: 8px;
  transition: background 120ms ease, border-color 120ms ease,
              color 120ms ease, transform 80ms ease;
  letter-spacing: -0.005em;
}
.drop-zone .dz-upload-btn svg { color: var(--text-mute); }
.drop-zone:hover .dz-upload-btn {
  background: var(--accent);
  border-color: var(--accent);
  color: #FFFFFF;
}
.drop-zone:hover .dz-upload-btn svg { color: #FFFFFF; }
.drop-zone:active .dz-upload-btn { transform: translateY(1px); }

.drop-zone .dz-hint {
  font-size: 11.5px;
  color: var(--text-mute);
  letter-spacing: 0.3px;
}

.drop-zone .dz-filename {
  font-family: var(--font-mono);
  font-size: 14px;
  font-weight: 600;
  color: var(--text);
  background: var(--surface-2);
  padding: 12px 18px;
  border-radius: 6px;
  max-width: 80%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  border: 1px solid var(--border-2);
}
.drop-zone.has-file .dz-filename {
  border-color: var(--good, #2C8C5F);
}
.drop-zone.has-file .dz-filename::before {
  content: "✓  ";
  color: var(--good, #2C8C5F);
  font-weight: 700;
}

/* ─── Drop-zone file chips ──────────────────────────────────────
   Per-file "chip" with a × remove button, rendered inside the drop
   zone's file-list area. Modeled after Claude's file-attachment
   chips — each chip is its own pill so users can drop individual
   files from the queue before clicking Upload. */
.drop-zone .dz-filelist {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  justify-content: center;
  align-items: flex-start;
  padding: 4px 8px;
  max-width: 100%;
}
.drop-zone .dz-chip {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 6px 6px 6px 10px;
  background: var(--surface);
  border: 1px solid var(--border-2);
  border-radius: 999px;
  font-size: 12.5px;
  font-family: var(--font-mono, ui-monospace, "SF Mono", Menlo, monospace);
  color: var(--text);
  max-width: 320px;
  transition: border-color 0.12s, background 0.12s, box-shadow 0.12s;
}
.drop-zone .dz-chip:hover {
  border-color: var(--text-soft);
  background: var(--surface-2);
}
.drop-zone .dz-chip-check {
  color: #1d7a3d;
  font-weight: 700;
  font-size: 13px;
  line-height: 1;
}
.drop-zone .dz-chip-name {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  max-width: 220px;
  font-weight: 500;
}
.drop-zone .dz-chip-size {
  font-size: 10.5px;
  color: var(--text-mute);
  font-variant-numeric: tabular-nums;
  font-family: var(--font-sans, system-ui);
}
.drop-zone .dz-chip-remove {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 20px;
  height: 20px;
  margin-left: 2px;
  padding: 0;
  background: transparent;
  border: 1px solid transparent;
  border-radius: 50%;
  color: var(--text-mute);
  font-size: 15px;
  line-height: 1;
  font-family: inherit;
  cursor: pointer;
  transition: background 0.12s, color 0.12s, border-color 0.12s;
}
.drop-zone .dz-chip-remove:hover {
  background: #FEE2E2;
  color: #B91C1C;
  border-color: #F5C2C2;
}
.drop-zone .dz-chip-remove:focus-visible {
  outline: 2px solid rgba(233, 123, 47, 0.45);
  outline-offset: 1px;
}

/* ─── Test Ingest — collapsed metadata ──────────────────────────── */
.ti-metadata {
  margin-bottom: 14px;
  background: var(--surface-soft);
  border: 1px solid var(--border);
  border-radius: 6px;
  padding: 0;
}
.ti-metadata > summary {
  cursor: pointer;
  padding: 10px 14px;
  list-style: none;
  display: flex;
  align-items: baseline;
  gap: 10px;
  font-size: 13px;
}
.ti-metadata > summary::-webkit-details-marker { display: none; }
.ti-metadata > summary::before {
  content: "▶";
  font-size: 9px;
  color: var(--text-mute);
  transition: transform 120ms ease;
  display: inline-block;
}
.ti-metadata[open] > summary::before { transform: rotate(90deg); }
.ti-metadata .ti-meta-title {
  font-weight: 600;
  color: var(--text);
}
.ti-metadata .ti-meta-hint {
  font-size: 11.5px;
  color: var(--text-mute);
}
.ti-metadata .ti-meta-grid {
  padding: 4px 14px 14px;
  border-top: 1px solid var(--border);
}
.ti-metadata .ti-field-hint {
  font-size: 11px;
  color: var(--text-mute);
  margin-top: 3px;
  line-height: 1.4;
}

/* ─── Dependency-status dots (status page OCR section) ──────────── */
.dep-dot {
  display: inline-block;
  width: 9px;
  height: 9px;
  border-radius: 50%;
  vertical-align: middle;
  margin-right: 6px;
  box-shadow: 0 0 0 2px rgba(0,0,0,0.04);
}
.dep-dot.ok  { background: var(--good, #2C8C5F); }
.dep-dot.bad { background: var(--bad, #C0392B); }

/* ─── Mobile + narrow-viewport adaptations ──────────────────────────
   The dashboard is desktop-first because it's an internal tool used at a
   desk. But Eric might check the inbox from his phone on the road, and a
   broken mobile layout is a "looks unfinished" signal. These rules kick
   in below 720px. Added 2026-05-13. */

/* Filter-pill bars (.lib-filter-bar) are declared inline per template
   (invoices, po_list, credits, catalogue_vendor…) but none of them set
   wrapping, so at narrow widths the pill row overflowed the viewport.
   One central rule: pills wrap onto extra rows instead. The count label
   (margin-left:auto) right-aligns on whichever row it lands. (2026-06-09) */
.lib-filter-bar {
  flex-wrap: wrap;
  row-gap: 8px;
}

/* ── Per-row action menu (PO library) — kebab at every width (2026-06-09) ─
   The three row actions (Download / Move / Delete) live in one <details>
   per row, collapsed to a "⋯" kebab at ALL widths. Inline they were a
   286px column — the widest in the table, and visual noise on every row.
   Behind the kebab the column is ~44px: the data columns stay wide, the
   destructive Delete is one level out of reach, and it matches the
   maintenance kebab in the page header. Click toggles the popover —
   native <details>, no JS. */
.row-actions { display: inline-block; position: relative; }
.row-actions-kebab {
  display: inline-block;
  list-style: none;
  cursor: pointer;
  padding: 4px 10px;
  font-size: 18px;
  line-height: 1;
  letter-spacing: 1px;
  color: #5A4A38;
  border-radius: 6px;
  transition: background 0.12s, color 0.12s;
}
.row-actions-kebab::-webkit-details-marker { display: none; }
.row-actions-kebab:hover { background: #FFF1E5; color: #8A4A16; }
.row-actions[open] .row-actions-kebab { background: #FFF1E5; color: #8A4A16; }
/* Closed: list hidden (native <details>). Open: right-anchored popover,
   one action per row. */
.row-actions > .row-actions-list { display: none; }
.row-actions[open] > .row-actions-list {
  display: flex;
  flex-direction: column;
  align-items: stretch;
  position: absolute;
  top: calc(100% + 4px);
  right: 0;
  z-index: 30;
  min-width: 150px;
  padding: 6px;
  background: #FFFFFF;
  border: 1px solid #ECE3D5;
  border-radius: 10px;
  box-shadow: 0 8px 24px rgba(61, 40, 23, 0.14);
}
.row-actions[open] > .row-actions-list form { display: block; margin: 0; }
.row-actions[open] > .row-actions-list .btn-text {
  display: block;
  width: 100%;
  margin: 0;
  text-align: left;
}

/* ── Split-screen band — 721px to 1100px (2026-06-09) ──────────────
   Half of a 1920 or 1536 display: the everyday "site next to Outlook"
   layout. Full-desktop spacing doesn't survive here — 220px sidebar
   + 40px gutters + 20px-per-side table cells blew the Library tables
   out of their card and put a horizontal scrollbar on the whole page.
   Gutters and table cells tighten — same hierarchy, same type sizes,
   just less air, so the row content keeps the width. (Wide tables are
   further contained by .table-scroll below.) */
@media (max-width: 1100px) and (min-width: 721px) {
  main.content { padding: 24px 28px 48px; }
  table.data th { padding: 14px 11px 10px; }
  table.data td { padding: 16px 11px; }
}

/* Contained table scroll (2026-06-09; reworked 2026-06-23). The invoice
   ledger is wider than main.content's 1400px cap (8 cols, nowrap supplier/
   service, item chips), so it has to be CONTAINED — left to spill it pushes
   Type/Actions past the card and puts a page-level scrollbar on the chrome
   ("content overflowing instead of loading nicely with scroll").
     This wants two things that fight each other on one wrapper: contain the
   horizontal overflow (overflow-x:auto) AND keep the sticky `thead th{top:0}`
   pinned while you scroll. A bare overflow-x:auto makes the wrapper an
   unbounded scroll box (overflow-y computes to auto too), so the header
   scrolls away with the page; pure overflow:visible spills the table out of
   the card. Both were tried and both read as broken.
     The fix that satisfies both: bound the wrapper height and let it scroll
   in BOTH axes. The table viewport never exceeds the screen, so nothing
   spills; the sticky header anchors to the top of this region and stays put
   as the rows scroll under it. .table-scroll is invoice-only, so this is
   scoped safely (no kebab-popover tables are wrapped). The .is-expanded
   fullscreen path sets its own flex-driven overflow:auto and overrides the
   max-height below. */
.inv-wrap:not(.is-expanded) .table-scroll {
  max-height: calc(100vh - 210px);
  overflow: auto;
}
.inv-wrap.is-expanded .table-scroll { max-height: none; }

@media (max-width: 720px) {
  /* Collapse the sidebar into a top strip on narrow screens. The nav
     becomes a horizontal scroll of links. */
  aside.nav {
    position: relative;
    width: 100%;
    height: auto;
    min-height: 0;
    padding: 12px 14px;
    border-right: none;
    border-bottom: 1px solid var(--border);
    flex-direction: row;
    overflow-x: auto;
    overflow-y: hidden;
  }
  aside.nav .brand { margin-bottom: 0; margin-right: 16px; flex-shrink: 0; }
  aside.nav ul {
    display: flex;
    flex-direction: row;
    gap: 14px;
    margin: 0;
    padding: 0;
    flex-shrink: 0;
  }
  aside.nav li { white-space: nowrap; }
  aside.nav .sys { display: none; }   /* hide sys-stats on mobile */
  main {
    margin-left: 0 !important;
    padding: 16px 14px;
  }

  /* Tables → render as stacked rows (data attributes act as inline
     labels via ::before). Used for inbox + purchase-log on mobile. */
  table.data thead { display: none; }
  table.data tbody tr {
    display: block;
    border-bottom: 1px solid var(--border);
    padding: 12px 8px;
    margin-bottom: 8px;
    background: var(--surface);
    border-radius: var(--radius);
  }
  table.data tbody td {
    display: flex;
    justify-content: space-between;
    align-items: center;
    padding: 4px 8px;
    border: none;
    font-size: 13px;
  }
  table.data tbody td.num,
  table.data tbody td.mono {
    text-align: left;     /* keep alignment consistent in stacked view */
  }
  table.data tbody td::before {
    content: attr(data-label);
    font-weight: 600;
    color: var(--text-mute);
    font-size: 11.5px;
    text-transform: uppercase;
    letter-spacing: 0.4px;
    margin-right: 12px;
  }

  /* KPI strip wraps onto two rows */
  .pl-kpis { grid-template-columns: 1fr 1fr; }

  /* Quick-filter pills wrap nicely */
  .quick-pills { flex-wrap: wrap; row-gap: 6px; }

  /* Forms become single-column */
  .filter-row {
    flex-direction: column;
    align-items: stretch;
    gap: 8px;
  }
  .filter-row input,
  .filter-row select { width: 100%; }

  /* Page header — title above meta block */
  .page-header {
    flex-direction: column;
    align-items: flex-start;
    gap: 10px;
  }
  .page-header .meta {
    flex-wrap: wrap;
    gap: 8px;
  }

  /* Detail-page kv pairs stack to one column */
  .kv {
    grid-template-columns: 1fr;
    gap: 4px;
  }
  .kv .k {
    font-size: 11px;
    color: var(--text-mute);
    text-transform: uppercase;
    letter-spacing: 0.4px;
    margin-top: 8px;
  }

  /* Reference grid stacks */
  .ref-grid { grid-template-columns: 1fr; }
  .status-grid { grid-template-columns: 1fr 1fr; }
}

@media (max-width: 420px) {
  /* Very narrow phones — tighter padding everywhere */
  main { padding: 12px 10px; }
  .page-header h1 { font-size: 22px; }
  .pl-kpis { grid-template-columns: 1fr; }
  .status-grid { grid-template-columns: 1fr; }
  /* KPI value tiles shrink slightly */
  .pl-kpi-value, .status-kpi-value { font-size: 22px; }
}

/* ─── Monthly Summary — two-line header styling ───────────────────
   Each <th> contains a bold short label + a small muted unit. Way
   easier to scan than "Total Finance Charges ($USD)" on one line.
   Added 2026-05-13 per Josh's feedback. */
.data.ms-headers thead th {
  vertical-align: bottom;
  padding: 12px 12px 9px;
  background: var(--surface);
  /* Single subtle bottom rule. Dropped the orange top edge + heavy 2px
     bottom for a cleaner look — Josh said the headers read sloppy
     2026-05-14. */
  border-top: none;
  border-bottom: 1px solid var(--border-2);
  text-align: right;
  white-space: nowrap;
}
.data.ms-headers thead th.sticky-month { text-align: left; }
.data.ms-headers thead th .th-label {
  display: block;
  font-size: 12px;
  font-weight: 600;
  letter-spacing: -0.005em;
  color: var(--text);
  text-transform: capitalize;   /* "lbs purchased" → "Lbs Purchased"; "COMEX" stays uppercase */
  line-height: 1.25;
}
.data.ms-headers thead th .th-unit {
  display: block;
  font-size: 10.5px;
  font-weight: 400;
  color: var(--text-soft);
  text-transform: none;        /* lowercase reads softer than ALL CAPS */
  letter-spacing: 0;
  margin-top: 2px;
  font-family: inherit;        /* drop mono — it clashed with the body font */
}
.data.ms tbody td {
  padding: 11px 12px;       /* roomier than the default 6/8 */
  font-size: 12.5px;
}
.data.ms tbody tr:hover td { background: var(--surface-soft); }
.data.ms tbody tr.total-row td {
  font-weight: 600;
  background: var(--surface-2);
  border-top: 2px solid var(--border-2);
}
/* Month-column styling, per row state. Updated 2026-05-14: dropped
   the italic body-font look (Josh: "kinda sloppy"). Now:
     realized  (default): system font, semibold, primary text color
     current   (.is-current on tr): PTI orange, bold, no italic
     projected (.projected on tr): grey, regular weight, no italic
   The data-cells of projected rows still get the soft-grey tint
   so they read as "projection only".
*/
.data.ms tbody td.sticky-month {
  font-style: normal;
  font-weight: 600;
  color: var(--text);
  font-family: inherit;
}
.data.ms tbody tr.projected td {
  background: rgba(0,0,0,0.015);
  color: var(--text-mute);
}
.data.ms tbody tr.projected td.sticky-month {
  font-weight: 400;
  color: var(--text-soft);
  font-style: normal;
}
.data.ms tbody tr.is-current td.sticky-month {
  font-weight: 700;
  color: var(--accent);
}

/* ─── Chart return strip ───────────────────────────────────────────
   Yahoo Finance-style summary bar that updates as the user hovers the
   chart. Shows current price + date, and the absolute + percent change
   from the start of the visible range. Added 2026-05-13. */
.chart-return-strip {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  padding: 10px 18px;
  background: var(--surface);
  border-bottom: 1px solid var(--border);
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  gap: 16px;
  flex-wrap: wrap;
}
.chart-return-strip .crs-left,
.chart-return-strip .crs-right {
  display: flex;
  align-items: baseline;
  gap: 10px;
}
.chart-return-strip .crs-price {
  font-size: 18px;
  font-weight: 700;
  color: var(--text);
  letter-spacing: -0.012em;
}
.chart-return-strip .crs-date {
  font-size: 11px;
  color: var(--text-mute);
  letter-spacing: 0.4px;
}
.chart-return-strip .crs-label {
  font-size: 10.5px;
  text-transform: uppercase;
  letter-spacing: 0.7px;
  color: var(--text-mute);
  font-family: var(--font-sans);
}
.chart-return-strip .crs-change {
  font-size: 15px;
  font-weight: 600;
}
.chart-return-strip .crs-pct {
  font-size: 13px;
  font-weight: 600;
}
.chart-return-strip .crs-change.up,
.chart-return-strip .crs-pct.up   { color: var(--good, #2C8C5F); }
.chart-return-strip .crs-change.down,
.chart-return-strip .crs-pct.down { color: var(--bad, #C0392B); }

/* ─── Chart range pills ────────────────────────────────────────────
   Yahoo Finance-style range selector below the chart card header.
   Active pill uses PTI Orange. Added 2026-05-13. */
.range-pills {
  display: flex;
  gap: 4px;
  padding: 10px 14px;
  flex-wrap: wrap;
  background: var(--surface-soft);
  border-bottom: 1px solid var(--border);
}
.range-pills .range-pill {
  border: 1px solid transparent;
  background: transparent;
  color: var(--text-mute);
  font-family: var(--font-mono);
  font-size: 11.5px;
  font-weight: 600;
  letter-spacing: 0.4px;
  padding: 5px 10px;
  border-radius: 4px;
  cursor: pointer;
  transition: background 100ms ease, color 100ms ease, border-color 100ms ease;
  line-height: 1.2;
  min-width: 36px;
  text-align: center;
}
.range-pills .range-pill:hover {
  background: var(--surface-2);
  color: var(--text);
  border-color: var(--border);
}
.range-pills .range-pill.active {
  background: var(--accent);
  color: #FFFFFF;
  border-color: var(--accent);
}
.range-pills .range-pill.active:hover {
  background: var(--accent-deep, #C75A18);
  border-color: var(--accent-deep, #C75A18);
}

/* ─── Purchase Log — sticky top scrollbar ───────────────────────────
   Mirrors the table's horizontal scrollLeft via JS so the user can pan
   left/right without first scrolling down to find the native bar.
   Added 2026-05-13 per Josh's feedback. */
.pl-top-scroll {
  position: sticky;
  top: 0;
  z-index: 30;
  overflow-x: auto;
  overflow-y: hidden;
  height: 14px;
  margin: 0 0 6px 0;
  background: var(--surface);
  border-bottom: 1px solid var(--border);
  border-radius: var(--radius) var(--radius) 0 0;
}
#pl-top-scroll-spacer {
  height: 1px;             /* needs *something* so scrollbar appears */
  width: 4000px;           /* overridden by JS to match table.scrollWidth */
}
/* Make the scrollbar a bit chunkier so it's an obvious affordance */
.pl-top-scroll::-webkit-scrollbar { height: 12px; }
.pl-top-scroll::-webkit-scrollbar-thumb {
  background: var(--border-2);
  border-radius: 6px;
}
.pl-top-scroll::-webkit-scrollbar-thumb:hover { background: var(--text-mute); }
.pl-top-scroll::-webkit-scrollbar-track {
  background: var(--surface-soft);
}
/* Firefox */
.pl-top-scroll { scrollbar-color: var(--border-2) var(--surface-soft); scrollbar-width: thin; }


/* ═══════════════════════════════════════════════════════════════════
   Contact picker (PO detail page, Compose & Send section)
   Two-column control per row: radio for "primary", checkbox for "CC".
   Primary row gets a subtle orange tint + a small PRIMARY tag.
   ═════════════════════════════════════════════════════════════════ */
/* Tabular contact picker: Primary · CC · Person · Email. */
.contact-table {
  width: 100%;
  border-collapse: collapse;
  font-size: 13px;
  border: 1px solid var(--border-2);
  border-radius: 6px;
  overflow: hidden;
  background: var(--surface-2);
}
.contact-table thead th {
  font-size: 9.5px;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  font-weight: 600;
  color: var(--text-mute);
  text-align: left;
  padding: 7px 10px;
  border-bottom: 1px solid var(--border-2);
  white-space: nowrap;
}
.contact-table th.ct-pick,
.contact-table td.ct-pick { text-align: center; width: 56px; }
.contact-table tbody td {
  padding: 7px 10px;
  border-top: 1px solid var(--border-2);
  vertical-align: middle;
}
.contact-table input[type="radio"]    { accent-color: #C0712F; cursor: pointer; }
.contact-table input[type="checkbox"] { accent-color: var(--accent); cursor: pointer; }
.contact-row:hover td { background: var(--surface); }
.contact-row[data-is-primary="1"] td { background: #FFF8F0; }
.contact-row[data-is-primary="1"]:hover td { background: #FFF3E6; }
.ct-person {
  font-weight: 600;
  color: var(--text);
  white-space: nowrap;
}
.ct-email {
  color: var(--text-mute);
  /* break-all shredded addresses mid-word in the narrow 460px PO rail
     ("frederic.fafar d@nexans.c om"). overflow-wrap:anywhere only breaks
     when a token genuinely can't fit, preferring natural points (dots/@),
     so emails wrap cleanly. 2026-06-30. */
  overflow-wrap: anywhere;
  word-break: normal;
  line-height: 1.35;
}
.contact-primary-tag {
  display: none;
  font-size: 9.5px;
  font-weight: 700;
  letter-spacing: 0.5px;
  text-transform: uppercase;
  color: #8A4A16;
  background: #FFE0C0;
  border: 1px solid #F5C394;
  padding: 1px 6px;
  border-radius: 999px;
}
.contact-row[data-is-primary="1"] .contact-primary-tag { display: inline-block; }


/* ═══════════════════════════════════════════════════════════════════
   Directory (/directory) — unified PTI + supplier contact list
   ═════════════════════════════════════════════════════════════════ */

/* ── Search + filter toolbar ─────────────────────────────────────── */
.dir-toolbar {
  display: flex;
  align-items: center;
  gap: 10px;
  margin: 14px 0 16px 0;
}
.dir-search {
  position: relative;
  display: flex;
  align-items: center;
}
.dir-search-icon {
  position: absolute;
  left: 14px;
  width: 16px;
  height: 16px;
  color: #A89880;
  pointer-events: none;
  transition: color 0.12s;
}
.dir-search input {
  width: 100%;
  /* Match the shared Org pill's box model (8px vertical padding, line-height 1,
     1px border, no explicit height) so the search bar and the pill render at the
     same height — and that height stays consistent with pills site-wide. */
  line-height: 1;
  padding: 8px 38px 8px 40px;
  border: 1px solid #ECE3D5;
  border-radius: 999px;
  background: #FFFCF7;
  font-size: 13px;
  font-family: inherit;
  color: var(--text);
  outline: none;
  box-shadow: 0 1px 2px rgba(31, 22, 16, 0.03);
  transition: border-color 0.12s, box-shadow 0.12s, background-color 0.12s;
}
.dir-search input::placeholder { color: #A89880; }
.dir-search input:hover { border-color: #D9C8AC; }
.dir-search input:focus {
  border-color: #C0712F;
  background: #fff;
  box-shadow: 0 0 0 3px rgba(233, 123, 47, 0.16);
}
.dir-search:focus-within .dir-search-icon { color: #C0712F; }
/* Chrome/Edge render a native clear "×" for type="search"; we ship our own
   clear button, so suppress the browser one to avoid a double-× (2026-07-06). */
.dir-search input::-webkit-search-cancel-button,
.dir-search input::-webkit-search-decoration {
  -webkit-appearance: none;
  appearance: none;
}
.dir-search-clear {
  position: absolute;
  right: 9px;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 22px;
  height: 22px;
  padding: 0;
  border: none;
  background: transparent;
  font-size: 16px;
  line-height: 1;
  color: #A89880;
  cursor: pointer;
  border-radius: 999px;
  transition: color 0.12s, background-color 0.12s;
}
.dir-search-clear:hover { color: #8A4A16; background: #FFF3E2; }

/* The Org filter (formerly a native <select> styled here) was replaced with
   the Open Orders multi-select pill — its .oo-ms* styling lives inline in
   directory.html alongside the component markup. The .dir-filter rules that
   used to sit here were orphaned by that swap and removed (2026-06-24). */

/* ── List rows ───────────────────────────────────────────────────── */
/* v8 — .dir-list is now a card that holds BOTH the header row and
   the body rows. Border + divider colors warmed from #E5E5E3 cool
   grey to #EFE6D2 so the card matches the rest of the warm-cream
   palette instead of fighting it. */
/* 2026-06-24 — the directory is the shared .data table (the PO library /
   Invoices component), NOT a CSS-grid card. Columns auto-size to their content
   so nothing truncates, and the card below is a contained both-axes scroll
   viewport (mirroring the Invoices ledger) so the table gets proper scrollbars
   inside its frame. The sticky thead, dividers, padding and header wash all
   come from table.data — only these directory tweaks live here. */
/* Contained scroll — mirrors the Invoices ledger (.inv-wrap .table-scroll).
   The card is a fixed-height viewport that scrolls in BOTH axes, so a table
   wider or taller than the card gets proper scrollbars INSIDE the card frame
   rather than spilling out or pushing a page-level scrollbar onto the chrome.
   The sticky thead (table.data thead th { top:0 }) anchors to the top of this
   region and stays put as the rows scroll under it. */
.dir-card { max-height: calc(100vh - 210px); overflow: auto; }
/* No cell wraps — full text always shown; overflow scrolls the page sideways. */
.dir-table td { white-space: nowrap; }
/* Rows aren't navigable here; the base table.data sets cursor:pointer. */
.dir-table tbody tr { cursor: default; }
/* Avatar gutter — snug column with the bubble centred. */
.dir-th-avatar    { width: 1px; padding-right: 0; }
.dir-avatar-cell  { width: 1px; padding: 14px 4px 14px 20px; }
/* Name leads a touch heavier than the supporting columns (beats the base
   table.data td weight via the extra .dir-table class). */
.dir-table td.dir-name { font-weight: 600; color: #1F1610; }

.dir-avatar {
  /* v7 — 32 → 36px so the avatar holds its own in the looser
     16px-padded row, and the initials read at 12px without feeling
     cramped. */
  width: 36px;
  height: 36px;
  border-radius: 50%;
  background: linear-gradient(135deg, #FFF1E5 0%, #FFE0C0 100%);
  display: flex;
  align-items: center;
  justify-content: center;
  font-weight: 700;
  font-size: 12px;
  color: #8A4A16;
  letter-spacing: 0.4px;
  user-select: none;
}
/* Per-org avatar tints — subtle so the badge below doesn't fight. */
.dir-avatar-pti      { background: linear-gradient(135deg, #FFF1E5 0%, #FFE0C0 100%); color: #8A4A16; }
.dir-avatar-nexans   { background: linear-gradient(135deg, #FFE4E4 0%, #FFC9C9 100%); color: #9C2F2F; }
.dir-avatar-glencore { background: linear-gradient(135deg, #E0F2E8 0%, #C2E2D0 100%); color: #1D6F3F; }
.dir-avatar-amrod    { background: linear-gradient(135deg, #FCE4E4 0%, #F5C6C6 100%); color: #B71C1C; }
.dir-avatar-hartree  { background: linear-gradient(135deg, #ECE4F2 0%, #D9C8E8 100%); color: #5B3F8A; }
.dir-avatar-sark     { background: linear-gradient(135deg, #E2EBF6 0%, #C6D7EE 100%); color: #1F5BA6; }
.dir-avatar-iwg      { background: linear-gradient(135deg, #DCEEF2 0%, #B9DDE4 100%); color: #0B6E83; }
.dir-avatar-hamelin  { background: linear-gradient(135deg, #EFE2D2 0%, #DCC5A4 100%); color: #7C5520; }

.dir-name-cell { min-width: 0; }
.dir-name {
  /* v7 — bumped from 13px to 14px so the name reads as primary
     identifier in the row, on par with body data elsewhere in the
     app. Weight 600 keeps it slightly stronger than supplier names
     in tables (which are 500) — directory names ARE the row. */
  font-size: 14px;
  font-weight: 600;
  color: #1F1610;
  letter-spacing: -0.005em;
  white-space: nowrap;
}
/* 2026-06-24 — Role is its own column (after Organization), not a subtitle
   under the name. Rendered identically to the Organization column (same
   14px / --text) per Josh; no truncation — the row scrolls horizontally
   instead (see .dir-scroll). */
.dir-role-cell {
  font-size: 14px;
  color: var(--text);
  white-space: nowrap;
}

/* Org pill — one chip per contact, color-keyed per org */
/* v10 — `justify-self: start` was sizing this grid item to its content
   width (max-content) instead of filling the org track, so long names
   ("International Wire Group") spilled past the track into the email
   column and the overflow/ellipsis below never engaged. Let it stretch
   to fill the track so the ellipsis can do its job. */
/* v8 — org column now plain text (org pill retired). The avatar's
   per-org tint already signals the company; this column just spells
   the abbreviation out.
   v9 — matched to the email cell per Josh: same 14px and primary
   --text color so org and email read at the same weight, rather than
   the org column dropping back to muted metadata. */
.dir-org-cell {
  font-size: 14px;
  color: var(--text);
  white-space: nowrap;       /* "PTI Cables Inc." stays on one line */
}
/* Legacy .dir-org-pill rules retained below for the case where
   another template still uses them — harmless if unreferenced. */
.dir-org-pill {
  display: inline-flex;
  align-items: center;
  height: 20px;
  padding: 0 9px;
  border-radius: 999px;
  background: var(--surface-2);
  color: var(--text-mute);
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.6px;
  border: 1px solid var(--border);
  text-transform: uppercase;
  white-space: nowrap;
}
.dir-org-pill-pti      { background: #FFF1E5; color: #8A4A16; border-color: #F5D7B8; }
.dir-org-pill-nexans   { background: #FFE4E4; color: #9C2F2F; border-color: #F5C9C9; }
.dir-org-pill-glencore { background: #E0F2E8; color: #1D6F3F; border-color: #C2E2D0; }
.dir-org-pill-amrod    { background: #FCE4E4; color: #B71C1C; border-color: #F5C6C6; }
.dir-org-pill-hartree  { background: #ECE4F2; color: #5B3F8A; border-color: #D9C8E8; }
.dir-org-pill-sark     { background: #E2EBF6; color: #1F5BA6; border-color: #C6D7EE; }
.dir-org-pill-iwg      { background: #DCEEF2; color: #0B6E83; border-color: #B9DDE4; }
.dir-org-pill-hamelin  { background: #EFE2D2; color: #7C5520; border-color: #DCC5A4; }

.dir-email-cell {
  /* v7 — bumped from 12px to 14px per Josh. Email is the primary
     reason to be on this page; making it harder to read than a
     phone-number tag was a hierarchy mistake. Same size as the
     name cell now — both read as primary content. */
  font-size: 14px;
  white-space: nowrap;
}
/* v7 — phone column was a single cell stacking (W) + (C) lines.
   Now split into .dir-work-cell + .dir-mobile-cell, each on its own
   column with a header label above ("Work" / "Cell"). Same font as
   email (14px) so the row reads as one coherent line. */
.dir-work-cell,
.dir-mobile-cell {
  font-size: 14px;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
.dir-email-cell a,
.dir-work-cell a,
.dir-mobile-cell a {
  color: var(--text);
  text-decoration: none;
}
.dir-email-cell a:hover,
.dir-work-cell a:hover,
.dir-mobile-cell a:hover {
  color: #8A4A16;
  text-decoration: underline;
}
/* .dir-phone-tag (the inline "(W)"/"(C)" chips) retired — the
   column headers now communicate that meaning. */

.dir-empty {
  padding: 32px 16px;
  text-align: center;
  color: var(--text-soft);
  font-size: 13px;
  border: 1px dashed var(--border-2);
  border-radius: 6px;
  margin-top: 12px;
}

/* On narrow screens the table behaves like every other .data table on the
   site — it keeps its columns and the page scrolls sideways to reach them
   (no bespoke stacked card). */


/* ═══════════════════════════════════════════════════════════════════
   Supplier-dedicated PO forms (added 2026-05-19)
   ──────────────────────────────────────────────────────────────────
   Tab bar across the top, per-supplier accent colors, polished card
   layouts, and the structured schedule-row builder + live-preview
   pane used by /po/new/nexans.
   ═════════════════════════════════════════════════════════════════ */

/* ── Tab bar ─────────────────────────────────────────────────────── */
/* v25 (2026-05-24) — Tab bar promoted to the v15 underline-only canon
   that previously lived as a page-local override on po_new_tolling.html
   + po_new_scrap.html. Was a boxed-with-top-stripe look that read
   different from page to page because Rod / Cathode didn't carry the
   local override. Now: single global rule, all four service forms
   render identically. Behavior:
     • Inactive tab — muted warm-brown, no chrome
     • Hover      — text darkens, no background change
     • Active     — bold dark text + 2px brand-orange bottom border
   The supplier/service accent classes (.rod, .scrap, etc.) are still
   accepted on tabs but no longer drive a colored stripe — the orange
   underline does the active-affordance work uniformly. */
.supplier-tabs {
  display: flex;
  align-items: stretch;
  gap: 4px;
  border-bottom: 1px solid #EFE6D2;
  margin-bottom: 28px;
  flex-wrap: wrap;
}
.supplier-tab {
  display: inline-flex;
  align-items: center;
  padding: 12px 18px;
  font-size: 14px;
  font-weight: 500;
  letter-spacing: 0;
  color: #8C7B66;
  text-decoration: none;
  border: 0;
  border-bottom: 2px solid transparent;
  border-radius: 0;
  margin-bottom: -1px;        /* overlap the hairline */
  background: transparent;
  transition: color 0.12s ease, border-color 0.12s ease;
}
.supplier-tab:hover {
  color: #1F1610;
  background: transparent;
  text-decoration: none;
}
/* Back-to-list affordance, parked at the right end of the tab row and
   vertically centered against the tabs. Refined warm pill: soft tinted
   fill, hairline border, gentle accent lift on hover with the chevron
   nudging left to reinforce the "back" direction. */
.supplier-tabs .tab-back {
  margin-left: auto;
  align-self: center;
  display: inline-flex;
  align-items: center;
  gap: 7px;
  padding: 7px 14px 7px 12px;
  font-size: 13px;
  font-weight: 500;
  color: #6B5942;
  background: #FBF6EC;
  border: 1px solid #EADFCB;
  border-radius: 999px;
  text-decoration: none;
  box-shadow: 0 1px 1px rgba(60, 42, 20, 0.03);
  transition: color 0.14s ease, background 0.14s ease,
              border-color 0.14s ease, box-shadow 0.14s ease;
}
.supplier-tabs .tab-back:hover {
  color: #8A4A16;
  background: #FFF1E5;
  border-color: #F0C9A6;
  box-shadow: 0 2px 6px rgba(155, 63, 15, 0.10);
  text-decoration: none;
}
.supplier-tabs .tab-back .chev {
  font-size: 17px;
  line-height: 1;
  margin-top: -1px;
  transition: transform 0.14s ease;
}
.supplier-tabs .tab-back:hover .chev {
  transform: translateX(-2px);
}
.supplier-tab.active {
  background: transparent;
  color: #1F1610;
  font-weight: 600;
  border: 0;
  border-bottom: 2px solid #C0712F;
}
/* v25 — top-stripe pseudo-element retired. The bottom-border carries
   the active affordance now, uniformly across all four service tabs.
   Kept as a `display: none` belt so any latent rule trying to draw
   a stripe can't reintroduce the older look. */
.supplier-tab.active::before {
  display: none;
}
/* Future-supplier / future-service accents: keep adding here as forms ship.
   Service-keyed (preferred going forward):
     tolling   → #C0712F (inherits the Nexans accent — Nexans is dominant)
     rod       → #B71C1C (red)
     cathode   → #0E7A3F (inherits the Glencore accent — Glencore-dominant)
     scrap     → #7A5538 (warm gunmetal — see above)
   Legacy supplier-keyed (kept for the supplier-keyed tabs):
     amrod     → #B71C1C (red)
     hartree   → #5B3F8A (purple)
     sark      → #1F5BA6 (cobalt blue)
     iwg       → #0B6E83 (teal)
     hamelin   → #7C5520 (warm brown)
*/
.supplier-tab.disabled {
  color: var(--text-soft);
  cursor: not-allowed;
  background: transparent;
  opacity: 0.55;
  user-select: none;
}
.supplier-tab.disabled:hover {
  background: transparent;
  color: var(--text-soft);
}

/* Supplier pill (used in page header alongside the H1) */
.supplier-pill {
  display: inline-flex;
  align-items: center;
  height: 22px;
  padding: 0 9px;
  border-radius: 999px;
  background: var(--surface-2);
  color: var(--text-mute);
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.8px;
  border: 1px solid var(--border);
}
.supplier-pill.nexans {
  background: #FFF1E5;
  color: #8A4A16;
  border-color: #F5D7B8;
}
.supplier-pill.scrap {
  /* Scrap PO header pill — warm tan / dark brown, distinct from
     the Nexans orange so the Scrap form is unambiguous at a glance
     even though Nexans is the only scrap supplier today. */
  background: #F4EDE5;
  color: #5A3F26;
  border-color: #E5D8C5;
}
.supplier-pill.tolling {
  /* Tolling PO header pill — same orange family as the Nexans pill
     since Nexans is the dominant (and currently only) tolling
     counterparty. Distinct enough from scrap that the two services
     don't read alike, but consistent with the existing Nexans
     brand color across the rest of the app. */
  background: #FFF1E5;
  color: #8A4A16;
  border-color: #F5D7B8;
}
.supplier-pill.rod {
  /* Rod PO header pill — red family matching SERVICE_PALETTE
     .rod_provisional in po_generator.py, so the form's header pill,
     the [Rod] tab accent stripe, and the PDF's order_type stripe
     all read as the same color story. */
  background: #FEEBEB;
  color: #7A1414;
  border-color: #F5C2C2;
}

/* ── Pricing display widget ─────────────────────────────────────────
   Used on the dedicated service-keyed PO forms (Scrap, Tolling, and
   future Rod/Cathode) where a single fixed rate replaces the legacy
   Nexans-form radio-card grid. Promoted to the global stylesheet on
   2026-05-20 so multiple forms can share the rules; per-service
   color overrides live inline in each template (e.g.
   .tolling-pricing-display tints these orange, .scrap colorway is
   the default brown). */
.pricing-display {
  display: flex;
  flex-direction: column;
  gap: 4px;
  padding: 10px 12px;
  border-radius: 8px;
  background: #F4EDE5;
  border: 1px solid #E5D8C5;
}

/* Deal-facts mini-card — 3-column "Premium / Contract / Commitment"
   layout used on /po/new/{rod,cathode,tolling,scrap}. Replaces the
   older .pricing-display widget on these forms (2026-05-22). Per-
   form palette overrides live inline in each template:
     .rod-deal-facts     — red
     .cathode-deal-facts — green
     .tolling-deal-facts — orange
     .scrap-deal-facts   — brown
*/
.deal-facts {
  background: var(--surface-2, #FAFAFA);
  border: 1px solid var(--border, #E5E5E5);
  border-radius: 8px;
  padding: 12px 16px;
}
.deal-facts-name {
  font-size: 14px;
  font-weight: 600;
  letter-spacing: 0.2px;
  margin-bottom: 12px;
  padding-bottom: 10px;
  border-bottom: 1px solid rgba(0, 0, 0, 0.06);
}
.deal-facts-grid {
  display: grid;
  grid-template-columns: 1fr 1fr 1fr;
  gap: 16px;
}
/* Settlement / billing-convention sentence — sits BELOW the data
   grid with its own divider above it (matches the divider below
   the name, sandwiching the data). Full opacity + normal weight
   because the convention info is settlement-critical, not flavor
   text. (Josh, 2026-05-22.) */
.deal-facts-subtitle {
  font-size: 13px;
  line-height: 1.5;
  margin-top: 14px;
  padding-top: 12px;
  border-top: 1px solid rgba(0, 0, 0, 0.06);
}
/* Notes block — supplier-specific contextual info (e.g. AmRod's
   shared monthly commitment). Same rhythm as the Convention
   subtitle but no top divider (sits directly below Convention).
   JS hides the whole block when no notes apply. */
.deal-facts-notes {
  font-size: 13px;
  line-height: 1.5;
  margin-top: 10px;
}

/* ── Commitment Tracker (used by /po/new/cathode + /po/new/rod) ──
   Promoted from the cathode template 2026-05-22 so the Rod form's
   tracker reuses the same look. Numbers row + horizontal progress
   bar + status pill. The bar is bounded at 100% always — overage
   is signalled via the projected-total number turning red (JS
   toggles .is-overage) and the status pill flipping warn / over. */
.commitment-tracker-card .tracker-numbers {
  display: grid;
  grid-template-columns: 1fr 1fr 1fr;
  gap: 16px;
  margin-bottom: 14px;
}
/* Closed past month: only the month-close total remains — drop the
   open-only cells and center the total over the bar. */
.commitment-tracker-card .tracker-numbers.is-closed {
  grid-template-columns: 1fr;
  justify-items: center;
  text-align: center;
}
.commitment-tracker-card .tracker-numbers.is-closed .tracker-num--open-only {
  display: none;
}
.commitment-tracker-card .tracker-num strong {
  font-size: 18px;
  color: #1A5A30;
}
.commitment-tracker-card .tracker-num strong.is-overage {
  color: #B71C1C;
}
.commitment-tracker-card .tracker-bar-wrap {
  margin-bottom: 10px;
}
.commitment-tracker-card .tracker-bar {
  position: relative;
  height: 18px;
  background: #F0F7F3;
  border: 1px solid #BBDDC4;
  border-radius: 6px;
  overflow: visible;
}
.commitment-tracker-card .tracker-bar-committed {
  position: absolute;
  left: 0;
  top: 0;
  height: 100%;
  background: #1E7C3D;
  border-radius: 6px 0 0 6px;
  transition: width 0.2s;
}
.commitment-tracker-card .tracker-bar-this-po {
  position: absolute;
  top: 0;
  height: 100%;
  background: #6BB985;
  transition: left 0.2s, width 0.2s;
}
.commitment-tracker-card .tracker-bar-labels {
  display: flex;
  justify-content: space-between;
  margin-top: 4px;
  font-size: 11px;
  color: var(--text-mute);
}
.commitment-tracker-card .tracker-bar-labels .tracker-cap {
  font-weight: 600;
  color: #1A5A30;
}
.commitment-tracker-card .tracker-status {
  margin-top: 8px;
  padding: 8px 12px;
  border-radius: 6px;
  font-size: 12.5px;
  background: #F0F7F3;
  color: #1A5A30;
}
.commitment-tracker-card .tracker-status.warn {
  background: #FFF1E5;
  color: #8A4A16;
  border: 1px solid #F5D7B8;
}
.commitment-tracker-card .tracker-status.over {
  background: #FEEBEB;
  color: #7A1414;
  border: 1px solid #F5C2C2;
}
/* "plain" modifier — strip the box, keep just the (colored) text.
   Used by the Rod tracker's Over Limit state (2026-06-04, Josh). */
.commitment-tracker-card .tracker-status.plain {
  background: none;
  border: none;
  padding: 0;
  font-size: 14px;
}
.deal-facts-val {
  font-size: 14px;
  font-weight: 600;
  margin-top: 2px;
}
.pricing-display-rate {
  display: flex;
  align-items: center;
  gap: 8px;
  /* `center` (not `baseline`) because the rate and the inline fee
     identifier are different sizes (18 vs 15px) — baseline-aligning
     them lets the larger text's cap-height tower over the smaller
     uppercase letters, making them feel misaligned. Centering on
     vertical midpoint reads cleaner when sizes differ.
     Josh, 2026-05-20. */
}
.pricing-display-rate-amt {
  font-size: 18px;
  font-weight: 700;
  color: #5A3F26;
  letter-spacing: -0.3px;
}
.pricing-display-rate-desc {
  font-size: 15px;
  font-weight: 500;
  letter-spacing: 0.6px;
  color: #7A5538;
  /* Tiny optical nudge downward — flex align-items: center on the
     parent boxes lines them up geometrically, but the all-caps
     letterforms have no descenders so they appear to float
     slightly high relative to the rate's mixed glyphs ($/digits/l/b).
     +2px gets it onto the same optical line. Josh, 2026-05-20. */
  position: relative;
  top: 2px;
  /* Service identifier inline beside the rate — uppercase
     ("NEXANS TOLL CATHODE FEE" / "NEXANS TOLL SCRAP FEE"). Sized
     slightly smaller than the rate (15 vs 18px) because the
     uppercase letterforms read visually larger than mixed-case
     digits at the same point size — 15px tracks the rate's
     visual weight without dominating it. Josh, 2026-05-20.
     The HTML carries the uppercase text directly (rather than
     via text-transform) so screen readers announce the proper
     word forms. */
}
.pricing-display-meta {
  font-size: 11px;
}
/* Service-keyed left-accent strip on the pricing card. Mirrors the
   `LINEBEFORE` accent on the PDF's summary card so the form and PDF
   echo each other visually. */
.scrap-accent {
  border-left: 3px solid #7A5538;
}
.tolling-accent {
  border-left: 3px solid #C0712F;
}

/* Pricing card two-column layout — widget on the left, secondary
   inputs (load size + reference period) stacked on the right.
   Replaces the older "widget on top, inputs in a row underneath"
   shape so the empty horizontal space on the right of the widget
   gets used (Josh, 2026-05-20). The grid uses `align-items:
   stretch` so the widget pulls up to the full height of the
   inputs stack; the widget itself then centers its content
   vertically (see `.pricing-card-grid .pricing-display` below). */
.pricing-card-grid {
  display: grid;
  grid-template-columns: minmax(0, 1.2fr) minmax(0, 1fr);
  gap: 18px;
  align-items: stretch;
}
.pricing-card-grid .pricing-display {
  /* Match the inputs column's height without leaving an awkward
     vertical gap when the widget content (rate + fee label) is a
     single line. Justify-content centers that line in the box so
     the rate sits at the visual midpoint of the row. */
  justify-content: center;
}
.pricing-card-inputs {
  display: grid;
  grid-auto-rows: auto;
  gap: 12px;
}
.pricing-card-inputs label {
  display: block;
}
.pricing-card-inputs input[type="text"] {
  width: 100%;
}
@media (max-width: 760px) {
  /* On narrow screens collapse back to single column so the widget
     and inputs don't squeeze each other awkwardly. */
  .pricing-card-grid { grid-template-columns: 1fr; }
}

/* Service-fee identifier line beneath the rate. Used when the
   widget needs to call out the supplier + service name in strong
   uppercase (e.g. "NEXANS CATHODE TOLLING FEE") rather than the
   muted inline `.pricing-display-rate-desc`. Base style is neutral
   slate; per-service overrides (e.g. .tolling-pricing-display
   .pricing-display-fee-label) tint it to match the accent. */
.pricing-display-fee-label {
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.6px;
  text-transform: uppercase;
  color: #4A4A4A;
  margin-top: 4px;   /* breathing room from the rate above */
}

/* "View Agreement →" link inside the pricing-display-rate flex
   row. `margin-left: auto` pushes it to the right end of the row,
   away from the rate + fee label which sit at the left. Quiet
   muted-grey color, dotted underline — reads as a discoverable
   secondary reference, not a primary action. Originally lived
   under the widget as a block link (2026-05-20 first try); moved
   inline-right after Josh asked for a layout that doesn't leave
   awkward vertical space. */
.pricing-display-contract-link {
  margin-left: auto;
  font-size: 11px;
  text-decoration: underline;
  text-decoration-style: dotted;
  text-decoration-thickness: 1px;
  text-underline-offset: 2px;
  color: var(--text-mute, #6A6A6A);
  white-space: nowrap;
}
.pricing-display-contract-link:hover {
  color: var(--text, #1A1A1A);
}
.supplier-pill.glencore {
  background: #E6F4EC;
  color: #1D6F3F;
  border-color: #C5E0CF;
}


/* ── Nexans form layout ─────────────────────────────────────────── */
.nexans-form {
  margin-top: 4px;
}
.nexans-grid {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(280px, 360px);
  gap: 18px;
  align-items: start;
}
@media (max-width: 1100px) {
  .nexans-grid {
    grid-template-columns: 1fr;
  }
}
.nexans-left  { display: grid; gap: 14px; }
.nexans-right { position: relative; }

/* Sticky preview on the right column */
.preview-sticky {
  position: sticky;
  top: 14px;
  display: grid;
  gap: 14px;
}

/* Card body padding to match existing .card vocabulary */
.card .card-body { padding: 14px 16px 16px; }

/* Accent stripe on key cards (Nexans-orange left border) */
.card.accent-card { border-left: 3px solid var(--border-2); }
.card.accent-card.nexans-accent   { border-left-color: #C0712F; }
.card.accent-card.glencore-accent { border-left-color: #0E7A3F; }

/* Generic field row + label styling shared by all supplier forms */
.field-row {
  display: grid;
  gap: 12px;
  margin: 0;
}
.field-row + .field-row { margin-top: 12px; }
.field-label {
  font-size: 11px;
  color: var(--text-mute);
  font-weight: 600;
  letter-spacing: 0.2px;
  margin-bottom: 4px;
}
.field-label .dim {
  font-weight: 400;
  color: var(--text-soft);
  margin-left: 4px;
}

/* ── Editorial card rebuild for ALL PO Generation forms (2026-06-01) ──
   The soft-shadow, borderless, natural-case card look was first built
   per-form on Scrap / Cathode Tolling / Rod, scoped to .scrap-form /
   .tolling-form / .rod-form. The forms that never got their own copy
   (Cathode, Glencore, Nexans legacy, Stranding) fell back to the global
   .card chrome (bordered + UPPERCASE header) and looked inconsistent
   (Josh, 2026-06-01: "the cathode screen doesn't have the same format").
   Promoted here to ONE source of truth scoped to .nexans-form so every
   PO form shares it and the look can't drift per form again. Scoped to
   .nexans-form so other .card usage app-wide is untouched. The per-form
   inline copies (still present in scrap/tolling/rod) are now redundant
   but harmless — identical values, more-specific selector wins ties. */
.nexans-form .card {
  background: #FFFFFF;
  border: 0;
  border-radius: 16px;
  box-shadow: 0 1px 2px rgba(31, 22, 16, 0.04),
              0 1px 16px rgba(31, 22, 16, 0.04);
  overflow: hidden;
}
.nexans-form .card .card-h {
  border-bottom: 0;
  padding: 26px 32px 4px;
}
.nexans-form .card .card-h h3 {
  font-size: 18px;
  font-weight: 600;
  text-transform: none;        /* kills the global uppercase + tracking */
  letter-spacing: -0.011em;
  color: #1F1610;
  margin: 0;
}
.nexans-form .card .card-body {
  padding: 16px 32px 28px;
}
.nexans-form .nexans-left {
  gap: 20px;
}
.nexans-form .field-row {
  gap: 18px;
  margin-top: 14px;
}
.nexans-form .field-row:first-child {
  margin-top: 0;
}
/* 2026-05-22 v14 — PO Generation form controls now match the
   .lib-filter-select pill style from /po/library so the two pages
   speak the same visual vocabulary. Per Josh: same dropdown look
   as the PO screen, full stop. Pill border-radius (999px), warm
   cream fill, custom chevron in muted warm-brown that turns brand
   orange on focus + an open-list with warm-cream option rows.
   Applies to all 5 PO Generation forms (rod / cathode / scrap /
   glencore / tolling) since they share .nexans-form. */
.nexans-form input[type="text"],
.nexans-form input[type="number"],
.nexans-form input[type="date"],
.nexans-form select,
.nexans-form textarea {
  width: 100%;
  /* v15.1 — taller padding so the 999px border-radius reads as a
     full pill instead of a slightly-rounded rectangle. Inputs were
     ~40px tall; now ~52px which lets the corner curves dominate
     the silhouette the way Airbnb's search bars and Linear's
     form fields do. */
  padding: 14px 22px;
  font-size: 14px;
  font-family: inherit;
  font-weight: 500;
  color: #1F1610;
  background-color: #FFFCF7;
  border: 1px solid #ECE3D5;
  border-radius: 999px;
  outline: none;
  cursor: pointer;
  transition: border-color 0.12s, background-color 0.12s,
              box-shadow 0.12s, color 0.12s;
  -webkit-appearance: none;
  appearance: none;
}
/* Textarea — pill is weird for multi-line, give it a generous
   rounded-rectangle instead. */
.nexans-form textarea {
  border-radius: 16px;
  cursor: text;
}
.nexans-form input[type="text"],
.nexans-form input[type="number"],
.nexans-form input[type="date"] {
  cursor: text;
}
.nexans-form input:hover,
.nexans-form select:hover,
.nexans-form textarea:hover {
  border-color: #D9C8AC;
  background-color: #FFF7EC;
}
.nexans-form input:focus,
.nexans-form select:focus,
.nexans-form textarea:focus {
  background-color: #FFFFFF;
  border-color: #C0712F;
  box-shadow: 0 0 0 3px rgba(233, 123, 47, 0.18);
}
/* v15.2 — normalize browser-default validation styling. Firefox
   (and sometimes Chrome) adds a pink/red tint to empty `:required`
   or `:invalid` fields by default — which made the PO Number look
   subtly different from PO Date / Status. Lock the resting visual
   so the form always reads as the same clean cream pill, never
   contingent on the browser's idea of "this field needs filling."
   Real validation feedback (when we add it) will use explicit
   classes or aria-invalid, not browser defaults. */
.nexans-form input:required,
.nexans-form input:invalid,
.nexans-form input:valid,
.nexans-form select:required,
.nexans-form select:invalid,
.nexans-form select:valid,
.nexans-form textarea:required,
.nexans-form textarea:invalid,
.nexans-form textarea:valid {
  background-color: #FFFCF7;
  border-color: #ECE3D5;
  box-shadow: none;
}
/* Keep hover + focus winning over the normalization above. */
.nexans-form input:hover,
.nexans-form select:hover,
.nexans-form textarea:hover {
  background-color: #FFF7EC;
  border-color: #D9C8AC;
}
.nexans-form input:focus,
.nexans-form select:focus,
.nexans-form textarea:focus {
  background-color: #FFFFFF;
  border-color: #C0712F;
  box-shadow: 0 0 0 3px rgba(233, 123, 47, 0.18);
}
/* Custom chevron on every select — same SVG + position as the
   .lib-filter-select on /po/library, just sized for the slightly
   taller form-field padding. Muted warm-brown stroke by default;
   brand-orange when focused (same swap pattern as the library). */
.nexans-form select {
  /* v15.1 — bumped padding-right to match the new taller pill;
     keeps the chevron from sitting right against the right edge
     of the longer label area. */
  padding-right: 44px;
  background-image:
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6' viewBox='0 0 10 6'%3E%3Cpath fill='none' stroke='%237a7a7a' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round' d='M1 1l4 4 4-4'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 18px center;
  background-size: 10px 6px;
}
.nexans-form select:focus {
  background-image:
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6' viewBox='0 0 10 6'%3E%3Cpath fill='none' stroke='%239B3F0F' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round' d='M1 1l4 4 4-4'/%3E%3C/svg%3E");
}
/* Open-list rows on the select — warm tint with orange filter
   values, same pattern as .lib-filter-select. */
.nexans-form select option {
  background-color: #FFF1E5;
  color: #8A4A16;
  font-weight: 600;
  padding: 4px 8px;
}

/* v21 — Calendar icon affordance on date inputs. flatpickr's
   altInput mode swaps the native date input (which had Chrome's
   automatic calendar icon at the right edge) for a plain text
   input — losing the visual cue that the user can click for a
   picker. Adding a quiet calendar SVG back as a background-image
   restores that signal.
   v21.2 — Targets `.pti-date-input` (explicit altInputClass set
   in the flatpickr init for PO Date) so the selector reliably
   matches the visible alt input across flatpickr versions. */
/* v21.4 — background-image-on-input approach abandoned. The
   `.pti-date-input` class wasn't reliably landing on flatpickr's
   visible alt input, so the icon never showed. The replacement
   approach uses a real DOM element (.date-field-icon) positioned
   over the input — lives in po_new_tolling.html. When we extend
   the calendar icon affordance to other date pickers across the
   app, we'll use the same wrap+icon pattern (not background-image
   on the input). */
.nexans-form label { display: block; }
.nexans-form .field-label {
  font-size: 12.5px;
  font-weight: 500;
  color: #6B5E4E;
  margin-bottom: 6px;
}

/* ── Account radio cards (Cathode / Scrap / Rod) ─────────────────── */
.radio-group {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 10px;
}
.radio-group-3 {
  /* Three-up grid for the Nexans pricing-track selector. Collapses
     to two columns at narrow widths, then a single stack on the
     mobile-ish breakpoint where the whole form goes one-column. */
  grid-template-columns: 1fr 1fr 1fr;
}
@media (max-width: 1280px) {
  .radio-group-3 { grid-template-columns: 1fr 1fr; }
}
@media (max-width: 700px) {
  .radio-group-3 { grid-template-columns: 1fr; }
}
.radio-card {
  position: relative;
  display: block;
  /* Leave room on the right for the absolutely-positioned radio circle
     (top-right corner, vertically aligned with the card title). */
  padding: 10px 36px 10px 12px;
  border: 1.5px solid var(--border-2);
  border-radius: 6px;
  cursor: pointer;
  background: var(--surface);
  transition: border-color 0.12s, background 0.12s, box-shadow 0.12s;
}
.radio-card:hover {
  border-color: var(--text-soft);
}
/* Custom-styled radio so we get a clean orange dot when checked
   without the browser's default outer ring/box. `appearance: none`
   strips all native styling; we redraw a 16px circle with a smaller
   orange dot inside via ::after when :checked.

   Positioned absolutely in the top-right corner of the card. The
   `top` value lines the radio's vertical center up with the cap-height
   of the radio-card-title so the two read as one row — no extra
   vertical space wasted on a radio-only row above the title. */
.radio-card input[type="radio"] {
  appearance: none;
  -webkit-appearance: none;
  width: 16px;
  height: 16px;
  margin: 0;
  padding: 0;
  border: 1.5px solid var(--border-2);
  border-radius: 50%;
  background: var(--surface);
  cursor: pointer;
  position: absolute;
  top: 12px;
  right: 12px;
  transition: border-color 0.12s, background 0.12s;
}
.radio-card input[type="radio"]:hover {
  border-color: var(--text-soft);
}
.radio-card input[type="radio"]:checked {
  border-color: #C0712F;
  background: var(--surface);
}
.radio-card input[type="radio"]:checked::after {
  content: "";
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: #C0712F;
}
.radio-card input[type="radio"]:focus-visible {
  outline: 2px solid rgba(233, 123, 47, 0.45);
  outline-offset: 2px;
}
.radio-card.selected {
  border-color: #C0712F;
  background: #FFF8F2;
  box-shadow: 0 0 0 3px rgba(233, 123, 47, 0.10);
}
/* Body is the title + tag stack inside a .radio-card. The card is
   `display: block` and the radio is absolutely positioned, so no flex
   layout is needed here — the body just flows in document order. */
.radio-card-body { display: block; }
.radio-card-title {
  font-size: 13px;
  font-weight: 600;
  color: var(--text);
  margin-bottom: 2px;
}
.radio-card-sub {
  font-size: 11.5px;
  color: var(--text-mute);
  line-height: 1.45;
  margin-bottom: 6px;
}
.radio-card-meta {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 11px;
}
.radio-card-meta .tag {
  background: var(--surface-2);
  padding: 2px 7px;
  border-radius: 999px;
  font-weight: 600;
  color: var(--text-mute);
  border: 1px solid var(--border);
}
.radio-card.selected .radio-card-meta .tag {
  background: #FFE6CC;
  color: #8A4A16;
  border-color: #F5C394;
}

/* ── Schedule row builder ────────────────────────────────────────── */
.schedule-rows {
  display: grid;
  gap: 8px;
}
/* Schedule grid template — single source of truth. Reused by the
   header row above and every .schedule-row below so the columns
   line up exactly:
     1. Loads / Day   (narrow number input)
     2. Dates         (capped flex so it never sprawls)
     3. Calendar btn  (28px icon)
     4. = N loads     (contribution badge, sized to content)
     5. Remove btn    (28px ×) */
.schedule-grid-cols {
  grid-template-columns: 100px minmax(180px, 320px) 28px auto auto 28px;
}

/* Proper table-header treatment — background tint + bottom rule so
   the labels read as structure, not floating text in whitespace.
   Same grid as .schedule-row so each label sits over its column. */
.schedule-header {
  display: grid;
  grid-template-columns: 100px minmax(180px, 320px) 28px auto auto 28px;
  gap: 8px;
  align-items: center;
  padding: 8px 8px;
  margin-bottom: 8px;
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: 5px;
  font-size: 9.5px;
  font-weight: 700;
  letter-spacing: 0.7px;
  text-transform: uppercase;
  color: var(--text-mute);
}
.schedule-header > div {
  white-space: nowrap;
  text-align: center;     /* center every label over its column */
  overflow: hidden;
  text-overflow: ellipsis;
}

.schedule-row {
  display: grid;
  grid-template-columns: 100px minmax(180px, 320px) 28px auto auto 28px;
  gap: 8px;
  align-items: center;
  padding: 6px 8px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 5px;
  transition: background 0.12s, border-color 0.12s;
  /* Establish a positioning context for the .row-date-picker overlay. */
  position: relative;
}

/* Per-row Days badge — date count for this row. Sits between the
   calendar icon and the Row Total so the eye reads left-to-right:
   "2 loads/day × 7 Days = 14 loads". Neutral grey so it doesn't
   compete with the orange Row Total. */
.schedule-row .row-daycount {
  display: inline-flex;
  align-items: baseline;
  gap: 4px;
  padding: 3px 9px;
  background: var(--surface-2);
  border: 1px solid var(--border-2);
  border-radius: 999px;
  font-size: 11px;
  color: var(--text-mute);
  font-weight: 500;
  white-space: nowrap;
  font-variant-numeric: tabular-nums;
  user-select: none;
}
.schedule-row .row-daycount strong {
  color: var(--text);
  font-weight: 700;
  font-size: 12px;
}

/* Per-row contribution badge — shows how many loads THIS row
   contributes to the total. Reveals the math behind the auto-detected
   per-day pattern: "2 loads × 7 dates = 14 loads". */
.schedule-row .row-contrib {
  display: inline-flex;
  align-items: baseline;
  gap: 4px;
  padding: 3px 10px;
  background: #FFF1E5;
  border: 1px solid #F5D7B8;
  border-radius: 999px;
  font-size: 11px;
  color: var(--text-mute);
  font-weight: 500;
  white-space: nowrap;
  font-variant-numeric: tabular-nums;
  user-select: none;
}
.schedule-row .row-contrib strong {
  color: #8A4A16;
  font-weight: 700;
  font-size: 12px;
}
/* Calendar icon button — visible affordance. The native date input
   below sits invisibly on top of it so showPicker() anchors the
   browser-rendered popup right next to this button. */
.schedule-row .row-calendar-btn {
  position: relative;
  width: 26px;
  height: 26px;
  padding: 0;
  border: 1px solid transparent;
  background: transparent;
  border-radius: 4px;
  font-size: 14px;
  line-height: 1;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  color: var(--text-soft);
  transition: background 0.12s, color 0.12s, border-color 0.12s;
}
.schedule-row .row-calendar-btn:hover {
  background: #FFF1E5;
  border-color: #F5D7B8;
  color: #8A4A16;
}
/* Hide the native <input type="date"> visually but keep it interactive
   so showPicker() can pop its calendar. The input is placed in the
   grid AFTER the .row-when and BEFORE the calendar button, but we
   absolutely position it over the calendar button so the browser
   anchors the popup next to the icon (not at the top-left corner).
   pointer-events:none lets clicks fall through to the actual button. */
.schedule-row .row-date-picker {
  position: absolute;
  right: 36px;          /* sits over the calendar button (28+8 gap)  */
  top: 50%;
  transform: translateY(-50%);
  width: 26px;
  height: 26px;
  padding: 0;
  margin: 0;
  border: 0;
  background: transparent;
  opacity: 0;
  pointer-events: none;
}
.schedule-row:hover { background: #FCFBF8; border-color: var(--border-2); }
.schedule-row .row-loads {
  text-align: center;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
}

/* Per-Day cell — number input + "Loads" suffix in one flex group.
   Anchors the unit "Loads" at the start of the row so the Row Total
   pill can drop the word and stay compact. */
.row-loads-group {
  display: flex;
  align-items: center;
  gap: 6px;
}
.row-loads-group .row-loads {
  flex: 0 0 50px;       /* keeps the number input small */
  width: 50px;
}
.row-loads-suffix {
  font-size: 11px;
  color: var(--text-mute);
  font-weight: 500;
  white-space: nowrap;
}
.schedule-row .row-loads-suffix {
  font-size: 12px;
  color: var(--text-mute);
  font-weight: 500;
  white-space: nowrap;
}
.schedule-row .row-perday {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  font-size: 12px;
  color: var(--text-mute);
  cursor: pointer;
  white-space: nowrap;
  user-select: none;
}
.schedule-row .row-perday input[type="checkbox"] {
  accent-color: #C0712F;
  width: 14px;
  height: 14px;
  cursor: pointer;
}
.schedule-row .row-when {
  font-family: var(--font-mono);
  font-size: 12.5px;
}
.schedule-row .row-remove {
  width: 26px;
  height: 26px;
  padding: 0;
  border: 1px solid transparent;
  background: transparent;
  color: var(--text-soft);
  border-radius: 4px;
  font-size: 18px;
  line-height: 1;
  cursor: pointer;
  transition: background 0.12s, color 0.12s, border-color 0.12s;
}
.schedule-row .row-remove:hover {
  background: #FBEAE9;
  color: var(--bad);
  border-color: #F2C9C6;
}
/* Schedule toolbar uses the SAME grid template as the rows above so
   the "+ Add load" button stays at the left edge and the Total pill
   sits in column 5 — directly under the Row Total column header.
   The vertical alignment makes the math read top-to-bottom:
   each row's Row Total sums to the grand Total at the bottom. */
.schedule-toolbar {
  display: grid;
  grid-template-columns: 100px minmax(180px, 320px) 28px auto auto 28px;
  gap: 8px;
  align-items: center;
  margin-top: 12px;
  padding: 12px 8px 0 8px;
  border-top: 1px dashed var(--border);
}
.schedule-toolbar > .btn {
  /* + Add load spans the first 3 columns so it sits flush-left
     under the Per Day input. */
  grid-column: 1 / 4;
  justify-self: start;
}
.schedule-toolbar > .total-counter {
  /* Total pill in column 5 (Row Total column). Sized to content +
     right-aligned within the column so it sits roughly above the
     Row Total badges without spreading edge-to-edge. */
  grid-column: 5;
  justify-self: end;
}
/* "Total: 21 Loads" — every word the same size and weight so it
   reads as a single line of running text inside an orange pill.
   Tight 4px gap between the three spans so it reads as one phrase
   rather than three separated chunks. */
.total-counter {
  display: inline-flex;
  align-items: baseline;
  gap: 4px;
  padding: 7px 16px;
  background: linear-gradient(135deg, #FFF1E5 0%, #FFE0C0 100%);
  border: 1px solid #F5D7B8;
  border-radius: 999px;
  font-size: 14px;
  color: #8A4A16;
  font-weight: 700;
  letter-spacing: 0.1px;
  font-variant-numeric: tabular-nums;
  box-shadow: 0 1px 2px rgba(155, 63, 15, 0.06);
  white-space: nowrap;
}
.total-counter > span,
.total-counter strong {
  font-size: 14px;
  font-weight: 700;
  color: #8A4A16;
}

/* ── Form actions row (Cancel / Generate) ────────────────────────── */
.nexans-form .form-actions {
  display: flex;
  justify-content: flex-end;
  gap: 10px;
  padding: 6px 0 0 0;
}

/* Submit buttons — the two commit actions at the foot of every PO form.
   A light frosted surface + one soft shadow so they read as gently
   raised rather than flat chips. Kept understated (no sheen gradients or
   glow) and scoped so the sitewide .btn in tables/headers is untouched. */
.nexans-form .form-actions .btn {
  padding: 8px 18px;
  font-size: 12.5px;
  font-weight: 600;
  border-radius: 9px;
  background: rgba(255, 255, 255, 0.6);
  border: 1px solid var(--border-2);
  -webkit-backdrop-filter: blur(8px);
  backdrop-filter: blur(8px);
  box-shadow: 0 1px 2px rgba(60, 30, 8, 0.05);
  transition: box-shadow .16s ease, background .16s ease, border-color .16s ease;
}
.nexans-form .form-actions .btn:hover {
  background: rgba(255, 255, 255, 0.85);
  border-color: rgba(168, 90, 28, 0.35);
  box-shadow: 0 2px 6px rgba(60, 30, 8, 0.08);
}
.nexans-form .form-actions .btn:active {
  box-shadow: inset 0 1px 2px rgba(60, 30, 8, 0.1);
}

/* Primary — solid PTI copper, just a soft shadow for a little lift. */
.nexans-form .form-actions .btn.primary {
  color: #fff;
  background: var(--accent);
  border-color: var(--accent);
  box-shadow: 0 1px 2px rgba(60, 30, 8, 0.1);
}
.nexans-form .form-actions .btn.primary:hover {
  background: var(--accent-deep);
  border-color: var(--accent-deep);
  box-shadow: 0 2px 6px rgba(168, 90, 28, 0.2);
}
.nexans-form .form-actions .btn.primary:active {
  box-shadow: inset 0 1px 2px rgba(60, 30, 8, 0.2);
}

/* ── Live preview pane ───────────────────────────────────────────── */
.preview-card .card-h .more,
.preview-meta-card .card-body { font-size: 11.5px; }

.pdf-mock {
  background: #FFFFFF;
  border: 1px solid var(--border-2);
  border-radius: 4px;
  padding: 14px;
  font-size: 11.5px;
  line-height: 1.45;
  color: var(--text);
}
.pdf-mock-header {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  padding-bottom: 10px;
  border-bottom: 1px solid var(--border);
}
.pdf-mock-title {
  font-weight: 700;
  letter-spacing: 0.8px;
  font-size: 11px;
  color: var(--text);
}
.pdf-mock-meta {
  display: grid;
  gap: 2px;
  text-align: right;
  font-size: 10.5px;
  color: var(--text-mute);
}
.pdf-mock-meta strong {
  color: var(--text);
  font-weight: 600;
  margin-right: 4px;
}
.pdf-mock-supplier {
  padding: 10px 0;
  border-bottom: 1px solid var(--border);
}
.pdf-mock-supplier .dim {
  font-size: 9.5px;
  text-transform: uppercase;
  letter-spacing: 0.6px;
  color: var(--text-soft);
  font-weight: 600;
  margin-bottom: 2px;
}
.pdf-mock-section {
  padding-top: 10px;
}
.pdf-mock-section .dim {
  font-size: 9.5px;
  text-transform: uppercase;
  letter-spacing: 0.6px;
  color: var(--text-soft);
  font-weight: 600;
  margin-bottom: 6px;
}
.pdf-mock-desc {
  margin: 0;
  padding: 0;
  background: none;
  border: none;
  font-family: var(--font-mono);
  font-size: 11.5px;
  line-height: 1.55;
  color: var(--text);
  white-space: pre-wrap;
  word-break: break-word;
}

/* OA-style preview: material summary card + line-items table.
   Mirrors the new Nexans PO PDF layout one-for-one so what the user
   sees in the preview is what they get when the PDF renders. */
.pdf-mock-summary {
  display: grid;
  grid-template-columns: 1fr auto;
  gap: 12px;
  align-items: start;
  padding: 8px 10px;
  border-left: 3px solid #C0712F;
  border: 1px solid var(--border);
  border-left-width: 3px;
  background: #FFFCF8;
  border-radius: 3px;
  margin-bottom: 10px;
}
.pdf-mock-material {
  font-size: 13px;
  font-weight: 700;
  letter-spacing: -0.01em;
  color: var(--text);
  margin-bottom: 2px;
}
.pdf-mock-matnr {
  font-size: 10.5px;
  color: var(--text-soft);
}
.pdf-mock-matnr strong { color: var(--text); font-weight: 600; }
.pdf-mock-pricing {
  font-size: 10.5px;
  color: var(--text-mute);
  text-align: right;
}
.pdf-mock-totals {
  font-size: 11.5px;
  font-weight: 700;
  color: var(--text);
  text-align: right;
  margin-top: 3px;
  font-variant-numeric: tabular-nums;
}

/* Line items table — 4-col layout (Item / Material / Quantity /
   Delivery) mirroring the PO PDF. Each Material cell stacks the
   product name + Nexans SKU + PTI SKU + TOLL tag. */
.pdf-mock-items {
  width: 100%;
  border-collapse: collapse;
  font-size: 10px;
  font-variant-numeric: tabular-nums;
}
.pdf-mock-items thead th {
  background: #FFF1E5;            /* warm orange tint, matches PDF */
  color: #8A4A16;                  /* dark PTI orange */
  font-weight: 700;
  font-size: 9px;
  text-transform: uppercase;
  letter-spacing: 0.6px;
  padding: 6px 6px;
  text-align: left;
  border-bottom: 1.5px solid #C0712F;
}
/* 7-col header alignment: # | Material | Qty | Svc | Delivery | $/lb | Total */
.pdf-mock-items thead th:nth-child(1)  { text-align: center; width: 22px; }
.pdf-mock-items thead th:nth-child(2)  { text-align: left;   }
.pdf-mock-items thead th:nth-child(3)  { text-align: right;  }
.pdf-mock-items thead th:nth-child(4)  { text-align: center; }
.pdf-mock-items thead th:nth-child(5)  { text-align: center; }
.pdf-mock-items thead th:nth-child(6)  { text-align: right;  }
.pdf-mock-items thead th:nth-child(7)  { text-align: right;  }
.pdf-mock-items tbody td.svc { text-align: center; }
.pdf-mock-items tbody td {
  padding: 5px 6px;
  border-bottom: 1px solid var(--border);
  color: var(--text);
  vertical-align: top;
}
.pdf-mock-items tbody tr:nth-child(even) td { background: #FBF9F4; }
.pdf-mock-items tbody tr:last-child td      { border-bottom: none; }
.pdf-mock-items tbody td.item-num {
  text-align: center;
  font-weight: 700;
  color: #C0712F;
  white-space: nowrap;
}
.pdf-mock-items tbody td.num {
  text-align: right;
  white-space: nowrap;
}
.pdf-mock-items tbody td:last-child {
  text-align: center;
  white-space: nowrap;
}

/* Material cell — stacked product identity */
.pdf-mock-items tbody td.mat { line-height: 1.45; }
.pdf-mock-items .mat-name {
  font-weight: 700;
  font-size: 10.5px;
  color: var(--text);
  margin-bottom: 1px;
}
.pdf-mock-items .mat-sku {
  font-size: 9.5px;
  color: var(--text-mute);
}
.pdf-mock-items .mat-sku strong {
  color: var(--text);
  font-weight: 600;
}
.pdf-mock-items .mat-tag {
  font-size: 9.5px;
  font-weight: 700;
  color: #8A4A16;
  margin-top: 2px;
}

.pdf-mock-items-empty td {
  text-align: center !important;
  color: var(--text-soft);
  font-style: italic;
  padding: 12px 8px !important;
  background: var(--surface) !important;
}

.preview-meta-card .field-label {
  margin-bottom: 2px;
}


/* PO Library — version chip ───────────────────────────────────
   Small pill that sits next to the PO number when the family has
   more than one revision. Click goes to the latest version's
   detail page; from there the Versions panel lists siblings. */
.lib-version-chip {
  display: inline-flex;
  align-items: baseline;
  gap: 3px;
  margin-left: 8px;
  padding: 1px 7px;
  border-radius: 999px;
  background: #FFF1E5;
  color: #8A4A16;
  border: 1px solid #F5D7B8;
  font-size: 10.5px;
  font-weight: 700;
  text-decoration: none;
  letter-spacing: 0.2px;
}
.lib-version-chip:hover {
  background: #FFE6CC;
  border-color: #E0A878;
}
.lib-version-chip-count {
  font-weight: 500;
  opacity: 0.75;
  font-size: 10px;
}

/* ─── Sortable column headers (2026-05-22 redesign) ────────────
   Hover + active states are restrained — a subtle warm tint
   indicates the active sort column without competing with the
   type. Arrows are slim chevrons (▴ ▾) instead of chunky ▲ ▼
   so they read as refined indicators, not loud icons. */
.sort-th {
  cursor: pointer;
  user-select: none;
  white-space: nowrap;
  transition: color 0.12s;
}
/* 2026-05-22 v3 — `.is-active` no longer paints a cream column
   background. The cream tint read as a stuck highlight competing
   with the row-hover state, and on a quiet headers row the
   background-as-signal felt heavy. The brand-orange arrow alone
   indicates the active sort (see .sort-th.is-active .sort-arrow
   below); the header text just shifts one shade darker for a
   whisper of confidence. */
.sort-th:hover {
  color: #6B5E4E;
}
.sort-th.is-active {
  color: #A85A1C;
}
/* Hovering the active (currently-sorted) header greys it like any other,
   matching the Open Orders ledger. Without this rule the copper is-active
   colour sits "stuck" under the cursor: `.sort-th:hover` and
   `.sort-th.is-active` have equal specificity and is-active is declared
   later, so it beat the plain hover. Bumping the hover to `.is-active:hover`
   (one extra class) lets the cursor win on every column. 2026-06-30. */
.sort-th.is-active:hover {
  color: #6B5E4E;
}
.sort-th-link {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  color: inherit;
  text-decoration: none;
  font: inherit;
  padding: 2px 0;
}
/* Override the global `a:hover { text-decoration: underline }` — a sortable
   header is a control, not body-text, so it shouldn't underline on hover
   (the color shift + arrow already signal interactivity). The color is left to
   `.sort-th:hover` so link and non-link headers shift identically. */
.sort-th-link:hover { color: inherit; text-decoration: none; }
/* Caret — matches the Open Orders ledger so every sortable table reads the
   same. It reserves its width so the header never shifts when it inks in;
   invisible at rest, a faint warm tan on hover (the "this sorts" affordance),
   and solid copper carrying a ▲/▼ direction on the active column. 2026-06-29. */
.sort-arrow {
  display: inline-block;
  width: 7px;
  margin-left: 5px;
  font-size: 9px;
  line-height: 1;
  color: #C9A777;
  opacity: 0;
  transition: opacity 0.12s ease, color 0.12s ease;
}
.sort-th:hover .sort-arrow { opacity: 0.5; }
.sort-th.is-active .sort-arrow {
  color: #A85A1C;             /* solid copper when active */
  opacity: 1;
}
/* Hovering the active column fades its caret too, so the whole header
   (title + arrow) recedes together — matching the Open Orders ledger,
   where the caret drops to opacity .5 on hover. Without this the
   is-active `opacity: 1` ties in specificity with `.sort-th:hover
   .sort-arrow` and, declared later, kept the arrow stuck at full copper
   while the title greyed — the arrow looked "left behind". 2026-06-30. */
.sort-th.is-active:hover .sort-arrow { opacity: 0.5; }
/* On right-aligned number columns the caret rides the value's side so it never
   splits a figure from its header. */
.sort-th.num .sort-arrow { margin-left: 0; margin-right: 5px; }
.sort-arrow-dim {
  opacity: 0.45;
}


/* ─── Invoice-link pill ────────────────────────────────────────
   Companion to the supplier-orange PO chip but in a teal palette so
   "links to an invoice" reads visually distinct from "links to a PO."
   Used wherever an invoice number wants to be a clickable pill — the
   PO detail page's Linked Invoices table, and the Purchase Log's
   Invoice Ref column. */
.invoice-pill {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 2px 9px;
  border-radius: 999px;
  background: #E6F2F5;
  color: #0B5263;
  border: 1px solid #BCD9DF;
  font-size: 12px;
  font-weight: 600;
  text-decoration: none;
  transition: background 0.12s, border-color 0.12s, color 0.12s;
}
.invoice-pill:hover {
  background: #D6EAEE;
  border-color: #9CC4CC;
  text-decoration: none;
}
.invoice-pill-arrow {
  font-size: 9px;
  opacity: 0.7;
}

/* ═══════════════════════════════════════════════════════════════════
   flatpickr warm-cream theme — v25 (2026-05-24)
   ───────────────────────────────────────────────────────────────────
   Promoted to global style.css so every form page that loads the
   flatpickr CDN script gets the themed calendar popup for free
   (Toll Cathode, Toll Scrap, Rod, Cathode, and any future form).
   Previously lived as a duplicated page-local <style> block in
   po_new_tolling.html and po_new_scrap.html — both now defer to
   this rule.

   The library renders into a `.flatpickr-calendar` div appended to
   document.body, so these rules are intentionally global (not
   scoped to .nexans-form) — they need to win wherever the popup
   opens.
   ═══════════════════════════════════════════════════════════════════ */
.flatpickr-calendar {
  background: #FFFCF7;
  border: 1px solid #EFE6D2;
  border-radius: 14px;
  box-shadow: 0 12px 36px rgba(31, 22, 16, 0.16),
              0 2px 6px rgba(31, 22, 16, 0.06);
  font-family: inherit;
  padding: 6px;
  width: 280px;
}
/* The open calendar must be opaque the instant it appears. flatpickr's
   fpFadeInDown entrance animation starts at opacity:0, and a single
   dropped/throttled animation frame (background tab, busy CPU) can leave
   the popup stuck transparent — it opens, but you can't see it. !important
   beats the keyframe so the popup is always visible; the slide still plays. */
.flatpickr-calendar.open { opacity: 1 !important; }
.flatpickr-calendar .flatpickr-rContainer { width: 100%; }
.flatpickr-calendar .flatpickr-days       { width: 100%; }
.flatpickr-calendar .dayContainer {
  width: 100%;
  min-width: 0;
  max-width: none;
}
.flatpickr-calendar.arrowTop:before,
.flatpickr-calendar.arrowTop:after,
.flatpickr-calendar.arrowBottom:before,
.flatpickr-calendar.arrowBottom:after {
  display: none;
}

/* Header: month/year + nav arrows. */
.flatpickr-months {
  padding: 6px 8px 10px;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 4px;
}
.flatpickr-months .flatpickr-month {
  color: #1F1610;
  fill: #1F1610;
  font-weight: 600;
  flex: 1 1 auto;
  height: auto;
  overflow: visible;
  text-align: center;
}
.flatpickr-current-month {
  font-size: 15px;
  font-weight: 600;
  padding: 0;
  position: static;
  width: auto;
  left: auto;
  height: auto;
}
.flatpickr-current-month .flatpickr-monthDropdown-months {
  background: transparent;
  font-weight: 600;
  color: #1F1610;
}
.flatpickr-current-month .flatpickr-monthDropdown-months:hover {
  background: #FFF7EC;
}
.flatpickr-current-month input.cur-year {
  font-weight: 600;
  color: #1F1610;
}
.flatpickr-months .flatpickr-prev-month,
.flatpickr-months .flatpickr-next-month {
  color: #8C7B66;
  fill: #8C7B66;
  padding: 6px;
  border-radius: 8px;
  position: static;
  width: 28px;
  height: 28px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  top: auto;
}
.flatpickr-months .flatpickr-prev-month:hover,
.flatpickr-months .flatpickr-next-month:hover {
  color: #8A4A16;
  fill: #8A4A16;
  background: #FFF7EC;
}
.flatpickr-months .flatpickr-prev-month svg,
.flatpickr-months .flatpickr-next-month svg {
  width: 12px;
  height: 12px;
}

/* Weekday header row */
.flatpickr-weekdays {
  background: transparent;
  padding: 4px 0;
}
.flatpickr-weekday {
  color: #8C7B66;
  font-weight: 500;
  font-size: 11.5px;
  letter-spacing: 0.02em;
}

/* Days grid */
.flatpickr-days {
  padding: 2px;
}
.dayContainer {
  padding: 2px;
  /* No `gap` here: the calendar is overridden to a fluid 280px width, so
     the 7 day cells at 14.2857% each sum to exactly 100%. Any flex `gap`
     pushes the row past 100% and wraps the 7th day, rendering 6 days per
     week. Flatpickr's pills already read as separated without a gap. */
}
.flatpickr-day {
  color: #1F1610;
  border-radius: 999px;
  font-weight: 500;
  font-size: 13px;
  height: 32px;
  line-height: 32px;
  border: 0;
}
.flatpickr-day:hover {
  background: #FFF1E5;
  color: #8A4A16;
}
/* v25b (2026-05-24) — Today is now a SOLID orange fill (was a thin
   outline ring). The outline read as too subtle on the schedule-row
   picker, where no day is "selected" (instance.clear() runs after
   every pick) — meaning today's outline was the only highlight on
   the calendar, and it was easy to miss. Solid fill matches the
   selected state on the PO Date picker so both calendars read the
   same way. */
.flatpickr-day.today {
  background: #C0712F;
  border-color: #C0712F;
  color: #FFFFFF;
  font-weight: 600;
}
.flatpickr-day.today:hover {
  background: #D26819;
  border-color: #D26819;
  color: #FFFFFF;
}
.flatpickr-day.selected,
.flatpickr-day.selected:hover {
  background: #C0712F;
  border-color: #C0712F;
  color: #FFFFFF;
}
.flatpickr-day.prevMonthDay,
.flatpickr-day.nextMonthDay {
  color: #B8AEA0;
}
.flatpickr-day.prevMonthDay:hover,
.flatpickr-day.nextMonthDay:hover {
  background: #FFFBF4;
  color: #6B5E4E;
}
.flatpickr-day.flatpickr-disabled,
.flatpickr-day.flatpickr-disabled:hover {
  color: #D9D2C5;
  background: transparent;
}

/* The input itself stays the form's pill style — flatpickr just
   opens over it. Keep the focus ring from fighting when calendar
   is open. */
.flatpickr-input.active {
  box-shadow: 0 0 0 3px rgba(233, 123, 47, 0.18);
  border-color: #C0712F;
  background: #FFFFFF;
}

/* .pti-date-input — flatpickr's altInput replaces the native date
   field with a plain text input. The visible alt input picks up
   this class via altInputClass: 'pti-date-input' in the init
   options. The form's existing input chrome already styles it; this
   block is intentionally empty as a hook for future tweaks if the
   alt input needs to diverge from the default text input look. */
.pti-date-input { /* hook for form-specific overrides */ }

/* ── Monthly Schedule & Commitment — launcher + pop-out overlay ───────
   Shared PO-form widget. Born on the Rod form (2026-06-12) and reused by
   the Cathode form (2026-06-15): the heavy Commitment Tracker + Monthly
   Schedule cards live inside a button-triggered overlay so the form reads
   as a clean vertical stack. The `.rod-*` class names are kept verbatim
   for both forms (the markup + open/close JS reference them) — they're the
   widget's name now, not rod-specific. The per-form reading width
   (`.rod-form` / `.cathode-form .rod-schedule-launch { max-width }`) stays
   inline in each form's <style>. */

/* Launcher — the button that opens the overlay. Quiet, full-width
   "go somewhere" row: icon · title + live readout · chevron. The live
   readout (#launch-sub) turns red via .is-over when the month is over
   the commitment. */
.rod-schedule-launch-btn {
  display: flex;
  align-items: center;
  gap: 16px;
  width: 100%;
  text-align: left;
  font: inherit;
  background: #FFFFFF;
  border: 1px solid #EFE6D2;
  border-radius: 14px;
  padding: 16px 20px;
  cursor: pointer;
  box-shadow: 0 1px 2px rgba(31, 22, 16, 0.04),
              0 1px 16px rgba(31, 22, 16, 0.04);
  transition: border-color .12s, box-shadow .12s, transform .04s;
}
.rod-schedule-launch-btn:hover {
  border-color: #E2C7A6;
  box-shadow: 0 2px 4px rgba(31, 22, 16, 0.06),
              0 2px 20px rgba(168, 90, 28, 0.08);
}
.rod-schedule-launch-btn:active { transform: translateY(1px); }
.rod-schedule-launch-ic {
  flex: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 40px; height: 40px;
  border-radius: 10px;
  background: #FBF1E2;
  color: #A85A1C;
}
.rod-schedule-launch-txt {
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.rod-schedule-launch-title {
  font-size: 15px;
  font-weight: 600;
  color: #1F1610;
  letter-spacing: -0.01em;
}
.rod-schedule-launch-sub {
  font-size: 12.5px;
  color: #8C7B66;
}
.rod-schedule-launch-btn.is-over .rod-schedule-launch-sub {
  color: #B71C1C;
  font-weight: 600;
}
.rod-schedule-launch-chev {
  flex: none;
  display: inline-flex;
  color: #B79B78;
}
.rod-schedule-launch-btn:hover .rod-schedule-launch-chev { color: #A85A1C; }

/* Overlay — full-screen panel the launcher opens. A dim scrim over the
   page, a centred panel that holds the (unchanged) Commitment Tracker +
   Monthly Schedule cards and scrolls internally on overflow. The cards
   keep their own .nexans-form chrome, so they look identical to before —
   only their container changed. */
.rod-overlay {
  position: fixed;
  inset: 0;
  z-index: 1000;
  display: flex;
  align-items: flex-start;
  justify-content: center;
  padding: 48px 24px;
}
.rod-overlay[hidden] { display: none; }
.rod-overlay-scrim {
  position: absolute;
  inset: 0;
  background: rgba(31, 22, 16, 0.42);
  backdrop-filter: blur(2px);
}
.rod-overlay-panel {
  position: relative;
  width: 100%;
  max-width: 1040px;
  max-height: calc(100vh - 96px);
  display: flex;
  flex-direction: column;
  background: #FBF7EF;
  border-radius: 18px;
  box-shadow: 0 24px 64px rgba(31, 22, 16, 0.30);
  overflow: hidden;
  animation: rod-overlay-in .16s ease-out;
}
@keyframes rod-overlay-in {
  from { opacity: 0; transform: translateY(8px); }
  to   { opacity: 1; transform: translateY(0); }
}
.rod-overlay-head {
  flex: none;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  padding: 20px 24px;
  border-bottom: 1px solid #EFE6D2;
  background: #FFFFFF;
}
.rod-overlay-head h2 {
  margin: 0;
  font-size: 18px;
  font-weight: 600;
  letter-spacing: -0.011em;
  color: #1F1610;
}
.rod-overlay-close {
  flex: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 34px; height: 34px;
  border: 1px solid #EFE6D2;
  border-radius: 9px;
  background: #FFFCF7;
  color: #6B5E4E;
  cursor: pointer;
  transition: border-color .12s, color .12s, background-color .12s;
}
.rod-overlay-close:hover {
  border-color: #E2C7A6;
  color: #A85A1C;
  background: #FBF1E2;
}
.rod-overlay-body {
  flex: 1 1 auto;
  overflow-y: auto;
  padding: 24px;
  display: flex;
  flex-direction: column;
  gap: 20px;
}
/* The relocated cards fill the panel width (they no longer share a row). */
.rod-overlay-body > .card { width: 100%; }
/* Lock background scroll while the panel is open. */
body.rod-overlay-open { overflow: hidden; }

/* ── Supplier Contacts picker (PO Gen) ─────────────────────────────────
   Tabular layout mirroring the Directory columns (Name · Role · Email ·
   Work · Cell) with a Primary radio + CC checkbox up front. The header
   row and every contact row share one grid template so the columns stay
   aligned. Values that don't fit clip to an ellipsis (.sc-clip) and the
   full string shows in the shared .sc-tip hover popup — the same instant
   styled tooltip used on the Open Orders ledger (JS in base.html). The
   card width is unchanged; the columns absorb the squeeze by truncating,
   never by widening. */
/* Scoped to `.sc-list .sc-row` (not bare `.sc-row`) so it outranks the
   form-level `.nexans-form label { display: block }` rule — a single
   class loses to class+element, which would force the <label> rows back
   to block and collapse the grid (header, a <div>, is unaffected). */
.sc-head, .sc-list .sc-row {
  display: grid;
  grid-template-columns:
    50px 32px minmax(72px, 1.1fr) minmax(72px, 1.2fr)
    minmax(90px, 1.5fr) minmax(84px, 0.95fr) minmax(84px, 0.95fr);
  align-items: center;
  gap: 0 8px;
}
/* Header band mirrors the Directory table head — warm wash, muted brown
   labels at 12.5px/500, hairline divider. */
.sc-head {
  font-size: 12.5px; font-weight: 500; color: #8C7B66;
  letter-spacing: 0.02em;
  padding: 11px 8px 9px;
  background: #FFF4E9;
  border-bottom: 1px solid #EFE6D2;
  border-radius: 8px 8px 0 0;
}
.sc-list { display: flex; flex-direction: column; gap: 4px; margin-top: 6px; }
.sc-row {
  padding: 8px; border: 1px solid #E5DDD0; border-radius: 6px;
  cursor: pointer; background: #FFFDF7;
}
.sc-row:hover { background: #FBF6EC; border-color: #DCD0BC; }
.sc-c-pri, .sc-c-cc { text-align: center; }
/* Copper-tinted radios / checkboxes so the picker reads on-brand rather
   than the browser's default blue. */
.sc-c-pri input, .sc-c-cc input {
  accent-color: #A85A1C;
  width: 16px; height: 16px;
  margin: 0; cursor: pointer; vertical-align: middle;
}
/* The contact radios/checkboxes are accent-colored native controls, not field
   rectangles — suppress the form-wide focus chrome (the peach
   box-shadow: 0 0 0 3px from each PO form's own <style>, e.g. .rod-form
   input:focus) that would otherwise paint a square behind them. Selector is
   deliberately 3-class deep so it outweighs those per-form rules. */
.sc-list .sc-c-pri input:focus,
.sc-list .sc-c-cc input:focus {
  outline: none;
  box-shadow: none;
}
.sc-name  { font-size: 13px; font-weight: 600; color: #1F1610; }
.sc-role  { font-size: 12px; color: #8C7B66; }
.sc-email { font-size: 12px; color: #444; }
.sc-phone { font-size: 12px; color: #4A3F33; }
.sc-phone.is-empty, .sc-role.is-empty { color: #B8AC99; }
.sc-clip {
  min-width: 0; white-space: nowrap;
  overflow: hidden; text-overflow: ellipsis;
}
.sc-clip.is-clipped { cursor: help; }

/* Shared hover popup for clipped contact cells — a light cream card (the
   default popup skin as of 2026-07-08; matches the Sent-card pills), built
   once in base.html and repositioned per cell. Mirrors .oo-tip. */
.sc-tip {
  position: fixed; z-index: 400; pointer-events: auto;
  user-select: text; cursor: text;
  max-width: 360px; padding: 8px 11px; border-radius: 9px;
  background: #FFFDFA; color: var(--text);
  border: 1px solid var(--border-2);
  font-size: 12.5px; line-height: 1.4;
  white-space: normal; word-break: break-word;
  box-shadow: 0 6px 20px rgba(31,22,16,.13), 0 1px 2px rgba(31,22,16,.08);
  opacity: 0; transform: translateY(3px);
  transition: opacity .1s ease, transform .1s ease;
}
.sc-tip:not(.is-on) { pointer-events: none; }
.sc-tip.is-on { opacity: 1; transform: none; }
/* Copper selection band, readable on the light card. */
.sc-tip ::selection, .sc-tip::selection {
  background: rgba(192, 113, 47, .24); color: var(--text);
}
.sc-tip::before, .sc-tip::after {
  content: ''; position: absolute; left: 0; right: 0;
}
.sc-tip::before { top: -10px; height: 10px; }
/* ::after is the hover bridge that connects the popup down to its trigger.
   Its height is set per-popup via --sc-bridge (JS place()) so it spans the
   EXACT gap to the name — a fixed height can't, since the real gap varies
   with line-height. Defaults to 10px for the in-place .sc-clip cells. */
.sc-tip::after  { bottom: calc(-1 * var(--sc-bridge, 10px)); height: var(--sc-bridge, 10px); }

/* ── Shared "Order" card chrome for the toll forms (2026-06-25) ─────────
   Cathode-Tolling and Scrap-Tolling reuse the Rod "Order" card look: an
   outlined "Item 1 — …" sub-card with a read-only item row above the load
   schedule. These mirror the `.rod-form .order-* / .os-*` rules (which live
   in the rod template, scoped to .rod-form) so all three cards render
   identically. The inner schedule keeps its own `.schedule-*` markup + JS;
   only this outer chrome is shared here. */
.tolling-form .order-sections,
.scrap-form .order-sections { display: flex; flex-direction: column; gap: 16px; }
.tolling-form .order-section,
.scrap-form .order-section {
  border: 1px solid #EAE0D2; border-radius: 12px;
  background: #FCFAF4; padding: 16px 16px 14px;
}
.tolling-form .os-section-title,
.scrap-form .os-section-title {
  display: flex; align-items: center; gap: 0;
  margin: -2px 0 14px; padding-bottom: 12px;
  border-bottom: 1px solid #EFE6D6;
}
.tolling-form .os-section-num, .scrap-form .os-section-num,
.tolling-form .os-section-name, .scrap-form .os-section-name {
  font-size: 16px; font-weight: 700; letter-spacing: -0.01em; color: #1A1A1A;
}
.tolling-form .os-section-name, .scrap-form .os-section-name {
  min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.tolling-form .os-section-name:not(:empty)::before,
.scrap-form .os-section-name:not(:empty)::before {
  content: '-'; margin: 0 8px; color: #1A1A1A; font-weight: 700;
}
.tolling-form .os-field, .scrap-form .os-field {
  display: flex; flex-direction: column; gap: 6px; min-width: 0;
}
.tolling-form .os-field-key, .scrap-form .os-field-key {
  font-size: 11px; letter-spacing: .02em; color: #8C7B66;
}
.tolling-form .os-itemrow, .scrap-form .os-itemrow {
  display: grid;
  grid-template-columns: minmax(0,1.3fr) minmax(0,1.3fr) minmax(0,1fr);
  gap: 14px; align-items: end;
}
/* Scoped through .os-itemrow (specificity 0,3,1) so the muted read-only color
   beats the form's own `.tolling-form input[type="text"] { color:#1F1610 }`
   rule (0,2,1) — otherwise the read-only fields render in full-dark text
   instead of rod's greyed look. */
.tolling-form .os-itemrow input.os-ro-field,
.scrap-form .os-itemrow input.os-ro-field {
  width: 100%; background: #FFFCF7; color: #5C4F3F; cursor: default;
  font-variant-numeric: tabular-nums;
}
.tolling-form .os-itemrow input.os-ro-field:hover,
.tolling-form .os-itemrow input.os-ro-field:focus,
.scrap-form .os-itemrow input.os-ro-field:hover,
.scrap-form .os-itemrow input.os-ro-field:focus {
  outline: none; box-shadow: none; border-color: #ECE3D5; background: #FFFCF7;
}
.tolling-form .os-itemrow .os-ro-field::placeholder,
.scrap-form .os-itemrow .os-ro-field::placeholder { color: #B6A78F; }

/* Default Lbs / Load line — narrow input + copper "Apply to all" pill
   (mirrors .rod-form .os-defaultline). */
.tolling-form .os-defaultline,
.scrap-form .os-defaultline {
  display: flex; align-items: flex-end; gap: 14px; margin-top: 14px;
}
.tolling-form .os-default-field, .scrap-form .os-default-field { flex: 0 0 auto; }
.tolling-form .os-default-lbs, .scrap-form .os-default-lbs {
  width: 170px; font-variant-numeric: tabular-nums;
}
.tolling-form .os-default-apply, .scrap-form .os-default-apply {
  -webkit-appearance: none; appearance: none;
  flex: 0 0 auto; align-self: flex-end;
  padding: 10px 16px; border-radius: 8px;
  font-family: inherit; font-size: 13px; font-weight: 600; color: #A85A1C;
  background: #FBF3EA; border: 1px solid #E3C9B3;
  cursor: pointer; white-space: nowrap;
  transition: background .12s, color .12s, border-color .12s;
}
.tolling-form .os-default-apply:hover, .scrap-form .os-default-apply:hover {
  background: #FFF7EC; color: #8A4A16; border-color: #C9A876;
}

/* + Add row — quiet outlined pill with a copper "+" (mirrors rod). */
.tolling-form .os-add-line, .scrap-form .os-add-line {
  -webkit-appearance: none; appearance: none;
  display: inline-flex; align-items: center; gap: 7px;
  margin-top: 12px; padding: 7px 14px 7px 11px; border-radius: 999px;
  border: 1px solid #E5DAC6; background: transparent; color: #7A6A55;
  font-family: inherit; font-size: 13px; font-weight: 500; cursor: pointer;
  transition: background .14s ease, color .14s ease, border-color .14s ease, box-shadow .14s ease;
}
.tolling-form .os-add-line::before, .scrap-form .os-add-line::before {
  content: "+"; flex: none; font-size: 16px; font-weight: 600; line-height: 1; color: #A85A1C;
}
.tolling-form .os-add-line:hover, .scrap-form .os-add-line:hover {
  background: #FFF7EC; color: #A85A1C; border-color: #C9A876;
  box-shadow: 0 1px 3px rgba(168,90,28,.08);
}

/* Footer totals — right-aligned, bold numbers above a hairline (mirrors rod). */
.tolling-form .os-foot, .scrap-form .os-foot { margin-top: 16px; }
.tolling-form .os-totals, .scrap-form .os-totals {
  display: flex; align-items: baseline; justify-content: flex-end; gap: 24px;
  padding-top: 12px; border-top: 1px solid #EDE4D6;
}
.tolling-form .os-totals-seg, .scrap-form .os-totals-seg {
  display: flex; align-items: baseline; gap: 7px;
}
.tolling-form .os-totals-k, .scrap-form .os-totals-k {
  font-size: 14px; font-weight: 600; color: #8C7B66;
}
.tolling-form .os-totals-v, .scrap-form .os-totals-v {
  font-size: 16px; font-weight: 700; color: #1F1610; letter-spacing: -0.01em;
  font-variant-numeric: tabular-nums;
}
