/* ════════════════════════════════════════════════════════════════════════════
   V3.2 visual port — theme overlay
   ════════════════════════════════════════════════════════════════════════════
   Founder direction 2026-06-03: revamp UI to match V3.2 (`Release/hornback-ims-v3.html`).
   The previous "industrial" V4 look (sharp corners, dark near-black topbar, dashed
   factory-tape stripe, warm cream body) is rejected. This overlay loads AFTER app.css
   in the page chain so it cleanly overrides without touching the original file.

   Reference token map (V3.2 → V4 override):
     --bg            #f1f5f9   →  --canvas
     --sidebar-bg    #1e2845   →  --ink-950 (topbar + sidebar)
     --radius        8px        →  default for buttons / cards / modals
     --primary       #f97316    →  already matches V4's --brand-orange
     --text          #1e293b    →  already close to V4's --ink-800
     fonts                       →  already aligned (Hanken Grotesk + Antonio + JetBrains Mono)

   Page-by-page rollout per project_v32_visual_overhaul_directive memory.
   ════════════════════════════════════════════════════════════════════════════ */

:root {
  /* ── Surface palette ── V3.2 cool slate scale. */
  --canvas: #f1f5f9;          /* body bg (was warm cream #F2EFEA) */
  --paper:  #ffffff;
  --ink-950: #1e2845;         /* topbar + sidebar deep navy (was near-black #0A0F14) */
  --ink-900: #233152;         /* sidebar hover band */

  /* Borders softer. V3.2 uses #e2e8f0 everywhere; V4 had ink-200 = #D6DCE3.
     Override the alias V4 components use so cards / inputs land identically. */
  --border-default: #e2e8f0;
  --ink-200: #e2e8f0;

  /* Shadows — V3.2 is much softer + uses slate-toned alpha not pure black. */
  --shadow-sm: 0 1px 2px rgba(15, 23, 42, 0.04);
  --shadow-md: 0 4px 6px -1px rgba(15, 23, 42, 0.07), 0 2px 4px -1px rgba(15, 23, 42, 0.04);
  --shadow-lg: 0 10px 15px -3px rgba(15, 23, 42, 0.09), 0 4px 6px -2px rgba(15, 23, 42, 0.05);
  --shadow-xl: 0 20px 25px -5px rgba(15, 23, 42, 0.10), 0 10px 10px -5px rgba(15, 23, 42, 0.04);
  --shadow-panel: -4px 0 18px rgba(15, 23, 42, 0.08);

  /* V3.2 standardises on 8px radius. Keep V4's --r-sm for tight tags + --r-pill for chips. */
  --r-md: 8px;
  --r-lg: 8px;
  --r-xl: 12px;
}

/* ── Body background — flips from warm cream to V3.2's cool slate. ── */
body {
  background: var(--canvas);
}

/* ════════════════════════════════════════════════════════════════════════════
   Topbar + sidebar — LIGHT theme (Zoho-Books style · 2026-06-15, founder direction).
   Replaces the industrial dark-navy chrome with a light surface, section sub-items
   indented FORWARD under their header, and a full orange pill on the active item.
   Overrides the app.css dark base (this stylesheet loads last).
   ════════════════════════════════════════════════════════════════════════════ */

/* ── Topbar: white surface, dark text ── */
.app-topbar {
  background: #ffffff;
  color: var(--ink-900);
  border-bottom: 1px solid var(--ink-200);
  box-shadow: 0 1px 2px rgba(15, 23, 42, 0.05);
}
a.brand-mark, .brand-mark {
  color: var(--ink-900);
  letter-spacing: 0.04em;
  font-weight: 700;
}
a.brand-mark:hover { color: var(--brand-orange); }
.topbar-icon-btn { color: var(--ink-500); }
.topbar-icon-btn:hover { background: var(--ink-100); color: var(--ink-900); }
/* Search sits on the light topbar now — light field, dark text. */
.topbar-search-input {
  background: #ffffff;
  border-color: var(--ink-200);
  color: var(--ink-900);
}
.topbar-search-input::placeholder { color: var(--ink-400); }
.topbar-search-input:focus { background: #ffffff; border-color: var(--brand-orange); }
.topbar-search-icon { color: var(--ink-400); }
.topbar-search-clear { background: var(--ink-200); color: var(--ink-700); }
.topbar-search-clear:hover { background: var(--danger); color: #ffffff; }

/* ── Sidebar: light surface ── */
.app-sidebar {
  background: #f6f7f9;
  color: var(--ink-600);
  border-right: 1px solid var(--ink-200);
}
/* Section headers — readable sentence-case, not tiny mono-caps. */
.nav-group-label {
  font-family: var(--font-ui);
  font-size: 14px;                 /* +2 — section headers stand out from items */
  font-weight: 700;                /* bold */
  letter-spacing: 0;
  text-transform: none;
  color: var(--ink-700);
  padding: 6px var(--space-4);
  margin: 0 var(--space-2) var(--space-1);
  border-radius: 6px;
}
/* "You are here" — the section CONTAINING the active item gets a light-grey fill.
   (The selected item itself keeps its orange pill.) */
.nav-group:has(.nav-item.active) > summary.nav-group-label { background: #e3e6ec; }
/* Outline chevron (Zoho-style) instead of the filled ▸ triangle — better visibility. */
summary.nav-group-label::before {
  content: "";
  box-sizing: border-box;
  width: 7px;
  height: 7px;
  border: 0 solid var(--ink-500);
  border-right-width: 1.6px;
  border-bottom-width: 1.6px;
  transform: rotate(-45deg);       /* points right when collapsed */
  transition: transform 140ms ease;
}
details[open] > summary.nav-group-label::before { transform: rotate(45deg); }   /* points down when open */
summary.nav-group-label:hover { color: var(--ink-900); }

/* Nav items — light + pill-shaped; indented FORWARD under the section (expanded only). */
.app-sidebar .nav-item {
  color: var(--ink-600);
  border-left: none;
  border-radius: 7px;
  transition: background 0.13s ease, color 0.13s ease;
}
.app-shell:not(.sidebar-collapsed) .nav-item {
  padding: 7px var(--space-3) 7px var(--space-8);        /* forward indent under header */
  margin: 1px var(--space-2);
}
.app-sidebar .nav-item:hover {
  background: #e6e8ee;
  color: var(--ink-900);
}
.app-sidebar .nav-item.active {
  background: var(--brand-orange);
  color: #ffffff;
  border-left: none;
  font-weight: 600;
}
.app-sidebar .nav-item.active .nav-icon { color: #ffffff; }
/* Expanded sidebar = text-only nav items (Zoho style); leading icons hidden.
   The collapsed rail still shows them (the only glyph once labels are hidden). */
.app-shell:not(.sidebar-collapsed) .nav-icon { display: none; }
/* Deeper nested sub-item (if used) — a touch more indent. */
.app-shell:not(.sidebar-collapsed) .nav-item-sub {
  padding-left: calc(var(--space-8) + var(--space-4));
}

/* Collapse toggle, group separators + "dev" tag — light treatment. */
.sidebar-toggle { border-color: var(--ink-300); color: var(--ink-500); }
.sidebar-toggle:hover { background: var(--ink-100); color: var(--ink-900); border-color: var(--ink-400); }
.app-shell.sidebar-collapsed .nav-group { border-top-color: var(--ink-200); }
.nav-soon { background: var(--ink-100); color: var(--ink-500); }

/* ════════════════════════════════════════════════════════════════════════════
   Sidebar brand (moved from topbar) + collapsed rail (line icon + small label)
   · 2026-06-15 founder direction (Zoho-style collapsed nav).
   ════════════════════════════════════════════════════════════════════════════ */
.sidebar-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-2);
  padding: var(--space-4) var(--space-4) var(--space-4) var(--space-5);   /* more breathing room from the left */
  border-bottom: 1px solid var(--ink-200);
  margin-bottom: var(--space-2);
}
.sidebar-brand { text-decoration: none; display: flex; align-items: center; gap: var(--space-2); min-width: 0; }
.sidebar-logo {
  height: 26px;                 /* a touch bigger than the wordmark */
  width: auto;
  display: block;
  flex-shrink: 0;
}
.sidebar-brand-full {
  font-family: 'Poppins', var(--font-ui), sans-serif;
  font-weight: 700;
  font-size: 15px;
  letter-spacing: 0;
  color: var(--ink-900);
  white-space: nowrap;
}
.sidebar-brand:hover .sidebar-brand-full { color: var(--brand-orange); }

/* Tighter gap between nav sections (founder: too much space between Overview / Purchases / …). */
.nav-group { margin-bottom: var(--space-2); }
.app-shell.sidebar-collapsed .nav-group { margin-bottom: var(--space-1); padding-top: var(--space-2); }

/* The nav-icon span wraps an inline SVG (Feather line icon). Size it; hidden when expanded. */
.nav-icon svg { display: block; width: 20px; height: 20px; }
.app-shell.sidebar-collapsed .nav-icon { display: flex; width: auto; align-items: center; justify-content: center; }

/* Collapsed rail: each item = icon on top + small short-label below (text-only expanded). */
.app-shell.sidebar-collapsed { grid-template-columns: var(--sidebar-w-collapsed) 1fr; }
.app-shell.sidebar-collapsed .nav-item {
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 3px;
  padding: 8px 2px;
  margin: 1px 6px;
}
.app-shell.sidebar-collapsed .nav-label { display: none; }
.app-shell.sidebar-collapsed .nav-label-short {
  display: block;
  font-size: 10px;
  line-height: 1.1;
  text-align: center;
  white-space: nowrap;
}
.app-shell:not(.sidebar-collapsed) .nav-label-short { display: none; }
/* Collapsed brand: hide the full name, show the compact HB mark, stack the head. */
.app-shell.sidebar-collapsed .sidebar-head { flex-direction: column-reverse; gap: var(--space-2); padding: var(--space-2) 0; }
.app-shell.sidebar-collapsed .sidebar-brand { justify-content: center; gap: 0; }
.app-shell.sidebar-collapsed .sidebar-brand-full { display: none; }   /* collapsed: logo only */
.app-shell.sidebar-collapsed .sidebar-logo { height: 30px; }          /* logo a touch bigger when it stands alone */

/* ── Section icons + collapsed hierarchy (founder 2026-06-15) ──
   Expanded: section header is the chevron + text (icon hidden).
   Collapsed: section header shows a BIG icon + small bold label, and its items show
   SMALLER icons — so the rail reads as section > items. */
.nav-group-icon { display: none; }
.nav-group-icon svg { display: block; width: 22px; height: 22px; }
.app-shell.sidebar-collapsed .nav-group-label {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 2px;
  padding: 8px 2px 6px;
  margin: 2px var(--space-2) 0;
  color: var(--ink-700);
}
.app-shell.sidebar-collapsed .nav-group-icon { display: flex; align-items: center; justify-content: center; }
.app-shell.sidebar-collapsed summary.nav-group-label::before { display: none; }   /* drop the disclosure chevron */
.app-shell.sidebar-collapsed .nav-group-text {
  display: block;
  font-size: 9.5px;
  font-weight: 700;
  line-height: 1.05;
  text-align: center;
  white-space: nowrap;
}
/* Items nested under a section — smaller icon than the section header (shows hierarchy). */
.app-shell.sidebar-collapsed .nav-item .nav-icon svg { width: 16px; height: 16px; }
.app-shell.sidebar-collapsed .nav-item { padding: 6px 2px; gap: 2px; }
.app-shell.sidebar-collapsed .nav-item .nav-label-short { font-size: 9px; color: var(--ink-500); }

/* ════════════════════════════════════════════════════════════════════════════
   Buttons — V3.2's clean 8px radius, soft hover, no industrial harshness.
   ════════════════════════════════════════════════════════════════════════════ */

.btn-primary, .btn-secondary, .btn-danger, .action-btn-primary, .seg-btn, .action-btn-secondary {
  border-radius: 8px;                                    /* unify default radius */
  transition: background 0.15s ease, border-color 0.15s ease, color 0.15s ease;
}
.btn-primary, .action-btn-primary {
  background: var(--brand-orange);                       /* override industrial dark-navy bg */
  color: #ffffff;
  font-weight: 600;
  box-shadow: 0 1px 2px rgba(249, 115, 22, 0.15);
  border: 1px solid var(--brand-orange);
}
.btn-primary:hover, .action-btn-primary:hover {
  background: var(--brand-orange-hover);
  border-color: var(--brand-orange-hover);
  color: #ffffff;
  box-shadow: 0 2px 4px rgba(249, 115, 22, 0.22);
}
/* Founder bugfix · 2026-06-12 — there was NO :disabled styling anywhere, so a blocked
   "Confirm & Save" looked fully clickable while clicks silently did nothing. M3 disabled
   state: greyed + not-allowed cursor. Applies app-wide to every button family. */
.btn-primary:disabled, .action-btn-primary:disabled,
.btn-secondary:disabled, .action-btn-secondary:disabled,
.btn-danger:disabled, .seg-btn:disabled {
  opacity: .45;
  filter: grayscale(45%);
  cursor: not-allowed;
  box-shadow: none;
}
.btn-primary:disabled:hover, .action-btn-primary:disabled:hover {
  background: var(--brand-orange);                       /* defeat the hover lift while disabled */
  border-color: var(--brand-orange);
  box-shadow: none;
}
.btn-secondary, .action-btn-secondary {
  background: #f8fafc;
  border: 1px solid #e2e8f0;
  color: var(--ink-800);
  font-weight: 500;
}
.btn-secondary:hover, .action-btn-secondary:hover {
  background: #f1f5f9;
  border-color: #cbd5e1;
}

/* Segmented buttons (filter chips, tabs) take the V3.2 soft pill look. */
.seg-btn {
  background: #ffffff;
  border: 1px solid #e2e8f0;
  color: var(--ink-700);
  border-radius: 8px;
  min-height: 38px;                 /* match .btn-secondary — was text-height, too short next to Columns */
  padding: 8px 14px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
}
/* 2026-07-30 — Dispatch Log "Zoho dispatched, not marked" toggle: pressed state.
   Accent-soft ground + accent border + dark ink (U5); reads as ON without competing
   with the single orange primary. */
.seg-btn.seg-btn-on {
  background: var(--accent-soft);
  border-color: var(--accent);
  color: var(--ink-950);
  font-weight: 600;
}
.seg-btn:hover {
  background: #f8fafc;
}
.seg-btn.active {
  background: var(--brand-orange);
  border-color: var(--brand-orange);
  color: #ffffff;
}

/* Salary structures — merged people-cost rollup (2026-07-09).
   Location header / group subheader / group subtotal / location subtotal / grand total rows. */
.data-table tr.cb-loc td    { background: var(--ink-100); font-weight: 700; color: var(--ink-900); border-top: 2px solid var(--ink-300); }
.data-table tr.cb-grp td    { background: var(--ink-50);  font-weight: 600; color: var(--ink-700); }
.data-table tr.cb-grp .cb-n { color: var(--ink-500); font-weight: 400; }
.data-table tr.cb-sub td    { border-top: 1px solid var(--ink-200); color: var(--ink-600); font-weight: 600; }
.data-table tr.cb-locsub td { border-top: 1px solid var(--ink-300); border-bottom: 2px solid var(--ink-300); font-weight: 700; color: var(--ink-900); }
.data-table tr.cb-grand td  { background: var(--ink-100); font-weight: 800; color: var(--ink-950); border-top: 2px solid var(--ink-400); }
.cb-dept { display: inline-block; margin-left: 8px; font-size: 11px; color: var(--ink-500);
           background: var(--paper); border: 1px solid var(--ink-200); border-radius: 10px; padding: 0 8px; vertical-align: middle; }
.data-table tr.cb-click { cursor: pointer; }
.data-table tr.cb-click:hover td { background: var(--ink-50); }
.cb-caret { display: inline-block; width: 14px; color: var(--ink-500); font-size: 11px; }
/* Pay-runs inside .fullbleed-page (2026-07-10, heading-jump fix): the layout's own fixed
   100vh height would overflow the fullbleed shell — flex-fill it instead. */
.fullbleed-page > .list-detail-layout { flex: 1 1 auto; min-height: 0; height: auto; }

/* Salary-structures master-detail (2026-07-10): rail and detail pane scroll INDEPENDENTLY.
   The old single .tab-panel-scroll scrolled both together, so picking another name from the
   rail dragged the open detail out of view (founder). */
.tab-panel-fill { flex: 1; min-height: 0; display: flex; flex-direction: column; overflow: hidden; padding: var(--space-4) 4px 0; }
.tab-panel-fill > .vendors-shell { flex: 1; min-height: 0; }
.tab-panel-fill .list-pane { overflow-y: auto; min-height: 0; display: flex; flex-direction: column; }
.tab-panel-fill .list-rail { flex: 1; min-height: 0; display: flex; flex-direction: column; }
.tab-panel-fill .list-rail-body { flex: 1; min-height: 0; overflow-y: auto; }
.tab-panel-fill .detail-pane { min-height: 0; }
.tab-panel-fill .detail-pane-body { flex: 1; min-height: 0; overflow-y: auto; }
.cb-info { color: var(--ink-500); cursor: help; font-size: 14px; margin-left: 4px; }
.cb-info:hover, .cb-info:focus-visible { color: var(--brand-orange); }

/* ════════════════════════════════════════════════════════════════════════════
   Round 2 fix · 2026-06-03 — Entity tab strip (Items: List / Anomalies / Price).
   Founder: "if the active tab can have more of something different". Lift the
   active tab with an orange bar + stronger weight + white card so it reads as
   the current view rather than just a button.
   ════════════════════════════════════════════════════════════════════════════ */

.entity-tabs {
  display: flex;
  gap: 4px;
  border-bottom: 1px solid #e2e8f0;
  padding: 0 var(--space-4);
  /* White "shelf" so the selected tab's grey fill (var(--canvas) #f1f5f9) is VISIBLE against it.
     Previously transparent → on PO/Logistics/Items the strip showed the grey page canvas behind, so
     the grey active fill merged in and the selection vanished. Vendors looked right only because it
     set background:#fff inline. Making it the default fixes every entity-tab page at once, matching
     Vendors. 2026-06-15 founder. */
  background: #fff;
}
.entity-tab {
  background: transparent;
  border: none;
  border-bottom: 3px solid transparent;
  border-radius: 0;
  color: var(--ink-500);
  padding: 10px 16px;
  font-size: 13px;
  font-weight: 600;
  letter-spacing: 0.01em;
  cursor: pointer;
  transition: color 0.15s ease, border-color 0.15s ease, background 0.15s ease;
  margin-bottom: -1px;                                    /* overlap container border for clean join */
}
.entity-tab:hover {
  color: var(--ink-700);
  background: rgba(248, 250, 252, 0.6);
}
.entity-tab.active {
  color: var(--brand-orange);
  border-bottom-color: var(--brand-orange);
  background: #ffffff;
}

/* SubAssemblies tabbar uses `.sa-tabbar` + `.sa-tab-btn` — apply the same V3.2
   shape so it stops looking boxy. */
.sa-tabbar {
  display: flex;
  gap: 4px;
  border-bottom: 1px solid #e2e8f0;
  padding: 0 var(--space-4);
  margin-bottom: var(--space-4);
  background: #fff;   /* white shelf so the grey active-tab fill shows (same fix as .entity-tabs) */
}
.sa-tab-btn {
  background: transparent;
  border: none;
  border-bottom: 3px solid transparent;
  border-radius: 0;
  color: var(--ink-500);
  padding: 10px 16px;
  font-size: 13px;
  font-weight: 600;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  gap: 6px;
  transition: color 0.15s ease, border-color 0.15s ease, background 0.15s ease;
  margin-bottom: -1px;
}
.sa-tab-btn:hover {
  color: var(--ink-700);
  background: rgba(248, 250, 252, 0.6);
}
.sa-tab-btn.active {
  color: var(--brand-orange);
  border-bottom-color: var(--brand-orange);
  background: #ffffff;
}
/* Unified active-tab look (2026-06-15 founder): the selected tab gets soft top corners + a subtle
   fill so it reads as a raised "folder tab", keeping the orange bottom-border — consistent across
   every tabbed surface (Inward, Logistics, Purchase Orders, Sub-Assemblies, …). */
.entity-tab, .sa-tab-btn { border-top-left-radius: 7px; border-top-right-radius: 7px; }
.entity-tab.active, .sa-tab-btn.active {
  background: var(--canvas);
  border-bottom-width: 3px;
}
/* Tab count = a live quantity for that section, not part of the heading — bracket + mute it
   ("List (36)", "Anomalies (3)"). 2026-06-15 founder. */
.entity-tab-count { color: var(--ink-500); font-weight: 500; margin-left: 4px; font-size: 12px; }
.entity-tab-count::before { content: "("; }
.entity-tab-count::after { content: ")"; }
.entity-tab.active .entity-tab-count { color: var(--brand-orange); }
/* Purchase Orders + Logistics emit `is-active` (not `active`) on the selected entity-tab, so the
   `.entity-tab.active` rules above never matched them — their active tab looked like plain text
   while Vendors/Items (which emit `active`) showed the orange folder. Alias `is-active` to the same
   computed look so every tabbed page is consistent regardless of which class the markup emits. This
   is CSS-only (no rebuild) — the founder's "PO/Logistics tabs don't match Vendors". 2026-06-15. */
.entity-tab.is-active {
  color: var(--brand-orange);
  border-bottom-color: var(--brand-orange);
  border-bottom-width: 3px;
  background: var(--canvas);
}
.entity-tab.is-active .entity-tab-count { color: var(--brand-orange); }

/* ════════════════════════════════════════════════════════════════════════════
   Cards / panels / modals — V3.2's calm paper-on-slate aesthetic.
   ════════════════════════════════════════════════════════════════════════════ */

.detail-pane, .modal, .card, .view-section, .form-section {
  border-radius: 8px;
}
.detail-pane {
  box-shadow: var(--shadow-panel);
}
.modal {
  border-radius: 12px;
  box-shadow: var(--shadow-xl);
  border: 1px solid #e2e8f0;
}
.modal-overlay {
  background: rgba(15, 23, 42, 0.45);
  backdrop-filter: blur(2px);
}

/* Section bars — restrained accent strip, not a brutal block.
   Round 3 fix · 2026-06-03 — first-letter clip on Daily Report. Cause: outer `.form-section`
   has its own 1px border + overflow that ate into the section-bar's left border-left + left
   padding. Switch to a positioned ::before marker so the orange accent doesn't consume any
   layout space, then put plenty of padding-left for the text. */
.section-bar {
  position: relative;
  background: #f8fafc;
  color: var(--ink-700);
  font-family: var(--font-ui);
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  padding: 8px 14px 8px 22px;
  border-bottom: 1px solid #e2e8f0;
  border-left: none;
}
.section-bar::before {
  content: "";
  position: absolute;
  left: 8px;
  top: 8px;
  bottom: 8px;
  width: 3px;
  background: var(--brand-orange);
  border-radius: 2px;
  pointer-events: none;
}

/* ════════════════════════════════════════════════════════════════════════════
   Tables — V3.2's clean header band + soft alternating rows.
   Round 2 fix · 2026-06-03 — founder reported "double line on the header".
   Cause: `.table-wrap` had its own border + `.data-table` had its own border + the
   `.zsync-bulk-row` toolbar row had a border-bottom, stacking 2-3 horizontal lines.
   Fix: ONE source of truth for the visual frame — `.table-wrap` (because it spans
   the full content area). Suppress the table's own border + drop the toolbar's.
   ════════════════════════════════════════════════════════════════════════════ */

/* Default V3.2 card frame on every .data-table. Pages with their own .table-wrap get
   the frame from the wrap instead (rules below suppress the redundant inner frame). */
.data-table {
  background: #ffffff;
  border: 1px solid #e2e8f0;
  border-radius: 8px;
  overflow: hidden;
  box-shadow: var(--shadow-sm);
  margin: 0 0 12px;
  width: 100%;
  border-collapse: separate;                             /* preserves radius corners */
}
/* When wrapped in .table-wrap, the wrap is the visible frame. */
.table-wrap {
  background: #ffffff;
  border: 1px solid #e2e8f0;
  border-radius: 8px;
  overflow: hidden;
  box-shadow: var(--shadow-sm);
  margin-bottom: 12px;
}
.table-wrap .data-table {
  border: none;
  border-radius: 0;
  box-shadow: none;
  margin: 0;
}
.data-table thead th {
  background: #f8fafc;
  color: var(--ink-700);
  font-weight: 600;
  font-size: 12px;
  letter-spacing: 0.02em;
  text-transform: none;
  border-bottom: 1px solid #e2e8f0;
  border-top: none;                                      /* avoid double-line at top */
}
.data-table tbody tr {
  border-bottom: 1px solid #f1f5f9;
}
.data-table tbody tr:last-child {
  border-bottom: none;                                   /* wrap bottom border replaces it */
}
.data-table tbody tr:hover {
  background: #f8fafc;
}
.data-table.clickable-rows tbody tr:hover {
  cursor: pointer;
}

/* Pre-table toolbar rows (.zsync-bulk-row is the one above the table on Vendors / Items /
   Consumables). Drop the bottom border — the table's own card frame replaces the separation. */
.zsync-bulk-row {
  border-bottom: none;
  padding: 8px 14px 12px;
}

/* ════════════════════════════════════════════════════════════════════════════
   Form controls — softer focus, 8px corners.
   ════════════════════════════════════════════════════════════════════════════ */

.field, input.field, select.field, textarea.field, input[type="text"], input[type="number"], input[type="date"], input[type="email"], input[type="password"], select, textarea {
  border-radius: 8px;
  border: 1px solid #e2e8f0;
  transition: border-color 0.15s ease, box-shadow 0.15s ease;
  /* 2026-06-10 founder NON-NEGOTIABLE rule (feedback_standard_input_size memory) — every
     input / select / textarea on every modal + form must be standard size, not cramped.
     Founder has asked 10+ times. These three properties make the .field class the
     reference standard. Local overrides (e.g. table-cell qty inputs) may tighten width
     but must preserve padding + min-height. */
  padding: 8px 12px;
  min-height: 38px;
  font-size: 13px;
}
.field-row .field, .form-grid .field, .modal .field {
  /* Modal + form fields: fill the column they live in. Stops "thin column" cropping. */
  width: 100%;
  box-sizing: border-box;
}
textarea.field, textarea {
  min-height: 64px;
  padding: 10px 12px;
}
.modal .modal-actions button, .modal .form-actions button,
.modal > div > button.btn-primary, .modal > div > button.btn-secondary {
  /* Same memory rule applied to action-bar buttons. */
  min-width: 110px;
  padding: 8px 16px;
  min-height: 38px;
}
.field:focus, input.field:focus, select.field:focus, textarea.field:focus,
input[type="text"]:focus, input[type="number"]:focus, input[type="date"]:focus,
input[type="email"]:focus, input[type="password"]:focus, select:focus, textarea:focus {
  outline: none;
  border-color: var(--brand-orange);
  box-shadow: 0 0 0 3px rgba(249, 115, 22, 0.15);
}

/* ════════════════════════════════════════════════════════════════════════════
   Page chrome — title row, toolbars, KPI stats, empty states.
   ════════════════════════════════════════════════════════════════════════════ */

.page-toolbar h1, .page-title {
  font-family: var(--font-display);
  font-weight: 700;
  letter-spacing: -0.01em;
  text-transform: uppercase;
  line-height: 0.95;
  color: var(--ink-800);
}
/* Note: the orange left-accent strip is already painted by `.page-toolbar::before`
   in app.css. Do NOT add a `border-left` here — see Round 3 fix at the bottom of
   this file for the duplicate-bar bug this caused on every page title. */

/* Stat cards — soft white surface, not heavy borders. */
.page-stat {
  background: #ffffff;
  border: 1px solid #e2e8f0;
  border-radius: 8px;
  padding: 10px 14px;
  box-shadow: var(--shadow-sm);
}
.page-stat-label {
  font-size: 10px;
  font-weight: 600;
  color: var(--ink-500);
  letter-spacing: 0.08em;
  text-transform: uppercase;
}
.page-stat-value {
  font-family: var(--font-display);
  font-size: 22px;
  font-weight: 700;
  letter-spacing: -0.01em;
}

/* Stock Ledger banner groups (founder 2026-07-13) — the same total broken up two ways.
   A group is ONE flex child of .page-stats: tiny caption on top, its cards in a row below,
   so "By process" / "By location" wrap together instead of scattering. */
.sl-stat-group { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
.sl-stat-group-cap {
  font-size: 9px;
  font-weight: 700;
  letter-spacing: 0.1em;
  text-transform: uppercase;   /* micro section label — allowed caps, same family as .page-stat-label */
  color: var(--ink-400);
  padding-left: 2px;
}
.sl-stat-group-row { display: flex; gap: 8px; flex-wrap: wrap; }

/* Page filter bar — V3.2 floats the bar inside a white card. */
/* founder 2026-08-02 — the bar was a plain block, so its selects sat almost touching each
   other and any table chrome dropped onto a second line. Flex + a real gap fixes the
   spacing on EVERY page that uses this bar, and lets the chrome sit right-aligned. */
.page-filter-bar {
  background: #ffffff;
  border: 1px solid #e2e8f0;
  border-radius: 8px;
  padding: 10px 14px;
  margin: 0 var(--space-4) var(--space-3);
  box-shadow: var(--shadow-sm);
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-3);
}
.page-filter-bar > .filter-group { flex: 1 1 auto; }

/* Filter field cluster — small label above a select for clarity. Replaces the
   segmented chip strips on Dispatch Log, Items Anomalies, etc. */
.filter-field {
  display: inline-flex;
  flex-direction: column;
  gap: 2px;
}
.filter-field-label {
  font-size: 10px;
  font-weight: 700;
  color: var(--ink-500);
  letter-spacing: 0.06em;
  text-transform: uppercase;
}
.filter-field .field {
  min-width: 160px;
  padding: 6px 28px 6px 10px;
  font-size: 13px;
}

/* Empty state lifted to V3.2's centered comfort. */
.empty-state {
  background: #ffffff;
  border: 1px solid #e2e8f0;
  border-radius: 8px;
  padding: 48px 24px;
  text-align: center;
  color: var(--ink-500);
}
.empty-state h3 {
  color: var(--ink-800);
  font-weight: 600;
  margin-bottom: 6px;
}

/* ════════════════════════════════════════════════════════════════════════════
   Status pills — V3.2 soft pastel chips on white background.
   ════════════════════════════════════════════════════════════════════════════ */

.pill {
  border-radius: 10px;
  padding: 2px 9px;
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.02em;
  text-transform: none;
}
.pill-color-green   { background: #dcfce7; color: #166534; border: 1px solid #bbf7d0; }
.pill-color-orange  { background: #ffedd5; color: #9a3412; border: 1px solid #fed7aa; }
.pill-color-blue    { background: #dbeafe; color: #1e40af; border: 1px solid #bfdbfe; }
.pill-color-purple  { background: #ede9fe; color: #5b21b6; border: 1px solid #ddd6fe; }
.pill-color-yellow  { background: #fef3c7; color: #92400e; border: 1px solid #fde68a; }
.pill-color-red     { background: #fee2e2; color: #991b1b; border: 1px solid #fecaca; }
.pill-color-gray    { background: #f1f5f9; color: var(--ink-700); border: 1px solid #e2e8f0; }
.pill-color-teal    { background: #ccfbf1; color: #115e59; border: 1px solid #99f6e4; }
.pill-warn          { background: #fffbeb; color: #92400e; border: 1px solid #fde68a; }

/* ════════════════════════════════════════════════════════════════════════════
   Subtle polish — scrollbars, code/mono treatment, link colour.
   ════════════════════════════════════════════════════════════════════════════ */

a { color: var(--brand-orange); }
a:hover { color: var(--brand-orange-hover); }

code, kbd, pre, samp {
  font-family: var(--font-mono);
  background: #f1f5f9;
  color: var(--ink-700);
  padding: 1px 5px;
  border-radius: 4px;
  font-size: 11.5px;
}

::-webkit-scrollbar { width: 10px; height: 10px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb { background: #cbd5e1; border-radius: 8px; border: 2px solid transparent; background-clip: content-box; }
::-webkit-scrollbar-thumb:hover { background: #94a3b8; border: 2px solid transparent; background-clip: content-box; }

/* ════════════════════════════════════════════════════════════════════════════
   Round 2 fix · 2026-06-03 — SubAssemblies boxy accordion fix.
   Founder: "subassembly table is still boxy, like, rounded edges on the other
   tables, but subassembly still has boxy kind of a shape." The page uses
   <details> accordions + custom record rows that bypass `.data-table` styling.
   Round their corners + give them the same soft white card frame.
   ════════════════════════════════════════════════════════════════════════════ */

/* The actual SubAssemblies page wraps content in `.sa-tab-body` and uses
   <details open style="border:1px solid …; border-radius:0; …"> directly with
   INLINE styles. CSS specificity won't beat inline styles for non-!important
   properties — so we use !important for border-radius + softer border colour. */
.sa-tab-body details {
  border-radius: 8px !important;
  border-color: #e2e8f0 !important;
  overflow: hidden;
  margin-bottom: 12px !important;
  box-shadow: var(--shadow-sm);
  background: #ffffff !important;
}
.sa-tab-body details > summary {
  border-radius: 0 !important;
  padding: 10px 14px !important;
  background: #f8fafc !important;
  border-bottom: 1px solid #e2e8f0;
}
/* Nested sub-group details (Fork / Lock / Suspension) — softer treatment. */
.sa-tab-body details details {
  border: 1px solid #e2e8f0 !important;
  background: #ffffff !important;
  margin: 0 0 8px 0 !important;
}
/* Per-record row hover. The Razor outputs each record as a div without a class
   inside the inner details — match by descendant. */
.sa-tab-body details details > div > div:hover,
.sa-tab-body details > div > div:hover {
  background: #f8fafc;
}

/* `.sa-tab-body` itself: the bordered wrapper holding the tabs' content.
   Match the V3.2 soft card frame instead of sharp ink-200 lines. */
.sa-tab-body {
  border: 1px solid #e2e8f0 !important;
  border-top: none !important;
  border-radius: 0 0 8px 8px !important;
  background: #ffffff !important;
  padding: 14px !important;
}

/* The full-page table on Sub-Assemblies (frame-pipeline-table) inherits .data-table
   when it has that class; force the same rounded card frame on the wrapper. */
.frame-pipeline-table thead th {
  background: #f8fafc;
  color: var(--ink-700);
  font-weight: 600;
  text-transform: none;
  letter-spacing: 0.02em;
  border-bottom: 1px solid #e2e8f0;
}

/* ════════════════════════════════════════════════════════════════════════════
   Round 3 fix · 2026-06-03 — double orange vertical-bar bug on page titles.
   Cause: app.css already paints an orange bar via `.page-toolbar::before` (the V3.2
   left-accent strip). My earlier rule added a SECOND orange `border-left` on the h1
   itself, sitting next to the ::before at a different height = visible "two bars".
   Fix: rely on the existing `.page-toolbar::before` only — strip the h1 border-left,
   restore default h1 padding so the title spacing reads cleanly.
   ════════════════════════════════════════════════════════════════════════════ */

.page-toolbar h1, .page-title {
  border-left: none;
  padding-left: 0;
}

/* ════════════════════════════════════════════════════════════════════════════
   U34 — Sticky table header. Keeps column headers visible while the table body
   scrolls. Per Rules-V4 U34: position:sticky with solid bg + bottom shadow, on a
   table that uses border-spacing:0 (not collapse) so sticky engages cleanly.
   ════════════════════════════════════════════════════════════════════════════ */
.data-table thead th {
  position: sticky;
  top: 0;
  z-index: 3;
  background: #f8fafc;
  box-shadow: 0 1px 0 #e2e8f0;
}
.table-wrap {
  max-height: calc(100vh - 250px);
  overflow: auto;
}

/* Batch-4c · 2026-06-13 — Frame Pipeline table: visible divider above each colour group
   (founder: "no divider between painted frames on the left side"). tr borders don't render
   with border-spacing, so the line goes on the row's cells. The fab column cell spans the
   whole frame type, so the divider runs from the colour column rightward — intended. */
.pl-pipeline tr.pl-colour-first > td {
  border-top: 2px solid #f3d1e3;
}

/* Batch-4b · 2026-06-12 — the Batch-4 `.dispatch-log-table { min-width: 1480px }` scroll
   fix is REMOVED (founder: "columns stop resizing at some length which is too much").
   The table-level floor meant shrinking one column just redistributed width to the others
   — total never went below 1480px. The column resizer writes width+min+max inline on each
   TH (app.js initColumnResize), so once any column is sized the table's intrinsic minimum
   = Σ column widths, and .table-wrap's overflow:auto produces the horizontal scrollbar
   whenever that sum exceeds the window. Default (unresized) view shares width with U35
   ellipsis like every other page. */

/* ════════════════════════════════════════════════════════════════════════════
   U35 — Uniform row height + ellipsis truncation. Long content gets `…` instead
   of wrapping the row. Pages can opt out per cell with `.cell-multiline`.
   ════════════════════════════════════════════════════════════════════════════ */
.data-table td {
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  max-width: 320px;
  height: 44px;
  vertical-align: middle;
}
.data-table th {
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.data-table td.cell-multiline {
  white-space: normal;
  overflow: visible;
  text-overflow: clip;
  height: auto;
  max-width: none;
}
/* Allow nested .muted helper text to wrap inside the cell while the main row
   stays on one line — used by Customer (with phone below) and IMS (date below). */
.data-table td .muted {
  display: block;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* MR-V32-ProdLogXref · 2026-06-04 — Period KPI pairs on Production Log.
   Inline rate strip (MTD + FY by stage) — small / dense / read-only. */
.prod-kpi-pair {
  display: inline-flex;
  align-items: baseline;
  gap: 6px;
  padding: 3px 10px;
  background: #fff;
  border: 1px solid var(--ink-100);
  font-size: 12px;
}
.prod-kpi-pair .prod-kpi-stage {
  font-size: 11px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: .3px;
  margin-right: 4px;
}
.prod-kpi-pair .prod-kpi-num {
  font-size: 15px;
  font-weight: 800;
  color: var(--ink-900);
  font-variant-numeric: tabular-nums;
}
.prod-kpi-pair .prod-kpi-label {
  font-size: 10px;
  color: var(--ink-500);
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: .3px;
  margin-left: 1px;
}
.prod-kpi-pair .prod-kpi-sep {
  color: var(--ink-300);
  font-weight: 400;
}

/* ════════════════════════════════════════════════════════════════════════════
   M3-UI-Overhaul Batch A.2 · 2026-06-08 — Global keyboard focus indicator (G1)
   ════════════════════════════════════════════════════════════════════════════
   Before this rule the entire stylesheet had only two :focus-visible rules
   (.data-table th + .user-avatar), so every other button / tab / nav item /
   dropdown / chip / icon-action was invisible on keyboard focus. The handoff
   doc (M3-UI-REVIEW-HANDOFF-2026-06-08.md §2 G1) calls this "the highest-
   leverage single fix" — one rule that lights up every keyboard-interactive
   control across all 28 pages.

   Specificity is intentionally low — :where() gives 0,0,0 so any component
   that wants a richer focus style can still override with a plain selector.

   Outline color uses the M3 secondary role (= --accent #1E40AF) so it stands
   out cleanly against orange brand surfaces and dark navy chrome alike.
   3px width clears WCAG SC 2.4.7 (Focus Visible) comfortably.

   border-radius is intentionally NOT set — modern browsers (Chromium 94+,
   Firefox 88+, Safari 16.4+) auto-curve outline to match the element's own
   border-radius, so the focus ring follows whatever shape the button has.
   ════════════════════════════════════════════════════════════════════════════ */
:where(
  .btn-primary, .btn-secondary, .btn-danger,
  .btn-icon, .btn-icon-danger,
  .seg-btn, .entity-tab, .sa-tab-btn,
  .pagination-btn, .dropdown-item,
  .list-menu-btn, .col-settings-btn,
  .nav-item, .icon-action-btn,
  .tfd-trigger, .tfd-menu-item,
  .bulk-btn, .bulk-clear,
  a.brand-mark, .topbar-icon-btn,
  .user-avatar,
  .filter-chip, .topbar-search-clear
):focus-visible {
  outline: 3px solid var(--md-sys-color-secondary);
  outline-offset: 2px;
}

/* ════════════════════════════════════════════════════════════════════════════
   M3-UI-Overhaul Batch A.3 · 2026-06-08 — Contrast token fixes (G3)
   ════════════════════════════════════════════════════════════════════════════
   Per M3-UI-REVIEW-HANDOFF §2 G3. Four WCAG-failing colour pairs across the
   app:

   1. .btn-primary text — #fff on #F97316 = 2.9:1 (fails 4.5:1 body, fails
      3:1 even as a UI affordance). Swap to M3 on-primary role (#2A1500
      dark brown) for 7.2:1 AAA contrast. The brand orange stays unchanged.

   2. .field:focus old ring (box-shadow 0 0 0 3px rgba(249,115,22,.15))
      ≈ 2.9:1 against the white field bg — fails 3:1 UI affordance. Replace
      with a clean two-state model: mouse focus shows the orange
      border-color only (subtle, design-aligned); keyboard focus
      (:focus-visible) layers the high-contrast 2px blue ring on top so
      keyboard users get a strong signal that mouse users don't need.

   3. .muted / .hint-inline / ::placeholder text was --ink-400 #8E99A8
      (or raw #9ca3af in some rules) — ≈ 2.5:1 on white = fails. Switch
      to --ink-500 #6A7686 = 4.6:1 ✓. Borders and decorative uses of
      --ink-400 left alone (≥3:1 is acceptable for non-text UI elements).

   4. .validation-message / .required / .modal-danger h3 — raw #dc2626
      = 4.0:1, fails 4.5:1 small-text. Switch to var(--danger) #B91C1C
      = 5.9:1 ✓. Tokenised so future palette tweaks propagate.
   ════════════════════════════════════════════════════════════════════════════ */

/* Fix #1 — .btn-primary text contrast (white-on-orange → dark-brown-on-orange). */
.btn-primary, .action-btn-primary,
.btn-primary:hover, .action-btn-primary:hover,
.btn-primary:focus, .action-btn-primary:focus {
  color: var(--md-sys-color-on-primary);
}

/* Fix #2 — .field focus state. Mouse-focus = orange border-color only.
   Keyboard-focus (:focus-visible) = orange border-color + blue 2px outline. */
.field:focus, input.field:focus, select.field:focus, textarea.field:focus,
input[type="text"]:focus, input[type="number"]:focus, input[type="date"]:focus,
input[type="email"]:focus, input[type="password"]:focus, select:focus, textarea:focus {
  outline: none;
  border-color: var(--brand-orange);
  box-shadow: none;  /* drop the 2.9:1 orange-alpha ring */
}
.field:focus-visible, input.field:focus-visible, select.field:focus-visible, textarea.field:focus-visible,
input[type="text"]:focus-visible, input[type="number"]:focus-visible, input[type="date"]:focus-visible,
input[type="email"]:focus-visible, input[type="password"]:focus-visible, select:focus-visible, textarea:focus-visible {
  outline: 2px solid var(--md-sys-color-secondary);
  outline-offset: 2px;
  border-color: var(--brand-orange);
  box-shadow: none;
}

/* Fix #3 — Muted/hint/placeholder text. Bump from --ink-400 (2.5:1) to
   --ink-500 (4.6:1). Scoped to text uses only; decorative --ink-400 borders
   (e.g. .col-settings-lock icon) are left alone. */
.data-table .muted,
.muted,
.hint,
.hint-inline {
  color: var(--ink-500);
}
::placeholder,
.topbar-search-input::placeholder,
.field::placeholder,
input::placeholder,
textarea::placeholder {
  color: var(--ink-500);
  opacity: 1;  /* Firefox defaults to 0.54 — kills contrast again */
}

/* Fix #4 — Error/required text. Tokenise to var(--danger) for 5.9:1. */
.validation-message,
.login-card .validation-message,
.required,
.modal-danger h3 {
  color: var(--danger);
}

/* ════════════════════════════════════════════════════════════════════════════
   M3-UI-Overhaul Batch A.4 · 2026-06-08 — Icon button hit area + radius (G5)
   ════════════════════════════════════════════════════════════════════════════
   .btn-icon / .btn-icon-danger were 26×26 with 6px radius in app.css:3063+.
   Two issues:

   1. 26×26 fails WCAG 2.2 SC 2.5.8 Target Size Minimum (24×24 is the AA
      floor — passes at 26, but the handoff recommends 40 for desktop
      dense to give comfortable cursor margin and bring close-buttons /
      delete-icons in dense lists within easy reach).

   2. 6px radius is inconsistent with v32-theme's 8px standard
      (.btn-primary/.seg-btn/.field all 8px). Tiny visual drift the eye
      catches across a row of mixed buttons.

   Using min-width / min-height overrides the existing 26×26 width/height
   declarations cleanly (min-* wins when larger than the declared dim).
   Visible glyph stays the same size — only the hit area grows.

   Also covers the FrameActivity modal close ✕ buttons (FrameActivity.razor
   :287, :380) which were `class="icon-btn"` (UNDEFINED) and now ride this
   styling correctly after the rename to `.btn-icon` in the same batch.
   ════════════════════════════════════════════════════════════════════════════ */
.btn-icon,
.btn-icon-danger {
  min-width: 40px;
  min-height: 40px;
  border-radius: 8px;
}

/* M3 Batch B (G4) — screen-reader-only helper text utility. Standard pattern:
   element is removed from visual layout but readable to assistive tech via
   aria-describedby. Used for input help strings, icon-button labels behind
   emoji, etc. */
.visually-hidden {
  position: absolute !important;
  width: 1px; height: 1px;
  padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0, 0, 0, 0);
  white-space: nowrap; border: 0;
}

/* ════════════════════════════════════════════════════════════════════════════
   M3-UI-Overhaul Batch D · 2026-06-08 — Sentence case sweep (G6)
   ════════════════════════════════════════════════════════════════════════════
   Per M3-UI-REVIEW-HANDOFF §2 G6: convert mechanically-uppercased UI text
   to sentence case (M3 standard). Allowed to KEEP caps: Antonio brand mark,
   .section-bar / .nav-group-label / .page-stat-label (deliberate display
   treatments, called out as "allowed" in the handoff).

   Targeted overrides — drop text-transform:uppercase from:

   1. Login card heading / subtitle / labels / button (app.css:457-506).
      The login is the founder's first impression; M3 says sentence case
      reads as friendlier and more modern. Brand mark "HORNBACK IMS" stays
      via Antonio's stencil character (text content unchanged).

   2. KPI / page-stat micro-labels rendered without a specific scope.
      Keeps .page-stat-label as ALLOWED caps per handoff.

   The unchanged display strings (e.g. "Sign in") already arrive sentence-
   case from the razor; this rule just stops force-shouting them.
   ════════════════════════════════════════════════════════════════════════════ */
.login-card h1,
.login-card .subtitle,
.login-card label,
.login-btn {
  text-transform: none;
  letter-spacing: normal;
}

/* Pill text — handoff §2 G6 calls out "DELETED" → "Deleted", "YOU" → "You".
   Razor literals get fixed at source; here we also catch the .pill-deleted
   class which has its own text-transform:uppercase in app.css. */
.pill-deleted,
.pill-you {
  text-transform: none;
  letter-spacing: 0.02em;
}

/* Sentence-case sweep — pattern #1 (2026-06-25).
   Keep uppercase: .page-toolbar h1 / .section-bar / .nav-group-label (brand moments).
   Sentence-case everything else readable at body size. */
.toolbar-left .subtitle,
.page-toolbar .subtitle,
.toolbar-left p {
  text-transform: none;
  letter-spacing: normal;
}
.form-field label,
.form-field > label {
  text-transform: none;
  letter-spacing: normal;
}
.filter-field-label {
  text-transform: none;
  letter-spacing: 0.02em;
}

/* M3 Batch D · Sub-Assemblies tabs — proper class-based rules replacing the
   ~250-char inline style="..." block on every .sa-tab-btn (handoff §3
   Section 5 Sub-Assemblies finding: "sa-tab-btn overridden by huge inline
   style= repainting active/inactive (hardcoded borders/box-shadow) →
   defeats theme; two tab styles in one file. Delete inline style.").
   Mirrors the visual treatment exactly but routes through tokens so the
   override layer can actually re-skin tabs without per-page edits. */
/* 2026-06-15 — Sub-Assemblies tabs were the odd one out: an all-around orange border on the active
   tab. Re-skinned to match the entity-tab folder-tab look (orange BOTTOM-border + soft top corners
   + subtle fill) so every tabbed page is consistent. */
.sa-tab-btn {
  padding: 10px 16px;
  font-size: 13px;
  font-weight: 600;
  cursor: pointer;
  margin-right: 4px;
  margin-bottom: -1px;
  background: transparent;
  border: none;
  border-bottom: 3px solid transparent;
  border-top-left-radius: 7px;
  border-top-right-radius: 7px;
  color: var(--ink-500);
  transition: background .12s ease, color .12s ease, border-color .12s ease;
}
.sa-tab-btn:hover:not(.active) {
  background: rgba(248, 250, 252, .6);
  color: var(--ink-700);
}
.sa-tab-btn.active {
  background: var(--canvas);
  border-bottom-color: var(--brand-orange);
  color: var(--brand-orange);
  font-weight: 600;
}

/* ════════════════════════════════════════════════════════════════════════════
   M3-UI-Overhaul Batch E · 2026-06-08 — Stat / KPI colour tokens (G3 follow-up)
   ════════════════════════════════════════════════════════════════════════════
   Handoff §2 G3: "KPI/dashboard amber #f59e0b on white ≈2.0:1, fails even
   large-text". Same for purple #a855f7 (~4.0:1) and sky #0ea5e9 (~3.0:1)
   used as page-stat-value colours across Home / DispatchLog / DispatchDashboard
   / DailyReport / Cogs / StockLedger / PurchaseOrders preview tile.

   Replacement tokens — same hue, darker shade, all clear 4.5:1 ✓ on white:

     --stat-warning  amber-700  #B45309 = 4.7:1 ✓  (= V4's --warning)
     --stat-info     sky-700    #0369A1 = 6.5:1 ✓
     --stat-violet   purple-700 #7E22CE = 5.5:1 ✓

   Razor pages updated in the same batch to reference these instead of the
   failing raw hex literals.
   ════════════════════════════════════════════════════════════════════════════ */
:root {
  --stat-warning: var(--warning);  /* #B45309 — already defined in app.css */
  --stat-info:    #0369A1;
  --stat-violet:  #7E22CE;
}

/* ── Categorical chart palette (2026-07-28) — donut / multi-series slices.
   12 hues that stay distinguishable side by side and all read on white. Charts reference
   var(--chart-N) so no component ever carries a raw hex (U3); the model list runs to ~14
   families, so the palette repeats past 12 rather than inventing near-identical hues. ── */
:root {
  --chart-1:  #F97316;  /* brand orange */
  --chart-2:  #0369A1;  /* sky   */
  --chart-3:  #15803D;  /* green */
  --chart-4:  #7E22CE;  /* purple*/
  --chart-5:  #B45309;  /* amber */
  --chart-6:  #BE185D;  /* rose  */
  --chart-7:  #0F766E;  /* teal  */
  --chart-8:  #1E40AF;  /* indigo*/
  --chart-9:  #B91C1C;  /* red   */
  --chart-10: #4D7C0F;  /* olive */
  --chart-11: #6D28D9;  /* violet*/
  --chart-12: #475569;  /* slate */
}

/* ── Donut foregrounds (2026-07-28) ─────────────────────────────────────────────
   Two derived families so a chart never carries a raw hex (U3):
     --chart-N-on   text drawn ON that slice's fill (the % printed on the ring)
     --chart-N-ink  the same hue used as TEXT ON PAPER (the label around the ring)
   Only the brand orange needs help: white on #F97316 is 2.81:1 and fails AA, and
   #F97316 as text on white is the same 2.81:1 — hence dark ink on it, and a
   darkened #C2410C (5.18:1) as its text hue. The other 11 clear 4.5:1 unchanged. ── */
:root {
  --chart-1-on:#1B1205; --chart-2-on:#fff;  --chart-3-on:#fff;  --chart-4-on:#fff;
  --chart-5-on:#fff;    --chart-6-on:#fff;  --chart-7-on:#fff;  --chart-8-on:#fff;
  --chart-9-on:#fff;    --chart-10-on:#fff; --chart-11-on:#fff; --chart-12-on:#fff;

  --chart-1-ink:#C2410C;
  --chart-2-ink:var(--chart-2);   --chart-3-ink:var(--chart-3);
  --chart-4-ink:var(--chart-4);   --chart-5-ink:var(--chart-5);
  --chart-6-ink:var(--chart-6);   --chart-7-ink:var(--chart-7);
  --chart-8-ink:var(--chart-8);   --chart-9-ink:var(--chart-9);
  --chart-10-ink:var(--chart-10); --chart-11-ink:var(--chart-11);
  --chart-12-ink:var(--chart-12);
}

/* Dark theme (tokens.css already defines [data-theme="dark"] for the rest of the app):
   the light-surface palette is far too dark to read on a dark ground, so the hues lift
   to their 400-weights and the on-slice ink flips to near-black. */
[data-theme="dark"] {
  --chart-1:#FB923C;  --chart-2:#38BDF8;  --chart-3:#4ADE80;  --chart-4:#C084FC;
  --chart-5:#FBBF24;  --chart-6:#F472B6;  --chart-7:#2DD4BF;  --chart-8:#818CF8;
  --chart-9:#F87171;  --chart-10:#A3E635; --chart-11:#A78BFA; --chart-12:#94A3B8;

  --chart-1-on:#0F141A; --chart-2-on:#0F141A; --chart-3-on:#0F141A; --chart-4-on:#0F141A;
  --chart-5-on:#0F141A; --chart-6-on:#0F141A; --chart-7-on:#0F141A; --chart-8-on:#0F141A;
  --chart-9-on:#0F141A; --chart-10-on:#0F141A;--chart-11-on:#0F141A;--chart-12-on:#0F141A;

  --chart-1-ink:var(--chart-1);   --chart-2-ink:var(--chart-2);
  --chart-3-ink:var(--chart-3);   --chart-4-ink:var(--chart-4);
  --chart-5-ink:var(--chart-5);   --chart-6-ink:var(--chart-6);
  --chart-7-ink:var(--chart-7);   --chart-8-ink:var(--chart-8);
  --chart-9-ink:var(--chart-9);   --chart-10-ink:var(--chart-10);
  --chart-11-ink:var(--chart-11); --chart-12-ink:var(--chart-12);
}

/* ── Donut chart ────────────────────────────────────────────────────────────────
   ONE centred SVG with its labels inside it — no legend column beside it, which is
   what pushed the ring off to one side. width:100% + height:auto + viewBox means the
   chart can never be wider than its grid cell, so it cannot introduce a horizontal
   scrollbar however narrow the cell gets. ── */
.donut-wrap { padding: var(--space-2) 0; }
.donut {
  display: block;
  width: 100%;
  height: auto;
  /* 2026-07-28 — 600 x (290/460) renders 378px tall, matching the 380px bar charts beside it
     (founder: "use the full height, it will create better visibility"). Label type is 11.5 SVG
     units, so raising this cap also lifts it from 12.0px to 15.0px as rendered. */
  max-width: 820px;
  margin: 0 auto;
}
.donut text      { font-family: inherit; }
/* Font sizes here are SVG USER UNITS, not pixels — they scale with the viewBox. The hole is 84
   units across, so d-total at 21 keeps a five-digit total clear of the ring on both sides. */
.donut .d-total  { font-size: 21px;   font-weight: 700; fill: var(--ink-900); font-variant-numeric: tabular-nums; }
.donut .d-sub    { font-size: 9px;    fill: var(--ink-500); letter-spacing: .05em; text-transform: uppercase; }
.donut .d-onring { font-size: 11px;   font-weight: 700; font-variant-numeric: tabular-nums; pointer-events: none; }
.donut .d-name   { font-size: 11.5px; font-weight: 600; }
.donut .d-pct    { fill: var(--ink-500); font-weight: 500; font-variant-numeric: tabular-nums; }
.donut .d-lead   { stroke-width: 1; opacity: .55; }

/* ── HR detail panel: clean definition grid (founder feedback 2026-06-24 — profile rows were
   crowded/jammed). Label sits small + muted above its value, generous spacing, two columns. ── */
.def-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--space-3) var(--space-6);
  margin: var(--space-2) 0 0;
}
.def-grid > div { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.def-grid dt {
  font-size: 11px;
  letter-spacing: .02em;
  color: var(--ink-500);
  margin: 0;
}
.def-grid dd {
  margin: 0;
  font-size: 14px;
  color: var(--ink-900);
  word-break: break-word;
}
.dp-section-title {
  font-size: 13px;
  font-weight: 600;
  color: var(--ink-700);
  margin: 0 0 var(--space-2);
}

/* founder 2026-08-02 — .dp-section / .dp-row / .dp-label / .doc-slot / .detail-footer were
   used by the Dealers detail pane (and .dp-section by Employees) but defined NOWHERE, so the
   whole pane rendered as one unspaced column with each label glued to its value
   ("Dealer codeHBDL-0004"). Defining them here fixes every page that already uses them. */
.dp-section { padding: var(--space-3) 0; }
/* U47 — the Dealers detail body has no padding of its own (unlike .detail-pane-body on
   Employees), so its sections were flush against the pane edge. Pad here, not on
   .dp-section, so Employees isn't double-indented. */
.detail-pane > .detail-body > .dp-section { padding-left: var(--space-4); padding-right: var(--space-4); }

/* founder 2026-08-02 — in the full-width detail pane a fixed 2-column def-grid strands each
   value in half a screen of white. Fill the width instead: as many ~260px columns as fit,
   so the same fields read as a compact block on a wide monitor and stack on a narrow one.
   Rows that opted into `grid-column:1/-1` still span everything. */
.detail-body .def-grid { grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); }

/* ── Customer map (founder 2026-08-03) ─────────────────────────────────────────
   Vertical split: the map keeps two thirds, the location list takes the last third
   beside it and scrolls independently. Stacked, the list squeezed the map into a
   letterbox and reading it meant scrolling the map off screen. */
/* founder 2026-08-03 — list on the LEFT, map on the right, and both run the full height of
   what's left of the window rather than a fixed box with dead space under it. */
.cmap-split {
  display: grid;
  grid-template-columns: 1fr 2fr;
  gap: var(--space-3);
  /* Measured, not guessed: topbar + title + tabs + filter bar + the excluded-records line,
     plus the page's own bottom padding. 21px short of this and the window grows a scrollbar. */
  height: calc(100vh - 292px);
  min-height: 460px;
}
.cmap-canvas {
  height: 100%; width: 100%; min-width: 0;
  border: 1px solid var(--ink-200);
  border-radius: 8px;
  background: var(--ink-50);
  /* founder 2026-08-03 — the profile card was rendering UNDER the map. Leaflet gives its own
     panes z-index 400 and its controls 1000, and with no stacking context of its own those
     numbers compete with the whole page. position + z-index:0 boxes them in, so the map can
     never out-stack the topbar or any dropdown hanging off it. */
  position: relative;
  z-index: 0;
  isolation: isolate;
}
.cmap-side {
  display: flex; flex-direction: column; min-height: 0; min-width: 0;
  border: 1px solid var(--ink-200);
  border-radius: 8px;
  background: var(--paper);
  overflow: hidden;
}
/* U4b — .section-bar full-bleeds with NEGATIVE side margins, so inside this overflow:hidden
   panel its first letters were clipped ("OP LOCATIONS"). Neutralised, and given real padding. */
.cmap-side .section-bar { margin: 0; padding: 8px 12px; }
.cmap-side-body { overflow-y: auto; flex: 1 1 auto; }
.cmap-row { padding: 8px 12px; border-bottom: 1px solid var(--ink-100); }
.cmap-row:hover { background: var(--ink-50); }
.cmap-row-top { display: flex; align-items: baseline; justify-content: space-between; gap: var(--space-2); }
.cmap-place {
  font-size: 13px; font-weight: 600; color: var(--ink-900);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.cmap-qty {
  font-size: 14px; font-weight: 800; color: var(--brand-orange);
  font-variant-numeric: tabular-nums; flex-shrink: 0;
}
.cmap-where { font-size: 11px; color: var(--ink-600); margin-top: 1px; }
/* Share sits under the count, right-aligned with it, so the eye reads one column of numbers. */
.cmap-qty { display: flex; flex-direction: column; align-items: flex-end; }
.cmap-pct { font-size: 10.5px; font-weight: 600; color: var(--ink-500); }
/* Month band in the invoice register — one per month of goods received, so a month can be
   asked a single question ("is July all at the office?") and answer it in the band itself. */
.data-table tbody tr.reg-month-band,
.data-table tbody tr.reg-month-band:hover { background: var(--ink-200); }
.reg-month-band td {
  padding: 5px 10px; border-top: 1px solid var(--ink-300);
  font-size: 11.5px; font-weight: 800; color: var(--ink-800);
  letter-spacing: .03em;
}
.reg-month-meta { font-weight: 600; color: var(--ink-600); margin-left: var(--space-3); letter-spacing: 0; }

/* A settled row — still there, deliberately quieter. Used by the invoice register for anything
   already pouched or marked sent, so the outstanding rows carry the eye. */
.data-table tbody tr.row-muted td { color: var(--ink-500); background: var(--ink-50); }

/* Cluster / pincode bubbles — the number is bicycles, the smaller figure its share of what the
   filters are currently showing. White on these fills clears 4.5:1 at this weight and size. */
.cmap-bubble > div {
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  border-radius: 50%; color: #fff; line-height: 1.05;
  border: 2px solid rgba(255,255,255,.85);
  box-shadow: 0 1px 4px rgba(15,23,42,.35);
}
.cmap-bubble-qty { font-size: 12px; font-weight: 800; font-variant-numeric: tabular-nums; }
.cmap-bubble-pct { font-size: 9.5px; font-weight: 600; opacity: .9; }
.marker-cluster, .marker-cluster div { background: none !important; }

@media (max-width: 1100px) {
  /* Narrow: the map goes back on top — a heat map you can't see is worse than a list you scroll. */
  .cmap-split { grid-template-columns: 1fr; height: auto; }
  .cmap-canvas { height: 52vh; order: -1; }
  .cmap-side { max-height: 40vh; }
}
.dp-section + .dp-section { border-top: 1px solid var(--ink-100); }

/* Edit-form row: label above a full-width control, on the grid. */
.dp-row { display: flex; flex-direction: column; gap: 4px; margin-bottom: var(--space-3); }
.dp-row > .dp-label {
  font-size: 11px;
  letter-spacing: .02em;
  color: var(--ink-500);
}
.dp-row .field, .dp-row textarea.field { width: 100%; }

.doc-slot {
  display: flex; flex-direction: column; align-items: flex-start; gap: 4px;
  min-width: 190px;
  padding: var(--space-3);
  border: 1px solid var(--ink-200);
  border-radius: 8px;
  background: var(--ink-50);
}
.doc-slot-label { font-size: 11px; font-weight: 600; color: var(--ink-600); }

.detail-footer {
  display: flex; justify-content: flex-end; gap: var(--space-2);
  padding-top: var(--space-4);
  margin-top: var(--space-2);
  border-top: 1px solid var(--ink-200);
}
@media (max-width: 720px) { .def-grid { grid-template-columns: 1fr; } }

/* ── BOM overlay — full-screen scrollable document (SKUs → "View BOM") ──────── */
.bom-overlay-bg {
  position: fixed; inset: 0;
  background: rgba(0,0,0,.45);
  z-index: 500;
}
.bom-overlay-doc {
  position: fixed;
  inset: 0;
  z-index: 501;
  overflow-y: auto;
  background: var(--surface, #fff);
  padding: var(--space-6) var(--space-8);
  max-width: 900px;
  margin: 0 auto;
  outline: none;           /* tabindex="-1" focus target; visual focus via overlay-bg */
}

/* Document letterhead */
.bom-doc-header {
  display: flex;
  align-items: flex-start;
  gap: var(--space-6);
  padding-bottom: var(--space-4);
  border-bottom: 2px solid var(--ink-900);
  margin-bottom: var(--space-5);
}
.bom-doc-company { flex: 1; }
.bom-company-name {
  font-family: 'Antonio', sans-serif;
  font-size: 28px;
  font-weight: 700;
  color: var(--ink-950);
  letter-spacing: .04em;
  line-height: 1;
}
.bom-company-sub {
  font-size: 12px;
  color: var(--ink-500);
  letter-spacing: .06em;
  margin-top: 2px;
}
.bom-doc-meta {
  flex: 1;
  display: flex;
  flex-direction: column;
  gap: 4px;
  align-items: flex-end;
}
.bom-meta-row { display: flex; gap: var(--space-2); align-items: baseline; }
.bom-meta-lbl {
  font-size: 11px;
  color: var(--ink-500);
  min-width: 72px;
  text-align: right;
}
.bom-meta-val {
  font-size: 13px;
  font-weight: 600;
  color: var(--ink-900);
}
.bom-doc-actions {
  display: flex;
  gap: var(--space-2);
  align-items: center;
}

/* BOM sections */
.bom-section {
  margin-bottom: var(--space-6);
}
.bom-section-hdr {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  font-size: 14px;
  font-weight: 700;
  color: var(--ink-900);
  margin-bottom: var(--space-3);
  padding-bottom: var(--space-1);
  border-bottom: 1px solid var(--ink-200);
}
.bom-section-num {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 22px; height: 22px;
  border-radius: 50%;
  background: var(--ink-900);
  color: #fff;
  font-size: 12px;
  font-weight: 700;
  flex-shrink: 0;
}
/* Group row — replaces numbered section headers; title left + group total right */
.bom-group-row {
  display: flex;
  justify-content: space-between;
  align-items: center;
  border-bottom: 2px solid var(--ink-200);
  padding-bottom: var(--space-2);
  margin-bottom: var(--space-3);
}
.bom-group-title {
  font-size: 14px;
  font-weight: 700;
  color: var(--ink-900);
}
.bom-group-total {
  font-size: 14px;
  font-weight: 700;
  color: var(--ink-900);
  font-variant-numeric: tabular-nums;
}

.bom-sub-lbl {
  font-size: 12px;
  font-weight: 600;
  color: var(--ink-600);
  margin: 0 0 var(--space-1);
}
.bom-kind-tag {
  font-size: 11px;
  font-weight: 400;
  color: var(--ink-500);
  border: 1px solid var(--ink-200);
  border-radius: var(--r-sm);
  padding: 1px 6px;
  margin-left: var(--space-1);
}
.bom-empty {
  font-size: 12px;
  color: var(--ink-500);
  font-style: italic;
  margin: 0 0 var(--space-2);
}

/* BOM data table — fixed 7-column grid shared by all group tables (Frame fab, Frame paint,
   Sub-asm, Direct parts) so columns lock to the same positions across every section. */
.bom-table {
  width: 100%;
  border-collapse: collapse;
  table-layout: fixed;
  font-size: 13px;
  margin-bottom: var(--space-2);
}
/* col 1: Part code  col 2: Description (flex)  col 3: Qty  col 4: Unit
   col 5: Purchase rate  col 6: Landed  col 7: Landed total */
.bom-table th:nth-child(1), .bom-table td:nth-child(1) { width: 108px; }
.bom-table th:nth-child(3), .bom-table td:nth-child(3) { width: 52px; }
.bom-table th:nth-child(4), .bom-table td:nth-child(4) { width: 52px; }
.bom-table th:nth-child(5), .bom-table td:nth-child(5) { width: 108px; }
.bom-table th:nth-child(6), .bom-table td:nth-child(6) { width: 108px; }
.bom-table th:nth-child(7), .bom-table td:nth-child(7) { width: 112px; }
.bom-table th {
  text-align: left;
  font-size: 11px;
  font-weight: 600;
  color: var(--ink-600);
  border-bottom: 1px solid var(--ink-200);
  padding: 4px 8px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.bom-table th.num, .bom-table td.num { text-align: right; font-variant-numeric: tabular-nums; }
.bom-table td {
  padding: 5px 8px;
  color: var(--ink-800);
  border-bottom: 1px solid var(--ink-100);
  vertical-align: top;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.bom-table tbody tr:last-child td { border-bottom: none; }
.bom-table td.mono { font-family: monospace; font-size: 12px; color: var(--ink-500); }

/* Total strip */
.bom-total-strip {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: var(--space-3) var(--space-4);
  background: var(--ink-950);
  color: #fff;
  border-radius: var(--r-md);
  margin-top: var(--space-4);
  font-size: 14px;
  font-weight: 600;
}
.bom-total-val {
  font-size: 18px;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
}

/* Secondary total strip — raw material total (lighter than the primary dark strip) */
.bom-total-strip-secondary {
  background: var(--ink-100);
  color: var(--ink-700);
  margin-top: var(--space-2);
  font-weight: 500;
}
.bom-total-val-secondary {
  font-size: 15px;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  color: var(--ink-700);
}

/* Footer note */
.bom-doc-footer {
  margin-top: var(--space-5);
  padding-top: var(--space-3);
  border-top: 1px solid var(--ink-100);
  display: flex;
  justify-content: space-between;
  font-size: 11px;
  color: var(--ink-400);
}

/* ── COGS breakdown modal — reuses .bom-overlay-doc; wider for the cost columns ─────
   The per-row "View COGS" trigger, the 14-column cost table, and the unit summary. */
.cogs-doc { max-width: 1180px; }

/* Row trigger — SKU code + inline magnifier; text-button styling, no width bloat */
.cogs-view-btn {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  background: none;
  border: none;
  padding: 2px 4px;
  margin: -2px -4px;
  font: inherit;
  font-weight: 600;
  color: var(--ink-900);
  cursor: pointer;
  border-radius: var(--r-sm);
}
.cogs-view-btn:hover { color: var(--brand-orange); }
.cogs-view-btn:hover .cogs-view-code { text-decoration: underline; }
.cogs-view-ico { color: var(--ink-400); flex-shrink: 0; }
.cogs-view-btn:hover .cogs-view-ico { color: var(--brand-orange); }
.cogs-view-btn:focus-visible { outline: 3px solid var(--md-sys-color-secondary); outline-offset: 2px; }

/* Wide cost table — horizontal scroll inside the modal doc */
.cogs-table-scroll { overflow-x: auto; margin-bottom: var(--space-2); }
/* 2026-07-11 — size to content (bounded), not width:100%. On an ultra-wide monitor a 100%-width table
   with a handful of short columns stretched across ~2000px, leaving a huge gap before the right-aligned
   ₹/unit column. width:auto + a shared min/max keeps all three breakdown tables compact and aligned. */
/* 2026-07-12 sweep — PicklistManager colour popover was invisible: .picklist-table td has
   overflow:hidden (app.css) and the 42px swatch cell is the popover's containing block, so the
   220px .pill-picker-popover was clipped to the cell. Exempt the swatch cell from clipping and
   open the popover to the RIGHT of the cell (into the row) so it stays inside the scroll area. */
.picklist-table td:first-child { overflow: visible; text-overflow: clip; }
.picklist-table .pill-picker-popover { left: 0; transform: none; }

/* Clean labour-breakdown tables (COGS → Labour) — proper grid + even spacing, first column sized to
   content (not stretched like .cogs-bom-table), numeric columns packed right. Founder UI fix 2026-07-12. */
.cogs-labour-tbl { width: 100%; border-collapse: collapse; font-size: 13px; }
.cogs-labour-tbl th {
  text-align: left; padding: 8px 14px; font-size: 11px; font-weight: 700; letter-spacing: .02em;
  color: var(--ink-500); border-bottom: 1.5px solid var(--ink-200); white-space: nowrap;
}
.cogs-labour-tbl td {
  padding: 8px 14px; color: var(--ink-800); border-bottom: 1px solid var(--ink-100); white-space: nowrap;
}
.cogs-labour-tbl th.r, .cogs-labour-tbl td.r { text-align: right; font-variant-numeric: tabular-nums; }
.cogs-labour-tbl td:first-child { font-weight: 500; color: var(--ink-900); }
.cogs-labour-tbl tbody tr:hover td { background: var(--ink-50); }
.cogs-labour-tbl tr.tot td {
  border-top: 1.5px solid var(--ink-300); border-bottom: none; font-weight: 700;
  color: var(--ink-900); background: var(--ink-50); padding-top: 9px; padding-bottom: 9px;
}
.cogs-labour-tbl .u { color: var(--ink-500); font-size: 11px; font-weight: 400; }

.cogs-bom-table {
  width: auto;
  min-width: 680px;
  max-width: 1040px;
  border-collapse: collapse;
  font-size: 12px;
}
/* First (label) column absorbs the slack so the numeric columns stay packed to the right, not floated
   far away with empty space in between. */
.cogs-bom-table th:first-child, .cogs-bom-table td:first-child { width: 100%; }
.cogs-bom-table th {
  text-align: left;
  font-size: 10.5px;
  font-weight: 600;
  color: var(--ink-600);
  border-bottom: 1px solid var(--ink-200);
  padding: 4px 8px;
  white-space: nowrap;
}
.cogs-bom-table th.dim-hdr { text-align: center; }
.cogs-bom-table th.num, .cogs-bom-table td.num { text-align: right; font-variant-numeric: tabular-nums; }
.cogs-bom-table td {
  padding: 4px 8px;
  color: var(--ink-800);
  border-bottom: 1px solid var(--ink-100);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  max-width: 240px;
}
.cogs-bom-table td.mono { font-family: monospace; font-size: 11px; color: var(--ink-500); }
.cogs-bom-table .cogs-dim-sep { border-left: 1px solid var(--ink-200); }
.cogs-bom-table tbody tr.cogs-subtotal-row td {
  border-top: 1px solid var(--ink-300);
  border-bottom: none;
  font-weight: 700;
  color: var(--ink-800);
  background: var(--ink-50);
}
/* Grand total across all groups — the strongest row in the per-item table */
.cogs-bom-table tbody tr.cogs-grand-row td {
  border-top: 2px solid var(--ink-400);
  border-bottom: none;
  font-weight: 800;
  color: var(--ink-950);
  background: var(--ink-100);
}
/* Each column's grand total as a share of unit COGS */
.cogs-bom-table tbody tr.cogs-pct-row td {
  border-bottom: none;
  font-weight: 700;
  color: var(--ink-600);
  background: var(--ink-100);
}

/* Reconciliation guard note (shown only if per-item landed ≠ summary landed) */
.cogs-recon-warn {
  margin-top: var(--space-2);
  padding: var(--space-2) var(--space-3);
  border-radius: var(--r-sm);
  background: var(--warning-soft);
  color: var(--ink-800);
  font-size: 12px;
}

/* Unit cost summary card (values pulled from the COGS row, not recomputed) */
.cogs-summary {
  max-width: 520px;
  margin-top: var(--space-5);
  border: 1px solid var(--ink-200);
  border-radius: var(--r-md);
  padding: var(--space-3) var(--space-4);
  background: var(--ink-50);
}
.cogs-summary-title {
  font-size: 12px;
  font-weight: 700;
  color: var(--ink-700);
  margin-bottom: var(--space-2);
}
.cogs-summary-row,
.cogs-summary-head {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: var(--space-3);
  padding: 5px 0;
  border-bottom: 1px solid var(--ink-100);
}
.cogs-summary-head {
  border-bottom: 1px solid var(--ink-300);
  font-size: 10.5px;
  text-transform: uppercase;
  letter-spacing: .04em;
}
.cogs-summary-head .cogs-summary-lbl,
.cogs-summary-head .cogs-summary-val,
.cogs-summary-head .cogs-summary-pct {
  font-size: 10.5px;
  font-weight: 700;
  color: var(--ink-500);
}
.cogs-summary-lbl { font-size: 12.5px; color: var(--ink-700); flex: 1 1 auto; }
.cogs-summary-val { font-size: 13px; font-variant-numeric: tabular-nums; color: var(--ink-900); font-weight: 600; min-width: 110px; text-align: right; }
.cogs-summary-pct { font-size: 12px; font-variant-numeric: tabular-nums; color: var(--ink-500); font-weight: 600; min-width: 64px; text-align: right; }
.cogs-summary-row.is-landed {
  border-top: 2px solid var(--ink-300);
  border-bottom: none;
  margin-top: 2px;
  padding-top: 8px;
}
.cogs-summary-row.is-landed .cogs-summary-lbl,
.cogs-summary-row.is-landed .cogs-summary-val,
.cogs-summary-row.is-landed .cogs-summary-pct { font-weight: 800; color: var(--ink-950); }
.cogs-summary-row.is-total {
  border-top: 2px solid var(--brand-orange);
  border-bottom: none;
  margin-top: 4px;
  padding-top: 10px;
}
.cogs-summary-row.is-total .cogs-summary-lbl { font-weight: 800; color: var(--ink-950); font-size: 14px; }
.cogs-summary-row.is-total .cogs-summary-val { font-weight: 800; color: var(--brand-orange); font-size: 16px; }
.cogs-summary-row.is-total .cogs-summary-pct { font-weight: 800; color: var(--ink-950); font-size: 13px; }
/* Landed % is a subtotal, not a competing slice — brackets + quiet weight, overriding the is-landed emphasis. */
.cogs-summary-row.is-landed .cogs-summary-pct.cogs-pct-sub { font-weight: 600; color: var(--ink-500); }
.cogs-bom-table tbody tr.cogs-pct-row td.cogs-pct-sub { color: var(--ink-500); font-weight: 500; }
.cogs-pct-hint { font-weight: 400; color: var(--ink-400); font-size: 11px; }
.cogs-summary-note {
  margin: var(--space-2) 0 0;
  font-size: 11px;
  color: var(--ink-500);
  line-height: 1.4;
}

/* ── Filter chips (status / priority / department filters) ────────────────── */
.filter-chips {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-1);
  align-items: center;
  margin-bottom: var(--space-2);
}

.filter-chip {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 6px 14px;
  min-height: 36px;
  border: 1.5px solid var(--ink-300);
  border-radius: var(--radius-full, 999px);
  background: transparent;
  color: var(--ink-600);
  font-size: 13px;
  font-weight: 500;
  cursor: pointer;
  white-space: nowrap;
  transition: border-color 0.12s, background 0.12s, color 0.12s;
}
.filter-chip:hover {
  border-color: var(--brand-orange);
  color: var(--brand-orange);
  background: color-mix(in srgb, var(--brand-orange) 6%, transparent);
}
.filter-chip.active {
  border-color: var(--brand-orange);
  background: color-mix(in srgb, var(--brand-orange) 12%, transparent);
  color: var(--brand-orange);
  font-weight: 600;
}

.chip-count {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 18px;
  height: 18px;
  padding: 0 4px;
  border-radius: 9px;
  background: var(--ink-200);
  color: var(--ink-600);
  font-size: 11px;
  font-weight: 600;
  line-height: 1;
}
.filter-chip.active .chip-count {
  background: color-mix(in srgb, var(--brand-orange) 20%, transparent);
  color: var(--brand-orange);
}

.chip-sep {
  display: inline-block;
  width: 1px;
  height: 20px;
  background: var(--ink-200);
  margin: 0 var(--space-1);
  align-self: center;
}

/* ── Warranty Policies page — period display helpers ──────────────────────── */
.wp-period--lifetime {
  color: var(--success);
  font-weight: var(--fw-semibold);
}
.wp-period--none {
  color: var(--ink-400);
}

/* ── Support tickets — SO search result rows (link dispatch modal) ─────────── */
.so-result-row {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-2) var(--space-3);
  cursor: pointer;
  border-left: 3px solid transparent;
  border-bottom: 1px solid var(--ink-100);
  transition: background var(--dur-fast);
}
.so-result-row:last-child    { border-bottom: none; }
.so-result-row:hover         { background: var(--ink-50); }
.so-result-row:focus-visible { outline: 2px solid var(--brand-orange); outline-offset: -2px; }
.so-result-row.is-selected   { border-left-color: var(--brand-orange); background: var(--ink-50); }
.so-result-row .so-ref  { font-family: var(--font-mono); font-size: var(--fs-sm); font-weight: var(--fw-semibold); min-width: 8rem; }
.so-result-row .so-name { flex: 1; font-size: var(--fs-sm); }
.so-result-row .so-date { font-size: var(--fs-sm); color: var(--ink-500); }

/* ── Permission matrix (Admin > Roles page) ────────────────────────────────── */
.roles-matrix { overflow-x: auto; }

.roles-matrix-table th.matrix-col,
.roles-matrix-table td.matrix-col {
  width: 72px;
  text-align: center;
  vertical-align: middle;
}

.roles-matrix-table .matrix-section-row td {
  background: var(--surface-dim, #eef2f7);
  font-weight: 600;
  font-size: var(--fs-sm);
  letter-spacing: 0.03em;
  padding: 5px 12px;
  color: var(--ink-700);
}

.roles-matrix-table .matrix-sub-label {
  padding-left: 24px;
  font-size: var(--fs-sm);
}

.roles-matrix-table .matrix-row-inactive td {
  color: var(--ink-400);
}

.roles-matrix-table input[type="checkbox"] {
  width: 16px;
  height: 16px;
  cursor: pointer;
  accent-color: var(--brand-orange);
}

.roles-matrix-table input[type="checkbox"]:disabled {
  cursor: not-allowed;
  opacity: 0.55;
}

/* ── Print — only the open BOM/COGS overlay document prints ────────────────
   founder 2026-07-23 — the old display:none-on-.fullbleed-page approach printed BLANK
   pages: the overlay is a CHILD of .fullbleed-page (Cogs), so hiding the parent hid the
   document too, while the topbar (different class) still printed. visibility survives
   nesting: hidden ancestors can have visible descendants — so hide EVERYTHING, then
   re-show the overlay subtree, and unpin it so it flows across print pages. Scoped with
   :has so normal pages (no overlay open) print unaffected. */
@media print {
  .no-print { display: none !important; }
  body:has(.bom-overlay-doc) * { visibility: hidden; }
  body:has(.bom-overlay-doc) .bom-overlay-doc,
  body:has(.bom-overlay-doc) .bom-overlay-doc * { visibility: visible; }
  body:has(.bom-overlay-doc) .bom-overlay-doc {
    position: absolute !important;
    left: 0 !important;
    top: 0 !important;
    width: 100% !important;
    max-width: 100% !important;
    height: auto !important;
    max-height: none !important;
    overflow: visible !important;
    padding: 12px !important;
    margin: 0 !important;
    border: none !important;
    box-shadow: none !important;
    z-index: auto !important;
  }
  body:has(.bom-overlay-doc) .bom-overlay-bg { display: none !important; }
  html, body { height: auto !important; }
  /* Ensure tables don't get clipped across pages. founder 2026-07-23 — sections taller than a
     page must FLOW (avoid stranded a giant gap before the matrix section); rows never split. */
  .bom-table { page-break-inside: auto; }
  .bom-table tr { page-break-inside: avoid; }
  .bom-section { page-break-inside: auto; }
  .bom-group-row { page-break-after: avoid; }
  .bom-total-strip { background: var(--ink-950) !important; -webkit-print-color-adjust: exact; print-color-adjust: exact; }
}

/* ─────────────────────────────────────────────────────────────────────────
   Support Tickets + Customer Records two-pane layout (list rail + detail).
   These pages use .split-pane / .has-detail, which previously had NO CSS —
   so the detail rendered as a full-width block BELOW the table (off-screen),
   making the page look like "just a table, nothing actionable" (founder report
   2026-07-03). Mirrors the proven .vendors-shell.with-detail pattern.
   ───────────────────────────────────────────────────────────────────────── */
.split-pane {
  display: flex;
  gap: var(--space-4);
  align-items: flex-start;
  width: 100%;
}
.split-pane .detail-pane { display: none; }
.split-pane.has-detail .list-pane {
  flex: 0 0 340px;
  min-width: 300px;
  max-width: 340px;
  /* 2026-07-29 (founder) — the rail must match the detail panel's height, not run the full
     list length down the page. Same sticky + viewport cap as .detail-pane below; the rail
     then scrolls INTERNALLY (.list-rail-body already has overflow-y:auto). */
  position: sticky;
  top: var(--space-3);
  max-height: calc(100vh - 140px);
  display: flex;
  flex-direction: column;
  min-height: 0;
}
.split-pane.has-detail .list-rail { flex: 1 1 auto; height: auto; min-height: 0; }
.split-pane.has-detail .list-rail-back { flex-shrink: 0; }
/* model left, order date right on one line; long model names ellipse instead of pushing the date */
.split-pane .list-rail-item .rail-code-split { display: flex; justify-content: space-between;
                                               align-items: baseline; gap: var(--space-2); }
.split-pane .list-rail-item .rail-code-model { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.split-pane .list-rail-item .rail-code-date  { flex-shrink: 0; }
.split-pane.has-detail .detail-pane {
  display: flex;
  flex-direction: column;
  flex: 1 1 auto;
  min-width: 0;
  background: var(--paper);
  border: 1px solid var(--ink-200);
  border-radius: var(--r-lg, 12px);
  position: sticky;
  top: var(--space-3);
  max-height: calc(100vh - 140px);
  overflow-y: auto;
}
@media (max-width: 900px) {
  .split-pane { flex-direction: column; }
  .split-pane.has-detail .list-pane { flex: none; min-width: 0; max-width: none; width: 100%;
                                      position: static; max-height: 40vh; }
  .split-pane.has-detail .detail-pane { position: static; max-height: none; }
}
/* 2026-07-14 (founder) — Customer Records table must take the FULL page width and scroll
   horizontally. A flex child defaults to min-width:auto, so the wide opt-in column set pushed
   the pane instead of engaging .table-wrap's overflow-x: extra columns were clipped with no
   scrollbar. min-width:0 releases the pane; nowrap + ellipsis (U35) makes wide column sets
   overflow INTO the scroll instead of crushing every cell. */
.split-pane .list-pane { flex: 1 1 auto; min-width: 0; width: 100%; }
.split-pane .cpr-table th,
.split-pane .cpr-table td { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 340px; }
/* 2026-07-14 (founder) — GROW, don't squeeze: the table must be able to exceed the pane so
   .table-wrap's horizontal scrollbar engages instead of compressing every cell.

   2026-07-28 FIX — U24 column resize, reported THREE times ("stuck at the max text width of that
   column content"). `width` must be a DEFINITE LENGTH. A table whose width is an intrinsic keyword
   (max-content / auto / fit-content) is sized from its grid's max-content contribution, so every
   cell's text becomes a hard floor and the `table-layout: fixed` that app.js sets inline can no
   longer shrink a column past its own text. Measured in Chromium against this exact cascade:
   dragging a 172px column to 12px left it at 172px and grew the table instead.

   This line ALONE is not the fix — on its own it re-creates the 2026-07-14 squeeze, because
   un-dragged columns fall back to auto and divide the pane equally. It ships WITH the two ordering
   changes in app.js initColumnResize (freeze before flipping to fixed; leave the last column
   width-less so it absorbs the slack). Any subset brings one of the two bugs back. */
.split-pane .cpr-table { width: 100%; min-width: 100%; }
/* 2026-07-14 (founder) — Customer Records detail: breathing room + visual hierarchy.
   The pane had zero left padding and label/value pairs rendered at equal weight. */
.split-pane .detail-pane .detail-header { padding: var(--space-3) var(--space-4) var(--space-2); }
.split-pane .detail-pane .detail-body { padding: var(--space-2) var(--space-4) var(--space-4); }
.split-pane .detail-section-title { font-size: 12px; letter-spacing: .4px; color: var(--ink-500);
  text-transform: none; border-bottom: 1px solid var(--ink-200); padding-bottom: 4px; }
.split-pane .detail-section .field-row label,
.split-pane .detail-section dt { color: var(--ink-500); font-size: 12px; }
.split-pane .detail-section dd { color: var(--ink-950); font-weight: 600; }

/* ─────────────────────────────────────────────────────────────────────────
   Support module (Customer Records + Support Tickets) — orphaned-class styling.
   These pages use class names that were never given CSS: .field only styles
   inside .form-section/.side-panel-body/etc (there is no bare .field), and
   .std-input / .detail-section / .detail-dl / .field-group / .field-label /
   .modal-body / several pills have no rule at all. Without these the support
   pages render with default browser inputs + flat sections (functional but
   unpolished). Scoped to support-only containers (.split-pane, .modal) so
   nothing else is affected. Modeled on .form-section .field (app.css:1502).
   Added 2026-07-03 after cross-validation of the support phases.
   ───────────────────────────────────────────────────────────────────────── */

/* Standard-size inputs (founder non-negotiable) on support pages + their modals */
.split-pane .field, .split-pane .std-input,
.split-pane select.field, .split-pane textarea.field,
.detail-pane .field, .detail-pane .std-input,
.detail-pane select.field, .detail-pane textarea.field,
.modal .field, .modal .std-input,
.modal select.field, .modal textarea.field {
  width: 100%;
  min-height: 38px;
  padding: 8px 12px;
  border: 1px solid var(--ink-300);
  border-radius: var(--radius-sharp, 4px);
  font-size: 14px;
  font-family: inherit;
  background: var(--paper);
  color: var(--ink-900);
  box-sizing: border-box;
  transition: border-color var(--dur-fast, .12s);
}
.split-pane textarea.field, .detail-pane textarea.field, .modal textarea.field { min-height: 60px; resize: vertical; }
.split-pane .field:focus, .split-pane .std-input:focus,
.detail-pane .field:focus, .detail-pane .std-input:focus,
.modal .field:focus, .modal .std-input:focus {
  outline: none;
  border-color: var(--brand-orange);
  box-shadow: none;
}

/* Detail sections + key/value lists (support-only class names) */
.detail-section { margin-bottom: var(--space-4); padding-bottom: var(--space-3);
                  border-bottom: 1px solid var(--ink-100); }
.detail-section:last-child { border-bottom: none; }
.detail-section > h4, .detail-section-title {
  margin: 0 0 var(--space-2); font-size: 13px; font-weight: 700;
  letter-spacing: .02em; color: var(--ink-700); }
.detail-dl { display: grid; grid-template-columns: minmax(96px, max-content) 1fr;
             gap: 4px var(--space-3); margin: 0; font-size: 14px; }
.detail-dl dt { color: var(--ink-500); }
.detail-dl dd { margin: 0; color: var(--ink-900); }

/* Form groups + labels */
.field-group { margin-bottom: var(--space-3); }
.field-label { display: block; margin-bottom: 4px; font-size: 13px;
               font-weight: 600; color: var(--ink-700); }

/* Modal body: give tall support modals their own scroll */
.modal-body { max-height: 68vh; overflow-y: auto; }

/* Inline validation messages */
.inline-error { background: color-mix(in srgb, var(--danger) 10%, transparent);
                color: var(--danger); border-radius: var(--radius-sharp, 4px);
                padding: 8px 12px; font-size: 13px; margin-bottom: var(--space-3); }
.inline-warn  { color: var(--warning); font-size: 13px; margin: 4px 0; }

/* Missing status pills used by the support pages */
.pill-danger, .pill-red    { background: color-mix(in srgb, var(--danger) 16%, transparent);  color: var(--ink-900); }
.pill-yellow               { background: color-mix(in srgb, var(--warning) 20%, transparent); color: var(--ink-900); }
.pill-grey                 { background: var(--ink-100); color: var(--ink-700); }

/* Support detail header + control rows (support-only class names) */
.detail-header { display: flex; align-items: center; gap: var(--space-2);
                 padding-bottom: var(--space-3); margin-bottom: var(--space-3);
                 border-bottom: 1px solid var(--ink-200); }
.detail-title-group { display: flex; align-items: center; gap: var(--space-2); flex-wrap: wrap; }
.detail-ref { font-weight: 700; }
.detail-title { font-weight: 700; }
.detail-sub { color: var(--ink-500); font-size: 13px; margin-left: var(--space-2); }
.detail-desc { color: var(--ink-700); font-size: 14px; margin: 0 0 var(--space-3); }
.detail-actions { display: flex; gap: var(--space-2); margin-left: auto; }
.detail-body { display: flex; flex-direction: column; }
.status-actions { display: flex; gap: var(--space-2); flex-wrap: wrap; }
.note-compose { display: flex; flex-direction: column; gap: var(--space-2); margin-bottom: var(--space-3); }
.note-compose-actions { display: flex; gap: var(--space-2); align-items: center; }
.note-type-badge { font-size: 11px; font-weight: 700; text-transform: uppercase;
                   letter-spacing: .03em; color: var(--ink-500); }
.note-text { margin: 0; font-size: 14px; color: var(--ink-900); }

/* Small helpers on support pages */
.empty-row { text-align: center; color: var(--ink-500); padding: var(--space-4); font-size: 14px; }
small.sub, .sub { display: block; color: var(--ink-500); font-size: 12px; }

/* Support edit form: field-group as a grid cell + column-span helpers (2026-07-03 redesign) */
.form-grid .field-group { margin-bottom: 0; display: flex; flex-direction: column; min-width: 0; }
.form-grid .span-2    { grid-column: span 2; }
.form-grid .span-3    { grid-column: span 3; }
.form-grid .span-full { grid-column: 1 / -1; }

.help-text { font-size: 13px; color: var(--ink-500); margin: 0 0 var(--space-2); }

/* ── W0.2 2026-07-04 · SKU ↔ Sub-assembly link master (SkuSubAsmMatrixPanel, "SKU links" tab) ── */
.ssl-tiles { display: flex; gap: var(--space-3); align-items: center; flex-wrap: wrap; margin-bottom: var(--space-3); }
.ssl-tile { display: flex; flex-direction: column; gap: 2px; border: 1px solid var(--ink-200); border-radius: 10px; padding: var(--space-2) var(--space-4); background: var(--paper); }
.ssl-tile-label { font-size: 11px; color: var(--ink-600); }
.ssl-tile-value { font-size: 20px; font-weight: 700; font-variant-numeric: tabular-nums; }
.ssl-sku-col { min-width: 64px; }
.ssl-sku-head-btn { background: none; border: none; color: inherit; font: inherit; cursor: pointer; padding: var(--space-1) var(--space-2); border-radius: 6px; min-height: 40px; white-space: nowrap; }
.ssl-sku-head-btn:focus-visible { outline: 2px solid var(--brand-orange); outline-offset: 1px; }
.ssl-cell-mark { text-align: center; font-variant-numeric: tabular-nums; }
.ssl-row-partial td { background: color-mix(in srgb, var(--warning) 12%, transparent); }
.ssl-kind-tag { font-size: 11px; color: var(--ink-600); }
.ssl-check-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(260px, 1fr)); gap: var(--space-1) var(--space-4); }
.ssl-check-row { display: flex; align-items: center; gap: var(--space-2); min-height: 40px; cursor: pointer; }
.ssl-confirm-panel { background: color-mix(in srgb, var(--warning) 14%, transparent); border: 1px solid var(--warning); border-radius: 8px; padding: var(--space-3) var(--space-4); margin-top: var(--space-3); }
.ssl-confirm-panel ul { margin: var(--space-1) 0 0; padding-left: var(--space-5); }
/* ── A1 2026-07-05 · transfer-order suggestion overlay (U47: floats, never shifts layout) ── */
.to-suggest {
  position: absolute; top: 100%; left: 0; z-index: 30;
  background: var(--paper); border: 1px solid var(--ink-300); border-radius: 8px;
  box-shadow: 0 8px 24px rgba(0,0,0,.12);
  max-height: 260px; overflow-y: auto; margin-top: 2px;
  /* founder 2026-07-15 — was pinned right:0 to the 240px search field, so each GRN suggestion
     (ref + vendor + invoice + PO + date + amount) wrapped across ~4 lines. Widen past the input
     and keep each suggestion on ONE line. */
  min-width: 560px; max-width: 760px; width: max-content;
}
.to-suggest .dropdown-item {
  display: block; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}

/* ── W0.2 2026-07-04 · Box kits panel (BoxMappings page) ── */
.bk-panel { border: 1px solid var(--ink-200); border-radius: 10px; background: var(--paper); margin-bottom: var(--space-3); overflow: hidden; }
.bk-panel-head { display: flex; align-items: center; justify-content: space-between; gap: var(--space-3); padding: var(--space-2) var(--space-4) 0; }
.bk-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(320px, 1fr)); gap: var(--space-3); padding: var(--space-3) var(--space-4) var(--space-4); }
.bk-card { border: 1px solid var(--ink-200); border-radius: 10px; padding: var(--space-3); display: flex; flex-direction: column; gap: var(--space-2); }
.bk-card-head { display: flex; align-items: center; justify-content: space-between; gap: var(--space-2); }
.bk-lines { display: flex; flex-wrap: wrap; gap: 4px; }
.bk-card-foot { display: flex; gap: var(--space-4); font-size: 12px; align-items: center; flex-wrap: wrap; }

/* U47 2026-07-04 — .section-bar's base style (app.css:1754) uses NEGATIVE side margins to
   full-bleed inside side-panel bodies. Inside any .modal (whose body is an overflow container)
   that left overhang is CLIPPED — orange marker + first letters vanish ("FAB"→"AB").
   Neutralise the trick inside every modal so headings keep full side padding. */
.modal .section-bar { margin-left: 0; margin-right: 0; }

/* U46 — wide-family matrices (Zop 32 / ZorQ 16 cols) scroll horizontally; the identifying
   first column stays pinned with an OPAQUE background so rows never lose their label.
   (Header row pinning comes from hornback.initStickyThead; horizontal sticky is safe in Chromium.) */
.ssl-matrix th:first-child,
.ssl-matrix td:first-child {
  position: sticky; left: 0; z-index: 3;
  background: var(--paper);
  border-right: 1px solid var(--ink-200);
  box-shadow: 2px 0 4px rgba(0,0,0,.04);
}
.ssl-matrix tr:nth-child(even) td:first-child { background: var(--paper); }
.ssl-matrix .ssl-row-partial td:first-child { background: color-mix(in srgb, var(--warning) 12%, var(--paper)); }
/* group header rows (kind · size) — opaque, label stays readable while scrolling (first-child sticky applies) */
.ssl-matrix .ssl-group-row td {
  background: var(--ink-100);
  color: var(--ink-700);
  font-weight: 600;
  font-size: 12px;
  text-align: left;
  border-top: 1px solid var(--ink-200);
}
/* Dispatch backlog — dealer group header row (founder 2026-08-01: "group the multiple sale orders
   of same vendors… total qty and order value on that row of dealer name on top"). Same treatment as
   the job-work vendor grouping below, so the two grouped tables read alike. */
.dash-group-row td {
  background: var(--ink-100);
  color: var(--ink-800);
  border-top: 2px solid var(--ink-200);
  padding-top: var(--space-2);
  padding-bottom: var(--space-2);
}
.dash-group-row:first-child td { border-top: none; }
/* The block must visibly END, or the next dealer's order reads as part of it (founder 2026-08-01).
   An accent rail down the left of the member rows brackets the block; the last one closes it. */
.dash-group-child td:first-child {
  box-shadow: inset 3px 0 0 0 var(--ink-300);
}
.dash-group-end td {
  border-bottom: 2px solid var(--ink-200);
}

/* Job work · Held at vendor — vendor group header row (founder 2026-07-31: "show as one group,
   one vendor and all material he has, instead of individual items with vendors on every row").
   Same shape as .ssl-group-row above: opaque tint, heavier label, rule above the group. */
.jw-vendor-group-row td {
  background: var(--ink-100);
  color: var(--ink-800);
  border-top: 2px solid var(--ink-200);
  padding-top: var(--space-3);
  padding-bottom: var(--space-2);
}
.jw-vendor-group-row:first-child td { border-top: none; }

.ssl-size-header { font-size: 11px; font-weight: 600; color: var(--ink-600); margin: var(--space-2) 0 var(--space-1); }
.ssl-base-info { display: block; font-size: 11px; color: var(--ink-600); font-weight: 400; }

/* 2026-07-06 — topbar search switched to type="search" (autofill-pair fix); hide the native
   WebKit clear ✕ so it doesn't double up with the component's own clear button. */
.topbar-search-input::-webkit-search-cancel-button,
.topbar-search-input::-webkit-search-decoration { -webkit-appearance: none; appearance: none; }

/* 2026-07-06 — switch-user (impersonation) banner. FIXED to the bottom edge, outside the
   .app-shell grid: zero layout shift and no phantom scrollbar on the 100vh shell. z 1200
   deliberately sits ABOVE modals (overlay z 200 / dialog z 201) so "Back to my account"
   is always clickable. Loud on purpose — never forget you're in someone else's view. */
.impersonation-banner {
  position: fixed;
  bottom: 0;
  left: 0;
  right: 0;
  z-index: 1200;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-4);
  padding: 6px var(--space-5);
  background: var(--warning);
  color: #fff;
  font-size: 13px;
  box-shadow: 0 -2px 6px rgb(0 0 0 / 25%);
}
.impersonation-banner .imp-msg strong { text-decoration: underline; text-underline-offset: 2px; }
.imp-return-form { margin: 0; }
.imp-return-btn {
  background: #fff;
  color: var(--warning);
  border: none;
  border-radius: var(--radius-md, 8px);
  padding: 4px 14px;
  font-size: 13px;
  font-weight: 600;
  min-height: 28px;
  cursor: pointer;
  white-space: nowrap;
}
.imp-return-btn:hover { background: #ffe9d6; }

/* 2026-07-06 — switch-user picker inside the profile dropdown. Small muted section label
   above the list of switchable users; the profile dropdown can scroll if the list is long. */
.dropdown-subhead {
  padding: 6px 14px 2px;
  font-size: 11px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: .04em;
  color: var(--ink-500, #6b7280);
}
.profile-dropdown .dropdown-body { max-height: 70vh; overflow-y: auto; }

/* 2026-07-06 — personalised welcome landing for users without dashboard access.
   Centred hero card; friendly, not a bare/empty page. Greets by first name. */
.welcome-hero {
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: calc(100vh - var(--topbar-h, 44px) - 80px);
  padding: var(--space-6);
}
.welcome-card {
  text-align: center;
  max-width: 460px;
  padding: var(--space-6) var(--space-6) calc(var(--space-6) + 4px);
  background: var(--surface, #fff);
  border: 1px solid var(--border-200, #e5e7eb);
  border-radius: 16px;
  box-shadow: 0 6px 24px rgb(0 0 0 / 6%);
}
.welcome-emoji { font-size: 44px; line-height: 1; margin-bottom: var(--space-3); }
.welcome-title { font-size: 26px; font-weight: 700; margin: 0 0 var(--space-2); color: var(--ink-950, #111827); }
.welcome-role { display: flex; align-items: center; justify-content: center; gap: 8px; margin: 0 0 var(--space-3); }
.welcome-brand { color: var(--ink-500, #6b7280); font-size: 14px; }
.welcome-sub { color: var(--ink-600, #4b5563); font-size: 14px; margin: 0; }

/* 2026-07-06 — notification bell (top-bar) + dropdown items. */
.notif-bell-btn { position: relative; }
.notif-badge {
  position: absolute; top: 0; right: 0;
  min-width: 16px; height: 16px; padding: 0 4px;
  background: var(--danger); color: #fff;
  font-size: 10px; font-weight: 700; line-height: 16px; text-align: center;
  border-radius: 8px; box-shadow: 0 0 0 2px var(--ink-950);
}
.notif-markall {
  background: none; border: none; color: var(--brand-orange, #d9822b);
  font-size: 12px; font-weight: 600; cursor: pointer; padding: 0;
}
.notif-markall:hover { text-decoration: underline; }
.notif-item {
  display: block; padding: 10px 14px; border-bottom: 1px solid var(--ink-100);
  text-decoration: none; color: var(--ink-900);
}
.notif-item:hover { background: var(--ink-50); }
.notif-item.unread { background: color-mix(in srgb, var(--brand-orange, #d9822b) 8%, transparent); }
.notif-item.unread .notif-item-title::before {
  content: ""; display: inline-block; width: 7px; height: 7px; margin-right: 6px;
  border-radius: 50%; background: var(--brand-orange, #d9822b); vertical-align: middle;
}
.notif-item-title { font-size: 13px; font-weight: 600; }
.notif-item-msg { font-size: 12px; color: var(--ink-600); margin-top: 2px; }
.notif-item-time { font-size: 11px; color: var(--ink-500); margin-top: 3px; }

/* 2026-07-06 founder hotfix — the HR-module pages (Employees, Onboarding, WorkerMasterPanel,
   MyLeaves, PayRuns, Reimbursements, one Inward dialog) shipped using .modal-backdrop/.modal-dialog,
   classes that existed in NO stylesheet (the U45 "ghost modal": the form rendered as plain divs at
   the BOTTOM of the page — founder: "add labour form comes at the bottom, invisible till scroll").
   Defined here as flex-centering aliases of the shared overlay/modal pattern so all 8 dialogs
   render as real centered modals without touching seven .razor files. */
.modal-backdrop {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, .5);
  z-index: 200;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--space-4);
}
.modal-dialog {
  background: var(--paper, #fff);
  border-radius: 8px;
  padding: 24px;
  max-width: 560px;
  width: 100%;
  max-height: 90vh;
  overflow-y: auto;
  box-shadow: 0 20px 50px rgba(0, 0, 0, .3);
}

/* ── Tab panel inside a .fullbleed-page (founder 2026-07-07, Salary banner-overlap fix) ──
   Per-tab content under an in-page .entity-tabs strip. Fills the remaining column-flex height
   and scrolls internally; NO negative margins (that's what overlapped the page banner when
   tab panels reused .fullbleed-page). 4px side padding so focus rings aren't clipped. */
.tab-panel-scroll {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  overflow-x: hidden;   /* only inner .table-wrap scrolls horizontally — kills the double scrollbar (2026-07-09) */
  padding: var(--space-4) 4px var(--space-6);
}

/* ── Review-sweep fixes (2026-07-07 dual-lens UI review) ─────────────────────── */
/* .form-grid-2 was used on 5+ HR pages but never defined — every edit form silently
   collapsed to one column (Employees, Onboarding, WorkerMasterPanel, LeaveManagement,
   MyProfile). Defined here as the intended 2-col grid, same rhythm as .form-grid. */
.form-grid-2 {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--space-3) var(--space-4);
  align-items: start;
}
@media (max-width: 720px) { .form-grid-2 { grid-template-columns: 1fr; } }

/* .modal-actions was used bare in 5 PurchaseOrders modals but only had scoped rules
   (.logistics-form .modal-actions) — buttons rendered unaligned. Base rule: */
.modal-actions {
  display: flex;
  justify-content: flex-end;
  gap: var(--space-3);
  margin-top: var(--space-4);
}

/* .field-error existed in markup (WarrantyPolicies) with no CSS anywhere. */
.field-error {
  color: var(--danger);
  font-size: 12px;
  margin-top: 4px;
}

/* ═══════════════════════════════════════════════════════════════════════════════════════
   Channel Sales vertical · S3 · 2026-07-11 — ASM MOBILE FIELD APP (/field/*)
   Mobile-first (base = phone, no media query). All classes prefixed `field-`. Tokens only.
   Desktop >=720px: the whole surface is centered to a 480px phone frame so the founder can
   demo it in a normal browser. Bottom tab bar is fixed and constrained to that frame.
   ═══════════════════════════════════════════════════════════════════════════════════════ */

/* ── Shell ── */
.field-layout {
  display: flex;
  flex-direction: column;
  min-height: 100vh;
  background: var(--canvas);
  font-family: var(--font-ui);
  color: var(--ink-700);
}
.field-topbar {
  position: sticky;
  top: 0;
  z-index: 30;
  display: flex;
  align-items: center;
  gap: var(--space-3);
  min-height: 48px;
  padding: var(--space-2) var(--space-4);
  background: var(--ink-950);
  color: var(--paper);
}
.field-brand {
  font-family: var(--font-display);
  font-size: 18px;
  letter-spacing: var(--ls-snug);
  color: var(--paper);
  white-space: nowrap;
}
.field-brand .field-brand-accent { color: var(--brand-orange-300); }
.field-topbar-spacer { flex: 1 1 auto; }
.field-user {
  font-size: 13px;
  color: var(--ink-200);
  max-width: 40vw;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.field-logout-form { display: inline-flex; margin: 0; }
.field-logout {
  display: inline-flex;
  align-items: center;
  min-height: 40px;
  padding: 0 var(--space-2);
  background: none;
  border: none;
  color: var(--brand-orange-300);
  font-size: 13px;
  font-weight: var(--fw-semibold);
  cursor: pointer;
}
.field-logout:hover { color: var(--paper); text-decoration: underline; }
.field-logout:focus-visible { outline: 3px solid var(--brand-orange-300); outline-offset: 2px; border-radius: var(--r-sm); }

.field-content {
  flex: 1 1 auto;
  padding: var(--space-4);
  padding-bottom: 84px;               /* clear the fixed tab bar */
  overflow-y: auto;
}

/* ── Bottom tab bar ── */
.field-tabbar {
  position: fixed;
  bottom: 0;
  left: 0;
  right: 0;
  z-index: 40;
  display: flex;
  background: var(--paper);
  border-top: 1px solid var(--ink-200);
  box-shadow: 0 -2px 8px rgba(15, 23, 42, .06);
}
.field-tab {
  flex: 1 1 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 2px;
  min-height: 56px;
  padding: var(--space-1) 2px;
  border-top: 3px solid transparent;
  color: var(--ink-500);
  font-size: 11px;
  font-weight: var(--fw-medium);
  text-decoration: none;
  text-align: center;
}
.field-tab:hover { color: var(--ink-700); }
.field-tab:focus-visible { outline: 3px solid var(--brand-orange); outline-offset: -3px; }
.field-tab.active {
  border-top-color: var(--brand-orange);
  color: var(--ink-950);
  font-weight: var(--fw-semibold);
}
.field-tab-icon { font-size: 18px; line-height: 1; }
.field-tab-label { line-height: 1.1; }

/* ── Page header ── */
.field-page-header { margin-bottom: var(--space-4); }
.field-h1 {
  margin: 0;
  font-family: var(--font-display);
  font-size: 26px;
  letter-spacing: var(--ls-snug);
  color: var(--ink-950);
}
.field-sub {
  margin: 2px 0 0;
  font-size: 13px;
  color: var(--ink-500);
}

/* ── Card ── */
.field-card {
  background: var(--paper);
  border: 1px solid var(--ink-200);
  border-radius: var(--r-xl);         /* 12px */
  padding: var(--space-3);
  margin-bottom: var(--space-3);
}
.field-card-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--space-2);
}
.field-card-title {
  margin: 0;
  font-size: 15px;
  font-weight: var(--fw-semibold);
  color: var(--ink-900);
}
.field-card-city { font-size: 12px; color: var(--ink-500); }
.field-card-body { margin-top: var(--space-2); }
.field-row {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--space-2);
}
.field-row-between { justify-content: space-between; }
.field-muted { color: var(--ink-500); font-size: 13px; }
.field-strong { font-weight: var(--fw-semibold); color: var(--ink-900); }
.field-targets {
  display: flex;
  gap: var(--space-4);
  margin-top: var(--space-2);
  font-size: 13px;
}
.field-target-label { color: var(--ink-500); }
.field-target-value { font-weight: var(--fw-semibold); color: var(--ink-900); font-variant-numeric: tabular-nums; }

/* ── Section labels inside a page ── */
.field-section-label {
  margin: var(--space-4) 0 var(--space-2);
  font-size: 12px;
  font-weight: var(--fw-semibold);
  letter-spacing: var(--ls-wide);
  text-transform: uppercase;
  color: var(--ink-500);
  font-family: var(--font-mono);
}

/* ── Badges / pills ── */
.field-badge {
  display: inline-block;
  padding: 2px 8px;
  border-radius: var(--r-pill);
  font-size: 11px;
  font-weight: var(--fw-semibold);
  background: var(--ink-100);
  color: var(--ink-700);
}
.field-badge-reorder  { background: var(--accent-soft); color: var(--accent); }
.field-badge-collection { background: var(--warning-soft); color: var(--warning); }
.field-badge-onboarding { background: var(--brand-purple-soft); color: var(--brand-purple); }
.field-pill {
  display: inline-block;
  padding: 2px 10px;
  border-radius: var(--r-pill);
  font-size: 11px;
  font-weight: var(--fw-semibold);
}
.field-pill-pending  { background: var(--ink-100); color: var(--ink-700); }
.field-pill-visited  { background: var(--success-soft); color: var(--success); }
.field-pill-skipped  { background: var(--danger-soft); color: var(--danger); }
.field-pill-warning  { background: var(--warning-soft); color: var(--warning); }
.field-pill-info     { background: var(--info-soft); color: var(--info); }

/* ── Chips (outcome / terms / mode selectors) ── */
.field-chip-row {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  margin: var(--space-1) 0;
}
.field-chip {
  display: inline-flex;
  align-items: center;
  min-height: 40px;
  padding: 0 var(--space-3);
  border: 1.5px solid var(--ink-300);
  border-radius: var(--r-pill);
  background: var(--paper);
  color: var(--ink-700);
  font-size: 13px;
  font-weight: var(--fw-medium);
  cursor: pointer;
}
.field-chip:hover { border-color: var(--ink-500); }
.field-chip:focus-visible { outline: 3px solid var(--brand-orange); outline-offset: 2px; }
.field-chip.selected {
  border-color: var(--brand-orange);
  background: var(--brand-orange-soft);
  color: var(--ink-950);
  font-weight: var(--fw-semibold);
}

/* ── Inputs (standard size — min-height 44px, full-width on mobile) ── */
.field-label {
  display: block;
  margin: var(--space-3) 0 var(--space-1);
  font-size: 13px;
  font-weight: var(--fw-medium);
  color: var(--ink-700);
}
.field-req { color: var(--danger); }
.field-input {
  width: 100%;
  min-height: 44px;
  padding: 0 var(--space-3);
  border: 1px solid var(--ink-300);
  border-radius: var(--r-md);
  background: var(--paper);
  color: var(--ink-900);
  font-family: var(--font-ui);
  font-size: 15px;                     /* >=16px-ish avoids iOS zoom; 15 acceptable */
  box-sizing: border-box;
}
textarea.field-input { min-height: 76px; padding: var(--space-2) var(--space-3); resize: vertical; }
.field-input::placeholder { color: var(--ink-400); }
.field-input:focus-visible,
.field-input:focus {
  outline: 3px solid var(--brand-orange);
  outline-offset: 1px;
  border-color: var(--brand-orange);
}
.field-input.field-invalid { border-color: var(--danger); }
.field-error-text {
  margin: var(--space-1) 0 0;
  color: var(--danger);
  font-size: 12px;
  font-weight: var(--fw-medium);
}

/* ── Buttons (block, full-width, reuse .btn-primary/.btn-secondary colours) ── */
.field-actions {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  margin-top: var(--space-4);
}
.field-actions .btn-primary,
.field-actions .btn-secondary,
.field-btn-block {
  width: 100%;
  min-height: 46px;
  justify-content: center;
}
.field-btn-inline {                    /* small inline action (Skip, change, add) */
  display: inline-flex;
  align-items: center;
  min-height: 40px;
  padding: 0 var(--space-3);
  border: 1.5px solid var(--ink-300);
  border-radius: var(--r-md);
  background: var(--paper);
  color: var(--ink-700);
  font-size: 13px;
  font-weight: var(--fw-medium);
  cursor: pointer;
}
.field-btn-inline:hover { border-color: var(--ink-500); }
.field-btn-inline:focus-visible { outline: 3px solid var(--brand-orange); outline-offset: 2px; }
.field-btn-inline.danger { color: var(--danger); border-color: var(--danger-soft); }

/* ── Stock stepper grid ── */
.field-stock-grid { display: flex; flex-direction: column; gap: var(--space-1); }
.field-stock-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-2);
  padding: var(--space-1) 0;
}
.field-stock-name {
  font-size: 14px;
  color: var(--ink-800);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.field-stepper { display: inline-flex; align-items: center; gap: var(--space-2); }
.field-step-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 40px;
  height: 40px;
  border: 1.5px solid var(--ink-300);
  border-radius: var(--r-md);
  background: var(--paper);
  color: var(--ink-900);
  font-size: 20px;
  line-height: 1;
  cursor: pointer;
}
.field-step-btn:hover { border-color: var(--brand-orange); }
.field-step-btn:focus-visible { outline: 3px solid var(--brand-orange); outline-offset: 2px; }
.field-step-btn:disabled { opacity: .4; cursor: not-allowed; }
.field-stepper-val {
  min-width: 40px;
  text-align: center;
  font-size: 16px;
  font-weight: var(--fw-semibold);
  font-variant-numeric: tabular-nums;
  color: var(--ink-900);
}

/* ── Pace / budget meters ── */
.field-meter { margin-top: var(--space-2); }
.field-meter-track {
  height: 8px;
  border-radius: var(--r-full);
  background: var(--ink-150);
  overflow: hidden;
}
.field-meter-fill {
  height: 100%;
  border-radius: var(--r-full);
  background: var(--success);
}
.field-meter-fill.under  { background: var(--warning); }
.field-meter-fill.over   { background: var(--danger); }
.field-meter-caption {
  margin-top: var(--space-1);
  font-size: 12px;
  color: var(--ink-500);
}
.field-nudge {
  margin-top: var(--space-1);
  font-size: 12px;
  font-weight: var(--fw-medium);
  color: var(--warning);
}
.field-warn-banner {
  margin-top: var(--space-2);
  padding: var(--space-2) var(--space-3);
  border-radius: var(--r-md);
  background: var(--warning-soft);
  color: var(--warning);
  font-size: 13px;
  font-weight: var(--fw-medium);
}

/* ── Typeahead ── */
.field-typeahead { position: relative; }
.field-typeahead-list {
  list-style: none;
  margin: var(--space-1) 0 0;
  padding: 0;
  border: 1px solid var(--ink-200);
  border-radius: var(--r-md);
  background: var(--paper);
  overflow: hidden;
}
.field-typeahead-item {
  display: block;
  width: 100%;
  text-align: left;
  min-height: 44px;
  padding: var(--space-2) var(--space-3);
  background: none;
  border: none;
  border-bottom: 1px solid var(--ink-100);
  color: var(--ink-800);
  font-size: 14px;
  cursor: pointer;
}
.field-typeahead-item:last-child { border-bottom: none; }
.field-typeahead-item:hover { background: var(--ink-50); }
.field-typeahead-item:focus-visible { outline: 3px solid var(--brand-orange); outline-offset: -3px; }
.field-typeahead-item .field-ta-code { color: var(--ink-500); font-size: 12px; }
.field-typeahead-add {
  color: var(--accent);
  font-weight: var(--fw-semibold);
}

/* ── Beat suggestions strip ── */
.field-suggest-strip {
  display: flex;
  gap: var(--space-2);
  overflow-x: auto;
  padding-bottom: var(--space-1);
}
.field-suggest-card {
  flex: 0 0 auto;
  width: 180px;
  padding: var(--space-2) var(--space-3);
  border: 1px solid var(--ink-200);
  border-radius: var(--r-lg);
  background: var(--paper);
}
.field-suggest-name {
  font-size: 13px;
  font-weight: var(--fw-semibold);
  color: var(--ink-900);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* ── Legend (expense caps) ── */
.field-legend {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2) var(--space-3);
  margin-top: var(--space-2);
  font-size: 12px;
  color: var(--ink-500);
}
.field-legend-item { white-space: nowrap; }

/* ── KPI tiles (scorecard) ── */
.field-kpi-grid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: var(--space-2);
}
.field-kpi {
  background: var(--paper);
  border: 1px solid var(--ink-200);
  border-radius: var(--r-xl);
  padding: var(--space-3);
}
.field-kpi-label { font-size: 12px; color: var(--ink-500); }
.field-kpi-value {
  margin-top: 2px;
  font-family: var(--font-display);
  font-size: 24px;
  color: var(--ink-950);
  font-variant-numeric: tabular-nums;
}
.field-kpi-sub { font-size: 12px; color: var(--ink-500); margin-top: 2px; }
.field-kpi-sub.good { color: var(--success); }
.field-kpi-sub.warn { color: var(--warning); }
.field-kpi-sub.bad  { color: var(--danger); }

/* ── Empty state ── */
.field-empty {
  text-align: center;
  padding: var(--space-8) var(--space-4);
  color: var(--ink-500);
}
.field-empty-icon { font-size: 32px; }
.field-empty-title { margin-top: var(--space-2); font-size: 15px; font-weight: var(--fw-semibold); color: var(--ink-700); }
.field-empty-sub { margin-top: var(--space-1); font-size: 13px; color: var(--ink-500); }

/* ── Time in/out stamp buttons ── */
.field-time-row { display: flex; gap: var(--space-2); }
.field-time-btn {
  flex: 1 1 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 2px;
  min-height: 52px;
  padding: var(--space-2);
  border: 1.5px solid var(--ink-300);
  border-radius: var(--r-md);
  background: var(--paper);
  color: var(--ink-700);
  cursor: pointer;
}
.field-time-btn:focus-visible { outline: 3px solid var(--brand-orange); outline-offset: 2px; }
.field-time-btn.stamped { border-color: var(--success); background: var(--success-soft); color: var(--success); }
.field-time-btn-label { font-size: 12px; }
.field-time-btn-value { font-size: 15px; font-weight: var(--fw-semibold); font-variant-numeric: tabular-nums; }

/* ── Week day tabs use the shared .entity-tabs; no override needed ── */

/* ── Desktop: phone-frame the whole surface ── */
@media (min-width: 720px) {
  .field-layout {
    max-width: 480px;
    margin: 0 auto;
    min-height: 100vh;
    box-shadow: var(--shadow-lg);
  }
  .field-tabbar {
    left: 50%;
    right: auto;
    width: 480px;
    transform: translateX(-50%);
  }
}

/* ═══════════════════════════════════════════════════════════════════════════════════════
   My Space · adaptive self-service (2026-07-11) — SAME desktop routes + look; a phone-quality
   view below 720px. Width-switch only: no new layouts, no route changes. All classes `msp-`.
   `.msp-page` marks each self-service page's root content; `.app-shell:has(.msp-page)` collapses
   the shell on phones (mirrors the existing :has() switches at v32-theme.css:104 / app.css:686).
   The M3 bottom navigation bar (.msp-tabbar) mirrors the .field-tab look (v32-theme.css:2205+):
   active = orange top-border indicator + --ink-950 text (NO orange fill — U2 stays intact).
   ═══════════════════════════════════════════════════════════════════════════════════════ */

/* ── M3 bottom navigation bar — hidden on desktop, shown on phone (<720px) ── */
.msp-tabbar {
  display: none;                       /* desktop default — flipped to flex below 720px */
  position: fixed;
  bottom: 0;
  left: 0;
  right: 0;
  z-index: 90;                         /* above content; below modal-overlay (200) + toast (300) */
  background: var(--paper);            /* opaque */
  border-top: 1px solid var(--ink-200);
  box-shadow: 0 -2px 8px rgba(15, 23, 42, .06);
  padding-bottom: env(safe-area-inset-bottom);   /* clear the phone home indicator */
}
.msp-tab {
  flex: 1 1 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 2px;
  min-height: 56px;                    /* ≥48px touch target */
  padding: var(--space-1) 2px;
  border-top: 3px solid transparent;   /* active indicator lives here — no filled-orange competitor */
  color: var(--ink-500);
  font-size: 11px;
  font-weight: var(--fw-medium);
  text-decoration: none;
  text-align: center;
}
.msp-tab:hover { color: var(--ink-700); }
.msp-tab:focus-visible { outline: 3px solid var(--brand-orange); outline-offset: -3px; }
.msp-tab.active {
  border-top-color: var(--brand-orange);
  color: var(--ink-950);
  font-weight: var(--fw-semibold);
}
.msp-tab-icon { font-size: 18px; line-height: 1; }
.msp-tab-label { line-height: 1.1; }

/* ── Responsive utilities (desktop defaults; flipped in the phone block) ── */
.msp-only { display: none; }           /* mobile-only content (card lists): hidden ≥720px, block below */
/* .msp-hide-m: visible on desktop by default; set to display:none below 720px (defined in the phone block) */

/* ── Mobile card-list helpers ── */
.msp-card-btn {                        /* a whole-card that acts as a row-select (mirrors row @onclick) */
  display: block;
  width: 100%;
  text-align: left;
  cursor: pointer;
  font-family: inherit;
}
.msp-card-btn.selected { border-color: var(--brand-orange); }
.msp-card-btn:focus-visible { outline: 3px solid var(--brand-orange); outline-offset: 2px; }
.msp-card-line {                       /* one label:value row inside a mobile card */
  display: flex;
  justify-content: space-between;
  gap: var(--space-3);
  margin-top: var(--space-1);
  font-size: 13px;
}
.msp-card-line .msp-card-k { color: var(--ink-500); }
.msp-btn-row { display: flex; gap: var(--space-2); margin-top: var(--space-3); }
.msp-btn-row > button { flex: 1 1 0; min-height: 48px; justify-content: center; }

/* ═══════════════════════ Phone view (<720px) ═══════════════════════ */
@media (max-width: 719.98px) {
  /* Shell → single column; sidebar hides; topbar (bell + avatar) stays reachable. */
  .app-shell:has(.msp-page) {
    grid-template-columns: 1fr;
    grid-template-areas:
      "topbar"
      "main";
  }
  .app-shell:has(.msp-page) .app-sidebar { display: none; }
  .app-shell:has(.msp-page) .app-main {
    padding: var(--space-3);
    padding-bottom: 88px;              /* clear the fixed .msp-tabbar */
    overflow-x: hidden;
  }

  /* Turn the mobile chrome on. */
  .msp-tabbar { display: flex; }
  .msp-only   { display: block; }
  .msp-hide-m { display: none; }

  /* Full-bleed self-service pages (MyProfile, LeaveApprovals) flow as one scroll — drop the
     desktop fixed-height + negative-margin breakout so nothing is clipped on a phone. */
  .msp-page.fullbleed-page {
    height: auto;
    overflow: visible;
    margin: 0;
    padding: 0;
  }

  /* List+detail pages (MyPayslips, Reimbursements) stack: list first, detail below. */
  .msp-page .list-detail-layout {
    flex-direction: column;
    height: auto;
    overflow: visible;
  }
  .msp-page .list-detail-layout .list-pane {
    max-width: none !important;        /* beat the desktop inline max-width:280px (MyPayslips) */
    width: 100%;
    flex: 0 0 auto;
    border-right: none;
    border-bottom: 1px solid var(--ink-200);
  }
  .msp-page .list-detail-layout .detail-pane.open { width: 100%; }

  /* Forms → single column; file-upload trigger labels reach the 48px touch target. */
  .msp-page .form-grid { grid-template-columns: 1fr; }
  .msp-page .form-section label.btn-secondary {
    min-height: 48px;
    display: inline-flex;
    align-items: center;
  }

  /* MyProfile: two-column kv grid → single column (photo block already centers). */
  .msp-page .view-grid { grid-template-columns: 1fr; }

  /* KPI rows → compact tiles. Cards WITHOUT an inline min-width (MyPayslips 3-up grid) shrink to
     fit 390px; cards with an inline min-width (MyProfile/MyAdvances flex-wrap) keep wrapping 2-up. */
  .msp-page .kpi-card  { min-width: 0; padding: var(--space-2); }
  .msp-page .kpi-value { font-size: 18px; }

  /* Detail panes: the wide claim-lines table already scrolls inside its .table-wrap
     (overflow-x:auto); belt-and-braces so a stray wide child can't push the page sideways. */
  .msp-page .detail-pane { overflow-x: auto; }

  /* Tables that stay visible on a phone (MyPayslips month list + payslip detail, MyAdvances
     recovery schedule): compact horizontal padding so 3–4 columns fit 390px, and ≥48px rows so
     the tappable month list meets the touch-target minimum. */
  .msp-page .data-table td,
  .msp-page .data-table th { padding-left: var(--space-2); padding-right: var(--space-2); }
  .msp-page .data-table td { height: 48px; }
}


/* ── Attachment visual confirmation (field app + My Space claims) · 2026-07-11 ──
   An attached photo must be unmistakable: thumbnail of the actual image inside a
   success-tinted chip (founder: "no visual confirmation that photo is attached"). */
.attach-confirm {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    padding: var(--space-2);
    background: var(--success-soft, color-mix(in srgb, var(--success) 12%, transparent));
    border: 1px solid var(--success);
    border-radius: 8px;
    max-width: 100%;
}
.attach-thumb {
    width: 56px;
    height: 56px;
    object-fit: cover;
    border-radius: 6px;
    border: 1px solid var(--ink-300);
    background: var(--paper);
    flex: 0 0 auto;
}
.attach-check { color: var(--success); font-weight: 700; font-size: 13px; }
.attach-name  { font-size: 12px; color: var(--ink-500); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 150px; display: inline-block; }
.attach-file-pill { display: inline-flex; align-items: center; gap: 4px; padding: 4px 10px; border: 1px solid var(--ink-300); border-radius: 999px; font-size: 12px; background: var(--paper); }

/* ══════════════════════════════════════════════════════════════════════════════
   OFFER LETTER GENERATOR · Phase 1 · 2026-07-13
   Chrome (editor layout, clause editor, in-hand card, pills) uses app tokens.
   The letter paper (.offer-paper) is a self-contained legal document — its colours
   are the attorney-vetted brand values from Mansi's tool, kept VERBATIM for PDF
   fidelity (per the build brief: print fidelity beats app tokens INSIDE the letter).
   ══════════════════════════════════════════════════════════════════════════════ */

/* ── List extras ── */
.offer-list-controls { display: flex; justify-content: flex-end; margin-bottom: var(--space-2); }
.data-table th.num-col, .data-table td.num-col { text-align: right; font-variant-numeric: tabular-nums; }
.offer-badge {
    font-size: 10px; padding: 2px 8px; border-radius: 999px; font-weight: 600; margin-left: 4px;
    vertical-align: middle; background: color-mix(in srgb, var(--brand-orange) 14%, var(--paper)); color: var(--ink-950);
}

/* ── Status / clause pills (dark ink on a light token tint → readable contrast) ── */
.pill.offer-pill-draft    { background: color-mix(in srgb, var(--ink-500) 16%, var(--paper));     color: var(--ink-950); }
.pill.offer-pill-sent     { background: color-mix(in srgb, var(--brand-orange) 16%, var(--paper)); color: var(--ink-950); }
.pill.offer-pill-accepted { background: color-mix(in srgb, var(--success) 20%, var(--paper));      color: var(--ink-950); }
.pill.offer-pill-declined { background: color-mix(in srgb, var(--danger) 16%, var(--paper));       color: var(--ink-950); }
.pill.offer-pill-edited   { background: color-mix(in srgb, var(--warning) 18%, var(--paper));      color: var(--ink-950); }
.pill.offer-pill-hidden   { background: color-mix(in srgb, var(--danger) 14%, var(--paper));       color: var(--ink-950); }

/* ── Editor two-pane layout ── */
.offer-editor {
    display: grid; grid-template-columns: minmax(360px, 460px) 1fr;
    gap: var(--space-6); align-items: start;
}
.offer-form-pane { display: flex; flex-direction: column; gap: var(--space-4); min-width: 0; }
.offer-preview-pane { display: flex; flex-direction: column; align-items: center; min-width: 0; }
.offer-preview-label { align-self: flex-start; font-size: 12px; color: var(--ink-500); margin-bottom: var(--space-3); }
@media (max-width: 1100px) { .offer-editor { grid-template-columns: 1fr; } }

/* ── In-hand calculator card (screen only — never in PDF) ── */
.offer-inhand {
    margin-top: var(--space-4); border: 1px solid var(--border); border-radius: 10px;
    padding: var(--space-3) var(--space-4); background: var(--paper);
}
.offer-inhand-title {
    font-size: 11px; font-weight: 700; letter-spacing: .05em; text-transform: uppercase;
    color: var(--ink-500); margin-bottom: var(--space-2);
}
.oih-row { display: flex; justify-content: space-between; gap: var(--space-3); padding: 3px 0; font-size: 12px; color: var(--ink-700); }
.oih-ded { color: var(--danger); }
.oih-total { border-top: 1px solid var(--border); margin-top: 4px; padding-top: 6px; font-weight: 700; color: var(--ink-950); font-size: 13px; }
.oih-annual { color: var(--ink-500); font-size: 11px; }
.oih-val { font-variant-numeric: tabular-nums; }
.oih-note { font-size: 10.5px; color: var(--ink-500); margin-top: 6px; line-height: 1.5; }

/* ── Custom rows / footnotes / add buttons ── */
.ocr-wrap, .ofn-wrap { display: flex; flex-direction: column; gap: 6px; margin-bottom: var(--space-2); }
.ocr-row { display: grid; grid-template-columns: 1fr 120px 36px; gap: 6px; align-items: center; }
.ofn-row { display: flex; gap: 6px; align-items: flex-start; }
.ofn-row textarea { flex: 1; }
.offer-add-btn {
    font-size: 12px; color: var(--brand-orange); background: none;
    border: 1px dashed var(--brand-orange); border-radius: 6px; padding: 6px 12px; cursor: pointer; width: 100%;
}
.offer-add-btn:hover { background: color-mix(in srgb, var(--brand-orange) 8%, var(--paper)); }
.offer-add-btn:focus-visible { outline: 2px solid var(--brand-orange); outline-offset: 2px; }

/* ── Clause editor ── */
.offer-clause-head-row { display: flex; justify-content: space-between; align-items: center; gap: var(--space-3); margin-bottom: var(--space-2); }
.offer-clause-editor { display: flex; flex-direction: column; gap: 6px; }
.oce-item { border: 1px solid var(--border); border-radius: 8px; overflow: hidden; }
.oce-item.oce-modified { border-color: var(--warning); }
.oce-item.oce-hidden { opacity: .6; }
.oce-header { display: flex; align-items: center; gap: 8px; padding: 8px 10px; cursor: pointer; background: var(--paper); -webkit-user-select: none; user-select: none; }
.oce-header:hover { filter: brightness(0.98); }
.oce-header:focus-visible { outline: 2px solid var(--brand-orange); outline-offset: -2px; }
.oce-num { font-size: 11px; font-weight: 700; color: var(--brand-orange); min-width: 18px; }
.oce-title { font-size: 12.5px; font-weight: 600; flex: 1; color: var(--ink-950); }
.oce-body { padding: 10px; border-top: 1px solid var(--border); }
.oce-textarea { min-height: 120px; line-height: 1.6; font-size: 12.5px; }
.oce-subs { display: flex; flex-direction: column; gap: 6px; margin-top: 8px; }
.oce-actions { display: flex; gap: 8px; flex-wrap: wrap; margin-top: 8px; }

/* ── Master template editor modal ── */
.offer-template-modal { max-width: 760px; width: 92vw; }
.offer-template-modal .modal-body { max-height: 70vh; overflow-y: auto; }
.otm-item { border: 1px solid var(--border); border-radius: 8px; padding: 10px; margin-bottom: 10px; }
.otm-head { display: flex; align-items: center; gap: 8px; margin-bottom: 6px; }
.otm-title { flex: 1; }
.otm-active { display: flex; align-items: center; gap: 4px; font-size: 12px; white-space: nowrap; }
.otm-body { min-height: 120px; line-height: 1.6; font-size: 12.5px; }
.otm-actions { display: flex; justify-content: flex-end; margin-top: 6px; }

/* ══════════════════════════════════════════════════════════════════════════════
   THE LETTER PAPER — #offerPaper / .offer-paper. Verbatim port of the tool's
   <style> #offerPaper block. Local --ol-* vars = attorney-vetted brand colours
   (legal doc; --ol-accent #E8650A is the exact vetted hue — do NOT re-tokenise).
   ══════════════════════════════════════════════════════════════════════════════ */
.offer-paper {
    --ol-accent: #E8650A;          /* legal-doc brand orange, verbatim from the vetted tool */
    --ol-accent-light: #FDF1E9;
    --ol-dark: #1A1A1A; --ol-mid: #555; --ol-light: #888; --ol-border: #E2E2E2;
    background: #fff; width: 100%; max-width: 800px; padding: 3rem 3.5rem;
    box-shadow: 0 4px 32px rgba(0,0,0,.09); border-radius: 4px;
    font-family: 'Inter', 'Hanken Grotesk', system-ui, sans-serif;
    font-size: 11.5px; line-height: 1.9; color: #222;
}
.offer-paper .lh { display: flex; justify-content: space-between; align-items: flex-start; margin-bottom: 1.75rem; padding-bottom: 1.25rem; border-bottom: 2px solid var(--ol-accent); }
.offer-paper .lh-logo { display: flex; flex-direction: column; align-items: flex-start; gap: 4px; }
.offer-paper .lh-logo-img { height: 50px; width: auto; }
.offer-paper .lh-logo-sub { font-size: 9.5px; color: var(--ol-light); letter-spacing: .04em; margin-top: 1px; }
.offer-paper .lh-addr { font-size: 10px; color: var(--ol-light); text-align: right; line-height: 1.65; }
.offer-paper .lh-addr a { color: var(--ol-accent); text-decoration: none; }
.offer-paper .date-line { text-align: right; font-size: 10.5px; color: var(--ol-light); margin-bottom: 1.25rem; }
.offer-paper .to-block { margin-bottom: 1.25rem; line-height: 1.7; }
.offer-paper .to-block strong { font-size: 12.5px; }
.offer-paper .subject-line { font-weight: 600; font-size: 12px; margin-bottom: .5rem; color: var(--ol-dark); }
.offer-paper .sep { height: 1px; background: var(--ol-border); margin: 1rem 0; }
.offer-paper p { margin: .5rem 0; font-size: 11.5px; }
.offer-paper .clause-head { font-size: 10.5px; font-weight: 700; text-transform: uppercase; letter-spacing: .07em; color: var(--ol-accent); margin: 1.25rem 0 .35rem; padding-left: 10px; border-left: 3px solid var(--ol-accent); }
.offer-paper .sub-clause { margin: .35rem 0 .35rem 1rem; padding-left: .75rem; border-left: 2px solid var(--ol-border); font-size: 11px; color: #333; line-height: 1.8; }
.offer-paper .ann-title { text-align: center; font-weight: 700; font-size: 11px; letter-spacing: .06em; text-transform: uppercase; color: var(--ol-dark); margin: 1.5rem 0 .2rem; }
.offer-paper .ann-sub { text-align: center; font-size: 10.5px; color: var(--ol-mid); margin-bottom: .75rem; }
.offer-paper .sal-t { width: 100%; border-collapse: collapse; font-size: 11px; margin: .5rem 0; }
.offer-paper .sal-t th { background: var(--ol-accent); color: #fff; padding: 5px 8px; text-align: left; font-weight: 600; font-size: 10px; letter-spacing: .04em; text-transform: uppercase; }
.offer-paper .sal-t th:last-child, .offer-paper .sal-t td:last-child { text-align: right; }
.offer-paper .sal-t th:nth-child(2), .offer-paper .sal-t td:nth-child(2) { text-align: right; }
.offer-paper .sal-t td { padding: 4px 8px; border-bottom: 1px solid var(--ol-border); font-variant-numeric: tabular-nums; }
.offer-paper .sal-indent { padding-left: 16px; }
.offer-paper .sal-t tr.section-row td { background: var(--ol-accent-light); font-weight: 600; font-size: 10px; text-transform: uppercase; letter-spacing: .04em; color: var(--ol-accent); }
.offer-paper .sal-t tr.custom-row td { background: #fafff7; font-style: italic; }
.offer-paper .sal-t tr.subtotal-row td { font-weight: 600; background: #fafafa; border-top: 1px solid #ccc; border-bottom: 1px solid #ccc; }
.offer-paper .sal-t tr.grand-row td { font-weight: 700; font-size: 12px; background: var(--ol-accent-light); color: var(--ol-accent); border-top: 2px solid var(--ol-accent); }
.offer-paper .footnotes { font-size: 10px; color: var(--ol-light); margin-top: .5rem; line-height: 1.7; }
.offer-paper .sig-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 2rem; margin-top: 2rem; font-size: 11px; }
.offer-paper .sig-line { border-top: 1px solid var(--ol-border); margin-bottom: .5rem; padding-top: .35rem; color: var(--ol-light); }
.offer-paper .sig-right { text-align: right; }
.offer-paper .sig-img { height: 58px; width: auto; display: block; margin: 8px 0 4px auto; }
.offer-paper .sig-muted { color: var(--ol-light); }

@media print {
    .offer-editor { display: block; }
    .offer-form-pane, .offer-preview-label { display: none; }
    .offer-paper { box-shadow: none; border-radius: 0; max-width: 100%; padding: 0; }
    .offer-paper .clause-head { page-break-after: avoid; }
    .offer-paper .sal-t, .offer-paper .sig-grid { page-break-inside: avoid; }
    .offer-paper p { orphans: 3; widows: 3; }
}

/* ═══════════════════════════════════════════════════════════════════════════════════════
   U1 · invalid field = red border. `aria-invalid` was set in markup (SupportTickets phone)
   with no CSS anywhere, so an invalid field looked identical to a valid one — the rule asks
   for a red border, not just a message. Token-only; applies to any .field. 2026-07-17
   ═══════════════════════════════════════════════════════════════════════════════════════ */
.field[aria-invalid="true"] {
  border-color: var(--danger);
}

/* ===== Task Module S1 (founder 2026-07-21 · Spec v1) ===== */
.task-group-label{font-family:var(--font-mono,monospace);font-size:11px;font-weight:800;letter-spacing:.08em;text-transform:uppercase;color:var(--ink-500);margin:16px 0 8px;}
.task-group-red{color:var(--danger);}
.task-card{background:var(--surface,#fff);border:1px solid var(--ink-150,#e3e8eb);border-radius:10px;padding:12px 16px;margin-bottom:8px;cursor:pointer;display:flex;align-items:center;gap:12px;}
.task-card:hover{box-shadow:0 2px 8px rgba(0,0,0,.08);}
.task-card:focus-visible{outline:2px solid var(--accent);outline-offset:2px;}
.task-overdue{border-left:4px solid var(--danger);}
.task-blocked{border-left:4px solid var(--md-sys-color-tertiary,#8E44AD);}
.task-card-main{flex:1;min-width:0;}
.task-card-title{font-weight:600;margin-bottom:2px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
.task-card-meta{font-size:11.5px;color:var(--ink-500);display:flex;gap:12px;flex-wrap:wrap;align-items:center;}
.task-due{font-size:11.5px;font-weight:700;white-space:nowrap;}
.task-due-red{color:var(--danger);}
.task-due-amber{color:var(--warning);}
.task-detail{background:var(--surface,#fff);border:1px solid var(--ink-150,#e3e8eb);border-radius:12px;padding:20px 24px;max-width:800px;margin:0 16px 24px;}
.task-detail-title{font-size:17px;font-weight:800;margin-bottom:8px;display:flex;align-items:center;gap:8px;flex-wrap:wrap;}
.task-statusrow{display:flex;gap:8px;margin:4px 0 12px;flex-wrap:wrap;}
.task-statusrow .pill{cursor:pointer;border:1px solid transparent;padding:6px 12px;}
.task-statusrow .pill:focus-visible{outline:2px solid var(--accent);outline-offset:2px;}
.task-chip-sel{border-color:currentColor;}
.task-grid{display:grid;grid-template-columns:1fr 1fr;gap:8px 24px;margin:12px 0;}
.task-lbl{font-size:10.5px;text-transform:uppercase;letter-spacing:.06em;color:var(--ink-500);font-weight:700;margin-bottom:2px;}
.task-donebox{background:var(--success-bg,#e8f5ec);border:1px solid var(--success,#1e7e34);border-radius:8px;padding:8px 12px;margin:12px 0;}
.task-donebox-lbl{color:var(--success,#1e7e34);font-weight:800;font-size:10.5px;text-transform:uppercase;letter-spacing:.06em;margin-bottom:2px;}
.task-blockbanner{background:var(--md-sys-color-tertiary-container,#f4eaf8);border:1px solid var(--md-sys-color-tertiary,#8E44AD);border-radius:8px;padding:8px 12px;margin:12px 0;color:var(--ink-800);}
.task-sec{font-size:12px;font-weight:800;text-transform:uppercase;letter-spacing:.06em;color:var(--ink-500);margin:16px 0 8px;border-top:1px solid var(--ink-150,#e3e8eb);padding-top:12px;}
.task-subitem{display:flex;align-items:center;gap:8px;padding:6px 0;cursor:pointer;}
.task-subitem input{width:16px;height:16px;accent-color:var(--accent);}
.task-subdone span{text-decoration:line-through;color:var(--ink-500);}
.task-activity{font-size:11.5px;color:var(--ink-500);padding:2px 0 2px 12px;border-left:2px solid var(--ink-150,#e3e8eb);margin-bottom:4px;}
.task-draftsub{display:flex;align-items:center;gap:8px;background:var(--bg-soft,#f6f8f9);border-radius:8px;padding:6px 12px;margin-top:4px;font-size:12.5px;}
.tfield{margin-bottom:12px;}
.tfield label{display:block;font-size:11px;font-weight:800;text-transform:uppercase;letter-spacing:.04em;color:var(--ink-500);margin-bottom:4px;}
.tfield .field{width:100%;}
.input-error{border-color:var(--danger) !important;background:var(--danger-bg,#fdecea);}

/* ═══════════════════════════════════════════════════════════════════════════════════════
   COGS breakdown document (founder 2026-07-23) — one shared column grid across EVERY group
   table so Part name starts at the same x throughout the document (each table was auto-
   sizing its own columns), and the unit-cost summary fills the width like the tables.
   Scoped to .cogs-doc; the print window links this sheet, so print inherits it too.
   ═══════════════════════════════════════════════════════════════════════════════════════ */
.cogs-doc .cogs-bom-table { width: 100%; table-layout: fixed; }
.cogs-doc .cogs-bom-table th, .cogs-doc .cogs-bom-table td {
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* View shows /unit + line pairs (12 columns): 4 identity columns take 42%, the 8 money
   sub-columns share the remaining 58% evenly (~7.25% each — fits N2 values). The print
   window overrides these with its own 8-column widths after stripping the /unit cells. */
.cogs-doc .cogs-bom-table th:nth-child(1) { width: 11%; }
.cogs-doc .cogs-bom-table th:nth-child(2) { width: 20%; }
.cogs-doc .cogs-bom-table th:nth-child(3) { width: 5.5%; }
.cogs-doc .cogs-bom-table th:nth-child(4) { width: 5.5%; }
.cogs-doc .cogs-summary { max-width: none; width: 100%; }

/* ════════════════════════════════════════════════════════════════════════════
   Reorder planning (2026-07-24) — U46: the buildable-kits table carries 14
   columns, so it can scroll sideways. Header row is pinned by
   hornback.initStickyThead (U34); the SKU column pins here so the operator
   never loses which bike a number belongs to. Opaque background is mandatory —
   a transparent pinned cell lets the scrolling columns show through (U39).
   Reference: .ssl-matrix above.
   ════════════════════════════════════════════════════════════════════════════ */
.rp-kits-table th:first-child,
.rp-kits-table td:first-child {
  position: sticky; left: 0; z-index: 3;
  background: var(--paper);
  border-right: 1px solid var(--ink-200);
  box-shadow: 2px 0 4px rgba(0,0,0,.04);
}
.rp-kits-table tr:nth-child(even) td:first-child { background: var(--paper); }

/* ════════════════════════════════════════════════════════════════════════════
   Job Work form modals (2026-07-24) — the shared `.form-grid` bottom-aligns its
   items (app.css:1794) so that a wrapping label can't push its input out of the
   row. In these modals a field carries HELPER TEXT BELOW the input instead, and
   bottom-alignment then lifts the neighbouring field's label out of line —
   the founder's "fields are up and down". Top-align here so every label starts
   on the same line and the helper text hangs below its own field.
   ════════════════════════════════════════════════════════════════════════════ */
.jw-form-modal .form-grid { align-items: start; row-gap: var(--space-4); }
.jw-form-modal .form-field > label { margin-bottom: 4px; font-size: 12px; font-weight: 600; color: var(--ink-700); }

/* ─────────────────────────────────────────────────────────────────────────
   2026-07-30 (founder) — Dispatch wide-table + pagination fixes.
   Measured in a live-CSS harness (Playwright, 1366px):
   1. Sticky right-pinned Actions column — the action buttons stay visible at every
      horizontal scroll position, so "the actions column disappeared" (clipped off-screen
      on narrow monitors) cannot happen again. Requires overflow:visible on the TABLE:
      .data-table's own overflow:hidden makes any position:sticky cell inert (measured —
      cells simply never pinned). Corner rounding is unaffected: .table-wrap clips.
   2. The horizontal scrollbar is styled thick + always drawn on EVERY .table-wrap, not
      only .page-scroll-body--table ones, so the scroll affordance is unmistakable.
   3. .pagination-bar acts as a pinned footer: sticky to the bottom of whichever
      container scrolls, opaque, always visible while the table scrolls (founder:
      "page option should stick like a footer everywhere").
   ───────────────────────────────────────────────────────────────────────── */
/* 2026-07-30 v3 (founder) — NATIVE sticky headers EVERYWHERE. Removing overflow:hidden from
   .data-table revived the global CSS sticky-th rule (v32-theme:839) that the overflow had
   silently disabled since day one — the JS transform fallback (and its scroll wobble) existed
   only to work around that. With overflow visible the corner rounding still holds (.table-wrap
   clips), native sticky pins with zero animation, and the transform is dead via !important
   (beats the inline style the JS writes on already-wired pages). */
.data-table { overflow: visible; }
.data-table thead { transform: none !important; }
.dispatch-log-table th[data-col-key="actions"] {
  position: sticky;
  right: 0;
  z-index: 5;                      /* corner cell — above both axes */
  background: #f8fafc;             /* SAME shade as every other header (founder flag) */
}
.dispatch-log-table td:last-child {
  position: sticky;
  right: 0;
  z-index: 3;
  background: var(--paper, #fff);
}
.dispatch-log-table tbody tr:hover td:last-child { background: #f8fafc; }
.dispatch-log-table.clickable-rows tbody tr:hover td:last-child { background: #eff6ff; }

.table-wrap::-webkit-scrollbar { height: 13px; width: 13px; }
.table-wrap::-webkit-scrollbar-thumb { background: #94a3b8; border-radius: 8px; }
.table-wrap::-webkit-scrollbar-thumb:hover { background: #64748b; }
.table-wrap::-webkit-scrollbar-track { background: #eef2f7; }
.table-wrap { scrollbar-width: thin; }

.pagination-bar {
  position: sticky;
  bottom: 0;
  background: var(--paper, #fff);
  border-top: 1px solid var(--ink-200);
  z-index: 4;
}

/* 2026-07-30 (founder) — DISABLED inputs must look and behave dead: grey ground, muted ink,
   no caret, no click (pointer-events kills the cursor-inside confusion). Enabled fields keep
   the white ground — the contrast IS the visual hierarchy. */
.field:disabled, .std-input:disabled,
select.field:disabled, textarea.field:disabled {
  background: var(--ink-100);
  color: var(--ink-400);
  border-color: var(--ink-200);
  cursor: not-allowed;
  pointer-events: none;
}

/* ══════════════════════════════════════════════════════════════════════════
   Dispatch planner — side-by-side result panes (founder 2026-08-01)
   ══════════════════════════════════════════════════════════════════════════
   Founder: "you kept both fg and parts section as a list and entire seciton is
   not scrollable neither the SKU section, the part section covers very small
   space… better we keep them side by side."

   Two faults, one cause. The right panel sized itself with height:calc(100% - 60px)
   — a guess at the toolbar's height. The toolbar is taller than 60px, so the panel
   ran past the bottom of .fullbleed-page (which is overflow:hidden), and its
   scrollbar sat below the fold: unreachable, so nothing scrolled. Stacked inside
   it, the SKU table took whatever height it wanted and the parts table got the
   scraps. Height by flex:1/min-height:0 instead of arithmetic, and the two tables
   sit side by side — every column here is 2-3 digits, so half-width is plenty. */
.dp-shell {
  flex: 1;
  min-height: 0;
  display: grid;
  grid-template-columns: 340px minmax(0, 1fr);
  gap: var(--space-3);
  padding-top: var(--space-3);
  overflow: hidden;
}
.dp-pane {
  background: var(--paper);
  border: 1px solid var(--ink-200);
  border-radius: 8px;
  display: flex;
  flex-direction: column;
  min-height: 0;
  min-width: 0;
  overflow: hidden;
}
.dp-right { min-width: 0; min-height: 0; display: flex; flex-direction: column; gap: var(--space-3); }
/* The two result tables. minmax(0,1fr) — not 1fr — or a wide table refuses to
   shrink below its content and pushes its neighbour off the panel. */
.dp-tables {
  flex: 1;
  min-height: 0;
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: var(--space-3);
  align-items: stretch;
}
/* Below ~1500px the two result panes get tight, so the order list gives back 40px.
   Below 1100px side-by-side stops helping at all and they stack. */
@media (max-width: 1500px) { .dp-shell { grid-template-columns: 300px minmax(0, 1fr); } }
@media (max-width: 1100px) { .dp-tables { grid-template-columns: minmax(0, 1fr); } }
.dp-scroll { flex: 1; min-height: 0; overflow: auto; }

/* U47 — .section-bar's base rule (app.css) full-bleeds with NEGATIVE side margins.
   Inside these overflow:hidden panes that overhang is clipped and eats the accent
   marker plus the first letter, exactly as it did in modals. Same neutralisation. */
.dp-pane .section-bar { margin-left: 0; margin-right: 0; }

.dp-table { width: 100%; border-collapse: collapse; font-size: 12px; }
.dp-table thead th {
  position: sticky; top: 0; z-index: 2;
  background: var(--ink-50);
  text-align: right;
  padding: 7px 8px;
  white-space: nowrap;
  font-size: 10.5px;
  font-weight: 700;
  color: var(--ink-600);
  border-bottom: 1px solid var(--ink-200);
}
.dp-table td {
  padding: 6px 8px;
  text-align: right;
  white-space: nowrap;
  font-variant-numeric: tabular-nums;
}
.dp-table thead th:first-child, .dp-table td:first-child { text-align: left; }
.dp-table tbody tr { border-top: 1px solid var(--ink-100); background: var(--paper); }
.dp-table tbody tr.row-shortage { background: #fef2f2; }
.dp-table tbody tr:hover { background: var(--ink-50); }
.dp-table tbody tr.row-shortage:hover { background: #fee2e2; }
/* U46 — a half-width pane scrolls sideways, so the identifying first column stays
   pinned. background:inherit carries the row's own tint (shortage red, group grey)
   instead of painting an opaque slab over it. */
.dp-table th:first-child, .dp-table td:first-child { position: sticky; left: 0; background: inherit; }
.dp-table thead th:first-child { z-index: 3; background: var(--ink-50); }
/* Two header levels: MODEL (carries its own totals) then size · speed. The tint lives on the
   ROW, not the cell, so the sticky first column inherits it instead of painting over it.
   Round 9 (founder) — "give the same treatment to the SKU breakdown": the model band goes DARK
   like the size bands in the rims / wheels tables. Its totals print white — the semantic colour
   coding lives on the colour rows beneath, where the ground is light enough to read it (U5). */
.dp-table tbody tr.dp-model-row, .dp-table tbody tr.dp-model-row:hover { background: var(--ink-700); }
.dp-model-row td { padding: 5px 8px; font-size: 11px; font-weight: 800; letter-spacing: .06em; text-transform: uppercase; color: #fff !important; border-top: none; }
/* Round 11 (founder) — "give the sub-group headers half the lighter shade; right now they have no
   colour and you have to look closely to see where a group starts and ends." ink-50 was barely a
   tint. ink-200 sits about halfway between the dark parent band and the white rows: unmistakable
   as a divider, still clearly subordinate. Darker ink for the label keeps it ≥4.5:1 (U5). */
.dp-table tbody tr.dp-group-row, .dp-table tbody tr.dp-group-row:hover { background: var(--ink-200); }
.dp-group-row td {
  padding: 4px 8px 4px 18px; font-size: 10px; font-weight: 800; letter-spacing: .05em;
  text-transform: uppercase; color: var(--ink-800);
  border-top: 1px solid var(--ink-300);
}
/* Colour rows sit one step in from their size · speed heading. Scoped to the SKU table —
   the parts table shares .dp-table and must NOT inherit the indent. */
.dp-table.dp-sku tbody tr:not(.dp-model-row):not(.dp-group-row) td:first-child { padding-left: 26px; }

/* ══════════════════════════════════════════════════════════════════════════
   Production log · entry cards — Wheels & Rims AND Sub-assemblies (founder 2026-08-01)
   ══════════════════════════════════════════════════════════════════════════
   "the box sizes is too big in vertical space and input box is too big — we can have max
   input of 3 digits in a day, and instead of giving input box at bottom its better to keep
   on right side, it will save vertical space as we have quite horizontal space."
   Label + stock on the left, a 3-digit input on the right, one row instead of four.
   Named `pe-` (production entry), not `wr-`: both entry tabs share it. */
.pe-card {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  background: var(--paper);
  border: 1px solid var(--ink-100);
  border-radius: 6px;
  padding: 5px 7px;
  min-height: 44px;              /* U7 — the row stays a comfortable touch target */
}
.pe-card.is-elec { border-left: 3px solid var(--warning-light); }
.pe-card.is-off  { opacity: .65; }
.pe-card-main { flex: 1; min-width: 0; }
.pe-card-name {
  font-weight: 700; font-size: 12px; color: var(--ink-900); line-height: 1.25;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.pe-card-meta {
  font-size: 10px; color: var(--ink-500); line-height: 1.3; margin-top: 1px;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.pe-card-meta strong { font-size: 13px; font-weight: 800; }
/* A day's build is never more than 3 digits, so the field is sized for 3 — not full width. */
.pe-qty {
  width: 58px; flex: 0 0 58px;
  text-align: right; font-weight: 700;
  padding: var(--space-1) var(--space-2);
  min-height: 34px;
}

/* U47 — .filter-group had NO rule at all, so its segmented buttons sat flush against each
   other (founder: "give some spacing between the buttons of 24,26-29 selection, they are
   adjacent to each other without any space"). Used by the wheel-size tabs, Expenses period
   granularity, Beat plan status and the PO filter cluster — all want the same gap. */
.filter-group { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-2); }

/* Fulfilment table ("What can ship today"). Column 1 is a 22px pin button, so the order
   identity is column 2 — .dp-table's right-align default would strand it. Nothing here is
   wide enough to scroll sideways, so the sticky first column is switched off; a sticky
   22px pin would pin nothing useful. */
.dp-table.dp-fulfil th:first-child, .dp-table.dp-fulfil td:first-child { position: static; padding-left: 6px; }
.dp-table.dp-fulfil th:nth-child(2), .dp-table.dp-fulfil td:nth-child(2),
.dp-table.dp-fulfil th:last-child,   .dp-table.dp-fulfil td:last-child { text-align: left; }
/* U35 — one line per row, ellipsis, full text in the title. The status sentence is the
   longest thing here and must not be allowed to grow the row. */
.dp-status { font-size: 10.5px; max-width: 300px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* founder 2026-08-02 — "highlight the columns: FG and frames in the SKU pane, same for rim and
   wheel." Each stage gets a faint tint so the column PAIRS read as groups: precursor stage
   (painted frames · rims) amber, finished stage (FG · wheels) blue. Semi-transparent so row
   states (shortage red, group bands) still show through; sticky first column unaffected. */
/* Round 7 — rims and wheels are now SEPARATE tables (4 columns each), so the whole rims table
   carries the precursor amber on its stock+build pair and the wheels table the finished blue.
   Round 8 (founder) — the tint on the HEADER cells must be a SOLID colour: a semi-transparent
   rgba REPLACED the thead's opaque ground, so scrolling numbers showed through the sticky
   header ("numbers bleeding inside the header section"). Body cells keep the rgba so shortage
   red and group bands still read through them. */
.dp-table.dp-sku td:nth-child(3), .dp-table.dp-sku td:nth-child(4),
.dp-table.dp-wheels td:nth-child(3), .dp-table.dp-wheels td:nth-child(4) {
  background: rgba(37, 99, 235, 0.055);
}
/* dp-sku lost its "To paint" column when frames moved to the sub-assemblies card, so the amber
   precursor tint is now the single painted-stock column. */
.dp-table.dp-sku td:nth-child(5),
.dp-table.dp-rims td:nth-child(3), .dp-table.dp-rims td:nth-child(4) {
  background: rgba(217, 119, 6, 0.06);
}
.dp-table.dp-sku thead th:nth-child(3), .dp-table.dp-sku thead th:nth-child(4),
.dp-table.dp-wheels thead th:nth-child(3), .dp-table.dp-wheels thead th:nth-child(4) {
  background: #edf1fa;   /* blue tint over ink-50, pre-mixed OPAQUE */
}
.dp-table.dp-sku thead th:nth-child(5),
.dp-table.dp-rims thead th:nth-child(3), .dp-table.dp-rims thead th:nth-child(4) {
  background: #f5efe4;   /* amber tint over ink-50, pre-mixed OPAQUE */
}

/* Round 8 (founder) — "group rims and wheels per size in a much better way, it's not very clear."
   The size band goes DARK so the eye can't miss the boundary: near-ink ground, white text. */
.dp-table tbody tr.dp-size-band, .dp-table tbody tr.dp-size-band:hover { background: var(--ink-700); }
.dp-size-band td {
  padding: 5px 10px; font-size: 11px; font-weight: 800; letter-spacing: .08em;
  text-transform: uppercase; color: #fff; border-top: none;
}
.dp-table.dp-sku tbody tr.dp-size-band td:first-child { padding-left: 10px; }

/* founder 2026-08-02 — "only what we must build". The shelf carries the tab strip and the
   toggle as one unit: the white ground and the 1px rule live here so the tablist stays a
   pure tablist and the control still reads as part of the same strip. */
.dp-tabshelf {
  display: flex; align-items: stretch; flex-shrink: 0;
  background: #fff; border-bottom: 1px solid var(--ink-200);
  border-radius: 8px 8px 0 0;
}
.dp-only-toggle {
  display: flex; align-items: center; gap: 8px;
  padding: 0 var(--space-4); margin-bottom: -1px;
  font-size: 12px; font-weight: 600; color: var(--ink-600);
  white-space: nowrap; cursor: pointer; -webkit-user-select: none; user-select: none;
  transition: color .15s ease;
}
.dp-only-toggle:hover { color: var(--ink-800); }
.dp-only-toggle.is-on { color: var(--brand-orange); }
.dp-only-toggle input {
  width: 15px; height: 15px; margin: 0; cursor: pointer;
  accent-color: var(--brand-orange);
}
.dp-only-toggle:focus-within { outline: 2px solid var(--brand-orange); outline-offset: -3px; border-radius: 6px; }

/* Round 8 (founder) — the results strip scrolls HORIZONTALLY: more tables are coming (sub-
   assemblies next), so panes sit side by side at a fixed comfortable width and the strip
   scrolls sideways once they outgrow the viewport. */
.dp-tables {
  display: flex;
  overflow-x: auto;
  align-items: stretch;
}
.dp-tables > .dp-pane { flex: 1 0 470px; min-width: 470px; }
/* Keep the header row legible: tint composites over the ink-50 header ground. */

/* ── Current stock at a glance (Production log → Current stock, founder 2026-08-04) ──
   Four stacked two-column tables rendered 350 rows — 148 of them zero — so the page ran
   metres long and nobody could see what we actually hold. Same numbers, laid out as dense
   chips grouped by tier and then by model family, zeros hidden. */
.gs-band { border: 1px solid var(--ink-200); border-radius: 10px; background: var(--paper);
           margin-bottom: var(--space-3); overflow: hidden; }
.gs-band-head { display: flex; align-items: baseline; gap: var(--space-3); flex-wrap: wrap;
                padding: var(--space-2) var(--space-4);
                background: var(--ink-50); border-bottom: 1px solid var(--ink-200);
                border-left: 4px solid var(--gs-accent, var(--ink-400)); }
.gs-band-title { font-weight: 700; font-size: 14px; color: var(--ink-900); }
.gs-band-meta  { font-size: 12px; color: var(--ink-600); font-variant-numeric: tabular-nums; }
.gs-band-total { margin-left: auto; font-family: var(--font-display); font-size: 20px;
                 font-weight: 700; color: var(--gs-accent, var(--ink-900));
                 font-variant-numeric: tabular-nums; }
.gs-cluster { padding: var(--space-2) var(--space-4) var(--space-3); }
.gs-cluster + .gs-cluster { border-top: 1px dashed var(--ink-200); }
.gs-cluster-cap { font-size: 11px; font-weight: 600; letter-spacing: .06em;
                  color: var(--ink-600); margin-bottom: var(--space-2); }
.gs-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
           gap: var(--space-2); }
.gs-chip { display: flex; align-items: center; justify-content: space-between; gap: var(--space-2);
           min-height: 44px; padding: var(--space-1) var(--space-3);
           border: 1px solid var(--ink-200); border-radius: 8px; background: var(--paper); }
/* Two lines, not one: a wheel is identified by its TAIL ("— 230 Spoke", "— 2.4 Tyre"), which is
   exactly what a single-line ellipsis eats. Clamp at two so rows stay level. */
.gs-chip-name { font-size: 13px; line-height: 1.25; color: var(--ink-800); overflow: hidden;
                display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
                overflow-wrap: anywhere; }
.gs-chip-qty  { font-size: 16px; font-weight: 700; color: var(--ink-950);
                font-variant-numeric: tabular-nums; }
/* Nothing in this tier — say so once, quietly, instead of an empty grid. */
.gs-empty { padding: var(--space-2) var(--space-4) var(--space-3);
            font-size: 13px; color: var(--ink-500); }

/* Reorder Step-1 history cards (founder 2026-08-04): the native details marker clipped
   against the flexed summary, so it is replaced with our own chevron that rotates open. */
.rp-hist details > summary { display: flex; align-items: flex-start; gap: 6px;
                             cursor: pointer; padding: 3px 0; list-style: none; }
.rp-hist details > summary::-webkit-details-marker { display: none; }
.rp-hist details > summary:focus-visible { outline: 2px solid var(--brand-orange); outline-offset: 1px; border-radius: 4px; }
.rp-hist .rp-chev { flex: 0 0 16px; color: var(--ink-500); font-size: 12px; line-height: 1.5;
                    transition: transform var(--dur-fast); }
.rp-hist details[open] > summary .rp-chev { transform: rotate(90deg); }

/* Mix summary (founder 2026-08-04): "of every 100 bicycles" — 3/6/12-month columns + trend.
   Grid keeps every number in a column (tabular), the bar measures the SUGGESTED share only. */
.rp-mix-head, .rp-mix-row {
  display: grid; grid-template-columns: 110px 1fr 44px 44px 44px 44px 52px;
  gap: var(--space-2); align-items: center; padding: 3px 0;
}
.rp-mix-head { font-size: 10px; font-weight: 600; letter-spacing: .06em;
               color: var(--ink-500); text-transform: none; }
.rp-mix-head span, .rp-mix-v, .rp-mix-per, .rp-mix-trend { text-align: right; }
.rp-mix-name { font-size: 13px; color: var(--ink-800); overflow: hidden;
               text-overflow: ellipsis; white-space: nowrap; }
.rp-mix-bar { height: 12px; border-radius: 6px; background: var(--ink-100); overflow: hidden; display: block; }
.rp-mix-bar > span { display: block; height: 100%; border-radius: 6px; background: var(--brand-orange); }
.rp-mix-v { font-size: 12px; color: var(--ink-600); font-variant-numeric: tabular-nums; }
.rp-mix-per { font-size: 15px; font-weight: 700; color: var(--ink-950); font-variant-numeric: tabular-nums; }
.rp-mix-trend { font-size: 11px; color: var(--ink-500); }
.rp-mix-trend.up { color: var(--success); }
.rp-mix-trend.down { color: var(--danger); }
/* the three small dimension cards: stacked 100% strip + compact legend rows */
.rp-stack { display: flex; height: 16px; border-radius: 8px; overflow: hidden;
            background: var(--ink-100); margin-bottom: var(--space-2); }
.rp-seg { display: block; height: 100%; }
.rp-mix-row-sm { grid-template-columns: 12px 1fr 40px 40px 40px 36px 44px; }
.rp-mix-row-sm .rp-mix-name { font-size: 12px; }
.rp-legend-dot { width: 10px; height: 10px; border-radius: 3px; display: inline-block; }
/* the what-if quantity column ("Bikes") joins the grid only when a number is typed */
.rp-mix-qtyon .rp-mix-head, .rp-mix-qtyon .rp-mix-row { grid-template-columns: 110px 1fr 44px 44px 44px 44px 52px 64px; }
.rp-mix-qtyon .rp-mix-row-sm { grid-template-columns: 12px 1fr 38px 38px 38px 32px 40px 56px; }
.rp-mix-bikes { font-size: 15px; font-weight: 700; color: var(--brand-orange); text-align: right;
                font-variant-numeric: tabular-nums; }
/* the clean-basis model table adds Clean + Set-% columns (windows stay as reference) */
.rp-basis .rp-mix-head, .rp-basis .rp-mix-row { grid-template-columns: 100px 1fr 40px 40px 40px 36px 44px 64px 56px 48px; }
.rp-basis.rp-mix-qtyon .rp-mix-head, .rp-mix-qtyon.rp-basis .rp-mix-row,
.rp-mix-qtyon .rp-basis .rp-mix-head, .rp-mix-qtyon .rp-basis .rp-mix-row
  { grid-template-columns: 100px 1fr 40px 40px 40px 36px 44px 64px 56px 48px 60px; }
.rp-mix-set { min-height: 32px; width: 60px; text-align: right; font-size: 12px;
              border: 1px solid var(--ink-200); border-radius: 6px; padding: 2px 6px;
              font-variant-numeric: tabular-nums; }

/* Parts-required card (projection tab): category sub-tabs + required/stock/short grid */
.rp-parts { display: flex; flex-direction: column; }
.rp-parts-head, .rp-parts-row { display: grid; grid-template-columns: 1fr 130px 110px 110px;
                                gap: var(--space-2); align-items: center; }
.rp-parts-head { font-size: 11px; color: var(--ink-500); padding: 2px 0 4px;
                 border-bottom: 1px solid var(--ink-150); }
.rp-parts-head span:nth-child(n+2), .rp-parts-v, .rp-parts-qty, .rp-parts-short
  { text-align: right; font-variant-numeric: tabular-nums; }
.rp-parts-row { padding: 3px 0; border-bottom: 1px dotted var(--ink-150); font-size: 12.5px; }
.rp-parts-row:last-child { border-bottom: 0; }
.rp-parts-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--ink-800); }
.rp-parts-v { color: var(--ink-600); font-size: 12px; }
.rp-parts-qty { font-weight: 600; color: var(--ink-900); }
.rp-parts-unit { font-weight: 400; color: var(--ink-500); font-size: 11px; }
.rp-parts-short { color: var(--ink-400); font-size: 12px; }
.rp-parts-short.is-short { color: var(--danger); font-weight: 700; }

/* Routine tasks: the per-period history dots (newest first) */
.rt-dot { width: 12px; height: 12px; border-radius: 50%; display: inline-block;
          border: 1px solid var(--ink-200); }
.rt-ontime { background: var(--success); border-color: var(--success); }
.rt-late   { background: var(--warning); border-color: var(--warning); }
.rt-missed { background: color-mix(in srgb, var(--danger) 30%, transparent); border-color: var(--danger); }
.rt-open   { background: transparent; border: 2px dashed var(--ink-300); }
.rt-nil    { background: color-mix(in srgb, var(--success) 25%, transparent); border-color: var(--success); }
.rt-rest   { background: var(--ink-150); border-color: var(--ink-200); }
/* founder 2026-08-06: the three cadence tables must line up column-for-column —
   fixed layout + the shared th widths make Daily/Weekly/Monthly one visual grid */
.rt-table { table-layout: fixed; width: 100%; }
/* founder 2026-08-06: a LIST — minimum height without cramping; one line per duty */
.rt-table td, .rt-table th { padding: 6px 10px; }
.rt-table .btn-secondary { min-height: 34px !important; }
.rt-delegate { min-height: 32px; max-width: 140px; font-size: 12px;
               border: 1px solid var(--ink-200); border-radius: 6px;
               padding: 3px 6px; background: var(--paper); color: var(--ink-700); }
.rt-check-grid { table-layout: fixed; width: 100%; }
.rt-check-grid td, .rt-check-grid th { padding: 4px 10px; font-size: 12.5px; }
.rp-mix-set:focus-visible { outline: 2px solid var(--brand-orange); outline-offset: 1px; }
.rp-mix-set::placeholder { color: var(--ink-400); }

/* Projection planning (founder 2026-08-05): a matrix — models as columns, four rows
   (entire timeline · clean quarters · pending orders · the founder's own numbers).
   Scrolls horizontally in its OWN container; the row-label column stays pinned (U46). */
.rp-proj-scroll { overflow-x: auto; padding: var(--space-2) var(--space-3) var(--space-3); }
.rp-proj { display: grid; gap: 2px var(--space-2); align-items: center; min-width: max-content; }
.rp-proj-corner, .rp-proj-rowlabel {
  position: sticky; left: 0; background: var(--paper); z-index: 1;
  padding-right: var(--space-2);
}
.rp-proj-model { font-size: 12px; font-weight: 700; color: var(--ink-800); text-align: right;
                 overflow: hidden; text-overflow: ellipsis; white-space: nowrap; padding: 4px 2px; }
.rp-proj-rowlabel { font-size: 12px; font-weight: 600; color: var(--ink-600); padding: 6px 0;
                    white-space: nowrap; border-right: 1px solid var(--ink-100); }
.rp-proj-rowlabel.rp-proj-yours { color: var(--brand-orange); font-weight: 700; }
.rp-proj-cell { font-size: 13px; text-align: right; font-variant-numeric: tabular-nums;
                color: var(--ink-800); padding: 4px 2px; }
.rp-proj-input { min-height: 40px; width: 76px; text-align: right; font-size: 13px; font-weight: 700;
                 border: 1px solid var(--ink-300); border-radius: 8px; padding: 4px 8px;
                 font-variant-numeric: tabular-nums; color: var(--ink-950); background: var(--paper); }
.rp-proj-input:focus-visible { outline: 2px solid var(--brand-orange); outline-offset: 1px; }
.rp-proj-input::placeholder { color: var(--ink-400); font-weight: 400; }

/* Sub-tabs inside the Current stock panel: the entity-tab look, one notch quieter, so the row
   never competes with the page's own tab strip sitting right above it.
   founder 2026-08-04 — pinned to the top of .page-scroll-body (the page's real scroll axis, see
   app.css:887) so scrolling FG never leaves you without a way back to the other tiers. Opaque
   canvas ground + z-index, or the cards would show through as it travels. */
.gs-subtabs { margin: 0 0 var(--space-3); position: sticky; top: 0; z-index: 5;
              background: var(--canvas); }
.gs-subtabs .entity-tab { font-size: 12px; padding: 8px 14px 6px; }

/* Frame tiers, second pass (founder 2026-08-04): one card per size — "Zop 24"" — with the
   speeds separated inside it, so a model's shape reads without hunting through a flat grid. */
.gs-cardgrid { display: grid; grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
               gap: var(--space-3); align-items: start; }
.gs-card { border: 1px solid var(--ink-200); border-radius: 10px; background: var(--paper);
           overflow: hidden; }
.gs-card-head { display: flex; align-items: baseline; justify-content: space-between;
                gap: var(--space-2); padding: var(--space-2) var(--space-3);
                background: color-mix(in srgb, var(--gs-accent, var(--ink-400)) 10%, transparent);
                border-bottom: 1px solid var(--ink-200); }
.gs-card-title { font-size: 13px; font-weight: 700; color: var(--ink-900); }
.gs-card-total { font-size: 16px; font-weight: 700; color: var(--ink-950);
                 font-variant-numeric: tabular-nums; }
/* Rims vs tyred wheels share a size card but are two different things (founder 2026-08-04):
   each gets a titled sub-header with its own accent, and a solid rule between the blocks —
   front / rear stay dashed captions inside, so the two levels never read as equals. */
.gs-block + .gs-block { border-top: 2px solid var(--ink-200); }
.gs-block-cap { display: flex; justify-content: space-between; gap: var(--space-2);
                align-items: baseline; padding: var(--space-1) var(--space-3);
                border-left: 3px solid var(--gs-block-accent);
                background: color-mix(in srgb, var(--gs-block-accent) 8%, transparent);
                font-size: 11px; font-weight: 700; letter-spacing: .04em;
                color: var(--gs-block-accent); }
.gs-block-qty { font-size: 13px; font-variant-numeric: tabular-nums; color: var(--ink-950); }

.gs-sec { padding: var(--space-2) var(--space-3); }
.gs-sec + .gs-sec { border-top: 1px dashed var(--ink-200); }
.gs-sec-cap { display: flex; justify-content: space-between; gap: var(--space-2);
              font-size: 11px; font-weight: 600; letter-spacing: .04em; color: var(--ink-600);
              margin-bottom: var(--space-1); }
.gs-sec-qty { font-variant-numeric: tabular-nums; color: var(--ink-800); }
.gs-line { display: flex; justify-content: space-between; gap: var(--space-2);
           padding: 3px 0; font-size: 13px; }
.gs-line-name { color: var(--ink-800); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.gs-line-qty  { font-weight: 700; color: var(--ink-950); font-variant-numeric: tabular-nums; }

/* ════════════════════════════════════════════════════════════════════════════
   Job Work · receive editor — the consumed-material table grows, never scrolls
   (founder 2026-08-07). Both .table-wrap definitions cap height (calc(100vh-250px))
   and hornback.fitTableHeight caps it further to fit the viewport with a 220px
   floor. This table sits low on the receive page, so it hit that floor: adding a
   4th material dropped it into a scroll area and the click read as "nothing
   happened". Markup carries data-no-fit (the JS opt-out); this lifts the CSS cap
   so the box follows its content and the sections below simply move down.
   ════════════════════════════════════════════════════════════════════════════ */
.jw-mat-wrap { max-height: none; }

/* A day that carries no expectation at all — before the person's first duty existed, or
   still in the future. Invisible on purpose: an empty cell must not read as a miss
   (founder 2026-08-07, day-submission month grid). */
.rt-none { background: transparent; border-color: transparent; }

/* ════════════════════════════════════════════════════════════════════════════
   Dispatch actions — "Mark remaining" must not look like "Mark dispatched"
   (founder 2026-08-08). Both were the same neutral button, so an order already
   half-shipped read exactly like one nobody had touched. Amber = part of this
   order has already gone out; neutral = nothing dispatched yet. The count lives
   in the IMS Disp column; this is the at-a-glance signal in the action itself.
   ════════════════════════════════════════════════════════════════════════════ */
.btn-mark-partial {
  background: var(--warning-soft);
  border-color: var(--warning-300);
  color: var(--warning);
  font-weight: 600;
}
.btn-mark-partial:hover:not(:disabled) {
  background: color-mix(in srgb, var(--warning) 18%, transparent);
  border-color: var(--warning);
}
/* the leading dot carries the meaning for anyone who can't separate the hues */
.btn-mark-partial::before {
  content: "◐";
  margin-right: 5px;
  font-size: 11px;
  vertical-align: baseline;
}

/* ════════════════════════════════════════════════════════════════════════════
   Dispatch actions column — the "✓ marked" chip sits in the same column as the
   Mark dispatched / Mark remaining buttons, so it must occupy the same box
   (founder 2026-08-08). It was a small pill next to 28px buttons, which made
   the column jump row to row. Same metrics as .btn-sm, still not a button:
   no pointer, no hover, no focus ring — it states a fact, it doesn't act.
   ════════════════════════════════════════════════════════════════════════════ */
.chip-marked {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 4px;
  min-height: 28px;
  padding: 4px 10px;
  font-size: 11px;
  font-weight: 600;
  border-radius: var(--r-md);
  border: 1px solid var(--success-300, var(--success));
  background: var(--success-soft);
  color: var(--success);
  white-space: nowrap;
  cursor: default;
}

/* Every control in the dispatch Actions column occupies ONE identical box — same width and
   height whether it is "Mark dispatched", "Mark remaining · 6 left" or the "✓ marked" chip
   (founder 2026-08-08, second pass: the first fix matched the height but not the width, so
   the column still stepped in and out row to row). The width fits the longest label. */
.dispatch-action {
  min-width: 148px !important;
  min-height: 28px !important;
  padding: 4px 10px !important;
  font-size: 11px !important;
  display: inline-flex !important;
  align-items: center;
  justify-content: center;
  box-sizing: border-box;
  white-space: nowrap;
}
