
:root {
  --leather: #A75628;
  --gold: #00C2D1;
  --gold-lt: #33D4E0;
  --accent: #00C2D1;
  --accent-lt: #33D4E0;
  --accent-rgb: 0,194,209;
  --cream: #FFFDF7;
  --parchment: #FDF3E3;
  --dark: #0A1628;
  --mid: #6B3A1F;
  --muted: #8B6E52;
  --border: #3D1F0A;
  /* LEDGER INK (item 30a). --rule and --rule-soft are --border at reduced alpha: a ledger's
     rules are the SAME ink as every other border on the instance, only quieter — a full-strength
     border around every row is the card stack this primitive replaces. --border is
     #0A1628-adjacent and is NOT substituted, so the literal triple below is the one
     already sitting in --border on the line above, written out because CSS cannot take alpha off
     a hex token. --hover / --accent-line / --rule-dark follow the brand accent.
     --on-dark-label / --on-dark-sub are the two cream alphas the app has used ON the dark ground
     since .metric-card.accent was written; they are lifted into tokens because the LEDGER region
     is not allowed a literal, and a missing token is how hardcoding starts. */
  --rule: rgba(61,31,10,.14);
  --rule-soft: rgba(61,31,10,.08);
  --hover: rgba(var(--accent-rgb),.09);
  --rule-dark: rgba(var(--accent-rgb),.18);
  --accent-line: rgba(var(--accent-rgb),.9);
  --on-dark-label: rgba(253,250,244,.5);
  --on-dark-sub: rgba(253,250,244,.42);
  --white: #FFFFFF;
  --green: #96D236;
  --green-dark: #6FA828;
  --green-lt: #EEF7D6;
  --red: #A32D2D;
  --red-lt: #FCEBEB;
  /* Warning pair. Added 7/28 because four call sites had each invented their own amber literal
     rather than share one. A missing token is how hardcoding starts. */
  --warn: #B45309;
  --warn-lt: #FFF4E5;
  --amber-lt: #FAEEDA;
  /* Neutral drop shadow for things that float above the page (chart tooltip, popovers). A shadow
     is not a brand decision -- it is the same black on every instance -- but it still needs a token,
     because the marked-region rule bans rgba() literals and a missing token is how hardcoding
     starts. */
  --shadow-pop: rgba(0,0,0,0.13);
  --blue: #185FA5;
  --blue-lt: #E6F1FB;
  /* Mobile-hardening z-index scale (F3): relative order is the contract, values are stops.
     nav < fab < checklist < spot < full-screen overlay < modal < lock — so the checklist bubble
     no longer pokes through a modal/detail view and the lock screen stays king of all. */
  --z-nav: 100;
  --z-fab: 200;
  --z-checklist: 300;
  --z-spot: 400;
  --z-overlay: 1000;
  --z-modal: 2000;
  --z-lock: 9999;
  /* HOUSE RULES (item 30b). TWO RADII and ONE TYPE SCALE, because eight radii and fourteen sizes
     is not a design, it is fourteen decisions nobody made twice. A control is 6px, a container is
     10px, and the 20px pill survives on .badge alone -- a status chip is a capsule by convention,
     a button is not (D-3). The steps are the sizes the client already leaned on, with the
     half-steps (10.5 / 11.5 / 13.5) and the 9px labels folded into the nearest one. --fs-6 is the
     figure size: Playfair, and the only size on the page that is allowed to be loud. */
  --r-control: 6px;
  --r-panel: 10px;
  --fs-1: 11px;
  --fs-2: 12px;
  --fs-3: 13px;
  --fs-4: 15px;
  --fs-5: 18px;
  --fs-6: 26px;
}
*{box-sizing:border-box;margin:0;padding:0;-webkit-tap-highlight-color:transparent}
html{scroll-behavior:smooth}
body{font-family:'DM Sans',sans-serif;background:var(--cream);color:var(--dark);min-height:100vh;line-height:1.5}
/* F1: single scroll-lock. position:fixed + a saved scrollY (restored in JS) is the only technique
   iOS Safari fully respects — plain overflow:hidden on <body> is ignored during touch scroll. */
body.scroll-locked{position:fixed;left:0;right:0;width:100%;overflow:hidden}

.app-header{background:var(--dark);padding:16px 20px;display:flex;align-items:center;justify-content:space-between;position:sticky;top:0;z-index:var(--z-nav);border-bottom:1px solid rgba(var(--accent-rgb),0.3)}
.app-logo{font-family:'Playfair Display',serif;font-size:var(--fs-5);color:var(--cream);letter-spacing:0.3px}
.app-logo span{color:var(--gold)}
.app-date{font-size:var(--fs-1);color:rgba(253,250,244,0.45);letter-spacing:0.5px}
.save-pill{font-size:var(--fs-1);padding:3px 10px;background:rgba(var(--accent-rgb),0.15);border:1px solid rgba(var(--accent-rgb),0.3);color:var(--gold);border-radius:var(--r-control);letter-spacing:0.3px}
/* SETTINGS_CSS_START -- the header control for item 45. Sized and coloured off the same tokens as
   the save pill beside it, so the header reads as one row rather than a button bolted onto it. */
.hdr-set{font-size:var(--fs-1);padding:3px 10px;background:transparent;border:1px solid rgba(var(--accent-rgb),0.3);color:var(--gold);border-radius:var(--r-control);letter-spacing:0.3px;cursor:pointer;font-family:inherit;margin-left:auto}
.hdr-set:hover{background:rgba(var(--accent-rgb),0.15)}
/* SETTINGS_CSS_END */

.bottom-nav{background:var(--dark);border-top:1px solid rgba(var(--accent-rgb),0.2);display:flex;justify-content:stretch;position:fixed;left:0;right:0;bottom:0;z-index:var(--z-nav);overscroll-behavior:contain;padding-bottom:env(safe-area-inset-bottom,0px)}
.bottom-nav::-webkit-scrollbar{display:none}
.nav-item{flex:1 1 0;min-width:0;display:flex;flex-direction:column;align-items:center;justify-content:center;padding:10px 6px 12px;cursor:pointer;color:rgba(253,250,244,0.4);font-size:var(--fs-1);letter-spacing:.04em;gap:4px;transition:color 0.2s;border:none;background:none}
.nav-item.active{color:var(--gold)}
.nav-icon{width:20px;height:20px}

.content{padding:20px 16px 24px;max-width:640px;margin:0 auto}
/* ITEM 30c (A-1): the bottom nav (81px) plus the chat button's own 64px, so the last row on every screen clears the
   button while it is showing. */
@media(max-width:899px){.content{padding-bottom:calc(81px + 64px + env(safe-area-inset-bottom,0px))}}
.section{display:none}
.section.active{display:block}

.metrics-grid{margin-bottom:24px}
.metric-card{background:var(--white);border:1px solid var(--border);border-radius:var(--r-panel);padding:14px 16px}
.metric-card.accent{background:var(--dark);border-color:rgba(var(--accent-rgb),0.3)}
.metric-label{font-size:var(--fs-1);color:var(--muted);letter-spacing:0.5px;text-transform:uppercase;margin-bottom:4px}
.metric-card.accent .metric-label{color:rgba(253,250,244,0.5)}
.metric-value{font-family:'Playfair Display',serif;font-size:var(--fs-6);color:var(--dark);font-weight:700;line-height:1}
.metric-card.accent .metric-value{color:var(--gold)}
.metric-sub{font-size:var(--fs-1);color:var(--muted);margin-top:4px}
.metric-card.accent .metric-sub{color:rgba(253,250,244,0.4)}

.section-title{font-family:'Playfair Display',serif;font-size:var(--fs-5);color:var(--dark);margin-bottom:16px;font-weight:600}
.section-subtitle{font-size:var(--fs-2);color:var(--muted);margin-top:-12px;margin-bottom:16px}

.card{background:var(--white);border:1px solid var(--border);border-radius:var(--r-panel);padding:16px;margin-bottom:12px}
.card-header{display:flex;align-items:flex-start;justify-content:space-between;margin-bottom:12px;gap:8px}
.card-title{font-size:var(--fs-4);font-weight:500;color:var(--dark)}
.card-sub{font-size:var(--fs-2);color:var(--muted);margin-top:2px}
.card-row{display:flex;justify-content:space-between;align-items:flex-start;padding:7px 0;border-bottom:1px solid var(--rule-soft);font-size:var(--fs-2);gap:8px}
.card-row:last-child{border-bottom:none}
.card-row-label{color:var(--muted);flex-shrink:0;min-width:90px}
.card-row-value{font-weight:500;color:var(--dark);text-align:right}
.card-row-value.green{color:var(--green)}
.card-row-value.amber{color:#854F0B}
.card-row-value.red{color:var(--red)}
.card-row-value.blue{color:var(--blue)}

/* LEDGER_START */
/* ONE ruled list for every dated list in the client (item 30a).

   WHAT IT REPLACES. Mileage, Receipts and Bills each rendered a stack of .card boxes: one
   bordered, rounded rectangle per row, --border ink all the way round every one, ~70px of height,
   and a green figure and a destructive link repeated on each. Tabular data dressed as a card
   stack is the tell, not the data. A row here has a top rule, a hover fill, and nothing else.

   HOW A CALLER USES IT. Put .ledger on the container and it owns the grid; override the three
   width custom properties and nothing else. Receipts do exactly that via .ledger.rcpt below, and
   the fourth list that wants this primitive overrides the same three. NEVER restate
   grid-template-columns in a caller — the whole point is that every ledger in the app is one
   column model with per-list widths.

   COLOUR. The deduction column is --dark like the rest of the row. Green on every row means
   nothing; the one accent on the screen is the gold figure in the summary band. */
.ledger{--c-date:52px;--c-mi:72px;--c-usd:74px;--c-act:26px}
/* Receipts: the miles column becomes an item count, the money column is wider, and the action
   column holds an expand caret rather than a 26px button. Three properties, no new grid. */
.ledger.rcpt{--c-mi:56px;--c-usd:78px;--c-act:22px}
.ledger-head,.lg-row,.lg-foot{display:grid;grid-template-columns:var(--c-date) 1fr var(--c-mi) var(--c-usd) var(--c-act);gap:12px;align-items:center}
.ledger-head{padding:0 6px 7px;font-size:var(--fs-1);letter-spacing:.14em;text-transform:uppercase;color:var(--muted);border-bottom:1px solid var(--rule)}
.ledger-head .n{text-align:right}
/* The column labels are a desktop affordance. On a phone the columns are self-evident and the
   labels cost a row of height the list needs more. */
@media(max-width:480px){.ledger-head{display:none}}
.lg-group{margin-bottom:16px}
.lg-head{position:sticky;top:0;z-index:1;background:var(--cream);display:flex;align-items:baseline;justify-content:space-between;gap:10px;padding:10px 6px 7px}
.lg-head.tap{cursor:pointer}
.lg-mcar{color:var(--muted);margin-left:4px}
.lg-head.tap{cursor:pointer}
.lg-head h3{font-family:'Playfair Display',serif;font-size:var(--fs-4);font-weight:600;color:var(--dark);margin:0}
/* Subtotals live in the head AND the foot: the head for the eye scanning down the months, the
   foot for the eye that has just read a column to the bottom. Both are computed from the rows
   between them on every paint, and neither is ever stored. */
.lg-tot{font-size:var(--fs-2);color:var(--muted);text-align:right;line-height:1.45}
.lg-tot b{color:var(--dark);font-weight:700;font-variant-numeric:tabular-nums}
/* The bills month figure keeps billFeeCell's own red/green ink — a fee is a cost and that is an
   existing signal, not the per-row green this item removed. */
.lg-bt{font-size:var(--fs-4);font-weight:700;white-space:nowrap;margin-top:2px}
.lg-foot{padding:8px 6px;border-top:1px solid var(--rule);font-size:var(--fs-2);color:var(--muted)}
.lg-foot b{color:var(--dark);font-weight:700}
.lg-row{padding:9px 6px;min-height:40px;border-top:1px solid var(--rule-soft);border-radius:var(--r-control)}
.lg-row.tap{cursor:pointer}
.lg-row:hover{background:var(--hover)}
/* The month is in the group head, so the row carries the day and the weekday only: a list of
   trips is read as "the 27th", never as "2026-07-27". */
.lg-date .dd{font-size:var(--fs-4);font-weight:600;color:var(--dark);line-height:1.15}
/* An explicit line-height, because the date cell is the TALLEST cell in a row whose description
   fits on one line -- it, and not the text, is what sets the row height on a phone. */
.lg-date .wd{font-size:var(--fs-1);line-height:1.2;text-transform:uppercase;letter-spacing:.05em;color:var(--muted)}
.lg-what{min-width:0}
.lg-what .p{font-size:var(--fs-3);line-height:1.4;font-weight:500;color:var(--dark);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.lg-what .m{font-size:var(--fs-2);line-height:1.4;color:var(--muted);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;margin-top:2px}
.lg-dot{display:inline-block;width:7px;height:7px;border-radius:50%;margin-right:6px;vertical-align:middle}
.lg-dot.ok{background:var(--green-dark)}
.lg-dot.est{background:var(--gold)}
/* vertical-align:middle and a line-height that fits INSIDE the meta line: an inline-block chip
   sitting on the baseline pushes its bottom padding below it, and a row with a note beside the
   chip measured 8px taller than a row without one. Rows in a ledger are the same height. */
.lg-chip{display:inline-block;vertical-align:middle;line-height:1.35;font-size:var(--fs-1);text-transform:uppercase;letter-spacing:.05em;padding:1px 6px;border-radius:var(--r-control);background:var(--parchment);color:var(--mid)}
/* A manual entry is the absence of a detection, so its chip is the absence of a fill. */
.lg-chip.manual{background:transparent;border:1px solid var(--rule)}
/* The chip is a desktop affordance; on a phone the destination is worth the whole line. A meta
   line left with nothing in it takes no height and no margin -- an empty row of nothing is how a
   ledger stops being one. */
.lg-what .m:empty{display:none}
@media(max-width:480px){.lg-what .m .lg-chip{display:none}}
.lg-num{text-align:right;font-variant-numeric:tabular-nums;font-size:var(--fs-3);color:var(--dark)}
.lg-num.sm{font-size:var(--fs-2);color:var(--muted)}
.lg-num.big{font-family:'Playfair Display',serif;font-size:var(--fs-4);font-weight:700}
/* D-5: twelve visible Remove links become zero. The action is revealed by hover or by keyboard
   focus anywhere in the row, and is permanently dim on a touch device, where there is no hover
   and an action nobody can find is a bug rather than restraint. */
.lg-act{width:26px;height:26px;padding:0;border:none;background:none;color:var(--muted);font-family:inherit;font-size:var(--fs-3);line-height:1;border-radius:var(--r-control);cursor:pointer;opacity:0;transition:opacity .12s}
.lg-row:hover .lg-act,.lg-row:focus-within .lg-act{opacity:1}
.lg-act:hover{background:var(--red-lt);color:var(--red)}
.lg-act:focus-visible{opacity:1;outline:2px solid var(--gold)}
@media(hover:none){.lg-act{opacity:.55}}
.lg-caret{text-align:center;font-size:var(--fs-1);color:var(--muted)}
/* Build-for-the-layman: a trip with no miles offers the verb, not a format to type. */
.lg-add{font-size:var(--fs-2);color:var(--mid);cursor:pointer;text-decoration:underline;text-decoration-color:var(--accent-line);text-underline-offset:3px}
.lg-mi{width:100%;min-width:0;font-family:inherit;font-size:var(--fs-3);text-align:right;color:var(--dark);background:var(--white);border:1px solid var(--rule);border-radius:var(--r-control);padding:3px 5px}
/* The expanded receipt table is a SIBLING of its row, not a child of the row grid: the row
   carries the toggle, and a click inside the table would bubble to it and shut the receipt under
   the operator’s finger. It is full width by virtue of being a block, so it needs no
   grid-column:1/-1 and does not declare one. */
.lg-row-expand{padding:0 2px 10px}
/* The summary band. Four figures on one dark ground, divided by rules rather than boxed four
   times over — the same replacement the rows got, applied to the tiles above them. */
.lg-summary{display:grid;grid-template-columns:repeat(4,1fr);background:var(--dark);border-radius:var(--r-panel);overflow:hidden}
.lg-cell{padding:12px 13px;border-left:1px solid var(--rule-dark);min-width:0}
.lg-cell:first-child{border-left:none}
.lg-cell.tap{cursor:pointer}
.lg-cell .l{font-size:var(--fs-1);text-transform:uppercase;letter-spacing:.1em;color:var(--on-dark-label)}
.lg-cell .v{font-family:'Playfair Display',serif;font-size:var(--fs-6);font-weight:700;line-height:1.15;color:var(--cream);margin-top:3px}
.lg-cell .v.gold{color:var(--gold)}
.lg-cell .v.dim{color:var(--on-dark-label)}
.lg-cell .s{font-size:var(--fs-1);color:var(--on-dark-sub);margin-top:3px;line-height:1.35}
@media(max-width:520px){.lg-summary{grid-template-columns:repeat(2,1fr)}.lg-cell:nth-child(3){border-left:none}.lg-cell:nth-child(-n+2){border-bottom:1px solid var(--rule-dark)}}
/* PHONE WIDTHS. 375px less the page gutters and four gaps leaves about 300px for five columns,
   and at the desktop widths the description gets 75px of it -- every destination ellipsises to
   "Lakeside ...", which is the one column he is actually reading. Same grid, same classes: three
   custom properties, which is exactly what D-7 says a per-list difference is allowed to cost. */
@media(max-width:480px){
.ledger{--c-date:34px;--c-mi:52px;--c-usd:62px;--c-act:36px}
.ledger.rcpt{--c-mi:30px;--c-usd:66px;--c-act:18px}
.lg-row,.lg-foot,.ledger-head{gap:8px;padding-left:2px;padding-right:2px}
.lg-head{padding-left:2px;padding-right:2px}
/* The row remove is a real touch target on a phone (item 30b, H-11): 36px, the height .btn declares. */
.lg-act{width:36px;height:36px}
}
/* LEDGER_END */

/* BTN_START */
/* THREE BUTTONS, AND THEY ARE THE WHOLE VOCABULARY (item 30b, A-5).
   Ten button treatments is why no two screens in this app shared one: every render function
   invented its own padding, radius and fill, so the same verb looked like a different verb on
   every tab. Primary is the dark/gold pair the header already used, secondary is an outlined
   white, quiet has no box at all, danger is the red pair the remove links used. min-height is
   36px on all of them because the layout harness measured 28px taps on four screens.
   A <button> inside a marked region carries one of these and nothing inline. */
.btn{display:inline-flex;align-items:center;justify-content:center;gap:6px;font-family:inherit;font-size:var(--fs-3);font-weight:500;line-height:1.2;padding:8px 14px;min-height:36px;border:1px solid transparent;border-radius:var(--r-control);background:none;color:var(--dark);cursor:pointer;white-space:nowrap;transition:background .12s,border-color .12s,opacity .12s}
.btn:disabled{opacity:.45;cursor:default}
.btn:focus-visible{outline:2px solid var(--gold);outline-offset:1px}
.btn-primary{background:var(--dark);color:var(--gold)}
.btn-secondary{border-color:var(--rule);background:var(--white);color:var(--dark)}
.btn-secondary:hover{border-color:var(--gold)}
.btn-quiet{background:none;color:var(--mid)}
.btn-quiet:hover{color:var(--dark);background:var(--hover)}
.btn-danger{background:var(--red-lt);color:var(--red)}
/* BTN_END */

/* LEDGER2_START */
/* THE REST OF THE LISTS (item 30b, Part B). 30a put Mileage, Receipts and the Bills month heads
   on .ledger and left nine lists behind, because its inventory grepped for class="card" inside
   render loops and the Sales days, the bills' children, the machine roster, the warehouse rows
   and the lead-finder results build their rows out of inline-styled <div>s that never say card.
   This block is what those nine cost: three custom properties each, a sub-row, and the classes
   the rows need so no render function has to reach for an inline style. NOTHING HERE RESTATES
   grid-template-columns -- that is declared once, in LEDGER above, and a per-list difference is
   allowed to cost three custom properties and nothing else (30a, D-7). */

/* Per-list column widths. A zero column is a real decision: leads have no money column and the
   roster has no date, and an empty track is how the same five-column grid says so. */
.ledger.sales{--c-date:52px;--c-mi:0;--c-usd:92px;--c-act:22px}
.ledger.bills{--c-date:62px;--c-mi:0;--c-usd:88px;--c-act:22px}
.ledger.leads{--c-date:44px;--c-mi:88px;--c-usd:0;--c-act:126px}
.ledger.roster{--c-date:0;--c-mi:0;--c-usd:104px;--c-act:22px}
.ledger.fry{--c-date:0;--c-mi:76px;--c-usd:64px;--c-act:82px}
.ledger.wh{--c-date:0;--c-mi:0;--c-usd:58px;--c-act:96px}
.ledger.lf{--c-date:0;--c-mi:52px;--c-usd:0;--c-act:118px}
.ledger.pending{--c-date:52px;--c-mi:62px;--c-usd:0;--c-act:154px}
.ledger.quicklog{--c-date:0;--c-mi:0;--c-usd:0;--c-act:116px}
@media(max-width:480px){
.ledger.sales{--c-date:34px;--c-usd:78px}
.ledger.bills{--c-date:44px;--c-usd:76px}
.ledger.leads{--c-date:38px;--c-mi:64px;--c-act:112px}
.ledger.roster{--c-usd:82px}
.ledger.fry{--c-mi:58px;--c-usd:50px;--c-act:66px}
.ledger.pending{--c-date:34px;--c-mi:0}
}

/* The head line a list carries when its head is a SENTENCE rather than a row of column labels:
   "171 days, 409 of 409 machine-days" on the left, the filtered total on the right. It is the
   strip that used to sit above the Sales and Bills lists as a parchment-filled box. The column
   labels above hide at 480px because the columns are self-evident; this line is a figure the
   operator reads, so it stays. */
.ledger-head.tot{display:flex;justify-content:space-between;gap:10px;align-items:baseline;text-transform:none;letter-spacing:0;font-size:var(--fs-2)}
.ledger-head.tot b{color:var(--dark);font-weight:700;font-variant-numeric:tabular-nums}
@media(max-width:480px){.ledger-head.tot{display:flex}}
/* A nested row: one machine inside a sales day, indented past the date column it does not have.
   The money column stays exactly where the parent's money column is, because 1fr absorbs the
   indent -- which is the whole reason a nested list can share the parent's grid at all. */
.lg-row.lg-sub{padding-left:12px;background:var(--cream)}
.lg-row.lg-sub:hover{background:var(--hover)}
/* A button used as a row (the machine roster). The UA gives a button a border, a background and
   its own font; the row's top rule is the only border it is allowed to keep. */
button.lg-row{border-left:none;border-right:none;border-bottom:none;background:none;font-family:inherit;text-align:left;width:100%;cursor:pointer;color:var(--dark)}
button.lg-row.sel{background:var(--hover);box-shadow:inset 3px 0 0 var(--gold)}
/* The state chip a warehouse row carries. Ink, not a fill: a filled red pill on every third row
   is the card stack's shouting moved into a smaller box. */
.lg-chip.bad{background:transparent;border:1px solid var(--red-lt);color:var(--red)}
.lg-chip.warn{background:transparent;border:1px solid var(--warn-lt);color:var(--warn)}
/* The avatar that stands in for a date on a lead row (D-5: a lead is read by state, not by the
   day it was typed). */
.lg-av{width:32px;height:32px;border-radius:50%;display:flex;align-items:center;justify-content:center;font-size:var(--fs-2);font-weight:500}
/* A select cannot wear .btn (the UA draws its own box), so it gets the secondary button's
   geometry and nothing else. */
.lg-sel{width:100%;min-width:0;font-family:inherit;font-size:var(--fs-2);font-weight:500;min-height:36px;padding:0 8px;border:1px solid var(--rule);border-radius:var(--r-control);background:var(--white);color:var(--dark);cursor:pointer}
/* The second line a row reveals when it is tapped: notes, a follow-up, a bill's line items. It is
   a SIBLING of the row (30a: the row carries the toggle, so a click inside a child would shut the
   row under the operator's finger) and it is full width by virtue of being a block. */
.lg-more{padding:2px 8px 10px 12px;font-size:var(--fs-2);line-height:1.5;color:var(--muted)}
.lg-more b{color:var(--dark);font-weight:600}
/* Bands with two and three cells. 30a's band is four across; the bills band is two and four of
   the seven are three, and a three-cell band must never fall back to the 2+1 the machine detail
   showed -- at phone width the third cell takes the whole row instead. */
.lg-summary.c3{grid-template-columns:repeat(3,1fr)}
.lg-summary.c2{grid-template-columns:repeat(2,1fr)}
@media(max-width:520px){
.lg-summary.c3{grid-template-columns:repeat(2,1fr)}
.lg-summary.c3 .lg-cell:nth-child(3){grid-column:1/-1;border-left:none;border-bottom:none}
.lg-summary.c3 .lg-cell:nth-child(-n+2){border-bottom:1px solid var(--rule-dark)}
.lg-summary.c2 .lg-cell{border-bottom:none}
}
/* The one figure on a band that is allowed to be red: a fee is a cost, and that is an existing
   signal rather than this item's decoration. */
/* A figure with a second figure under it: 30-day sales with the net beneath, on the roster row.
   Not bold, not coloured by default -- the ink comes from the .mr-pos / .mr-neg pair the roster
   has used since it was written. */
/* The row a wheel is currently centred on (the machine whose detail is open). A 3px inset gold
   bar rather than a border, because a border on one row of a ruled list moves the other rows. */
.lg-row.ctr{background:var(--cream);box-shadow:inset 3px 0 0 var(--gold)}
/* A chip that marks a state rather than naming a thing: gold ink, no fill. */
.lg-chip.now{background:transparent;border:1px solid var(--gold);color:var(--gold)}
/* The wheel's chevrons. A .btn, so nothing is inline, with the one geometry a full-width 24px
   affordance inside a fixed-height box needs -- it is not a tap target in a list, it is a nudge. */
.btn.wheel{width:100%;min-height:24px;padding:1px 0;font-size:var(--fs-1)}
/* A stock figure below zero is not a small number, it is a WRONG number -- a missing purchase or
   a misread pack size -- and a figure running low is a warning. Those two states are the only
   times a .lg-num is not the row's own ink. */
/* The few shapes the warehouse needs of its own: a form row that is a form and not a record, the
   count question with its answer buttons, and the quiet threshold line under a row. Declared here
   so the warehouse region carries no inline style at all (item 30b, A-5). */
.wh-packrow{display:flex;align-items:center;justify-content:space-between;gap:10px;flex-wrap:wrap;padding:7px 6px;border-top:1px solid var(--rule-soft)}
.wh-packname{font-size:var(--fs-3);flex:1 1 160px;min-width:0}
/* PICTURES-POLISH P1: a product picture left of its name. Only present when there IS a picture, so a
   receipt-only product renders exactly as before. The name wraps beside the picture, never off screen. */
.wh-pic{display:flex;align-items:center;gap:10px;min-width:0}
.wh-pic-t{flex:1 1 auto;min-width:0}
.wh-pic-t .p{white-space:normal;overflow-wrap:anywhere}
.wh-packacts{display:flex;gap:6px;flex-wrap:wrap;align-items:center}
.wh-other{width:82px;text-align:left}
/* ITEM 43: the free-text pack size and the Set button that commits it are ONE control in two
   boxes, so they wrap as one. Before this they were two siblings of the wrapping strip, and the
   82px input was routinely the last thing to fit on the first line -- which put it against the
   right edge of the phone, under the chat bubble, while its own Set button had already wrapped
   below it. Grouping them costs no height (Set was wrapping anyway) and moves the input inboard. */
.wh-packother{display:flex;gap:6px;align-items:center}
.wh-form{margin:2px 6px 8px;padding:11px;background:var(--hover);border-radius:var(--r-panel)}
.wh-q{font-size:var(--fs-2);margin-bottom:7px;color:var(--dark)}
.wh-in{width:110px;text-align:left}
.wh-reasons{display:flex;gap:6px;flex-wrap:wrap;margin:9px 0 10px}
.wh-acts{display:flex;gap:8px;flex-wrap:wrap}
.wh-th{display:flex;align-items:center;gap:8px;flex-wrap:wrap}
/* The five secondary verbs an opened lead-finder result offers. They wrap; they never scroll,
   because these are the actions of one record and not the toolbar of a screen. */
.lf-acts{display:flex;gap:6px;flex-wrap:wrap;align-items:center;padding:0 6px}
/* The bills editor, in the block behind a row: one line per fee line, a notes box, Save/Cancel. */
.bill-erows{font-size:var(--fs-2);padding:0 6px}
.bill-erow{display:flex;justify-content:space-between;align-items:center;gap:8px;padding:3px 0;border-bottom:1px solid var(--rule-soft)}
.bill-notes{width:100%;text-align:left;margin-top:8px}
.lg-acts{display:flex;gap:8px;flex-wrap:wrap;padding:10px 6px 0}
.lf-acts a.btn{text-decoration:none}
.wh-warn{color:var(--warn)}
.lg-num.bad{color:var(--red)}
.lg-num.warn{color:var(--warn)}
.lg-num small{display:block;font-size:var(--fs-1);font-weight:400;font-variant-numeric:tabular-nums}
.lg-cell .v.red{color:var(--red)}
.lg-cell .v.green{color:var(--green)}
/* The restock list keeps its check square and its strike-through; what it adopts is the ledger's
   rhythm, so a restock row and a receipt row are the same height on the same phone. */
.rk-row{display:flex;align-items:center;gap:12px;padding:9px 16px;border-top:1px solid var(--rule-soft);cursor:pointer;background:var(--white);transition:background .15s}
.rk-row:hover{background:var(--hover)}
.rk-row.on{background:var(--green-lt)}
.rk-box{width:24px;height:24px;border-radius:var(--r-control);border:2px solid var(--rule);background:var(--white);display:flex;align-items:center;justify-content:center;flex-shrink:0;color:var(--white);font-size:var(--fs-3);font-weight:600}
.rk-row.on .rk-box{background:var(--green-dark);border-color:var(--green-dark)}
.rk-what{flex:1;min-width:0}
.rk-row.on .rk-what{opacity:.45}
.rk-nm{font-size:var(--fs-3);font-weight:500;color:var(--dark);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.rk-row.on .rk-nm{text-decoration:line-through}
.rk-meta{font-size:var(--fs-1);color:var(--muted);margin-top:2px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.rk-n{text-align:right;flex-shrink:0;font-variant-numeric:tabular-nums}
.rk-n .q{font-size:var(--fs-4);font-weight:700}
.rk-n .q.out{color:var(--red)}
.rk-n .q.low{color:var(--warn)}
.rk-n .u{font-size:var(--fs-1);color:var(--muted)}
/* A warehouse verb ("Record first count") is wider than the 96px action track on a phone. It wraps
   inside the track rather than pushing the page sideways (item 30b, B-9; visual-check C at 375). */
@media(max-width:480px){.ledger.wh .lg-row .btn{white-space:normal;line-height:1.15;padding:6px 8px;text-align:center}}
/* ITEM 40-B: the match-queue row's two verbs drop onto their own line, at the left margin, on any
   screen narrow enough for the list to run full-bleed. WHY 672: .content is max-width 640 with 16px
   of padding each side, so below 672 the list reaches the viewport edge -- and .ai-fab is fixed
   there (48px wide at right:12px, so it owns the rightmost 60px). Measured live at 375:
   the 96px action track ends at x 359 and the bubble starts at x 315, so the bubble covered the
   right 44px of "Not a product" on whichever of the 71 rows was passing under it. Stacking moves
   both verbs clear of the bubble entirely AND gives them the row's full width, so the label stops
   wrapping inside a 96px box. The FAB is deliberately NOT moved or hidden: it is on every screen
   and it is not this row's to redesign. The empty date/miles/money cells are hidden rather than
   collapsed, because the row keeps the ledger grid at wider widths. */
@media(max-width:672px){
.ledger.wh .lg-row.wh-mq{display:block}
.ledger.wh .lg-row.wh-mq>div:nth-child(1),.ledger.wh .lg-row.wh-mq>div:nth-child(3),.ledger.wh .lg-row.wh-mq>div:nth-child(4){display:none}
.ledger.wh .lg-row.wh-mq>div:nth-child(5){display:flex;gap:8px;flex-wrap:wrap;margin-top:7px}
.ledger.wh .lg-row.wh-mq .btn{white-space:nowrap}
/* ITEM 43: the same collision on the two COUNT lists, finished the way 40-B was started but not
   the same rule, because these rows are not the queue row. 40-B could hide its empty cells; these
   carry a meaningful fourth cell ("not counted") which is the whole answer the row gives before a
   count exists, so it is kept and moved rather than hidden. The row becomes a wrapping flex: the
   name takes the first line whole, then the status and the verb sit together on a second line at
   the LEFT margin. Measured with the layout harness at 375: the 96px action track ended at x 357
   against a bubble starting at x 315, so 42px of "Record first count" and "Count what I have" was
   covered on whichever row was passing under it -- and these lists run to 40 and 60 rows, so every
   row visits that height while scrolling. WHY THE SECOND LINE IS ACCEPTED HERE: it is the only
   option that moves the verb without putting something else under the bubble in its place. The
   alternatives were measured and rejected -- a right gutter clips the product name on every row
   (the name ellipsises, so the loss is permanent and silent), and swapping the status outboard of
   the verb just trades which ink the bubble covers. Below 672px only, for the same reason 40-B
   chose that width: above it .content is inset and there is no collision. */
.ledger.wh .lg-row.wh-cnt{display:flex;flex-wrap:wrap;align-items:center;gap:8px}
.ledger.wh .lg-row.wh-cnt>div:nth-child(1),.ledger.wh .lg-row.wh-cnt>div:nth-child(3){display:none}
.ledger.wh .lg-row.wh-cnt>div:nth-child(2){flex:1 1 100%;min-width:0}
.ledger.wh .lg-row.wh-cnt>div:nth-child(4){text-align:left}
.ledger.wh .lg-row.wh-cnt .btn{white-space:nowrap}
}
/* The warehouse "How this works" guide: an explanation, not a record, and not a box either. */
.wh-guide{margin-bottom:16px;border-top:1px solid var(--rule);border-bottom:1px solid var(--rule)}
/* LEDGER2_END */
/* WH_SECTIONS_CSS_START -- WAREHOUSE-OVERHAUL (2026-10-03): the Warehouse tab's own section bar.
   The control is the app's segmented sub-tab (.subnav-pill, solid when you are on it), because a
   section is a PLACE, not a filter. Its container is not .subnav, which is phone-only and hidden on
   desktop: this bar shows at every width, scrolls sideways on its own at 375px (the page never does),
   and sticks under the app header. --wh-top is the header height, set by the client at paint time. */
.wh-secbar{position:sticky;top:var(--wh-top,0px);z-index:4;display:flex;gap:0;overflow-x:auto;scrollbar-width:none;-webkit-overflow-scrolling:touch;overscroll-behavior-x:contain;margin:0 0 14px;border:1px solid var(--rule);border-radius:var(--r-control);background:var(--white);max-width:100%;width:max-content}
.wh-secbar::-webkit-scrollbar{display:none}
.wh-secbar .subnav-pill{min-height:40px;padding:0 13px;border:none;border-radius:0;background:var(--white);flex:0 0 auto}
.wh-secbar .subnav-pill:first-child{border-radius:var(--r-control) 0 0 var(--r-control)}
.wh-secbar .subnav-pill:last-child{border-radius:0 var(--r-control) var(--r-control) 0}
.wh-secbar .subnav-pill.active{background:var(--dark);color:var(--gold)}
.wh-badge{display:inline-block;min-width:18px;margin-left:6px;padding:0 5px;border-radius:9px;background:var(--gold);color:var(--dark);font-size:var(--fs-1);font-weight:700;line-height:18px;text-align:center}
.wh-src{display:inline-block;margin-left:6px;font-size:var(--fs-1);color:var(--muted);font-weight:400;white-space:nowrap}
.wh-sec{min-width:0}
.wh-search{width:100%;max-width:360px;margin:0 0 10px;text-align:left}
.wh-chips{display:flex;gap:6px;flex-wrap:wrap;margin:8px 0}
.wh-vp-comp{display:flex;align-items:center;gap:8px;flex-wrap:wrap;padding:7px 6px;border-top:1px solid var(--rule-soft);font-size:var(--fs-3)}
.wh-vp-comp .n{flex:1 1 160px;min-width:0;overflow-wrap:anywhere}
.wh-vp-comp .q{color:var(--muted);font-size:var(--fs-2);white-space:nowrap}
.wh-prod{display:flex;align-items:center;gap:8px;flex-wrap:wrap;padding:7px 6px;border-top:1px solid var(--rule-soft);font-size:var(--fs-3)}
.wh-prod .n{flex:1 1 180px;min-width:0;overflow-wrap:anywhere}
.wh-prod .lg-mi{width:auto;max-width:100%;min-width:0;flex:1 1 140px;text-align:left}
/* Below 672px the list runs full-bleed and the chat bubble owns the rightmost 60px of the viewport
   (item 40-B). Every control the sections add that scrolls with the page keeps out of that lane:
   the search box stops short of it, and the product and pack-component rows keep a right gutter. */
@media(max-width:672px){
.wh-search{max-width:calc(100% - 64px)}
.wh-prod,.wh-vp-comp{padding-right:60px}
}
/* WH_SECTIONS_CSS_END */
/* SALES_BOARD_START -- the day drill as a side-by-side board (item 30g, 2026-09-15).
   Two machines open on one day used to mean two stacked product lists a screen apart, with the
   quantity landing wherever each product name happened to stop -- nothing lined up, and the one
   question the view exists to answer (what sold where) could not be read off it. The machine rows
   now stay together as the day's summary and every OPEN machine becomes a COLUMN below them, same
   rhythm, quantity and money each on their own track. auto-fit folds the board back to a single
   column under about 320px of room, which is the stacked list it replaces, so the phone is
   unchanged in shape and only gains the alignment. */
.sd-bar{display:flex;align-items:center;gap:10px;flex-wrap:wrap;padding:9px 6px 0}
.sd-hint{font-size:var(--fs-1);color:var(--muted)}
.sd-board{display:grid;grid-template-columns:repeat(auto-fit,minmax(300px,1fr));gap:10px;padding:8px 6px 12px;align-items:start}
.sd-col{background:var(--white);border:1px solid var(--border);border-radius:var(--r-panel);padding:9px 12px 4px;min-width:0}
.sd-h{display:flex;align-items:baseline;justify-content:space-between;gap:8px;padding-bottom:7px;border-bottom:1px solid var(--parchment)}
.sd-h .n{font-size:var(--fs-2);font-weight:600;color:var(--dark);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.sd-h .v{font-size:var(--fs-2);font-weight:700;color:var(--gold);flex:0 0 auto}
/* The open machine row wears the same selected treatment the roster uses, so the row and its
   column read as one thing. button.lg-row.sel does not reach it: these rows are divs. */
.lg-row.lg-sub.sel{background:var(--hover);box-shadow:inset 3px 0 0 var(--gold)}
/* SALES_BOARD_END */

.badge{display:inline-block;font-size:var(--fs-1);font-weight:500;padding:3px 9px;border-radius:20px;flex-shrink:0;letter-spacing:0.3px}
.b-signed{background:var(--green-lt);color:#27500A}
.b-hot{background:var(--red-lt);color:#791F1F}
.b-warm{background:var(--amber-lt);color:#633806}
.b-cold{background:#F1EFE8;color:#444441}
.b-urgent{background:var(--red-lt);color:#791F1F}
.b-high{background:var(--amber-lt);color:#633806}
.b-medium{background:#F1EFE8;color:#444441}

.lead-card{background:var(--white);border:1px solid var(--border);border-radius:var(--r-panel);padding:16px;margin-bottom:10px}
.lead-top{display:flex;align-items:flex-start;gap:12px;margin-bottom:10px}
.avatar{width:44px;height:44px;border-radius:50%;display:flex;align-items:center;justify-content:center;font-size:var(--fs-3);font-weight:500;flex-shrink:0}
.av-blue{background:var(--blue-lt);color:#0C447C}
.av-amber{background:var(--amber-lt);color:#633806}
.av-green{background:#EAF3DE;color:#27500A}
.av-teal{background:#E1F5EE;color:#085041}
.av-purple{background:#EEEDFE;color:#3C3489}
.lead-name{font-size:var(--fs-4);font-weight:500;color:var(--dark);display:flex;align-items:center;gap:8px;flex-wrap:wrap}
.lead-biz{font-size:var(--fs-2);color:var(--muted);margin-top:2px}
.lead-contact{font-size:var(--fs-1);color:#999;margin-top:3px}
.lead-note{font-size:var(--fs-2);color:var(--muted);font-style:italic;margin-top:8px;padding-top:8px;border-top:1px solid var(--parchment)}
.lead-footer{display:flex;align-items:center;justify-content:space-between;margin-top:10px;gap:8px}
.follow-tag{font-size:var(--fs-1);color:var(--muted)}
.status-select{font-size:var(--fs-1);padding:4px 8px;border-radius:var(--r-control);border:1px solid var(--border);background:var(--cream);color:var(--dark);cursor:pointer}

.filter-row{display:flex;gap:8px;margin-bottom:16px;overflow-x:auto;padding-bottom:4px}
.filter-row::-webkit-scrollbar{display:none}
.filter-pill{font-size:var(--fs-2);padding:6px 16px;border-radius:var(--r-control);cursor:pointer;border:1px solid var(--border);background:var(--white);color:var(--muted);white-space:nowrap;flex-shrink:0;transition:all 0.15s}
.filter-pill.on{background:var(--dark);color:var(--gold);border-color:var(--dark)}

.lf-chip{font-size:var(--fs-2);padding:7px 14px;border-radius:var(--r-control);cursor:pointer;border:1px solid var(--border);background:var(--white);color:var(--muted);white-space:nowrap;font-weight:500;transition:all 0.15s;font-family:inherit}
.lf-chip:hover{border-color:var(--gold);color:var(--dark)}
.lf-chip.active{background:var(--dark);color:var(--gold);border-color:var(--dark);font-weight:600}

/* TODOS_LG_START */
/* B-4. The to-do list was ALREADY ruled rows on a phone -- the only list in the app that was --
   and the desktop block then re-boxed it into a two-column grid of bordered cards, so the same
   list was two different designs at two widths. What changes here is the rhythm (9px of padding
   and the soft rule, the same as every other ledger row) and the divider weight; the checkbox,
   the text, the meta line, the priority badge and the sort are untouched. The desktop grid and
   its border/background/radius are deleted below, not overridden, so there is one to-do row. */
.todo-item{display:flex;align-items:flex-start;gap:12px;padding:9px 6px;border-bottom:1px solid var(--rule-soft)}
.todo-item:last-child{border-bottom:none}
.todo-item:hover{background:var(--hover)}
.check-box{width:22px;height:22px;border-radius:var(--r-control);border:1.5px solid var(--rule);flex-shrink:0;cursor:pointer;display:flex;align-items:center;justify-content:center;margin-top:1px;transition:all 0.15s;background:var(--white)}
.check-box.done{background:var(--green);border-color:var(--green)}
.check-mark{width:12px;height:7px;border-left:2px solid white;border-bottom:2px solid white;transform:rotate(-45deg);margin-top:-2px;display:none}
.check-box.done .check-mark{display:block}
.todo-body{flex:1}
.todo-text{font-size:var(--fs-3);color:var(--dark);line-height:1.5}
.todo-text.done{text-decoration:line-through;color:#bbb}
.todo-meta{display:flex;align-items:center;gap:8px;margin-top:5px;flex-wrap:wrap}
.todo-due{font-size:var(--fs-1);color:var(--muted)}
/* ITEM 41 (B): the same-job line. Its own row under the meta line (flex-basis:100%), because it is
   a sentence about the row rather than another chip beside the date. */
.todo-dup{flex-basis:100%;font-size:var(--fs-1);color:var(--muted);line-height:1.4}
/* TODOS_LG_END */

.info-card{background:var(--dark);border-radius:var(--r-panel);padding:16px;margin-bottom:12px}
.info-card .card-title{color:var(--gold);font-family:'Playfair Display',serif;margin-bottom:12px}
.info-row{display:flex;justify-content:space-between;padding:6px 0;border-bottom:1px solid var(--rule-dark);font-size:var(--fs-2)}
.info-row:last-child{border-bottom:none}
.info-label{color:rgba(253,250,244,0.5)}
.info-value{color:var(--cream);font-weight:500;text-align:right;max-width:60%}
.info-value.gold{color:var(--gold)}
.info-value.red{color:#F09595}

.empty{text-align:center;padding:40px 20px;color:var(--muted);font-size:var(--fs-3)}
.empty .lg-acts{justify-content:center;padding-top:14px}

/* .progress-wrap / .progress-label / .progress-bar / .progress-fill removed 2026-07-31 with the
   Year-1 break-even card. .cl-progress-bar below is the checklist's own bar and is unrelated. */
.tw-strip{display:flex;gap:10px;flex-wrap:wrap}
.tw-cell{flex:1 1 180px;min-width:150px;background:var(--white);border:1px solid var(--gold-lt);border-radius:var(--r-panel);padding:12px 14px}
.tw-lab{font-size:var(--fs-1);font-weight:600;letter-spacing:0.06em;text-transform:uppercase;color:var(--muted)}
.tw-val{font-family:'Playfair Display',serif;font-size:var(--fs-6);font-weight:700;color:var(--dark);line-height:1.15;margin-top:3px}
.tw-sub{font-size:var(--fs-1);color:var(--muted);margin-top:4px;line-height:1.45}
.tw-wait{font-size:var(--fs-2);color:var(--muted);margin-top:6px;line-height:1.45}
.tw-spark{display:flex;align-items:flex-end;gap:3px;height:38px;margin-top:8px}
.tw-bar{flex:1;background:var(--gold);border-radius:0;min-height:2px}
.tw-bar.z{background:var(--gold-lt);opacity:0.45}

.alert{background:var(--red-lt);border:1px solid #F09595;border-radius:var(--r-panel);padding:12px 14px;margin-bottom:16px;font-size:var(--fs-2);color:var(--red);display:flex;gap:10px;align-items:flex-start}
.alert-icon{flex-shrink:0;margin-top:1px}

/* AI CHAT */
.ai-fab{position:fixed;bottom:calc(80px + env(safe-area-inset-bottom,0px));right:16px;width:52px;height:52px;background:var(--gold);border-radius:50%;border:none;cursor:pointer;display:flex;align-items:center;justify-content:center;box-shadow:0 4px 16px rgba(var(--accent-rgb),0.4);z-index:var(--z-fab);transition:transform 0.2s,box-shadow 0.2s}
.ai-fab:active{transform:scale(0.93)}
/* MOBILE FURNITURE, 2026-08-01. The bottom nav is display:none only ABOVE 900px, but these
   floats used a 640px breakpoint, so between 640 and 899 they sat ON the nav. On a phone the
   demo pill sat inside the nav band at z-index 9998 and took the Dashboard tap (log [15]).
   Offsets are calc() off env(safe-area-inset-bottom) because Firefox on Android reserves a
   bottom inset that a fixed pixel offset does not know about. */
@media(min-width:900px){.ai-fab{bottom:24px;right:24px}}
#demo-guide-pill{position:fixed;left:14px;bottom:calc(88px + env(safe-area-inset-bottom,0px));z-index:var(--z-fab)}
@media(min-width:900px){#demo-guide-pill{bottom:16px}}
@media(max-width:899px){.ai-fab{width:44px;height:44px;bottom:calc(84px + env(safe-area-inset-bottom,0px));right:12px}.ai-fab svg{width:20px;height:20px}}
.ai-fab svg{width:24px;height:24px;color:var(--dark)}
.ai-chips{display:flex;flex-wrap:wrap;gap:6px;padding:8px 12px 0}
.ai-chip{background:var(--cream);border:1px solid var(--gold);color:var(--dark);border-radius:var(--r-control);padding:6px 12px;font-family:inherit;font-size:var(--fs-2);font-weight:600;cursor:pointer}
.ai-chip:active{transform:scale(0.96)}
.ai-fab-tip{position:fixed;right:80px;bottom:calc(88px + env(safe-area-inset-bottom,0px));z-index:var(--z-fab);background:var(--dark);color:var(--cream);border:none;font-family:inherit;font-size:var(--fs-2);font-weight:600;padding:9px 13px;border-radius:var(--r-control);cursor:pointer;box-shadow:0 2px 10px var(--border)}
@media(min-width:900px){.ai-fab-tip{right:88px;bottom:32px}}
@media(max-width:899px){.ai-fab-tip{display:none !important}}
@keyframes aiFabPulse{0%{transform:scale(1)}50%{transform:scale(1.12)}100%{transform:scale(1)}}
.ai-fab.pulse-once{animation:aiFabPulse 0.9s ease-in-out 2}
body.mach-open .ai-fab-tip{display:none}

.ai-panel{position:fixed;bottom:148px;right:16px;width:calc(100vw - 32px);max-width:380px;background:var(--white);border:1px solid var(--border);border-radius:var(--r-panel);box-shadow:0 8px 40px rgba(26,15,6,0.18);z-index:199;display:none;flex-direction:column;max-height:60vh}
.ai-panel.open{display:flex}
@media(min-width:640px){.ai-panel{bottom:90px;right:24px}}
/* Pop-out. Mobile expanded = near-fullscreen (dvh + safe-area insets); desktop expanded = ~half viewport. Internal scroll preserved by the flex column (messages flex:1). */
.ai-panel.expanded{top:0;left:0;right:0;bottom:0;width:100vw;max-width:100vw;height:100vh;height:100dvh;max-height:100dvh;border-radius:0;box-sizing:border-box;padding-top:env(safe-area-inset-top,0px);padding-bottom:env(safe-area-inset-bottom,0px)}
/* Desktop expanded = TRUE SPLIT, only at >=768px (below that each half would be ~320px — too cramped,
   so <768px keeps the near-fullscreen overlay above). Chat is a flush right-half column starting just
   below the full-width sticky header (--ai-split-top = header height, set in JS); the dashboard
   (.content) reflows into the left half via margin-right:50vw. Mobile/small (<768px) is unaffected. */
@media(min-width:768px){.ai-panel.expanded{top:var(--ai-split-top,60px);left:auto;bottom:0;right:0;width:50vw;max-width:50vw;height:auto;max-height:none;border-radius:0;border-left:1px solid var(--border);padding-top:0;padding-bottom:0}
body.chat-split .content{max-width:none;margin:0;margin-right:50vw}}
.ai-hdr-btns{margin-left:auto;display:flex;gap:2px;flex-shrink:0}
.ai-hdr-btn{width:28px;height:28px;border:none;background:transparent;color:var(--muted);cursor:pointer;display:flex;align-items:center;justify-content:center;border-radius:var(--r-control);padding:0}
.ai-hdr-btn:hover{background:var(--parchment);color:var(--dark)}
.ai-hdr-btn svg{width:16px;height:16px}
.ai-expand-btn .ic-collapse{display:none}
.ai-panel.expanded .ai-expand-btn .ic-expand{display:none}
.ai-panel.expanded .ai-expand-btn .ic-collapse{display:block}
.ai-panel-header{padding:14px 16px 12px;border-bottom:1px solid var(--border);display:flex;align-items:center;gap:10px}
.ai-panel-title{font-family:'Playfair Display',serif;font-size:var(--fs-4);font-weight:600;color:var(--dark)}
.ai-panel-sub{font-size:var(--fs-1);color:var(--muted)}
.ai-dot{width:8px;height:8px;background:var(--green);border-radius:50%;flex-shrink:0}
.ai-messages{flex:1;min-height:0;overflow-y:auto;overflow-x:hidden;padding:12px 16px;display:flex;flex-direction:column;gap:10px;overscroll-behavior:contain}
/* min-height:0 is load-bearing (Benjamin, 08-14: "the chat bubble is full, it won't scroll,
   they are fighting for control"): a flex-column child's min-height defaults to AUTO, so the
   messages area refused to shrink below its content, never became scrollable, and every wheel
   tick fell through to the page behind it. Tables get their own inner scroll for the same
   reason sideways: a wide answer table must scroll inside the bubble, never widen it. */
.ai-msg table{display:block;max-width:100%;overflow-x:auto;overscroll-behavior-x:contain}
.ai-msg{overflow-wrap:anywhere}
.ai-msg{max-width:88%;font-size:var(--fs-3);line-height:1.5;padding:10px 13px;border-radius:var(--r-panel)}
.ai-msg.assistant{background:var(--parchment);color:var(--dark);align-self:flex-start;border-bottom-left-radius:4px}
.ai-msg.user{background:var(--dark);color:var(--cream);align-self:flex-end;border-bottom-right-radius:4px}
.ai-msg.thinking{color:var(--muted);font-style:italic}
.ai-input-row{padding:10px 12px;border-top:1px solid var(--border);display:flex;gap:8px;align-items:flex-end}
.ai-input{flex:1;border:1px solid var(--border);border-radius:var(--r-control);padding:9px 12px;font-size:var(--fs-3);font-family:'DM Sans',sans-serif;resize:none;outline:none;background:var(--cream);color:var(--dark);max-height:80px;min-height:38px}
.ai-input:focus{border-color:var(--gold)}
.ai-send{width:36px;height:36px;background:var(--gold);border:none;border-radius:var(--r-control);cursor:pointer;display:flex;align-items:center;justify-content:center;flex-shrink:0;transition:opacity 0.15s}
.ai-send:disabled{opacity:0.4;cursor:default}
.ai-send svg{width:16px;height:16px;color:var(--dark)}
.ai-img-btn{width:32px;height:32px;border-radius:var(--r-control);border:1px solid var(--border);background:var(--cream);cursor:pointer;display:flex;align-items:center;justify-content:center;flex-shrink:0;color:var(--muted);transition:all 0.15s}
.ai-img-btn:hover{border-color:var(--gold);color:var(--gold)}
.ai-img-preview-wrap{position:relative;display:inline-block;margin-right:6px}
.ai-img-preview-wrap img{width:48px;height:48px;object-fit:cover;border-radius:var(--r-control);border:1px solid var(--border)}
.ai-img-remove{position:absolute;top:-6px;right:-6px;width:16px;height:16px;background:var(--red);border-radius:50%;border:none;cursor:pointer;display:flex;align-items:center;justify-content:center;color:white;font-size:var(--fs-1);line-height:1}

@media(min-width:640px){
  .bottom-nav{display:none}
  .desktop-nav{display:flex!important}
  .app-header{padding:16px 32px}
  .content{padding:24px 32px}
}

/* ── Desktop wide layout (>=1000px) — mobile & tablet (<1000px) untouched ── */
@media(min-width:1000px){
  /* ITEM 30b (B-3, B-4, B-10): the to-do, leads and lead-finder grids are DELETED, not
     overridden. Each of those lists is now a single-column ledger, and a ledger laid out in a
     360px grid column is the "open blank squares" bug #bills-list already hit for the same
     reason. One list, one column, every width. */
  /* BILLS V4 (8/10): #bills-list is deliberately NOT in the two-column rule below. The bills list
     is month headers with children that expand BENEATH them; in a filled grid the children landed
     in the opposite column and the stretched parent rendered as a large blank box (founder-reported
     as "open blank squares"). The list reads strictly top-to-bottom, full width. */
  /* ITEM 30c (B-2): the sales, receipts and mileage auto-fill grid is DELETED as well. All three are single-column
     ledgers, and a ledger in a 400px column is the same blank-squares bug. */
  #sales>.card,#bills>.card,#mileage>.card{max-width:640px}
}
.desktop-nav{display:none;gap:4px}
.desktop-nav-btn{font-size:var(--fs-3);font-weight:500;padding:7px 16px;cursor:pointer;color:rgba(253,250,244,0.5);border:none;background:none;border-radius:var(--r-control);transition:all 0.2s}
.desktop-nav-btn.active{background:rgba(var(--accent-rgb),0.15);color:var(--gold)}
.desktop-nav-btn:hover{color:var(--gold)}
/* Sub-nav pills for grouped mobile tabs (Money, Leads). Mobile-only (<640px); hidden on desktop
   and whenever the active group has no sub-sections (:empty). CSS owns visibility — JS only sets innerHTML. */
/* Closed <details> content gets a REAL display:none (V4, 8/10). Chromium hides it with
   content-visibility, which leaves phantom layout boxes: the collapsed Add-a-bill form still
   reported element rects, so visual-check's ink test saw "visible" inputs under the bottom nav
   the moment the Bills tab got short enough not to scroll. Same rendered result for a person,
   honest geometry for anything that measures. */
details:not([open])>*:not(summary){display:none!important}   /* !important: the form div carries an inline display:grid, and the collapsed state must beat it */
.subnav{display:none;gap:8px;overflow-x:auto;padding-bottom:14px;-webkit-overflow-scrolling:touch;overscroll-behavior:contain}
.subnav::-webkit-scrollbar{display:none}
@media(max-width:639px){.subnav:not(:empty){display:flex}}
/* 38px -> 44px. Not requested, but the layout harness flags it on every phone section and this is
   the PRIMARY in-section navigation, so it was the smallest tap target on the busiest control. The
   pill shape and the invert-on-active treatment are untouched, so it stays visually a place, not a
   filter — that distinction is the point of the FILTER_CHIP region below. */
.subnav-pill{flex:0 0 auto;font-size:var(--fs-3);font-weight:600;padding:9px 16px;min-height:44px;display:inline-flex;align-items:center;border-radius:var(--r-control);cursor:pointer;border:1px solid var(--border);background:var(--white);color:var(--muted);white-space:nowrap;font-family:inherit;transition:all 0.15s}
/* FILTER_CHIP_START */
/* Filters are NOT places. Navigation is a solid pill that inverts when you are on it; a filter is
   an outlined chip with a leading dot that fills when it is on. Different shape, different weight,
   different active treatment, so the two can never be confused at a glance. 44px min height is the
   standard touch target — these measured 30 to 32px before, which is why they were hard to hit. */
.filter-chip,.filter-pill,.lf-chip{flex:0 0 auto;display:inline-flex;align-items:center;gap:6px;font-size:var(--fs-2);font-weight:600;padding:0 13px;min-height:44px;border-radius:var(--r-control);cursor:pointer;border:1px dashed var(--amber-lt);background:transparent;color:var(--muted);white-space:nowrap;font-family:inherit;transition:all 0.15s}
.filter-chip::before,.filter-pill::before,.lf-chip::before{content:'';width:7px;height:7px;border-radius:50%;border:1.5px solid var(--muted);background:transparent;flex:0 0 auto}
/* .filter-pill marks itself on with .on, not .active — filterLeads() has always toggled .on and
   still does. Leaving it out of the active selectors below meant the legacy dark-solid rule higher
   up kept winning, so an active LEADS filter still looked exactly like an active nav pill, which is
   the one thing this whole region exists to prevent. Both class names, deliberately. */
.filter-chip.active,.filter-pill.active,.filter-pill.on,.lf-chip.active{border-style:solid;border-color:var(--gold);color:var(--dark);background:var(--gold-lt)}
.filter-chip.active::before,.filter-pill.active::before,.filter-pill.on::before,.lf-chip.active::before{background:var(--gold);border-color:var(--gold)}
.filter-group{display:flex;flex-direction:column;gap:6px;min-width:0}
.filter-group>.fg-label{font-size:var(--fs-1);font-weight:600;letter-spacing:0.06em;text-transform:uppercase;color:var(--muted)}
.filter-group>.fg-row{display:flex;gap:6px;flex-wrap:wrap}
.filter-bar{display:flex;gap:14px;flex-wrap:wrap;align-items:flex-start}
/* FILTER_CHIP_END */
.subnav-pill.active{background:var(--dark);color:var(--gold);border-color:var(--dark);font-weight:600}
/* TOOLBAR_START */
/* ITEM 30c, PART A: THE PHONE CHROME.
   A-2  One toolbar row per screen, and it SCROLLS instead of wrapping (D-3): a wrapped row of buttons was the largest
        single consumer of phone height on Sales and Receipts. Buttons in one row, filters and chips in a second row
        directly beneath, never mixed. tbSync sets .over when a row overflows, and the right edge fades so a hidden
        button reads as "more this way".
   A-3  Sub-tabs are a segmented control: one hairline round the group, no border between segments, the active one
        the dark fill. Same class names, so renderSubnav is untouched.
   A-5  The disclosure every explanation now sits behind: one line, then a quiet button.
   A-1  The chat button hides while the page scrolls down (FAB_SCROLL) and comes back on the way up; no motion when
        the operator has asked for less. */
.toolbar{display:flex;flex-wrap:nowrap;align-items:center;gap:8px;overflow-x:auto;scrollbar-width:none;-webkit-overflow-scrolling:touch;padding-bottom:2px;margin-bottom:12px}
.toolbar::-webkit-scrollbar{display:none}
.toolbar>*{flex:0 0 auto}
.toolbar:empty{display:none}
.toolbar.filters{padding-bottom:0}
.toolbar.over{-webkit-mask-image:linear-gradient(90deg,var(--dark) 80%,transparent);mask-image:linear-gradient(90deg,var(--dark) 80%,transparent)}
.section-title+.toolbar{margin-top:-6px}
body.tab-machines:not(.chat-split) #machines>.toolbar{flex:0 0 auto}
.sm-chip::before{display:none}
.sm-dot{display:inline-block;width:8px;height:8px;border-radius:50%;margin-right:6px}
.subnav{gap:0;padding-bottom:0;margin-bottom:14px;border:1px solid var(--rule);border-radius:var(--r-control);width:max-content;max-width:100%}
.subnav-pill{min-height:36px;padding:0 14px;border:none;border-radius:0;background:var(--white)}
.subnav-pill:first-child{border-radius:var(--r-control) 0 0 var(--r-control)}
.subnav-pill:last-child{border-radius:0 var(--r-control) var(--r-control) 0}
.subnav-pill.active{background:var(--dark);color:var(--gold)}
.disc{margin:4px 0 14px}
.disc>summary{list-style:none}
.disc>summary::-webkit-details-marker{display:none}
.disc-line{font-size:var(--fs-2);color:var(--muted);line-height:1.5;margin-top:8px}
.disc-body{font-size:var(--fs-2);color:var(--muted);line-height:1.6;padding:6px 2px 2px}
.add-bill{margin-bottom:16px}
.md-boundary{background:none;border:none;border-radius:0;padding:0;color:var(--muted);font-size:var(--fs-2);margin-bottom:10px}
.md-nav.toolbar{margin-bottom:0;padding:4px 9px}
.md-nav .btn{border:none;background:none;border-radius:var(--r-control);font-weight:500;font-size:var(--fs-2);padding:0 12px;min-height:36px;color:var(--mid)}
.md-nav .btn.on{background:var(--hover);color:var(--dark)}
.due-over{color:var(--red)}
/* ITEM 41 (B): a due VENDBase cannot read ("This week") is a NOTE, not a date. It renders in the
   same words it was stored in — dueWords is untouched — but it must not wear the same ink as a
   date the app has actually checked, or an April note reads as current every morning for ever. */
.due-note{font-style:italic}
.ai-fab{transition:transform .12s ease-out}
.ai-fab.fab-hidden{transform:translateY(calc(100% + 120px))}
@media(max-width:899px){.ai-fab{width:48px;height:48px}}
@media(prefers-reduced-motion:reduce){.ai-fab{transition:none}}
/* TOOLBAR_END */

/* RAIL_START */
/* ITEM 30c, PART B: A DESKTOP THAT IS NOT A PHONE (D-6).
   At 1000px and up a 220px rail replaces the header's row of buttons: the nav groups as rows, the active group's
   sections indented beneath it, the chat button at the foot. renderNav builds it from the same NAV_GROUPS and
   NAV_SECTIONS config as the bottom nav -- a third output, not a third list. The header is a 48px bar with the page
   title, and the content column is 1100px, left-aligned against the rail. Below 1000px none of this renders.
   The nav badge is the setup count (A-1), on the Dashboard item of both navs. */
.rail{display:none}
.hdr-title{display:none}
.nav-item{position:relative}
.nav-badge{display:inline-flex;align-items:center;justify-content:center;min-width:16px;height:16px;padding:0 4px;border-radius:var(--r-control);background:var(--red);color:var(--white);font-size:var(--fs-1);font-weight:600;line-height:1}
.nav-badge[hidden]{display:none}
.nav-item .nav-badge{position:absolute;top:5px;left:calc(50% + 6px)}
.ledger.setup{--c-date:0;--c-mi:0;--c-usd:0;--c-act:22px;margin-bottom:14px}
@media(min-width:1000px){
body{padding-left:220px}
.rail{display:flex;flex-direction:column;position:fixed;left:0;top:0;bottom:0;width:220px;height:100vh;background:var(--dark);z-index:var(--z-nav);overflow-y:auto}
.rail-logo{padding:18px 20px 16px;font-family:'Playfair Display',serif;font-size:var(--fs-5);font-weight:600;color:var(--cream)}
.rail-row{display:flex;align-items:center;gap:12px;width:100%;min-height:40px;padding:0 20px;border:none;border-radius:0;background:none;color:var(--on-dark-label);font-family:inherit;font-size:var(--fs-3);font-weight:500;text-align:left;cursor:pointer}
.rail-row svg{width:18px;height:18px;flex:0 0 auto}
.rail-row:hover{color:var(--cream)}
.rail-row.active{color:var(--gold);box-shadow:inset 3px 0 0 var(--gold)}
.rail-row .nav-badge{margin-left:auto}
.rail-sub{min-height:36px;padding-left:50px;font-size:var(--fs-2)}
.rail-sub.active{box-shadow:none}
.desktop-nav{display:none!important}
.app-header{height:48px;padding:0 32px}
.app-header .app-logo{display:none!important}   /* 30d (D-4): the header markup carries an inline display:flex, which beat this rule, so the name showed twice */
.hdr-title{display:block;font-family:'Playfair Display',serif;font-size:var(--fs-4);font-weight:600;color:var(--cream)}
.content{max-width:1100px;margin:0;padding:24px 32px}
}
/* RAIL_END */

/* SPLIT_START */
/* ITEM 30c (B-3, D-7). LIST + DETAIL, THE WAY MACHINES ALREADY DOES IT, ON ITS NUMBERS: a 380px list and a detail
   pane that takes the rest, at 900px and up with the chat closed. Machines, Leads and Receipts share the class.
   Below 900px, or with the chat split open, the detail pane does not render and rows expand in place exactly as
   30a and 30b built them. */
.split-detail{display:none}
@media(min-width:900px){
body:not(.chat-split) .split{display:grid;grid-template-columns:380px minmax(0,1fr);gap:12px;align-items:start}
body:not(.chat-split) .split-detail{display:block;position:sticky;top:64px;min-width:0;padding-left:20px;border-left:1px solid var(--rule)}
body:not(.chat-split) .split .lg-row.sel{background:var(--hover);box-shadow:inset 3px 0 0 var(--gold)}
body:not(.chat-split) .split-list .lg-row-expand{display:none}
}
/* ITEM 30d (D-1). Inside the split a lead row keeps its avatar and its name and gives back the two cells the detail pane
   already carries, the temperature badge and the status select. In a 380px list those two and their tracks took 306px
   and left the name 62. The cells stay in the DOM, so the phone row is the row it was. */
@media(min-width:900px){
body:not(.chat-split) .split .ledger.leads{--c-mi:0;--c-act:0}
body:not(.chat-split) .split .ledger.leads .lg-row > .lg-temp,
body:not(.chat-split) .split .ledger.leads .lg-row > .lg-sel{display:none}
}
.sd-top{display:flex;align-items:center;gap:12px;margin-bottom:12px}
.sd-name{font-size:var(--fs-4);font-weight:600;color:var(--dark)}
.sd-sub{font-size:var(--fs-2);color:var(--muted)}
.sd-top .badge{margin-left:auto}
.sd-line{font-size:var(--fs-3);color:var(--dark);padding:9px 0;border-top:1px solid var(--rule-soft)}
.sd-line .k{display:block;font-size:var(--fs-1);text-transform:uppercase;letter-spacing:.06em;color:var(--muted);margin-bottom:2px}
.sd-sel{max-width:220px;margin-top:10px}
/* SPLIT_END */

/* ── Lock screen ── */
/* ── Machines page: roster → detail redesign (namespaced mach-/mr-/md-) ── */
/* .mach-strip is the fleet band's own margin and nothing else: the columns, the ground, the
   dividers and the type all come from .lg-summary now (item 30b, B-5). The class name survives
   because the desktop no-scroll model pins this strip by name in the block above. */
.mach-strip{margin-bottom:12px}
.mach-ctrl{display:flex;align-items:center;gap:7px;margin-bottom:10px;flex-wrap:wrap}
.mach-ctrl .lbl{font-size:var(--fs-1);text-transform:uppercase;letter-spacing:.07em;color:var(--muted);font-weight:600}
/* 26px -> 44px. Flagged by visual-check.js at BOTH viewports and the last thing on the app still
   under the standard touch target. Left alone last batch only because the Machines page had just
   collapsed and I would not touch its layout blind; the harness now measures every section's
   scroll reach, so it is safe to do with evidence. inline-flex + align-items so the min-height
   grows the box rather than leaving the label floating at the top of it. */
.mach-sortbtn{display:inline-flex;align-items:center;font-size:var(--fs-2);padding:5px 13px;min-height:44px;border-radius:var(--r-control);border:1px solid var(--border);background:var(--white);color:var(--muted);font-weight:600;cursor:pointer;font-family:inherit}
.mach-sortbtn.on{background:var(--dark);color:var(--gold);border-color:var(--dark)}
.mach-filterwrap{margin-left:auto;display:flex;align-items:center;gap:6px;flex:1;justify-content:flex-end;min-width:140px}
.mach-count{font-size:var(--fs-1);color:var(--muted);white-space:nowrap}
.mach-filter{font-size:var(--fs-3);padding:7px 11px;border:1px solid var(--border);border-radius:var(--r-control);background:var(--white);color:var(--dark);outline:none;max-width:220px;width:100%;font-family:inherit}
.mach-filter:focus{border-color:var(--gold)}
.mach-layout{display:grid;grid-template-columns:1fr;gap:12px;align-items:start}
/* Desktop split: roster fixed ~380px, detail flex-fills ALL remaining width (minmax(0,…) lets it
   shrink so wide content wraps instead of clipping). The page also breaks out of the 640px reading
   column (below) so the pane isn't cramped. */
@media(min-width:900px){.mach-layout{grid-template-columns:380px minmax(0,1fr)}}
body.chat-split .mach-layout{grid-template-columns:1fr}
/* Break the Machines page out of the narrow .content column so the split can fill the width. */
@media(min-width:900px){body.tab-machines:not(.chat-split) .content{max-width:1400px}}
body.tab-machines.chat-split .content{max-width:none}
/* Machines desktop (≥900px, chat closed): full-viewport two-pane app — page scroll ELIMINATED. Body
   becomes a fixed-height flex column so the app header pins at top; inside it the fleet strip + sort
   row pin (flex:0 0 auto) and the roster/detail columns each fill the remaining height with min-
   height:0 and scroll independently — one scrollbar per column, none on the page. Chat-split + mobile
   keep the ordinary scrolling model (this whole block is scoped :not(.chat-split) and ≥900px). */
/* ITEM 30e (E-1): THE MODEL NOW HAS A LOWER BOUND ON HEIGHT. Everything above the columns comes
   straight out of what a column can show, so on a short window there is nothing left: measured with
   visual-check at 1440, the chrome above .mach-layout is 316.2px (app header 48 + title row 27 +
   fleet strip 91.2 + sort row 44, plus the gaps), which left about 384px per column on a 700px-tall
   window -- less than the Who-is-buying card needs on its own, and ZERO machine rows above the fold.
   T IS DERIVED, NOT CHOSEN: 316.2 measured + 500 (a column below 500px cannot hold the card and five
   roster rows together), rounded up to the nearest 20 = 820. Below 820px tall the page scrolls the
   ordinary way and both columns grow to their content; at 900px tall and up nothing changes. */
@media(min-width:900px) and (min-height:820px){
  body.tab-machines:not(.chat-split){height:100vh;overflow:hidden;display:flex;flex-direction:column}
  body.tab-machines:not(.chat-split) .app-header{flex:0 0 auto}
  body.tab-machines:not(.chat-split) .content{flex:1 1 0;min-height:0;display:flex;flex-direction:column;overflow:hidden;width:100%}
  body.tab-machines:not(.chat-split) #machines{flex:1 1 0;min-height:0;display:flex;flex-direction:column}
  /* #machines wraps a title/actions row + #machines-list; pin the title row and make the list the
     inner flex column that actually holds the strip, sort row, and the two-pane layout. */
  body.tab-machines:not(.chat-split) #machines > div:first-child{flex:0 0 auto}
  body.tab-machines:not(.chat-split) #machines-list{flex:1 1 0;min-height:0;display:flex;flex-direction:column}
  body.tab-machines:not(.chat-split) .mach-strip,
  body.tab-machines:not(.chat-split) .mach-ctrl{flex:0 0 auto}
  /* flex-basis 0 (not auto): the layout grows into the FREE space under the pinned strip/sort row
     instead of starting from its tall content height — that's what lets the columns bound to the
     viewport and scroll internally rather than overflowing the page. */
  body.tab-machines:not(.chat-split) .mach-layout{flex:1 1 0;min-height:0;grid-template-rows:minmax(0,1fr);align-items:stretch}
  body.tab-machines:not(.chat-split) .mach-lcol{min-height:0;overflow-y:auto;-webkit-overflow-scrolling:touch}
  body.tab-machines:not(.chat-split) .mach-detail{min-height:0;max-height:none;position:static;overflow-y:auto;-webkit-overflow-scrolling:touch}
}
/* ROSTER_CSS_START */
/* ITEM 30b (B-8). A ledger's rows touch -- the rules do the separating -- so the 6px gap between
   roster cards goes with the cards. .mach-row keeps only what .lg-row cannot know: the side
   padding that lines it up with the fleet band above it. The .top/.nm/.sn/.mid/.fin rules that
   dressed the card are DELETED rather than overridden: .mach-row sits later in this file than
   .lg-row, so a surviving grid-template-columns there would silently beat the primitive. */
.mach-roster{display:flex;flex-direction:column}
/* Left column wraps the roster + the fleet-level HAHA/tax cards so they share ONE scroll context in
   the desktop no-scroll model (below). On mobile/chat-split it's a plain stacked block. */
.mach-lcol{display:flex;flex-direction:column;min-width:0}
.mach-aside{margin-top:12px}
/* ITEM 30e (E-3): the by-machine name in the 380px split column, which is D-1's arithmetic in a
   different table. MEASURED, and worse than expected: the name track lands at 64px, so even
   "Fixture Site 3" clipped, never mind a real 36-character business name at 291px.
   WHERE THIS DEPARTS FROM THE PROMPT, AND WHY: 30e's E-3 said to zero --c-mi and hide "the visits
   cell". Those are two different columns. Read off fryRowsHtml, the children are
   [empty][name][Customers -> --c-mi][Visits -> --c-usd][Avg -> --c-act], so --c-mi is CUSTOMERS and
   zeroing it would have dropped the wrong figure while leaving Visits in place. The code wins:
   Visits is --c-usd, and that is the one that goes.
   Dropping it returns 64px, which is still short of the 291px a long name needs -- so the name is
   also allowed to WRAP here rather than ellipsise. Two lines of a readable business name beats one
   line of "Gore ...", and the three tiles above plus the detail pane's Customers tab still carry
   visits per customer, so no figure is actually lost. Phone widths are untouched. */
@media(min-width:900px){
body:not(.chat-split) .mach-lcol .ledger.fry{--c-usd:0}
body:not(.chat-split) .mach-lcol .ledger.fry .lg-vis{display:none}
body:not(.chat-split) .mach-lcol .ledger.fry .lg-what .p{white-space:normal;overflow:visible;text-overflow:clip}
}
.mach-row{padding-left:6px;padding-right:6px}
/* ROSTER_CSS_END */
.mr-pos{color:#2E7D32}.mr-neg{color:#C62828}
.mr-chip{font-size:var(--fs-1);font-weight:600;padding:2px 7px;border-radius:var(--r-control);display:inline-flex;align-items:center;gap:3px;white-space:nowrap;line-height:1.5}
.mr-chip.quiet{background:transparent;color:var(--muted);border:1px solid var(--parchment)}
.mr-chip.quiet .pen{opacity:.55;font-weight:400}
.mr-chip.sugg{background:var(--amber-lt);color:#8A5A00;border:1px dashed #C89A4A}
.mr-chip.sugg .tag{font-weight:600;opacity:.85}
.mr-chip.frozen{background:#E9F1FB;color:#2F5C93;border:1px solid #BCD6F2}
.mach-badge{font-size:var(--fs-1);font-weight:600;padding:2px 7px;border-radius:var(--r-control);white-space:nowrap}
.mb-low{background:#FFEBEE;color:#C62828}.mb-med{background:#FFFDE7;color:#F57F17}.mb-ok{background:#F1F8E9;color:#2E7D32}
.mb-sync{background:var(--parchment);color:var(--muted)}.mb-stale{background:var(--amber-lt);color:#B45309}
.mb-setup{background:var(--amber-lt);color:#8A5A00;border:1px dashed #C89A4A}
/* detail */
.mach-detail{background:var(--white);border:1px solid var(--parchment);border-radius:var(--r-panel);overflow-y:auto;max-height:calc(100vh - 96px);max-height:calc(100dvh - 96px);overscroll-behavior:contain}
/* ITEM 30e (E-1, the second half). Below T the no-scroll block stops applying, so the page scrolls
   again -- but the rule directly above gives .mach-detail its OWN overflow-y:auto and max-height, so
   the detail pane keeps a nested scroller on a page that now scrolls, which is the same complaint
   with extra steps. MEASURED TWICE, and the first placement was wrong: with this block sitting
   ABOVE the declaration it overrides, the cascade handed the base rule the win on equal specificity
   and the pane still computed max-height:604px (700 - 96) at 1545x700. It has to come after. The
   height alone is also not enough -- the overflow has to go too. Both columns give up their
   scrollers here and grow to their content instead. */
@media(min-width:900px) and (max-height:819px){
.mach-detail{max-height:none;overflow:visible}
.mach-lcol{overflow:visible}
}
.mach-detail.empty{display:flex;align-items:center;justify-content:center;text-align:center;color:var(--muted);padding:50px 20px;min-height:220px}
.md-head{background:var(--dark);color:var(--cream);padding:13px 16px}
.md-head .dnm{font-family:'Playfair Display',serif;font-size:var(--fs-5);font-weight:600}
.md-head .dsn{font-size:var(--fs-2);color:rgba(255,253,247,0.6);font-variant-numeric:tabular-nums;margin-top:1px}
.md-head .dchips{display:flex;gap:6px;margin-top:8px;flex-wrap:wrap;align-items:center}
/* Mobile back control: filled dark/gold pill so it stands out from the white section-nav pills on
   the cream strip (was a low-contrast white ‹ that read as just another section tab). */
.md-back{display:none;align-items:center;justify-content:center;gap:3px;font-size:var(--fs-3);font-weight:600;line-height:1;background:var(--dark);color:var(--gold);border:1px solid var(--dark);border-radius:var(--r-control);min-width:40px;padding:7px 14px;cursor:pointer;font-family:inherit;flex:0 0 auto;box-shadow:0 1px 5px rgba(0,0,0,0.2)}
.md-back .md-back-ic{font-size:var(--fs-5);line-height:1;margin-top:-1px}
.md-back:hover{background:#000;border-color:var(--gold)}
/* Section nav: sticky wrapper holds the position + the right-edge fade; the inner strip scrolls with
   NO visible scrollbar (chips swipe/drag). The fade sits over empty cream when the chips fit, and
   hints "more →" when they overflow (narrow panes / chat open). */
.md-navwrap{position:sticky;top:0;z-index:5;background:var(--cream);border-bottom:1px solid var(--parchment)}
.md-navwrap::after{content:'';position:absolute;top:0;right:0;height:100%;width:22px;background:linear-gradient(90deg,rgba(255,253,247,0),var(--cream));pointer-events:none}
.md-nav{display:flex;gap:4px;padding:7px 9px;overflow-x:auto;-webkit-overflow-scrolling:touch;scrollbar-width:none;-ms-overflow-style:none}
.md-nav::-webkit-scrollbar{display:none}
.md-nav button{font-size:var(--fs-2);font-weight:600;padding:5px 10px;border-radius:var(--r-control);border:1px solid var(--parchment);background:var(--white);color:var(--muted);white-space:nowrap;cursor:pointer;font-family:inherit}
.md-nav button.on{background:var(--gold);color:var(--dark);border-color:var(--gold)}
.md-body{padding:2px 15px 16px}
.md-sec{border-bottom:1px solid var(--parchment);padding:12px 0}
.md-sec:last-child{border-bottom:none}
.md-sec>h3{font-size:var(--fs-1);text-transform:uppercase;letter-spacing:.07em;color:var(--muted);display:flex;align-items:center;justify-content:space-between;font-weight:600;cursor:pointer;user-select:none;margin:0}
.md-sec>h3 .car{transition:transform .18s;color:var(--gold);font-size:var(--fs-1)}
.md-sec.closed>h3 .car{transform:rotate(-90deg)}
.md-sec .sbody{margin-top:10px}
.md-sec.closed .sbody{display:none}
/* Metric tiles wrap to the PANE width (auto-fit), never clip — works in the wide split, the narrow
   chat-open pane, and mobile alike, so it needs no viewport breakpoint. */
/* .md-reg is the Customers band's margin and nothing else now (item 30b, B-5). */
.md-reg{margin-bottom:2px}
.md-boundary{background:var(--amber-lt);border:1px solid #E5C98F;border-radius:var(--r-panel);padding:8px 11px;font-size:var(--fs-2);color:#8A5A00;margin-bottom:10px}
.md-boundary b{color:var(--dark)}
/* ITEM 62: the five-row .md-regtable became a list of expandable rows. A row is a grid rather
   than a table cell so the expanded panel can open IN PLACE underneath it without a colspan, and
   so the row itself can be the 44px button the pattern opens from. */
.md-regchips{display:flex;gap:6px;flex-wrap:wrap;margin:12px 0 2px}
.md-reghead,.md-regrow{display:grid;grid-template-columns:1fr auto auto auto;gap:10px;align-items:center}
.md-reghead{font-size:var(--fs-1);color:var(--muted);font-weight:600;letter-spacing:0.06em;text-transform:uppercase;padding:0 3px 4px}
.md-regs{display:block;margin-bottom:2px}
.md-regrow{width:100%;min-height:44px;padding:6px 3px;border:0;border-bottom:1px solid var(--rule-soft);border-radius:0;background:transparent;color:var(--dark);font-family:inherit;font-size:var(--fs-3);text-align:left}
button.md-regrow{cursor:pointer}
button.md-regrow:focus-visible{outline:2px solid var(--gold);outline-offset:-2px}
.md-regrow .rk{color:var(--muted);font-variant-numeric:tabular-nums}
.md-regrow .vs,.md-regrow .sp,.md-regrow .sh{font-variant-numeric:tabular-nums;text-align:right}
.md-regrow .sp{font-weight:600}
.md-regrow .sh{color:var(--muted)}
.md-regnote{font-size:var(--fs-1);color:var(--muted);padding:2px 3px 8px;border-bottom:1px solid var(--rule-soft)}
.md-regdet{padding:10px 3px 12px;border-bottom:1px solid var(--rule-soft);font-size:var(--fs-2)}
.md-regdet .usual{color:var(--dark);font-weight:600;font-size:var(--fs-3);margin-bottom:2px}
.md-regdays{display:grid;grid-template-columns:repeat(7,1fr);gap:4px;margin:10px 0}
/* The label takes the SAME ink as the number. It began on var(--muted), which reads 1.46:1 on
   the busiest cell -- the one cell the operator is actually looking at -- because the fill darkens
   toward the accent while the ink stayed put. Measured at the eyeball stop, 2026-09-20: the number
   on var(--dark) is 4.63:1 at full accent and 14.8:1 on an empty day, and the label now matches it.
   Hierarchy comes from size and weight instead of from a second colour. */
.md-regday{border-radius:var(--r-control);padding:4px 0;text-align:center;font-size:var(--fs-1);font-weight:400;color:var(--dark)}
.md-regday b{display:block;font-size:var(--fs-2);font-weight:600;color:var(--dark);font-variant-numeric:tabular-nums}
.md-reghours{color:var(--muted);margin-bottom:6px}
.md-regprod{display:flex;justify-content:space-between;gap:10px;padding:3px 0;color:var(--muted)}
.md-regprod b{color:var(--dark);font-weight:600}
.md-regfoot{color:var(--muted);font-size:var(--fs-1);margin-top:8px}
.md-note{font-size:var(--fs-1);color:var(--muted);margin-top:8px}
/* WHENCSS_START — item 64, the When it sells grid. Weight is carried by the LIGHTNESS of this
   instance's own accent (machHeatBg, the ONE heat helper the top buyers day strip also calls), so
   there is no second palette and no hex anywhere in here. NO NUMBER AND NO LABEL SITS INSIDE A
   CELL: the row and column labels sit outside the squares on the card's own surface and the
   readout is a line of body text under the grid, which is why the 1.46:1 label item 62 shipped on
   exactly this pattern cannot recur here. The ROW is the 44px target. */
.md-whsum{color:var(--dark);font-weight:600;font-size:var(--fs-3);margin:12px 0 2px;line-height:1.4}
.md-whwrap{margin:10px 0 2px}
.md-whwrap.wide{overflow-x:auto;overscroll-behavior-x:contain;-webkit-overflow-scrolling:touch}
.md-whgrid{display:grid;gap:2px;align-items:stretch}
.md-whcorner,.md-whcol{min-height:15px}
.md-whcol{font-size:var(--fs-1);color:var(--muted);text-align:center;line-height:1.2}
.md-whrowlab{font-size:var(--fs-1);color:var(--muted);display:flex;align-items:center;min-height:44px}
.md-whcell{border:0;border-radius:var(--r-control);padding:0;margin:0;width:100%;min-height:44px;display:block;cursor:pointer;box-shadow:inset 0 0 0 1px var(--rule-soft)}
.md-whcell:focus-visible{outline:2px solid var(--gold);outline-offset:-2px}
.md-whcell.sel{box-shadow:inset 0 0 0 2px var(--dark)}
.md-whread{font-size:var(--fs-2);color:var(--dark);margin:8px 0 2px;min-height:19px;line-height:1.4}
.md-whpeaks{margin-top:10px}
.md-whpeakhead{font-size:var(--fs-1);color:var(--muted);font-weight:600;letter-spacing:0.06em;text-transform:uppercase;padding:0 0 4px}
.md-whpeak{display:flex;justify-content:space-between;gap:10px;padding:3px 0;font-size:var(--fs-2);color:var(--muted)}
.md-whpeak span:first-child{color:var(--dark)}
.md-whcell:hover{box-shadow:inset 0 0 0 2px var(--dark)}
.md-whleg{display:flex;align-items:center;gap:8px;flex-wrap:wrap;font-size:var(--fs-1);color:var(--muted);margin:6px 0 2px}
.md-whlegbar{display:inline-block;width:96px;height:10px;border-radius:var(--r-control);box-shadow:inset 0 0 0 1px var(--rule-soft)}
.md-whtip{position:fixed;left:0;top:0;z-index:var(--z-modal);pointer-events:none;opacity:0;transition:opacity .09s ease;background:var(--white);border:1px solid var(--border);border-radius:var(--r-panel);box-shadow:0 4px 16px var(--shadow-pop);padding:8px 10px;font-size:var(--fs-2);line-height:1.4;color:var(--dark);width:max-content;max-width:260px}
.md-whtip.on{opacity:1}
.md-whtip b{display:block;font-weight:600}
.md-whtip span{display:block;color:var(--muted)}
.md-whopen{margin:8px 0 2px}
/* WHENCSS_END */
/* ROTCSS_START -- the machine card's Rotation list (2026-09-28). A list of rows, not a table: seven
   columns do not fit a phone, and every figure here needs its words beside it anyway. */
.md-rotlist{margin-top:10px}
.md-rotrow{padding:9px 0;border-top:1px solid var(--rule-soft)}
.md-rotrow:first-child{border-top:0}
.md-rothead{display:flex;justify-content:space-between;align-items:baseline;gap:10px}
.md-rotname{color:var(--dark);font-weight:600;font-size:var(--fs-3);min-width:0;overflow-wrap:anywhere}
.md-rotrate{color:var(--dark);font-size:var(--fs-2);white-space:nowrap}
.md-rotmeta{color:var(--muted);font-size:var(--fs-2);line-height:1.45;margin-top:2px}
.md-rotzero{color:var(--dark);font-weight:600}
.md-rotacts{display:flex;align-items:center;gap:8px;flex-wrap:wrap;margin-top:6px;font-size:var(--fs-2);color:var(--muted)}
.md-rotin{width:84px}
/* Sales on the planogram tiles (PGSALES). The ring is --blue: none of slotColor's stock colours. */
.pg-shead{display:flex;flex-wrap:wrap;align-items:center;gap:4px 14px;margin:2px 0 4px}
.pg-period{border:0;background:transparent;font-family:inherit;font-size:var(--fs-2);color:var(--muted);padding:0;min-height:32px;cursor:pointer}
.pg-period strong{color:var(--dark);font-weight:600}
.pg-key{display:inline-flex;align-items:center;gap:7px;font-size:var(--fs-1);color:var(--muted)}
.pg-keyring{display:inline-block;width:11px;height:11px;border-radius:3px;box-shadow:0 0 0 2px var(--blue);flex:none}
.pg-snote{margin:2px 0 4px}
.pg-sold{font-size:10px;font-weight:600;color:var(--mid);line-height:1.25;margin-top:3px;overflow-wrap:anywhere}
.pg-sold:empty{display:none}
.pg-sold.muted{color:var(--muted);font-weight:500;font-style:italic}
.pg-ring{box-shadow:0 0 0 3px var(--blue)}
/* ROTCSS_END */
/* VBPOPCSS_START — item 65 part C. The page's own dropdown and calendar. Same surface, rule, ink
   and radius as the panel they sit over; the chosen row carries the accent as a BAR and bold text,
   never a filled system highlight, because a filled row is the operating system's idiom and it
   fights every other selected state on this page. Tokens only, the two radii, the one type scale,
   and 44px targets on everything a finger lands on. */
.vb-pop{position:fixed;left:0;top:0;z-index:var(--z-modal);display:none;background:var(--white);border:1px solid var(--rule);border-radius:var(--r-panel);box-shadow:0 8px 22px var(--shadow-pop);padding:4px;font-size:var(--fs-3);color:var(--dark);max-height:60vh;overflow-y:auto;overscroll-behavior:contain}
.vb-pop.on{display:block}
.vb-opt{display:block;width:100%;min-height:44px;text-align:left;border:0;background:none;font-family:inherit;font-size:var(--fs-3);color:var(--dark);padding:9px 12px 9px 16px;border-radius:var(--r-control);cursor:pointer;position:relative}
.vb-opt:hover,.vb-opt:focus-visible{background:var(--hover);outline:none}
.vb-opt.on{font-weight:600}
.vb-opt.on::before{content:'';position:absolute;left:5px;top:10px;bottom:10px;width:3px;border-radius:var(--r-control);background:var(--gold)}
/* A refused option is SHOWN, greyed, with its reason under it. Never silently absent: a control
   that drops an option the moment it stops applying is a control that lies about the product. */
.vb-opt.off{color:var(--muted);cursor:default}
.vb-opt.off:hover{background:none}
.vb-optl{display:block}
.vb-optwhy{display:block;font-size:var(--fs-1);color:var(--muted);line-height:1.35;margin-top:2px;white-space:normal}
/* The calendar. 44px days at 375 fit inside the viewport minus 16px, so the day target is the same
   size as every other target on a phone. */
.vb-pop.cal{padding:10px;width:340px;max-width:100%;overflow:visible}
.vb-calh{display:flex;align-items:center;justify-content:space-between;gap:6px;margin-bottom:4px}
.vb-calh b{font-size:var(--fs-3);font-weight:600}
.vb-calnav{width:44px;height:44px;border:0;background:none;font-family:inherit;font-size:var(--fs-3);line-height:1;color:var(--dark);border-radius:var(--r-control);cursor:pointer}
.vb-calnav:hover:not([disabled]){background:var(--hover)}
.vb-calnav[disabled]{opacity:.3;cursor:default}
.vb-calg{display:grid;grid-template-columns:repeat(7,minmax(0,1fr));gap:2px}
.vb-calwd{font-size:var(--fs-1);color:var(--muted);text-align:center;padding:4px 0}
.vb-calpad{display:block}
.vb-calday{border:0;background:none;font-family:inherit;font-size:var(--fs-2);min-height:44px;padding:0;border-radius:var(--r-control);cursor:pointer;color:var(--dark)}
.vb-calday:hover:not([disabled]),.vb-calday:focus-visible{box-shadow:inset 0 0 0 2px var(--dark);outline:none}
.vb-calday.in{background:var(--hover);border-radius:0}
.vb-calday.end{background:var(--gold);color:var(--dark);font-weight:600;border-radius:var(--r-control)}
.vb-calday[disabled]{opacity:.3;cursor:default}
.vb-calf{font-size:var(--fs-1);color:var(--muted);margin-top:8px;line-height:1.4}
/* VBPOPCSS_END */
/* SALESXCSS_START — item 65, the Sales explorer. Every colour is an instance token and every size
   is on the house scale; the two radii and nothing else. What the mockup contributed is the
   STRUCTURE — six equal labelled fields in one ruled panel, a ruled figures strip instead of
   floating cards, text links for the presets — and where it disagreed with a house primitive the
   primitive won: the machine cut is .filter-chip and the picture's switches are .btn. No new
   capsule, no new radius, no gradient except the heat key's, no fade-in. */
.sx-ctl{display:grid;grid-template-columns:repeat(6,minmax(0,1fr));border:1px solid var(--rule);border-radius:var(--r-panel);background:var(--white);overflow:hidden}
.sx-f{display:block;padding:8px 12px 9px;border-left:1px solid var(--rule-soft);min-width:0;position:relative}
.sx-f:first-child{border-left:0}
.sx-fl{display:block;font-size:var(--fs-1);letter-spacing:0.06em;text-transform:uppercase;color:var(--muted);margin-bottom:2px}
/* The value is the control. It never changes width when its value changes, so the row does not
   reflow as the operator reads it. */
.sx-fv{display:block;width:100%;min-width:0;min-height:36px;border:0;background:transparent;font-family:inherit;font-size:var(--fs-4);font-weight:600;color:var(--dark);padding:0 16px 0 0;margin:0;cursor:pointer;text-align:left;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.sx-fv:focus{outline:none}
.sx-f:hover{background:var(--hover)}
.sx-f:focus-within{box-shadow:inset 0 -2px 0 var(--gold)}
.sx-f::after{content:'';position:absolute;right:12px;bottom:18px;width:6px;height:6px;border-right:1.5px solid var(--muted);border-bottom:1.5px solid var(--muted);transform:rotate(45deg);pointer-events:none}
.sx-starts{display:flex;gap:2px 14px;flex-wrap:wrap;align-items:center;margin:10px 0 14px}
.sx-slab{font-size:var(--fs-1);letter-spacing:0.06em;text-transform:uppercase;color:var(--muted)}
.sx-look{border:0;background:none;padding:10px 0;min-height:44px;font-family:inherit;font-size:var(--fs-3);color:var(--muted);cursor:pointer;text-decoration:underline;text-underline-offset:3px;text-decoration-color:var(--rule)}
.sx-look:hover{color:var(--dark);text-decoration-color:var(--dark)}
.sx-look.on{color:var(--dark);font-weight:600;text-decoration-color:var(--gold);text-decoration-thickness:2px}
.sx-zoomout{border:0;background:none;font-family:inherit;font-size:var(--fs-3);font-weight:600;color:var(--gold);cursor:pointer;padding:10px 0;min-height:44px;margin-left:auto}
/* The change and the sparkline sit on the DARK figures band, so they wear the on-dark pair the
   band already uses rather than the page's own red and green. */
.sx-delta{font-size:var(--fs-2);font-weight:600;margin-left:8px}
.sx-delta.up{color:var(--green)}
.sx-delta.down{color:var(--red-lt)}
.sx-spark{display:inline-block;width:88px;height:20px;margin-left:8px;vertical-align:middle}
.sx-panel{padding:14px 16px 16px}
.sx-sub{display:flex;justify-content:space-between;align-items:flex-start;gap:10px;flex-wrap:wrap;margin-bottom:6px}
.sx-t{font-weight:600;font-size:var(--fs-4);color:var(--dark);line-height:1.3}
.sx-n{font-size:var(--fs-2);color:var(--muted);margin-top:2px;line-height:1.4}
.sx-tools{display:flex;gap:6px;flex-wrap:wrap}
.sx-note{font-size:var(--fs-2);color:var(--muted);margin-top:8px;line-height:1.5}
.sx-legend{display:flex;gap:14px;flex-wrap:wrap;font-size:var(--fs-2);color:var(--muted);margin:2px 0 6px}
.sx-legend i{width:9px;height:9px;border-radius:var(--r-control);display:inline-block;margin-right:6px}
.sx-svg{display:block;width:100%;overflow:visible}
.sx-mk:hover,.sx-mk:focus{opacity:0.78;outline:none}
.sx-z{cursor:zoom-in}
.sx-rows{margin-top:6px}
.sx-row{display:grid;grid-template-columns:minmax(110px,230px) 1fr 104px;gap:14px;align-items:center;padding:10px 0;min-height:44px;border:0;border-top:1px solid var(--rule-soft);background:none;width:100%;font-family:inherit;font-size:var(--fs-3);color:var(--dark);text-align:left}
.sx-rows>*:first-child{border-top:0}
button.sx-row{cursor:pointer}
button.sx-row:hover{background:var(--hover)}
.sx-row.dim{opacity:0.45}
.sx-rn{white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.sx-rn i{width:8px;height:8px;border-radius:var(--r-control);display:inline-block;margin-right:8px}
.sx-trs{display:grid;gap:3px;min-width:0}
.sx-tr{height:10px;background:var(--rule-soft);border-radius:var(--r-control);overflow:hidden;display:block}
.sx-tr b{display:block;height:100%;border-radius:var(--r-control)}
.sx-ra{text-align:right;font-weight:600}
.sx-ra small{display:block;color:var(--muted);font-size:var(--fs-2);font-weight:400}
.sx-grids{display:grid;gap:18px;margin-top:6px}
.sx-grids.many{grid-template-columns:repeat(2,minmax(0,1fr))}
.sx-gpanel{min-width:0}
.sx-gtitle{font-size:var(--fs-3);font-weight:600;margin-bottom:4px;color:var(--dark)}
.sx-gtitle i{width:8px;height:8px;border-radius:var(--r-control);display:inline-block;margin-right:7px}
.sx-gtitle small{font-weight:400;color:var(--muted);font-size:var(--fs-1)}
.sx-tblw{overflow-x:auto;overscroll-behavior-x:contain;-webkit-overflow-scrolling:touch;margin-top:12px;border-top:1px solid var(--rule)}
.sx-tbl{width:100%;border-collapse:collapse;font-size:var(--fs-3)}
.sx-tbl th{font-size:var(--fs-1);letter-spacing:0.06em;text-transform:uppercase;color:var(--muted);text-align:left;font-weight:600;padding:8px;border-bottom:1px solid var(--rule);white-space:nowrap}
.sx-tbl td{padding:6px 8px;border-bottom:1px solid var(--rule-soft);white-space:nowrap}
.sx-tbl td.n,.sx-tbl th.n{text-align:right}
/* At 375 the six fields are three rows of two, the Start from row scrolls sideways inside itself
   rather than wrapping into four lines, and the grids stack. The PAGE never scrolls sideways. */
@media(max-width:760px){
  .sx-ctl{grid-template-columns:repeat(2,minmax(0,1fr))}
  .sx-f{border-top:1px solid var(--rule-soft)}
  .sx-f:nth-child(odd){border-left:0}
  .sx-f:nth-child(-n+2){border-top:0}
  .sx-starts{flex-wrap:nowrap;overflow-x:auto;overscroll-behavior-x:contain;white-space:nowrap;scrollbar-width:none}
  .sx-starts::-webkit-scrollbar{display:none}
  .sx-slab{display:none}
  .sx-look,.sx-zoomout{flex:0 0 auto}
  .sx-zoomout{margin-left:14px}
  .sx-grids.many{grid-template-columns:1fr}
  .sx-row{grid-template-columns:1fr 96px;row-gap:4px}
  .sx-rn{grid-column:1/-1}
}
/* SALESXCSS_END */
/* AI chat markdown tables (mdToHtml). The panel is narrow, so the wrapper scrolls sideways
   instead of the table squeezing product names into one letter per line. */
.aitblw{overflow-x:auto;overscroll-behavior-x:contain;-webkit-overflow-scrolling:touch;margin:8px 0;border:1px solid var(--parchment);border-radius:var(--r-panel)}
.aitbl{border-collapse:collapse;font-size:var(--fs-3);line-height:1.35;width:100%}
.aitbl th{padding:6px 9px;text-align:left;background:var(--parchment);color:var(--dark);font-weight:600;white-space:nowrap;border-bottom:1px solid var(--border)}
.aitbl td{padding:5px 9px;border-top:1px solid var(--parchment);vertical-align:top}
.aitbl td.n,.aitbl th.n{text-align:right;font-variant-numeric:tabular-nums;white-space:nowrap}
.aitbl td.c,.aitbl th.c{text-align:center}
.aitbl tr:nth-child(even) td{background:rgba(0,0,0,.02)}
.ai-hr{border:0;border-top:1px solid var(--parchment);margin:10px 0}
/* Chat-split keeps the scrolling model (single column, page scrolls) — detail sticks under the header
   there. The no-scroll two-pane model above owns the chat-closed case (detail is position:static). */
@media(min-width:900px){body.tab-machines.chat-split .mach-detail{position:sticky;top:80px}}
@media(max-width:899px){
  .mach-detail{display:none}
  body.mach-open .mach-detail{display:block;position:fixed;inset:0;z-index:var(--z-overlay);max-height:none;height:100dvh;border-radius:0}
  /* F1: scroll-lock is now the single lockScroll() mechanism (body.scroll-locked) — mach-open no
     longer locks on its own. Keep the class: the CSS above and the back button still key off it. */
  body.mach-open .md-back{display:inline-flex}
  /* Inventory-first card (2026-09-04): five buttons in the jump strip. At phone width the default
     padding overflowed by ~25px and the last one clipped behind the fade; tightened so all five fit
     at 375 without scrolling. Desktop keeps the roomier spacing. */
  .md-nav{gap:3px;padding:7px 8px}
  .md-nav button{font-size:var(--fs-1);padding:5px 8px}
  /* F3: no floating FAB/checklist bubble riding on top of the full-screen detail view. */
  body.mach-open .ai-fab,body.mach-open #demo-guide-pill,body.mach-open #vb-spot,body.mach-open #vb-spot-ring{display:none}
}
/* Spotlight (claude/SPOTLIGHT-SPEC.md rev 3). A RING on the real control, not a card in the corner.
   Both layers are pointer-events:none on purpose: the page underneath stays completely live, so one
   tap on the ringed control does the real thing and clears the spotlight in the same gesture. There
   is no control of ours to hit, and nothing of ours to hit by accident.
   The glow uses rgba(var(--accent-rgb),n), which is the house pattern for a brand-tinted colour and
   keeps this region free of the hex/rgb literals predeploy bans. */
#vb-spot-ring{position:fixed;z-index:var(--z-spot);pointer-events:none;border:3px solid var(--gold);border-radius:var(--r-panel);opacity:0;transition:opacity 220ms ease-out;box-shadow:0 0 0 3px rgba(var(--accent-rgb),0.30),0 0 26px 8px rgba(var(--accent-rgb),0.55),inset 0 0 14px rgba(var(--accent-rgb),0.28)}
#vb-spot-ring.vb-spot-in{opacity:1}
#vb-spot-ring.vb-spot-out{opacity:0;transition-duration:140ms}
@keyframes vbSpotPulse{
  0%,100%{box-shadow:0 0 0 3px rgba(var(--accent-rgb),0.30),0 0 26px 8px rgba(var(--accent-rgb),0.55),inset 0 0 14px rgba(var(--accent-rgb),0.28)}
  50%{box-shadow:0 0 0 8px rgba(var(--accent-rgb),0.14),0 0 42px 15px rgba(var(--accent-rgb),0.78),inset 0 0 20px rgba(var(--accent-rgb),0.42)}
}
/* Pulses for SPOT_PULSE_MS then holds a steady glow. A thing that pulses forever stops being a
   signal and becomes a twitch, and this one has no timer to take it off the screen. */
#vb-spot-ring.vb-spot-pulse{animation:vbSpotPulse 1.6s ease-in-out infinite}
/* Dark on the cream page, the same pairing as the app header, because the rev 2 card was muted grey
   on a light card and Benjamin could not read it. */
#vb-spot{position:fixed;z-index:var(--z-spot);pointer-events:none;background:var(--dark);color:var(--cream);border:1px solid rgba(var(--accent-rgb),0.45);border-radius:var(--r-panel);padding:10px 14px;box-shadow:0 6px 24px var(--shadow-pop);opacity:0;transform:translateY(4px);transition:opacity 220ms ease-out,transform 220ms ease-out}
#vb-spot.vb-spot-in{opacity:1;transform:translateY(0)}
#vb-spot.vb-spot-out{opacity:0;transition-duration:140ms}
.vb-spot-eyebrow{font-size:var(--fs-1);font-weight:600;letter-spacing:0.7px;text-transform:uppercase;color:var(--gold)}
.vb-spot-body{font-size:var(--fs-4);line-height:1.45;margin-top:3px;font-weight:600}
.vb-spot-cue{font-size:var(--fs-1);line-height:1.4;margin-top:7px;color:var(--gold);font-weight:600}
@media(prefers-reduced-motion:reduce){
  #vb-spot-ring,#vb-spot{transition:opacity 200ms linear}
  #vb-spot,#vb-spot.vb-spot-in{transform:none}
  #vb-spot-ring.vb-spot-pulse{animation:none}
}
/* ── Responsive summary/breakdown grids: mirror the Machines .md-reg auto-collapse so no tab crams
   at 390px. Base columns live here (not inline) so the max-width media queries can override. ── */
/* #sales-summary and #receipts-summary own no columns of their own any more: both are bands
   (item 30b, B-5), and .lg-summary.c3 is where three cells across is declared. */
.bill-breakdown4{display:grid;grid-template-columns:repeat(4,1fr);gap:6px}
@media(max-width:520px){.bill-breakdown4{grid-template-columns:repeat(2,1fr)}}
#lock-screen{position:fixed;inset:0;z-index:var(--z-lock);background:var(--dark);display:flex;align-items:center;justify-content:center;padding:24px}
#lock-screen.hidden{display:none}
.lock-card{background:#231108;border:1px solid rgba(var(--accent-rgb),0.25);border-radius:var(--r-panel);padding:40px 32px;width:100%;max-width:360px;text-align:center}
.lock-logo{font-family:'Playfair Display',serif;font-size:var(--fs-6);color:var(--cream);margin-bottom:4px}
.lock-logo span{color:var(--gold)}
.lock-tagline{font-size:var(--fs-2);color:rgba(253,250,244,0.4);letter-spacing:0.5px;margin-bottom:32px}
.lock-label{font-size:var(--fs-2);color:rgba(253,250,244,0.5);letter-spacing:0.5px;text-align:left;margin-bottom:8px;display:block}
.lock-input{width:100%;padding:13px 16px;background:rgba(255,255,255,0.06);border:1px solid rgba(var(--accent-rgb),0.25);border-radius:var(--r-control);color:var(--cream);font-size:var(--fs-4);font-family:'DM Sans',sans-serif;letter-spacing:0.1em;outline:none;transition:border-color 0.2s;margin-bottom:16px}
.lock-input:focus{border-color:rgba(var(--accent-rgb),0.6)}
.lock-btn{width:100%;padding:13px;background:var(--gold);color:var(--dark);font-family:'DM Sans',sans-serif;font-size:var(--fs-4);font-weight:500;border:none;border-radius:var(--r-control);cursor:pointer;transition:opacity 0.15s;letter-spacing:0.3px}
.lock-btn:hover{opacity:0.88}
.lock-btn:disabled{opacity:0.5;cursor:not-allowed}
.lock-error{font-size:var(--fs-2);color:#E87070;margin-top:12px;min-height:18px}

/* ─── SETUP CHECKLIST ─── */
/* ITEM 30c (A-1): the floating bubble, its pulse animation and its completed pill are deleted with it. */
.cl-next { padding: 11px 14px; border-bottom: 1px solid var(--border); background: var(--parchment); }
.cl-next-label { font-size:var(--fs-1); text-transform: uppercase; letter-spacing: 0.7px; color: var(--muted); font-weight: 600; margin-bottom: 6px; }
.cl-next-btn { display: block; width: 100%; text-align: left; font-size:var(--fs-3); font-weight: 600; color: var(--dark); background: var(--gold); border: none; border-radius:var(--r-control); padding: 10px 12px; cursor: pointer; line-height: 1.35; }
.cl-next-btn:hover { filter: brightness(1.05); }
.cl-item.cl-optional .cl-item-text strong { text-decoration: none; }
.cl-item.cl-optional.checked .cl-item-text strong { text-decoration: none; color: var(--dark); }
.cl-intro {
  padding: 10px 14px 12px;
  border-bottom: 1px solid var(--border);
  font-size:var(--fs-1);
  color: var(--muted);
  line-height: 1.5;
}
.cl-intro strong { display: block; color: var(--dark); font-size:var(--fs-2); margin-bottom: 4px; }
#cl-panel {
  display: none;
  position: fixed;
  bottom: calc(140px + env(safe-area-inset-bottom,0px));
  right: 20px;
  z-index: calc(var(--z-checklist) + 10);
  /* 340px + right:20px left a 375px phone with 15px of breathing room and no way to shrink.
     min() keeps the desktop width and lets the panel fit the screen it is actually on. */
  width: min(340px, calc(100vw - 32px));
  max-height: min(70vh, calc(100vh - 200px));
  background: var(--white);
  border-radius:var(--r-panel);
  box-shadow: 0 8px 40px rgba(0,0,0,0.2);
  overflow: hidden;
  overscroll-behavior: contain;
  flex-direction: column;
}
#cl-panel.open { display: flex; }
.cl-panel-header {
  background: var(--dark);
  padding: 14px 16px;
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-shrink: 0;
}
.cl-panel-header h3 {
  font-family: 'Playfair Display', serif;
  font-size:var(--fs-4);
  color: var(--cream);
  margin: 0;
}
.cl-panel-header span {
  font-size:var(--fs-1);
  color: var(--gold);
  font-weight: 600;
}
.cl-progress {
  height: 4px;
  background: var(--border);
  flex-shrink: 0;
}
.cl-progress-bar {
  height: 100%;
  background: var(--gold);
  transition: width 0.3s ease;
}
.cl-panel-body {
  overflow-y: auto;
  flex: 1;
  padding: 8px 0;
}
.cl-section-title {
  font-size:var(--fs-1);
  font-weight: 600;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--gold);
  padding: 10px 14px 4px;
}
.cl-item {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  padding: 8px 14px;
  cursor: pointer;
  border-bottom: 1px solid var(--border);
  transition: background 0.1s;
}
.cl-item:hover { background: var(--parchment); }
.cl-item:last-child { border-bottom: none; }
.cl-item input[type=checkbox] {
  margin-top: 1px;
  width: 14px;
  height: 14px;
  accent-color: var(--gold);
  flex-shrink: 0;
  cursor: pointer;
}
.cl-item-text { flex: 1; }
.cl-item-text strong {
  display: block;
  font-size:var(--fs-2);
  color: var(--dark);
  margin-bottom: 2px;
  line-height: 1.3;
}
.cl-item-text span { font-size:var(--fs-1); color: var(--muted); line-height: 1.4; }
/* ITEM 41 (A): the self-attested marker and the one line that explains it. A tick on one of these is
   the operator's word, not a check VENDBase performed, and the panel now says which is which. */
.cl-self { display: inline-block; margin-bottom: 3px; padding: 1px 6px; border-radius:var(--r-control); background: var(--parchment); color: var(--muted); font-size:var(--fs-1); font-weight: 600; letter-spacing: 0.2px; white-space: nowrap; }
.cl-self-note { padding: 8px 14px; border-bottom: 1px solid var(--border); font-size:var(--fs-1); color: var(--muted); line-height: 1.5; }
.cl-item.checked .cl-item-text strong { text-decoration: line-through; color: var(--muted); }
.cl-nav .cl-cta { color: var(--gold); font-weight: 500; }
.cl-item.checked .cl-cta { display: none; }
.cl-panel-footer {
  padding: 10px 14px;
  border-top: 1px solid var(--border);
  flex-shrink: 0;
  font-size:var(--fs-1);
  color: var(--muted);
  text-align: center;
  cursor: pointer;
}
.cl-panel-footer:hover { color: var(--dark); }

/* ── Spreadsheet import modal (IMPORT-SPEC §4) ──────────────────────────────────────────────
   Mobile rules from the 7/19 hardening: the scroll lives on ONE inner element with
   overscroll-behavior:contain so a flick cannot leak through to the page below; 100dvh, not
   100vh, so the iOS URL bar cannot crop the footer buttons; safe-area padding on the bottom.
   Under 640px the column mapping stacks to one assignment per row and every table swaps to
   cards — a columns x sample-rows grid is unusable on a phone and intimidating on a laptop. */
.imp-shell { background: var(--white); border-radius:var(--r-panel); max-width: 900px; margin: 0 auto; overflow: hidden; }
.imp-scroll {
  padding: 18px 20px;
  padding-bottom: calc(18px + env(safe-area-inset-bottom, 0px));
  max-height: calc(100dvh - 130px);
  overflow-y: auto;
  overscroll-behavior: contain;
  -webkit-overflow-scrolling: touch;
}
.imp-maprow {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr) minmax(0, 1fr);
  gap: 10px; align-items: center;
  padding: 9px 0; border-bottom: 1px solid var(--border);
}
.imp-maprow select { width: 100%; font-size:var(--fs-2); padding: 7px; border: 1px solid var(--border); border-radius:var(--r-control); background: var(--white); }
.imp-maphdr { font-size:var(--fs-3); font-weight: 600; color: var(--dark); word-break: break-word; }
.imp-mapsample { font-size:var(--fs-1); color: var(--muted); word-break: break-word; }
.imp-tablewrap { overflow-x: auto; overscroll-behavior-x: contain; }
.imp-card { border: 1px solid var(--border); border-radius:var(--r-panel); padding: 10px 12px; margin-bottom: 8px; background: var(--parchment); }
.imp-card-line { display: flex; justify-content: space-between; gap: 12px; font-size:var(--fs-2); padding: 2px 0; color: var(--dark); }
.imp-card-line span:first-child { color: var(--muted); }
.imp-desk { display: block; }
.imp-mob { display: none; }
.imp-narrow-only { display: none; }
/* The column list on a wide file runs past the fold, so the primary action rides with the
   operator instead of hiding at the bottom. Negative bottom margin cancels .imp-scroll's own
   bottom padding so the bar sits flush on the scrollport edge, safe area included. */
.imp-actions {
  position: sticky; bottom: 0; z-index: 1;
  display: flex; gap: 8px; flex-wrap: wrap; align-items: center;
  margin: 16px -20px calc(-18px - env(safe-area-inset-bottom, 0px));
  padding: 12px 20px calc(12px + env(safe-area-inset-bottom, 0px));
  background: var(--white); border-top: 1px solid var(--border);
}
.imp-assumed { color: var(--muted); font-size:var(--fs-1); font-weight: 600; }
@media (max-width: 640px) {
  .imp-maprow { grid-template-columns: 1fr; gap: 4px; padding: 11px 0; }
  .imp-desk { display: none; }
  .imp-mob { display: block; }
  .imp-narrow-only { display: block; }
  .imp-scroll { padding: 14px 14px; padding-bottom: calc(20px + env(safe-area-inset-bottom, 0px)); max-height: calc(100dvh - 120px); }
  .imp-actions { margin: 14px -14px calc(-20px - env(safe-area-inset-bottom, 0px)); padding: 11px 14px calc(11px + env(safe-area-inset-bottom, 0px)); }
}
