/* Inventory & Sales — shared styles */
* { box-sizing: border-box; touch-action: manipulation; } /* taps never double-tap-zoom on iOS */
/* iOS Safari auto-zooms any focused input under 16px — keep ALL fields at 16px minimum */
input, select, textarea { font-size: 16px; }
/* Belt-and-suspenders: on phones NOTHING may drop a form control below 16px.
   This !important floor beats any component rule OR inline style, so no text
   box, dropdown or number field can ever focus-zoom the page. Desktop keeps
   its component-specific sizes (no zoom there). */
@media (max-width: 700px) {
  input, select, textarea { font-size: 16px !important; }
}
/* Dropdowns match normal text boxes everywhere (macOS renders native selects tiny) */
select {
  -webkit-appearance: none; -moz-appearance: none; appearance: none;
  width: 100%; padding: 11px 36px 11px 12px;
  border: 1px solid var(--line); border-radius: 10px;
  color: var(--ink); cursor: pointer;
  background: #fff url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8'%3E%3Cpath d='M1 1l5 5 5-5' stroke='%2367748c' stroke-width='2' fill='none' stroke-linecap='round'/%3E%3C/svg%3E") no-repeat right 12px center;
}
:root {
  --bg: #f2f4f7;
  --card: #ffffff;
  --ink: #1a2233;
  --muted: #67748c;
  --line: #e4e8ef;
  --surface-1: #f4f6f9;      /* subtle row band — ledger order grouping + totals */
  --primary: #6d28d9;        /* app theme: purple */
  --primary-dark: #5b21b6;
  --primary-light: #ede9fe;  /* light purple button fill */
  --primary-press: #ddd6fe;
  --green: #059669;
  --red: #dc2626;
  --amber: #b45309;
  --radius: 14px;
  --shadow: 0 1px 3px rgba(16, 24, 40, .08), 0 1px 2px rgba(16, 24, 40, .04);
}
html, body {
  margin: 0; padding: 0;
  background: var(--bg); color: var(--ink);
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  font-size: 16px; line-height: 1.45;
  -webkit-font-smoothing: antialiased;
  /* UC-011: iOS Safari font boosting inflated SOME text blocks (and stuck
     after landscape rotation) — the app's own sizes are the only sizes. */
  -webkit-text-size-adjust: 100%;
  text-size-adjust: 100%;
}
a { color: var(--primary); text-decoration: none; }
a:hover { text-decoration: underline; }

.topbar {
  background: #1e0a47; color: #fff;
  padding: 12px 18px;
  display: flex; align-items: center; gap: 12px; flex-wrap: wrap;
  position: sticky; top: 0; z-index: 50;
}
.topbar .brand { font-weight: 700; font-size: 17px; letter-spacing: .2px; }
.topbar .who { color: #c4b5fd; font-size: 14px; }
.topbar .spacer { flex: 1; }
.topbar a { color: #ddd6fe; font-size: 14px; font-weight: 600; }
/* Buttons in the header: light chip on the dark purple bar */
.topbar .btn, .topbar .btn.primary {
  background: var(--primary-light); border-color: var(--primary-light); color: var(--primary-dark);
}
.topbar .btn:hover, .topbar .btn.primary:hover { background: var(--primary-press); }

.wrap { max-width: 980px; margin: 0 auto; padding: 18px 14px 80px; }
.wrap.narrow { max-width: 560px; }
/* The Sales Ledger has many columns, so its card alone breaks out wider than
   the 980px page and re-centers on the viewport; every other card keeps the
   page width. Desktop only — on phones it's full-width like the rest. */
@media (min-width: 701px) {
  #lg-collapse {
    width: min(1240px, calc(100vw - 32px));
    /* Re-center this wider-than-page card on the viewport with margin math
       only — NOT a transform. A non-identity transform promotes the card to
       its own compositor layer, which Chromium sometimes fails to clear when
       the Accounting tab flips to display:none, leaving a ghost of the ledger
       painted over other pages. Pure margin keeps it in the normal layer. */
    margin-left: calc(50% - min(1240px, calc(100vw - 32px)) / 2);
  }
}

.card {
  background: var(--card); border: 1px solid var(--line);
  border-radius: var(--radius); box-shadow: var(--shadow);
  padding: 18px; margin-bottom: 16px;
}
.card h2 { margin: 0 0 12px; font-size: 18px; }
/* Desktop pages (admin/manager): divider line under every card title */
body.desktop .card > h2 {
  border-bottom: 1.5px solid var(--line);
  padding-bottom: 12px; margin-bottom: 14px;
}
/* A collapsed card is nothing but its title, so the divider has nothing left
   to divide — it goes, along with the space it sat in. What remains is the
   card's own 18px padding above and below the title, evenly. */
body.desktop .card.card-shut > h2 {
  border-bottom: none; padding-bottom: 0; margin-bottom: 0;
}
/* Transfer History: the Units cell carries BOTH states and swaps in place —
   the bare total while the row is closed, the unit list itself once it opens.
   The chips already carry their own quantities, so the total would only be
   repeating itself; it steps aside rather than pushing the list elsewhere. */
.tbl tr .xf-list { display: none; }
.tbl tr.xf-open .xf-total { display: none; }
.tbl tr.xf-open .xf-list { display: block; }
/* Desktop pages: dropdowns and text fields stay a tidy width instead of
   stretching across the whole page */
body.desktop .field select,
body.desktop .field input { max-width: 340px; }
.card h3 { margin: 18px 0 8px; font-size: 15px; color: var(--muted); text-transform: uppercase; letter-spacing: .4px; }

.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 8px;
  border: 1.5px solid var(--primary); background: var(--primary); color: #fff;
  border-radius: 8px; padding: 10px 16px; font-size: 15px; font-weight: 600;
  cursor: pointer; transition: background .12s, transform .05s;
  -webkit-tap-highlight-color: transparent;
}
.btn:hover { background: var(--primary-dark); }
.btn:active { transform: scale(.98); }
.btn.primary { background: var(--primary); border-color: var(--primary); color: #fff; }
.btn.primary:hover { background: var(--primary-dark); }
.btn.green { background: var(--primary); border-color: var(--primary); color: #fff; }
.btn.green:hover { background: var(--primary-dark); }
.btn.danger { background: #fff; border-color: #fca5a5; color: var(--red); }
.btn.big { width: 100%; padding: 16px; font-size: 17px; border-radius: 12px; }
.btn.small { padding: 5px 10px; font-size: 13px; border-radius: 8px; }
.btn:disabled { opacity: .5; cursor: not-allowed; }
/* Period picker as a segmented control: the selected range is filled, the
   others sit as outlines so it's clear which window you're looking at. */
.range-btn { background: #fff; color: var(--primary); }
.range-btn:hover { background: var(--primary-light); }
.range-btn.active, .range-btn.active:hover { background: var(--primary); color: #fff; }

.field { margin-bottom: 14px; }
.field label { display: block; font-size: 13px; font-weight: 600; color: var(--muted); margin-bottom: 5px; }
.field input, .field select, .field textarea {
  width: 100%; padding: 11px 12px; font-size: 16px;
  border: 1px solid var(--line); border-radius: 10px; background: #fff; color: var(--ink);
}
.field input:focus, .field select:focus, .field textarea:focus {
  outline: none; border-color: var(--primary); box-shadow: 0 0 0 3px rgba(109, 40, 217, .14);
}
.row { display: flex; gap: 10px; align-items: center; flex-wrap: wrap; }
.row > .field { flex: 1; min-width: 130px; margin-bottom: 0; }

.grid { display: grid; gap: 10px; }
.grid.cols-2 { grid-template-columns: repeat(2, 1fr); }
.grid.cols-3 { grid-template-columns: repeat(3, 1fr); }
@media (max-width: 640px) {
  .grid.cols-3 { grid-template-columns: repeat(2, 1fr); }
}

table.tbl { width: 100%; border-collapse: collapse; font-size: 14px; }
.tbl th {
  text-align: left; font-size: 12px; text-transform: uppercase; letter-spacing: .4px;
  color: var(--muted); padding: 8px 10px; border-bottom: 2px solid var(--line);
  white-space: nowrap;
}
.tbl td { padding: 9px 10px; border-bottom: 1px solid var(--line); vertical-align: middle; }
/* Tall multi-line cells (e.g. transfer items): pin every column to the top row */
.tbl.top td { vertical-align: top; padding-top: 12px; }
.tbl tr:last-child td { border-bottom: none; }
.tbl td.num, .tbl th.num { text-align: right; font-variant-numeric: tabular-nums; }
.tbl tfoot td { font-weight: 700; border-top: 2px solid var(--line); }
.tbl-scroll { overflow-x: auto; }
/* A table whose rows carry an Options dropdown must not clip it — those tables
   (Batches, Products) are narrow enough not to need horizontal scroll. Because
   they cannot scroll, they also cannot afford an extra column on a phone: see
   .bulk-col below, which folds Bulk into the facility cell instead. */
.tbl-scroll:has(.menu-wrap) { overflow: visible; }
/* Bulk Qty is its own column on desktop only — the phone layout further down
   lays each row out as a fixed grid, which a sixth cell would break. There it
   folds into the facility cell instead; see .bulk-col in the 700px block. */
.bulk-inline, .fac-inline { display: none; }
/* A count riding inside a button. Grey on purple could not be read and dimmed
   white was still too faint (owner, twice) — it now sits at full strength in
   the button's own ink, parenthesised so it reads as a count, not an amount. */
.chip-n { font-weight: 700; opacity: 1; }
.btn.primary .chip-n.on { color: #fff; }
/* Dense variant for wide, column-heavy tables (the sales ledger) so all
   columns fit one line on desktop. */
.tbl.compact { font-size: 12.5px; }
.tbl.compact th, .tbl.compact td { padding: 6px 7px; }
.tbl.compact th { white-space: nowrap; }
/* Sales Ledger: a stronger rule opens each order; its lines share one shade */
#s-body tr.lg-ord-top > td { border-top: 2px solid var(--line); }
/* Compact type tag in the Txn column (FC / CS / IN); hover shows the full
   label + field salesman name via the title attribute. */
.txn-badge {
  /* keeps the hover tooltip (title=) but a normal cursor — the ?-cursor read
     as something being wrong rather than "there's a hint here" */
  display: inline-block; font-size: 10.5px; font-weight: 700; line-height: 1.4;
  padding: 0 5px; border-radius: 5px; vertical-align: middle; cursor: default;
  background: var(--primary-light); color: var(--primary-dark);
}
/* Where the order's money IS (owner rule 2026-07-22): traffic-light states
   on every owner counter order in the ledger. */
.txn-badge.tb-paid { background: #d1fae5; color: #065f46; }
.txn-badge.tb-await { background: #fef3c7; color: #92400e; }
.txn-badge.tb-open { background: #eef1f5; color: var(--muted); }
/* Structured dialog bodies (owner rule 2026-07-22): money popups reuse the
   same order-block + mathbox markup as the rest of the app, and scroll rather
   than truncate. */
.dlg-body { max-height: 52vh; overflow-y: auto; margin: 2px -4px 4px; padding: 0 4px; }
.dlg-body .mathbox { margin-top: 6px; }
.dlg-order { padding: 8px 2px; border-top: 1px dashed var(--line); }
.dlg-order:first-child { border-top: none; padding-top: 2px; }
/* Inventory stock chip: desktop shows "Quantity:", mobile swaps to "qty:"
   (the mobile override lives in the max-width media query). */
.q-short { display: none; }

/* Inside-sale modal line rows (desktop widths; per-cell mobile labels hidden) */
.is-table .is-qty { width: 70px; }
.is-table .is-price { width: 90px; }
.is-table .m-lbl { display: none; }

/* Inside-sale checklist modal: the WHOLE modal scrolls (top fields included)
   and only the money footer sticks — same on mobile and desktop. Wider on
   desktop for the product list. */
.is-modal { display: block; overflow-y: auto; -webkit-overflow-scrolling: touch; padding-bottom: 0; }
/* Wider than the standard 480px modal for the product list — .modal.is-modal
   outranks the base .modal max-width; screen width still caps it on phones. */
.modal.is-modal { max-width: 720px; }
.is-scroll { margin: 0; padding: 0; }
.is-foot {
  position: sticky; bottom: 0; z-index: 2; background: #fff;
  border-top: 1.5px solid var(--line); margin-top: 10px; padding-top: 12px;
  /* Side padding so the right-aligned totals + Amount Paid don't hug the edge */
  padding-left: 12px; padding-right: 12px;
  padding-bottom: calc(6px + env(safe-area-inset-bottom, 0px));
  box-shadow: 0 -6px 14px rgba(15, 23, 42, .08);
}
/* Amount Paid uses the app's standard text-box look (matches .field input) — it
   sits in a flex row, not a .field, so the styling is applied directly. Value is
   bold; the box stays consistent with every other input. */
#is-paid {
  box-sizing: border-box; width: 150px; max-width: 150px;
  padding: 11px 12px; font-size: 16px; font-weight: 700;
  border: 1px solid var(--line); border-radius: 10px; background: #fff; color: var(--ink);
}
#is-paid:focus {
  outline: none; border-color: var(--primary); box-shadow: 0 0 0 3px rgba(109, 40, 217, .14);
}
/* Header + each card's head share ONE grid, so the columns line up:
   [checkbox] Product | Batch | Price | Stock. Two grids only align when their
   column widths are identical fixed values — hence the shared template. */
.is-hd, .is-head {
  display: grid;
  grid-template-columns: 22px minmax(0, 1fr) 70px 82px 44px;
  gap: 8px; align-items: center;
}
.is-hd {
  margin: 14px 0 6px; padding: 0 12px;
  font-size: 11px; font-weight: 700; letter-spacing: .4px;
  text-transform: uppercase; color: var(--muted);
}
.is-hd .r { text-align: right; }
/* Phones: tighten the side columns and ease the name font so typical
   "Product (X)" names fit whole; a genuinely long name still ellipsizes and
   the A/B/C card color carries the class. (Name is a span — the 16px input
   zoom-floor doesn't apply here.) */
@media (max-width: 700px) {
  .is-hd, .is-head { grid-template-columns: 22px minmax(0, 1fr) 56px 68px 28px; gap: 6px; }
  .is-head .is-name { font-size: 14.5px; }
  .is-head .is-batch { font-size: 12px; }
}
.is-item {
  border: 1.5px solid var(--line); border-radius: 10px;
  padding: 9px 12px; margin-bottom: 8px;
}
/* The whole card carries the product's A/B/C class color */
.is-item.is-a { border-color: #86efac; background: #f0fdf4; }
.is-item.is-b { border-color: #93c5fd; background: #eff6ff; }
.is-item.is-c { border-color: #fdba74; background: #fff7ed; }
/* Selected = a purple ring over the class color, so both read at once */
.is-item.on { box-shadow: inset 0 0 0 2px var(--primary); }
.is-head { cursor: pointer; -webkit-user-select: none; user-select: none; }
.is-head .is-sel { width: 21px; height: 21px; margin: 0; accent-color: var(--primary); }
.is-head .is-name { min-width: 0; font-weight: 600; display: flex; align-items: center; gap: 5px; }
.is-head .is-nm { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* Age chip in the tight row: compact, and let it never shrink away */
.is-head .age-chip { margin-left: 0; flex-shrink: 0; padding: 1px 6px; font-size: 11px; }
.is-head .is-batch {
  color: var(--muted); font-size: 12.5px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.is-head .is-price-ref { text-align: right; white-space: nowrap; color: var(--muted); font-size: 13px; }
.is-head .is-ref { text-align: right; white-space: nowrap; font-weight: 700; }
.is-inputs { display: none; align-items: center; gap: 8px; flex-wrap: wrap; margin-top: 8px; padding-left: 32px; }
.is-item.on .is-inputs { display: flex; }
.is-inputs .m-lbl { font-size: 12px; font-weight: 600; color: var(--muted); }
.is-inputs input { width: 68px; min-height: 32px; padding: 4px 8px; text-align: center; }
.is-inputs .is-line { margin-left: auto; font-weight: 700; white-space: nowrap; }
/* Collapsible sub-section inside a container card (facilities inside the
   Facilities card; Managers/Field/Settings/Password inside This Facility) —
   a hairline divider separates them, and the header row is tap-friendly. */
/* Container cards (Facilities, This Facility) drop the title divider — each
   sub-section already carries its own top divider, so keeping the h2 rule
   would double the line right under the heading. */
body.desktop .container-card > h2 { border-bottom: none; padding-bottom: 0; margin-bottom: 6px; }
.fac-sub { border-top: 1px solid var(--line); }
/* Breathing room between the facility-name row (with Platform Admins) and the
   first section (Managers) in the Facility Settings card. */
#tf-name-row { margin-bottom: 16px; }
/* Add-manager / add-field-salesman rows get a divider above (separating them
   from the roster list) so the sections read clearly. */
.staff-add { border-top: 1px solid var(--line); margin-top: 12px; padding-top: 14px; }
.fac-sub > .collapse-h, .fac-sub > div:first-child { padding: 12px 0 12px; }
/* proper header look (override .card h3's uppercase-muted styling) */
.fac-sub .collapse-h h3 { font-size: 16px; color: var(--ink); text-transform: none; letter-spacing: 0; margin: 0; }
.fac-sub.collapsed .collapse-body { display: none; }
.fac-sub .collapse-body { padding-bottom: 12px; }
/* Balances card: the two collapsible sections (Awaiting Confirmation, Payment
   History) read as distinct accordion bars — filled/bordered/rounded, spaced
   apart, with a bottom rule when open — instead of hairline rows that run
   together. Scoped to this card so Settings sub-sections are untouched. */
#collections-card .fac-sub {
  border: 1px solid var(--line); border-top: 1px solid var(--line);
  border-radius: 10px; margin-top: 12px; overflow: hidden;
}
#collections-card .fac-sub > .collapse-h {
  padding: 13px 14px; background: var(--bg);
}
#collections-card .fac-sub > .collapse-h:hover { background: #e9edf3; }
#collections-card .fac-sub:not(.collapsed) > .collapse-h { border-bottom: 1px solid var(--line); }
#collections-card .fac-sub .collapse-body { padding: 12px 14px; }
#collections-card .fac-sub .collapse-h .caret { color: var(--ink); }

.badge {
  display: inline-block; padding: 2px 9px; border-radius: 999px;
  font-size: 12.5px; font-weight: 700;
  /* a pill whose text wraps turns into a tall blob — never wrap mid-badge;
     a pill longer than its container ellipsizes instead of overflowing */
  white-space: nowrap; max-width: 100%; overflow: hidden;
  text-overflow: ellipsis; vertical-align: bottom;
}
.badge.ok { background: #d1fae5; color: #065f46; }
.badge.low { background: #fef3c7; color: #92400e; }
.badge.out { background: #fee2e2; color: #991b1b; }
.badge.gray { background: #eef1f5; color: var(--muted); }
/* Quantity badges: light green fill, darker green outline, squarer corners */
.badge.qty {
  background: #dcfce7; color: #166534;
  border: 1px solid #16a34a; border-radius: 6px;
}
/* Product name chip + plain-text quantity. Chip color follows the (A)/(B)/(C)
   classification in the product name; unclassified products default to green. */
.nm-chip {
  /* Rounded product pill, unified with the sales cards: pale A/B/C fill,
     class-colored border, default-ink text (not class-colored). Keeps its
     rounder pill corners — only the colors changed. */
  display: inline-block; background: #f0fdf4; color: var(--ink);
  border: 1.5px solid #86efac; border-radius: 10px;
  padding: 4px 10px; font-weight: 700; font-size: 15px;
}
/* Inside a scrollable table a wrapped chip becomes a tall blob — keep it one
   line and let .tbl-scroll pan horizontally instead */
.tbl .nm-chip { white-space: nowrap; }
.nm-chip.cls-b { background: #eff6ff; border-color: #93c5fd; }
.nm-chip.cls-c { background: #fff7ed; border-color: #fdba74; }
/* Custom Label row (Batch Labels page): batch codes are long, so on phones
   stack it — batch on top, name below, the Download button below that and
   right-aligned. Desktop keeps the three fields inline. */
@media (max-width: 700px) {
  #cl-row { flex-direction: column; align-items: stretch; }
  #cl-row > .field { width: 100%; min-width: 0; }
  #cl-row .cl-golbl { display: none; }
  #cl-row .cl-gowrap { justify-content: flex-end; min-height: 0; }
}
.nm-chip.sm { font-size: 12.5px; padding: 2px 8px; font-weight: 600; }
.nm-chip.lg { font-size: 18px; padding: 6px 12px; }
/* Batch age chips, traffic-light aging: green = oldest (move it first),
   yellow = older middle batches, red = newest */
.age-chip {
  display: inline-block; padding: 2px 8px; border-radius: 6px;
  font-size: 12.5px; font-weight: 700; margin-left: 6px;
}
.age-chip.oldest { background: var(--green); color: #fff; }
.age-chip.mid { background: #facc15; color: #713f12; }
.age-chip.new { background: var(--red); color: #fff; }
/* Name chip + age chip always ride side by side — never wrap apart */
.chip-pair { display: inline-flex; align-items: center; gap: 6px; white-space: nowrap; max-width: 100%; }
.chip-pair .age-chip { margin-left: 0; flex-shrink: 0; }
/* Age chip inside a pick button: tighter so the button stays compact */
button .age-chip { margin-left: 4px; padding: 1px 6px; font-size: 11.5px; }

/* Dropdown menu (facility card "Manage ▾" etc.): a light-purple trigger and a
   floating panel of actions — collapses a cluster of buttons into one control */
.menu-wrap { position: relative; display: inline-block; }
.menu-pop {
  position: absolute; right: 0; top: calc(100% + 4px); z-index: 30;
  background: #fff; border: 1px solid var(--line); border-radius: 10px;
  box-shadow: 0 8px 24px rgba(15, 23, 42, .16); padding: 6px; min-width: 190px;
  display: none;
}
.menu-wrap.open .menu-pop { display: block; }
.menu-pop button {
  display: block; width: 100%; text-align: left; background: none; border: none;
  padding: 9px 12px; border-radius: 7px; font-size: 14px; color: var(--ink); cursor: pointer;
}
.menu-pop button:hover { background: var(--primary-light); }
.menu-pop button.danger { color: var(--red); }
.menu-pop .menu-sep { border-top: 1px solid var(--line); margin: 5px 2px; }
/* Phone guard: the panel must never be wider than the screen itself
   (clampMenu() in admin.html shifts it back on-screen after open) */
@media (max-width: 480px) {
  .menu-pop { max-width: calc(100vw - 24px); }
}
/* Sell-page topbar Options menu: the topbar spacer pushes the button to the
   RIGHT edge, so the pop keeps the default right:0 anchor (opens inward) and
   gets finger-size rows for one-thumb use at the counter. */
.menu-pop.mob { min-width: 232px; max-width: calc(100vw - 24px); }
.menu-pop.mob button { padding: 12px 14px; font-size: 15.5px; font-weight: 600; }

/* Accounting math-box drill-downs: tappable rows expand an indented detail
   list (pros by salesman, fee lines with their required note, open balances) */
.mp-click { cursor: pointer; }
.mp-click:hover { background: var(--primary-light); border-radius: 6px; }
.mp-caret { color: var(--muted); font-size: 11px; display: inline-block; width: 12px; }
.mp-sub {
  display: flex; justify-content: space-between; align-items: baseline; gap: 12px;
  padding: 3px 0 3px 18px; font-size: 13px; color: var(--ink);
}
.mp-sub .money { white-space: nowrap; font-variant-numeric: tabular-nums; }

/* Return picker: one tappable row per price tier, the way the ticket rang up */
.ret-tier {
  display: flex; justify-content: space-between; align-items: center; gap: 10px;
  border: 1px solid var(--line); border-radius: 8px; padding: 9px 12px;
  margin-top: 6px; font-size: 14px; cursor: pointer;
}
.ret-tier:hover { background: var(--primary-light); border-color: var(--primary); }
.ret-tier.done { opacity: .55; cursor: default; }
.ret-tier.done:hover { background: none; border-color: var(--line); }

/* Per-row action-button cluster (products, batches, staff rows): real gaps
   instead of literal-space separators, wraps cleanly on narrow screens */
.row-actions { display: inline-flex; flex-wrap: wrap; gap: 6px; justify-content: flex-end; }

/* Transfer status chips: light purple until confirmed, solid purple once received */
.status-chip {
  display: inline-block; padding: 4px 12px; border-radius: 8px;
  font-size: 12.5px; font-weight: 700;
  background: var(--primary-light); border: 1.5px solid var(--primary); color: var(--primary-dark);
}
.status-chip.filled { background: var(--primary); color: #fff; }

/* Quantity chip: light purple, matching the Customer Total style (owner's pick) */
.qty-chip {
  /* was a purple pill — quantities read as plain bold text now, matching how
     numbers are listed everywhere else in the app */
  display: inline-block; background: none; color: var(--ink);
  border: none; border-radius: 0;
  padding: 0; font-weight: 700; font-size: 14.5px;
}
/* Sale modal: the in-stock count is the number the seller checks before every
   sale — bigger there than the listing-size text used elsewhere */
#sale-modal .qty-chip { font-size: 17px; }
.qty-text { color: var(--ink); font-weight: 600; font-size: 14px; }

.stat-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 10px; }
.stat {
  background: var(--card); border: 1px solid var(--line); border-radius: var(--radius);
  padding: 14px 16px; box-shadow: var(--shadow);
  /* A tile may shrink below its content: without this, one long money value
     widens its grid track and pushes the whole strip past the card edge.
     fitStatStrip (shared.js) then shrinks the text to fit INSIDE the tile. */
  min-width: 0;
}
/* Phones: every stat strip is exactly 2-up — overrides the dashboard's inline
   4-col so tiles keep readable width; minmax(0,1fr) so tracks never grow past
   the card no matter how long the number gets. */
@media (max-width: 700px) {
  .stat-grid { grid-template-columns: repeat(2, minmax(0, 1fr)) !important; }
}
.stat .lbl { font-size: 12px; font-weight: 700; text-transform: uppercase; letter-spacing: .4px; color: var(--muted); }
.stat .val { font-size: 24px; font-weight: 800; margin-top: 2px; font-variant-numeric: tabular-nums; }
.stat .val.green { color: var(--green); }
.stat .val.red { color: var(--red); }
/* KPI tiles on the Accounting page reuse .stat: a third note line, plus an
   amber "needs action" variant for pending confirmations */
.stat .note { margin-top: 3px; }
.stat.warn { background: #fffbeb; border-color: #fbbf24; }
.stat.warn .val { color: #b45309; }
/* Window tags (U14): every money tile names its clock. Period-following tiles
   carry the live window in the accent color; standing tiles a quiet gray. The
   tag sits top-right so the label row stays clean. */
/* UC-007 (owner, 2026-07-26): the chip KEEPS its pill shape — it just moves
   from the right float to centered at the tile top, with a faint rule under
   it and the tile data below. Tiles without a chip render no rule. */
/* Sales Ledger window chip — the .win pill, inline and left-aligned (the tile
   version centres itself inside a .stat, so it cannot be reused as-is). No
   no-print class: a printed ledger must state the range it covers. */
.lg-win {
  display: inline-block; font-size: 9px; font-weight: 700; letter-spacing: .06em;
  text-transform: uppercase; color: var(--muted); border: 1px solid var(--line);
  border-radius: 99px; padding: 3px 9px; line-height: 1; background: var(--bg);
  white-space: nowrap; vertical-align: middle;
}
.lg-win:empty { display: none; }
.stat .win {
  display: block; width: fit-content; margin: 0 auto 8px;
  font-size: 9px; font-weight: 700; letter-spacing: .06em; text-transform: uppercase;
  color: var(--muted); border: 1px solid var(--line); border-radius: 99px;
  padding: 3px 9px; line-height: 1; background: var(--bg); white-space: nowrap;
  /* A long company name (or a combined "A / B" scope) used to run straight
     out of the tile — nowrap with no ceiling could only overflow. It now
     stops at the tile's edge and wraps, centred, rather than escaping.
     Owner-reported 2026-07-30 with ZONA / LMP. */
  max-width: 100%; box-sizing: border-box; text-align: center;
}
@supports (overflow-wrap: anywhere) { .stat .win { overflow-wrap: anywhere; } }
.stat .win.wrap { white-space: normal; line-height: 1.35; border-radius: 10px; }
.stat .win + .lbl { border-top: 1px solid var(--line); padding-top: 8px; }
.stat .win.live { color: var(--primary-dark); border-color: var(--primary-press); background: var(--primary-light); }
/* Changing the period softly washes the tiles that follow it — the chips that
   move answer visibly; the standing ones sit still. */
@keyframes chip-pulse {
  0% { background: var(--primary-light); }
  100% { background: var(--card); }
}
.stat.pulse { animation: chip-pulse .7s ease-out; }
@media (prefers-reduced-motion: reduce) { .stat.pulse { animation: none; } }
/* Money-action buttons on phones (U9): Accept/Reject and Record Payment get
   real thumb targets — everything else keeps the compact .small. */
.btn.small.touch { padding: 9px 14px; font-size: 14px; }
/* Queue count on the money tab button — the "waiting on this console" nudge
   visible from every tab (settlements + payments awaiting acceptance). */
.tab-count {
  margin-left: 6px; display: inline-block; vertical-align: 1px;
  background: var(--primary); color: #fff; border-radius: 99px;
  font-size: 11px; font-weight: 700; line-height: 1.5; padding: 0 7px;
}
/* Avg Sale Price tile: three compact class lines instead of one big .val, so it
   sits as a normal stat tile in the flow grid. Smaller font on phones keeps each
   "(A) $000.00 (00 units)" line on one row inside the 2-up cell. */
.stat.avg-chip .avg-lines { margin-top: 4px; font-size: 13px; line-height: 1.7; }
.stat.avg-chip .avg-lines > div { padding: 1px 0; white-space: nowrap; }
.stat.avg-chip .avg-lines .avg-units { font-size: .85em; }
@media (max-width: 700px) { .stat.avg-chip .avg-lines { font-size: 11px; line-height: 1.65; } }
/* Tiny class badge: the (A)/(B)/(C) marker as a small pill in the A/B/C colors
   (A green, B blue, C orange) — pale fill + class-colored border, ink text like
   .nm-chip. Only the letter is a badge, not the whole line. */
.cls-badge {
  display: inline-block; min-width: 16px; text-align: center;
  background: #f0fdf4; border: 1px solid #86efac; border-radius: 5px;
  padding: 0 4px; font-weight: 800; font-size: 10.5px; line-height: 15px;
  color: var(--ink); vertical-align: baseline;
}
.cls-badge.cls-b { background: #eff6ff; border-color: #93c5fd; }
.cls-badge.cls-c { background: #fff7ed; border-color: #fdba74; }

/* Role gating: applyRoleUi stamps body.partner for a partner admin; each
   side's cards/tabs simply never exist for the other */
body.partner .owner-only { display: none !important; }
/* A partner runs one facility, so the Batches "Total" column just repeats the
   Qty — hide it for them (owner keeps it for its multi-facility breakdown). */
body.partner .col-total { display: none; }
body:not(.partner) .partner-only { display: none !important; }

/* Segmented picker (Your product / LMP product / All) */
.seg { display: inline-flex; background: var(--bg); border: 1px solid var(--line); border-radius: 9px; padding: 3px; gap: 2px; }
.seg button {
  border: 0; background: transparent; color: var(--muted); font: inherit; font-weight: 600;
  font-size: 13px; padding: 6px 12px; border-radius: 7px; cursor: pointer; white-space: nowrap;
}
.seg button.on { background: #fff; color: var(--ink); box-shadow: 0 1px 3px rgba(15,23,42,.15); }

/* Collapsible card sections (Sales Ledger, Payment History, Activity Log) */
.collapse-h {
  width: 100%; display: flex; align-items: center; gap: 8px; background: none;
  border: 0; padding: 0; font: inherit; color: var(--ink); cursor: pointer; text-align: left;
}
.collapse-h .caret { margin-left: auto; color: var(--muted); transition: transform .15s ease; }
.collapse-card.collapsed .caret { transform: rotate(-90deg); }
.collapse-card.collapsed .collapse-body { display: none; }

/* Slim in-card banner (partner: "what you owe lives on Payments") */
.note-banner {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  background: var(--primary-light); border: 1px solid var(--primary);
  border-radius: 10px; padding: 10px 14px; font-size: 14px;
}

/* Phones: filter rows (Activity Log, Ledger) must wrap, never clip — date
   inputs give up their fixed minimum and share the row */
@media (max-width: 520px) {
  .row > .field { min-width: 105px; }
  .row .field input[type="date"] { min-width: 0; max-width: 100%; }
}

/* Tabs */
.tabs { display: flex; gap: 6px; flex-wrap: wrap; margin-bottom: 16px; }
.tabs button {
  border: 1px solid var(--line); background: #fff; color: var(--muted);
  padding: 8px 14px; border-radius: 8px; font-size: 14px; font-weight: 600; cursor: pointer;
}
.tabs button.active { background: var(--primary); border-color: var(--primary); color: #fff; }

/* Modal */
.modal-back {
  position: fixed; inset: 0; background: rgba(16, 24, 40, .55);
  display: none; align-items: flex-end; justify-content: center; z-index: 100;
  padding: 0;
}
.modal-back.open { display: flex; }
.modal {
  background: #fff; width: 100%; max-width: 480px;
  border-radius: 18px 18px 0 0;
  /* extra bottom padding keeps buttons clear of the iPhone address bar / home indicator */
  padding: 20px 18px calc(30px + env(safe-area-inset-bottom, 0px));
  max-height: 96vh; max-height: 96dvh; overflow-y: auto;
}
@media (min-width: 640px) {
  .modal-back { align-items: center; padding: 20px; }
  .modal { border-radius: 18px; }
}
.modal h2 { margin: 0 0 4px; font-size: 19px; }
/* The page behind ANY open popup stays put — every popup in the app is a
   .modal-back (static or dynamically created), so one selector covers all. */
html:has(.modal-back.open), body:has(.modal-back.open) { overflow: hidden; }

/* Partner Payments page: math box and pros list share the row on desktop so
   the card fills instead of one narrow column floating in white space. */
.pay-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 22px; align-items: start; }
@media (max-width: 700px) { .pay-grid { grid-template-columns: 1fr; } }

/* Phones: date inputs have a wide intrinsic minimum on iOS — clamp them to
   their field so the ledger / activity-log From+To pickers never overlap each
   other or spill past the card edge. */
@media (max-width: 700px) {
  .row .field { min-width: 0; }
  .lg-filters .field, .log-filters .field { flex: 1 1 46%; min-width: 0; }
  /* iOS Safari: native date inputs carry an intrinsic minimum width that
     ignores flex sizing entirely — the From/To pickers overlapped each other
     and spilled past the card. Stripping the native appearance (mobile only)
     makes them obey their field; our .field input styling supplies the look. */
  .field input[type="date"] {
    width: 100%; max-width: 100%; min-width: 0;
    -webkit-appearance: none; appearance: none;
    display: block; height: 46px;
  }
  .field input[type="date"]::-webkit-date-and-time-value { text-align: left; }
}
/* Return popup: the pick list can run long — Close rides the bottom of the
   visible area instead of hiding below a full page of scroll. */
#ret-close {
  position: sticky; bottom: 0;
  box-shadow: 0 -10px 14px -8px rgba(16, 24, 40, .25);
}
/* Action buttons stay visible even when tall popup content scrolls */
.modal .modal-actions {
  position: sticky; bottom: 0; background: #fff;
  padding-top: 10px; margin-bottom: -6px;
}
.modal .sub { color: var(--muted); font-size: 14px; margin-bottom: 14px; }

/* Quantity stepper */
.stepper { display: flex; align-items: stretch; gap: 6px; }
.stepper button {
  width: 46px; min-height: 42px; font-size: 20px; font-weight: 700;
  border: 1.5px solid var(--primary);
  border-radius: 6px;
  background: var(--primary);          /* solid dark purple, matching all buttons */
  color: #fff;
  cursor: pointer;
}
.stepper button:active { background: var(--primary-dark); }
.stepper input {
  flex: 1; text-align: center; font-size: 18px; font-weight: 700;
  border: 1px solid var(--line); border-radius: 6px; background: #fff; color: var(--ink);
  -moz-appearance: textfield; appearance: textfield;
}
/* the +/− buttons replace the browser's tiny number spinners */
.stepper input::-webkit-outer-spin-button,
.stepper input::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }
/* plain number boxes with no spinner arrows */
input.no-spin::-webkit-outer-spin-button,
input.no-spin::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }
input.no-spin { -moz-appearance: textfield; appearance: textfield; }
/* Compact variant for placing the stepper in a header line */
.stepper.inline { flex-shrink: 0; }
.stepper.inline button { width: 42px; }
.stepper.inline input { flex: 0 0 52px; width: 52px; }
/* Mini variant for table rows (e.g. transfer send-qty) */
.stepper.mini { gap: 4px; justify-content: flex-end; }
.stepper.mini button { width: 30px; min-height: 30px; font-size: 15px; border-radius: 5px; }
.stepper.mini button:disabled { opacity: .4; cursor: not-allowed; }
.stepper.mini input { flex: 0 0 54px; width: 54px; min-height: 30px; padding: 2px; font-size: 15px; }
/* Phones: the transfer batch list must not scroll sideways. The +/− buttons
   go (typing beats tap-tap-tap there anyway), the Batch ID and stock columns
   fold away — stock re-appears as "· N" beside the product chip (.t-have,
   desktop-hidden), long names ellipsize, paddings tighten. Desktop keeps the
   full table. Covers admin AND manager (same form). */
.t-have { display: none; }
@media (max-width: 700px) {
  .stepper.mini button { display: none; }
  .stepper.mini input { flex: 0 0 58px; width: 58px; min-height: 38px; font-size: 16px; }
  td input[data-talias] { max-width: 92px !important; }
  table:has(#t-lines) th:nth-child(1), #t-lines td:nth-child(1),
  table:has(#t-lines) th:nth-child(3), #t-lines td:nth-child(3) { display: none; }
  table:has(#t-lines) th, #t-lines td { padding-left: 5px; padding-right: 5px; }
  /* fixed layout: the table CANNOT outgrow the card — content ellipsizes */
  table:has(#t-lines) { table-layout: fixed; width: 100%; }
  table:has(#t-lines) th:nth-child(4), #t-lines td:nth-child(4) { width: 68px; }
  table:has(#t-lines) th:nth-child(5), #t-lines td:nth-child(5) { width: 102px; }
  table:has(#t-lines) th { white-space: normal; }
  table:has(#t-lines) th .muted { display: none; }  /* "(display name)" hint */
  #t-lines td { overflow: hidden; }
  #t-lines .nm-chip {
    max-width: 100%; overflow: hidden; text-overflow: ellipsis;
    white-space: nowrap; display: inline-block; vertical-align: middle;
  }
  .t-have { display: inline; }
}

/* Sale math summary */
.mathbox {
  background: #f8fafc; border: 1px solid var(--line); border-radius: 10px;
  padding: 12px 14px; margin: 14px 0; font-size: 15px;
}
/* Money rows everywhere (not just inside a mathbox — the Confirm Order popup
   lists lines bare, and text was running straight into the dollar figure).
   The gap guarantees daylight even when a long name pushes into the money. */
.mrow { display: flex; justify-content: space-between; gap: 16px; padding: 3px 0; }
/* A money row is 15px whether or not it sits inside a mathbox — section
   header rows (Pros & Fees days, the Payments card's sections) live outside
   one and were inheriting the page's 16px, reading a notch larger than every
   other row in the app (owner-caught 2026-07-30). Matches the 15px of
   .mathbox rows and of the card's own h3 headings. */
.mrow { font-size: 15px; }
.mrow.total { border-top: 1px solid var(--line); margin-top: 6px; padding-top: 8px; font-weight: 800; font-size: 17px; }
.mrow .money { white-space: nowrap; }
/* A box's own 12px edge IS the space under its closing row — the row must not
   stack its padding on top of it (owner 2026-07-30, the same rule pfPad
   applies to the first/last rows of a picker list). */
.mathbox > .mrow:last-child { padding-bottom: 0; }

/* Available Products (sell page): one tall, tappable row per product in a
   table layout — Product | Price | Quantity — colored by A/B/C classification */
.prod-head {
  display: grid; gap: 8px; padding: 0 12px 6px;
  font-size: 12px; font-weight: 700; text-transform: uppercase;
  letter-spacing: .4px; color: var(--muted);
}
.prod-row {
  display: grid; gap: 8px; align-items: center;
  width: 100%; text-align: left; font: inherit; cursor: pointer;
  /* Same scheme as the inside-sale cards: pale A/B/C tint, class-colored
     border, default-ink text (not class-colored), muted secondary info. */
  background: #f0fdf4; border: 1.5px solid #86efac; color: var(--ink);
  border-radius: 10px;
  padding: 12px; margin: 0;
}
.prod-row + .prod-row { margin-top: 8px; }
.prod-row.cls-b { background: #eff6ff; border-color: #93c5fd; }
.prod-row.cls-c { background: #fff7ed; border-color: #fdba74; }
.prod-row:active { transform: scale(.99); }
.prod-row .p-name { font-weight: 600; font-size: 15px; }
.prod-row .p-price { font-weight: 500; color: var(--muted); }
.prod-row .p-qty { font-weight: 700; }
.prod-head .num, .prod-row .num { text-align: right; }

/* Scanner overlay */
#scan-overlay {
  position: fixed; inset: 0; background: #000; z-index: 200; display: none;
  flex-direction: column;
}
#scan-overlay.open { display: flex; }
#scan-overlay .scan-top { display: flex; justify-content: space-between; align-items: center; padding: 14px 16px; color: #fff; }
#qr-reader { flex: 1; overflow: hidden; }
#qr-reader video { object-fit: cover; }

.muted { color: var(--muted); }
.small { font-size: 13px; }
.right { text-align: right; }
.mt { margin-top: 14px; }
.money { font-variant-numeric: tabular-nums; }
.pos { color: var(--green); font-weight: 700; }
.neg { color: var(--red); font-weight: 700; }

/* Toasts sit dead-center so they can't be missed (bottom felt buried).
   pointer-events:none keeps taps working right through them. */
.toast {
  position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%);
  background: #101828; color: #fff; padding: 14px 22px; border-radius: 12px;
  /* Above every modal/dialog layer (they sit at 400) — an error on a popup
     must land on top of the popup, never behind it. */
  font-size: 15px; font-weight: 600; z-index: 700; box-shadow: 0 8px 24px rgba(0,0,0,.25);
  opacity: 0; transition: opacity .2s; pointer-events: none; max-width: 90vw; text-align: center;
}
.toast.show { opacity: 1; }
.toast.err { background: var(--red); }

/* Custom product dropdown (Inside Sale modal): name left, stock right, code
   muted — a scannable list where a native select ran everything together. */
.dd { position: relative; }
.dd-btn {
  width: 100%; text-align: left; background: #fff; color: var(--ink);
  border: 1px solid var(--line); border-radius: 10px; padding: 10px 12px;
  font-size: 15px; cursor: pointer;
}
.dd-btn:after { content: "▾"; float: right; color: var(--muted); }
.dd-pop {
  display: none; position: absolute; top: calc(100% + 4px); left: 0; z-index: 60;
  min-width: 300px; max-height: 280px; overflow: auto;
  background: #fff; border: 1px solid var(--line); border-radius: 10px;
  box-shadow: var(--shadow);
}
.dd.open .dd-pop { display: block; }
.dd-pop button {
  display: flex; justify-content: space-between; align-items: center; gap: 16px;
  width: 100%; padding: 10px 12px; background: #fff; border: 0;
  border-bottom: 1px solid var(--line); text-align: left; font-size: 14px;
  color: var(--ink); cursor: pointer; white-space: nowrap;
}
.dd-pop button:last-child { border-bottom: 0; }
.dd-pop button:hover { background: var(--primary-light); }
.dd-meta { color: var(--muted); font-size: 12px; }

.empty { text-align: center; color: var(--muted); padding: 26px 10px; font-size: 14.5px; }
/* Three distinct looks for the same container (mega-review): a failed load is
   tinted red so it can't be mistaken for a real empty; a still-loading state
   pulses so motion signals "working" vs a static "nothing here". A two-line
   empty pairs a headline with a quiet next-step sub-line. */
.empty.err { color: var(--red); }
.empty .sub { display: block; margin-top: 5px; font-size: 13px; opacity: .8; }
.empty.loading { animation: emptyPulse 1.6s ease-in-out infinite; }
@keyframes emptyPulse { 0%, 100% { opacity: .5; } 50% { opacity: 1; } }
/* "Live" indicator for auto-refreshing feeds — its own small rule (not the
   period-tag token), a quiet purple pill with a soft breathing dot. */
.live-pill {
  display: inline-flex; align-items: center; gap: 5px;
  font-size: 11px; font-weight: 700; letter-spacing: .04em; text-transform: uppercase;
  color: var(--primary); background: var(--primary-light);
  padding: 2px 8px; border-radius: 999px; vertical-align: middle;
}
.live-pill::before {
  content: ""; width: 6px; height: 6px; border-radius: 50%;
  background: var(--primary); animation: livePulse 1.8s ease-in-out infinite;
}
@keyframes livePulse { 0%, 100% { opacity: .35; } 50% { opacity: 1; } }

/* Toggle switch */
.switch { position: relative; width: 48px; height: 28px; flex-shrink: 0; }
.switch input { opacity: 0; width: 0; height: 0; }
.switch .track {
  position: absolute; inset: 0; background: #cbd5e1; border-radius: 999px;
  transition: background .15s; cursor: pointer;
}
.switch .track::after {
  content: ""; position: absolute; top: 3px; left: 3px; width: 22px; height: 22px;
  background: #fff; border-radius: 50%; transition: left .15s;
  box-shadow: 0 1px 3px rgba(0,0,0,.25);
}
.switch input:checked + .track { background: var(--green); }
.switch input:checked + .track::after { left: 23px; }
.setting-row { display: flex; align-items: center; gap: 14px; padding: 12px 0; border-bottom: 1px solid var(--line); }
.setting-row:last-child { border-bottom: none; }
.setting-row .info { flex: 1; }
.setting-row .info .t { font-weight: 600; }
.setting-row .info .d { font-size: 13px; color: var(--muted); }

/* Mobile fallback for the desktop consoles (admin/manager): functional, not
   fancy. Scoped to small screens only — desktop rendering is untouched. */
@media (max-width: 700px) {
  /* tab bar becomes its own card: all tabs visible, no scrolling */
  body.desktop .tabs {
    display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px;
    background: var(--card); border: 1px solid var(--line);
    border-radius: var(--radius); box-shadow: var(--shadow); padding: 10px;
  }
  body.desktop .tabs button { padding: 9px 6px; font-size: 13px; }
  /* forms stack one field per line; fields use the full width */
  body.desktop .row { flex-wrap: wrap !important; }
  body.desktop .row > .field { flex: 1 1 100% !important; }
  /* ...except paired fields (ledger + log Salesperson/Product, From/To) which
     sit two-up at half width each; min-width:0 stops date inputs overrunning */
  body.desktop .row > .field.half { flex: 1 1 calc(50% - 5px) !important; min-width: 0 !important; }
  /* desktop-sized buttons are marginal thumb targets — give row buttons (Add
     facility/manager/salesman etc.) a proper 46px tap height on phones */
  body.desktop .row > .btn { min-height: 46px; padding-top: 12px; padding-bottom: 12px; }
  /* free the desktop max-width AND pin to the field — a select otherwise keeps
     its intrinsic width (longest option name) and overflows the viewport */
  body.desktop .field select, body.desktop .field input {
    max-width: none; width: 100%; box-sizing: border-box;
  }
  /* dashboard stats: two per row instead of five crushed columns */
  body.desktop .stat-grid { grid-template-columns: repeat(2, minmax(0, 1fr)) !important; }
  /* Create Bulk Inventory: quantity stepper + target price share a line */
  body.desktop .row > .field:has(> .stepper) { flex: 0 0 auto !important; }
  body.desktop .row > .field:has(> #b-new-price) { flex: 1 1 0 !important; min-width: 110px; }
  /* Transfer History: shrink the "Transfer" header to "#" so columns fit */
  body.desktop th.th-xfer { font-size: 0; }
  body.desktop th.th-xfer::before { content: "#"; font-size: 12px; }
  /* Batches card: "Batch ID" header shrinks to "ID"; action buttons drop to
     their own right-aligned line under each row */
  body.desktop th.th-bid { font-size: 0; }
  body.desktop th.th-bid::before { content: "ID"; font-size: 12px; }
  body.desktop table.batches thead th:last-child { display: none; }
  body.desktop table.batches tr {
    display: grid; grid-template-columns: 1.1fr 1.6fr .9fr 1fr .5fr;
    align-items: start;
  }
  /* partner has no Total column — drop its grid track and hide the cell (the
     mobile "th/td display:block" rule outspecifies the base .col-total hide) */
  body.desktop.partner table.batches tr { grid-template-columns: 1.1fr 1.6fr .9fr 1fr; }
  body.desktop.partner table.batches .col-total { display: none; }
  body.desktop table.batches th,
  body.desktop table.batches td { display: block; border-bottom: 0; }
  body.desktop table.batches thead tr { border-bottom: 2px solid var(--line); }
  body.desktop table.batches tbody tr { border-bottom: 1px solid var(--line); }
  body.desktop table.batches tbody tr:last-child { border-bottom: none; }
  body.desktop table.batches td:last-child {
    grid-column: 1 / -1; text-align: right; padding-top: 10px; padding-bottom: 12px;
  }
  /* Products: same stacked-grid rows so the Options menu drops to its own line */
  body.desktop table.products thead th:last-child { display: none; }
  body.desktop table.products tr {
    display: grid; grid-template-columns: 1.6fr 1fr .7fr .7fr; align-items: start;
  }
  body.desktop table.products th,
  body.desktop table.products td { display: block; border-bottom: 0; }
  body.desktop table.products thead tr { border-bottom: 2px solid var(--line); }
  body.desktop table.products tbody tr { border-bottom: 1px solid var(--line); }
  body.desktop table.products tbody tr:last-child { border-bottom: none; }
  body.desktop table.products td:last-child {
    grid-column: 1 / -1; text-align: right; padding-top: 10px; padding-bottom: 12px;
  }
  /* ---- .qsplit: the two admin lists that split Qty into Bulk + Facility ----
     Only the owner's Batches and Products cards carry that extra column, so
     every rule here is scoped to .qsplit — the manager console shares
     table.batches/.products but has no split and keeps the layout above.

     On a phone the Bulk column folds away entirely (a sixth cell would spill
     onto a phantom row and wreck the fixed grid) and the same figure prints as
     a labelled line stacked over the facility quantities — "Bulk: 46" above
     "COT: 17", the same order the desktop columns read. The body.desktop
     prefix is needed to outspecify the "th, td { display: block }" rules. */
  body.desktop table.qsplit th.bulk-col,
  body.desktop table.qsplit td.bulk-col { display: none; }
  body.desktop table.qsplit .bulk-inline { display: block; }
  body.desktop table.qsplit .fac-inline { display: inline; }
  /* Splitting Qty in two costs width these rows do not have — and unlike every
     other table, these two cannot pan sideways to compensate (their Options
     menu must stay unclipped), so anything that does not fit is simply lost off
     the edge. Date goes on a phone: the Old/New age chip beside the product
     already says which batch to move first, which is the only thing the date
     was being read for. That pays for the quantity lines. */
  body.desktop table.batches.qsplit th.col-date,
  body.desktop table.batches.qsplit td.col-date { display: none; }
  /* An fr track floors at min-content, so a long batch code or a no-wrap
     product chip still pushed the row past the card edge; minmax(0,…) lets the
     tracks shrink to fit instead. */
  body.desktop table.batches.qsplit tr {
    grid-template-columns: minmax(0,1.3fr) minmax(0,1.1fr) minmax(0,1.08fr) minmax(0,.68fr);
  }
  body.desktop.partner table.batches.qsplit tr {
    grid-template-columns: minmax(0,1fr) minmax(0,1.7fr) minmax(0,1.1fr);
  }
  body.desktop table.products.qsplit tr {
    grid-template-columns: minmax(0,1.35fr) minmax(0,.8fr) minmax(0,.5fr) minmax(0,1.25fr);
  }
  /* The uppercase tracking costs ~4px a header — enough, in tracks this narrow,
     to push "TOTAL" off the card. Dropping it buys the whole word back. */
  body.desktop table.qsplit th { letter-spacing: 0; white-space: normal; }
  body.desktop table.qsplit .nm-chip { white-space: normal; }
  /* The name chip and its Old/New chip ride side by side everywhere else, but
     together they set a min-width wider than a phone column — so here the age
     chip drops beneath the name instead of forcing the row past the card. */
  body.desktop table.qsplit .chip-pair { flex-wrap: wrap; white-space: normal; }
  body.desktop table.batches.qsplit td:first-child { overflow-wrap: anywhere; }
  /* "Bulk: 46" and "COT: 17" each belong on one line — the <br> between them
     still breaks, nowrap only stops a label parting from its number. */
  body.desktop table.qsplit td.qty-cell { white-space: nowrap; }
  /* "Batches" is one word too long for its track and would clip off the card,
     so it shortens the same way "Batch ID" becomes "ID" above. */
  body.desktop table.products.qsplit th.col-nbatch { font-size: 0; }
  body.desktop table.products.qsplit th.col-nbatch::before { content: "#"; font-size: 12px; }
  body.desktop .wrap { padding-left: 10px; padding-right: 10px; }

  /* Owner report M2: the Sales Breakdown date inputs are bare (not .field-
     wrapped), so they miss the stacking rules and overflow iPhone width.
     Pin them full-width, and neutralize the desktop "margin-top:18px" Apply
     alignment hacks that leave a stray gap once fields stack. */
  body.desktop input[type="date"] { max-width: none !important; width: 100%; box-sizing: border-box; }
  body.desktop .row > input[type="date"] { flex: 1 1 100% !important; }
  body.desktop #set-range-label { flex: 1 1 100% !important; }
  body.desktop .row > .btn[style*="margin-top"] { margin-top: 0 !important; }

  /* M3/M4: action-button clusters (facility staff rows, product/staff table
     rows, the card-heading Manage menu) drop to their own line and wrap
     instead of shoving content off-screen. */
  body.desktop .card > h2 > span[style*="float"] { float: none !important; display: block; margin-top: 8px; text-align: right; }
  /* inventory stock chip: shorten "Quantity:" to "qty:" on phones */
  body.desktop .q-long { display: none; }
  body.desktop .q-short { display: inline; }
  body.desktop td.right, body.desktop td[style*="right"] {
    white-space: normal !important;
  }
  /* Live-inventory ±1 steppers are desktop-only; phones type a number + Set */
  body.desktop .adj-steppers { display: none; }

  /* Facility Settings name row on phones: label on its own line, input + Save
     share the next line, Platform Admins drops below, then a little breathing
     room before the Managers section. */
  #tf-name-row .tf-name-label { flex: 1 1 100%; }
  #tf-name-row #set-company-name { flex: 1 1 auto; max-width: none !important; }
  #tf-name-row .tf-spacer { display: none; }
  #tf-name-row .tf-admins { flex: 1 1 100%; }

  /* Inside-sale modal on phones: product selector on its own line, then
     Qty / Price / line-total / delete on the next line (headers hidden). */
  .is-table thead { display: none; }
  .is-table, .is-table tbody { display: block; }
  .is-table tr {
    display: flex; flex-wrap: wrap; align-items: center; gap: 8px 10px;
    padding: 10px 0; border-bottom: 1px solid var(--line);
  }
  .is-table td { display: flex; align-items: center; gap: 6px; border: 0; padding: 0; }
  .is-table td.is-cell-prod { flex: 1 1 100%; }
  .is-table .is-batch { width: 100%; }
  .is-table .is-qty, .is-table .is-price { width: 74px; }
  .is-table .m-lbl { display: inline; font-size: 12px; color: var(--muted); }
  .is-table td.is-cell-line { margin-left: auto; font-weight: 600; }
}

@media print {
  .topbar, .tabs, .no-print { display: none !important; }
  body { background: #fff; }
  .card { box-shadow: none; border: none; }
}

/* In-page settlement-card math (2026-07-23): the page-scale variant of the
   dialog mathbox — same rows, compact type and padding, so inbox cards match
   the console instead of shouting at popup size. */
.mathbox.compact { padding: 8px 12px; margin: 8px 0 0; font-size: 13px; }
.mathbox.compact .mrow { padding: 2px 0; }
.mathbox.compact .mrow.total { font-size: 13.5px; padding-top: 6px; margin-top: 4px; }
