
  /* ---- Brand font (Avenir LT Std) — Medium file = weights 400–600, Black file = 700–900 ---- */
  @font-face{font-family:'Avenir LT Std';src:url('fonts/AvenirLTStd-Medium.otf') format('opentype');font-weight:400 600;font-style:normal;font-display:swap}
  @font-face{font-family:'Avenir LT Std';src:url('fonts/AvenirLTStd-Black.otf') format('opentype');font-weight:700 900;font-style:normal;font-display:swap}

  :root{
    /* colors.css — verbatim */
    --coral:#F26A4B;--coral-deep:#D9512E;--coral-soft:#FAD9CE;--coral-tint:#FDEEE7;
    --blush:#FBE2DA;--blush-deep:#F5D3C7;--navy:#21395B;--navy-deep:#14263F;
    --muted:#5C6474;--line:#EFDDD4;--surface:#FFFFFF;
    --green:#2FA36A;--green-deep:#227A4E;--amber:#F4B740;--amber-deep:#DFA028;--scrim:rgba(38,22,30,.42);
    /* shape.css */
    --radius-chip:9px;--radius-control:12px;--radius-btn:16px;--radius-card:20px;--radius-sheet:26px;--radius-pill:999px;
    --shadow-card:0 3px 10px rgba(38,22,30,.06);--shadow-lift:0 10px 22px rgba(38,22,30,.10);
    --dur-press:.08s;--dur-hover:.13s;--dur-state:.15s;--ease-pop:cubic-bezier(.2,1.4,.4,1);
    /* typography.css */
    --font:'Avenir LT Std',ui-sans-serif,system-ui,sans-serif;
    /* handy input border */
    --ib:#E8D5CB;
    /* ---- state-tint tokens (brief 154, R16). VALUES LIFTED FROM THE APP'S OWN MOST-COPIED
       LITERALS — the amber trio alone existed as ~15 hand-typed copies across public/js —
       so introducing them changes nothing visually. Raw hex outside this file is a
       deviation (see docs/design/DESIGN-LANGUAGE.md). --danger is deliberately NOT coral:
       coral is the PRIMARY colour and must never mean "destructive"; #C62828 is the red
       the app had already reached for by hand (day_check missing-orders, prep printer). */
    --amber-tint:#FBF0D8;--amber-line:#F0DCA6;--amber-ink:#9A6B12;
    --green-tint:#E2F4EA;--green-line:#BFE6D0;
    --info-tint:#EEF1FB;--info-line:#C9D2EE;--info-ink:#3A4A8C;
    --grey-tint:#F1F1F6;--grey-line:#D8D8E6;--grey-ink:#4A4A66;
    --danger:#C62828;--danger-deep:#921F1F;
    /* ---- 161 additions, each an exact lift of a shipped literal (dE 0 — no recolour).
       --danger-tint: the MISSING pill's wash (today.js) — 154 shipped danger/deep only.
       --coral-dust: the cancelled/unsent border three ordering screens hand-typed; nearest
       existing is --blush-deep at dE76 12.2, so converging would have been a visible
       recolour (161 item 8 — the near-miss trap).
       --mark-red: apicbase_cutover's fault red; vs --danger dE76 7.5 — material, so it
       keeps its own token (recorded so a future ruling can converge deliberately). Its
       amber sibling #E0A020 vs --amber-deep is dE76 2.5 on small marks and WAS converged.
       --taupe/-deep: prep's plan-step pair; nearest existing is a cool near-black (dE76
       56.5) — no honest home before this. */
    --danger-tint:#FDE2E2;
    /* the lighter ROW wash behind a danger row (today.js's missing-order row). Its own value, not a
       squint at --danger-tint: measured dE76 7.8 apart, which is a recolour, not a tokenisation (the
       157 near-miss rule). */
    --danger-wash:#FFF4F4;
    --coral-dust:#E8B4A8;
    --mark-red:#C0392B;
    --taupe:#B4AC9E;--taupe-deep:#8A8378;
  }
  /* touch-action:manipulation kills iPad double-tap zoom on every control while keeping scroll +
     pinch-zoom (accessibility). The recipe-step drag handle's inline touch-action:none still wins. */
  *{box-sizing:border-box;-webkit-tap-highlight-color:transparent;touch-action:manipulation}
  body{margin:0;font-family:var(--font);background:var(--blush);color:var(--navy);font-weight:600;-webkit-font-smoothing:antialiased}
  .wrap{max-width:560px;margin:0 auto;padding:0 16px 96px}
  header{position:sticky;top:0;z-index:10;background:var(--blush);padding:16px 16px 10px;max-width:560px;margin:0 auto}
  /* wide screens (landscape iPad): table-ish screens use the full width. The iPad shell keeps a FIXED
     116px right rail, so wide content centers in the space LEFT of the rail (16px gutters) — a plain
     centered max-width:1160px ran the table's right columns underneath the rail. */
  @media(min-width:1024px){
    /* the sticky bar under a wide screen centers on the same rail-free area as the content above it */
    .subbar.wide .inner{ margin-left:calc((100vw - 116px - min(760px, 100vw - 148px)) / 2); margin-right:auto }
  }
  @media(min-width:1024px){ header.wide,.wrap.wide{
    max-width:min(1160px, calc(100vw - 148px));
    margin-left:calc((100vw - 116px - min(1160px, calc(100vw - 148px))) / 2);
    margin-right:auto;
  } }
  .top{display:flex;align-items:center;justify-content:space-between;gap:10px}
  h1{font-size:25px;font-weight:900;margin:0;letter-spacing:-.01em}
  .sub{color:var(--muted);font-size:13.5px;font-weight:600}
  /* >>> .bar DOES NOT WRAP AND DOES NOT SCROLL — BY DESIGN, AND THAT IS A CONSTRAINT, NOT
     >>> AN OVERSIGHT (161 item 12). It is the one-line control row (search + button, ‹ date ›).
     >>> Measured: two chip rows on it pushed body scrollWidth to 553 on a 375 viewport. A bar
     >>> holding MORE than ~2 variable-width children must say so itself — inline
     >>> flex-wrap:wrap (the events form), or a .rooms/.hscroll strip for unbounded sets (R15).
     >>> A default wrap here was considered and rejected: dozens of shipped bars rely on the
     >>> single line, so flipping the default would reflow screens no brief has audited. */
  .bar{display:flex;gap:8px;align-items:center;margin-top:10px}
  /* >>> 16px IS A FUNCTIONAL MINIMUM ON iOS, NOT A TYPE CHOICE. Safari auto-zooms the whole page when a
     >>> focused input's font-size is under 16px, and our viewport meta deliberately has no maximum-scale
     >>> (pinch-zoom stays available — clamping it is an accessibility regression). Once the page is zoomed,
     >>> iOS resolves position:fixed against the LAYOUT viewport, so .subbar tears off the bottom edge and
     >>> floats across the content; on the shop iPad the donut count's submit bar ended up parked mid-list,
     >>> covering whole flavour rows (Bram, filmed 2026-08-12). These fields were 15px, one pixel under.
     >>> ANY new focusable field wants >=16px for the same reason — .dcival .qy and the .delchip date
     >>> overlay below are the same fix. */
  .inp{flex:1;background:var(--surface);border:1.5px solid var(--ib);border-radius:12px;padding:12px 14px;font-size:16px;font-family:inherit;color:var(--navy);font-weight:600}
  .inp::placeholder{color:var(--muted);font-weight:600}
  .inp:focus{outline:none;border-color:var(--coral)}
  textarea.inp{resize:none}
  /* >>> 44px IS THE FLOOR, AND IT COST A RULING TO GET HERE (brief 181; Bram, 2026-08-24: "yes" to
     >>> growing .chip app-wide). 161 measured .chip at 34px against R14's 44px minimum and DEFERRED,
     >>> because every chip in the app moves together and that is nobody's screen pass to decide. The
     >>> floor is min-height + centring rather than fatter padding on purpose: padding would also move
     >>> the horizontal rhythm of every strip, while a min-height leaves the text metrics and the
     >>> left/right spacing exactly as they were and only guarantees the target. */
  .chip{display:inline-flex;align-items:center;justify-content:center;min-height:44px;border:1.5px solid var(--ib);background:var(--surface);border-radius:999px;padding:8px 13px;font-size:12.5px;font-weight:800;white-space:nowrap;cursor:pointer;color:var(--navy);font-family:inherit}
  .chip.on{background:var(--coral);border-color:var(--coral);color:#fff}
  .sec{font-size:11px;font-weight:800;text-transform:uppercase;letter-spacing:.07em;color:var(--muted);margin:16px 4px 6px;cursor:pointer;display:flex;justify-content:space-between}
  /* cards: 16px horizontal gutter comes from the card; rows carry vertical padding */
  .card{background:var(--surface);border:1px solid var(--line);border-radius:20px;box-shadow:var(--shadow-card);padding:2px 16px;overflow:hidden}
  .row{display:flex;align-items:center;gap:12px;padding:12px 0;border-bottom:1px solid var(--line)}
  .row:last-child{border-bottom:0}
  .nm{font-size:15px;font-weight:700}
  .rs{font-size:13.5px;color:var(--muted);font-weight:600;line-height:1.45}
  .badge{display:inline-block;background:#FBF0D8;border:1px solid #F0DCA6;color:#9A6B12;border-radius:999px;padding:1px 8px;font-size:10.5px;font-weight:800;margin-left:6px}
  /* "predicted" provenance chip — a shop placed no donut order, so the forecast is standing in for it
     (src/donut_order_source.js). Used on the Rounds packing shop columns + the prep plan's flavour rows.
     Deliberately quiet-but-distinct from .badge, and it must survive the print sheets (the bakery packs
     off paper — a stand-in has to look like one there too). */
  .rndpred{display:inline-block;background:#EAF1FB;border:1px solid #C3D8F2;color:#2C5B96;border-radius:999px;padding:0 6px;font-size:9.5px;font-weight:800;margin-left:5px;letter-spacing:.02em;white-space:nowrap;vertical-align:middle}
  @media print{ .rndpred{background:transparent;border:1px solid #666;color:#222;-webkit-print-color-adjust:exact;print-color-adjust:exact} }
  /* "⚠ not updating" — a live screen (nav.js live*) that has failed its last few polls says so rather than
     going quietly still. QUIET ON PURPOSE: the data underneath is real, only its AGE is unknown, so this
     must read as a footnote and never as an error — a coral alarm on a shop's delivery card every time the
     wifi hiccups would train people to ignore the one time it matters. Never printed: a printed sheet is a
     snapshot by definition and cannot be "not updating". */
  .livestale{display:inline-block;background:#FBF0D8;border:1px solid #F0DCA6;color:#9A6B12;border-radius:999px;padding:0 7px;font-size:9.5px;font-weight:800;margin-left:6px;letter-spacing:.02em;white-space:nowrap;vertical-align:middle;text-transform:none}
  @media print{ .livestale{display:none} }
  /* toggle */
  .sw{position:relative;width:50px;height:30px;flex:none;cursor:pointer}
  .sw input{opacity:0;width:0;height:0;position:absolute}
  .sw .track{position:absolute;inset:0;background:#E3D5CC;border-radius:999px;transition:var(--dur-state)}
  .sw .knob{position:absolute;top:3px;left:3px;width:24px;height:24px;background:#fff;border-radius:50%;transition:var(--dur-state);box-shadow:0 1px 3px rgba(0,0,0,.2)}
  .sw input:checked + .track{background:var(--green)}
  .sw input:checked + .track + .knob{transform:translateX(20px)}
  .savedtick{color:var(--green);font-weight:900;font-size:13px;opacity:0;transition:opacity .2s}
  .savedtick.show{opacity:1}
  /* three INDEPENDENT per-location assortment flags — compact labelled checkboxes (Count / Order / Shops).
     Wrap gracefully on a phone; on = coral-tinted pill. Same markup on the catalogue row and item detail. */
  .flags{display:flex;gap:6px;flex-wrap:wrap;align-items:center;margin-left:auto}
  .flag{display:inline-flex;align-items:center;gap:5px;font-size:11.5px;font-weight:800;line-height:1;color:var(--navy);background:var(--surface);border:1.5px solid var(--ib);border-radius:999px;padding:6px 10px 6px 8px;cursor:pointer;white-space:nowrap;user-select:none;transition:background var(--dur-state),border-color var(--dur-state),color var(--dur-state)}
  .flag input{width:15px;height:15px;accent-color:var(--coral);flex:none;cursor:pointer;margin:0}
  .flag.on{background:var(--coral-tint);border-color:var(--coral);color:var(--coral-deep)}
  /* steppers — 40x40 radius 12, −/+ in coral-deep, value 17/900 (muted at 0) */
  .par{display:flex;align-items:center;gap:8px;margin-top:8px}
  .step{width:40px;height:40px;border:1.5px solid var(--ib);background:var(--surface);border-radius:12px;font-size:19px;font-weight:900;color:var(--coral-deep);cursor:pointer;display:flex;align-items:center;justify-content:center;flex:none;font-family:inherit;line-height:1;padding:0}
  .step:active{transform:translateY(1px)}
  /* the explicit color above defeats the browser's own greying, so a disabled .step has to say so itself
     — the reorder ↑/↓ at the ends of a list are disabled, and a bakery iPad gets no other feedback */
  .step:disabled{opacity:.35;cursor:default}
  .parv,.qy{width:56px;text-align:center;font-size:17px;font-weight:900;border:1.5px solid var(--ib);border-radius:12px;padding:8px 0;font-family:inherit;color:var(--navy);-moz-appearance:textfield}
  .parv::-webkit-outer-spin-button,.parv::-webkit-inner-spin-button,.qy::-webkit-outer-spin-button,.qy::-webkit-inner-spin-button{-webkit-appearance:none;margin:0}
  .qy.z{color:var(--muted)}
  /* 370 — the standing-order table's per-day amount. The native number spinner is what Bram called
     "very small" on the iPad: the rule above suppresses it for .parv and .qy only, so .inp cells kept
     showing the platform's own ~10px chevrons and none of the app's controls. The .step.sm buttons
     beside the input are the replacement; this hides what they replace. */
  .soday{-moz-appearance:textfield}
  /* MEASURED, not assumed: .step.sm's own ::after is -8px top/bottom but only -6px left/right, giving a
     40x44 hit area. 44 is the number Bram's iPad needs in BOTH directions, and the 1024px measurement
     showed the table has the room (a seven-day shop's table is 1019px inside a 1024px card and does not
     scroll), so these two widen to 44x44. Scoped to the day steppers — .step.sm elsewhere is unchanged. */
  .step.sm.dstep::after{left:-8px;right:-8px}
  .soday::-webkit-outer-spin-button,.soday::-webkit-inner-spin-button{-webkit-appearance:none;margin:0}
  /* primary + variant buttons with signature hard shadow */
  .btn{display:block;width:100%;border:0;border-radius:16px;padding:15px;font-size:18px;font-weight:900;color:#fff;background:var(--coral);box-shadow:0 5px 0 var(--coral-deep);cursor:pointer;font-family:inherit;transition:transform var(--dur-press),box-shadow var(--dur-press)}
  .btn:active{transform:translateY(4px);box-shadow:0 1px 0 var(--coral-deep)}
  .btn:disabled{background:#D9C6BD;box-shadow:0 5px 0 #C4AEA3;cursor:default}
  .btn:disabled:active{transform:none;box-shadow:0 5px 0 #C4AEA3}
  .btn.green{background:var(--green);box-shadow:0 5px 0 var(--green-deep)}
  .btn.green:active{box-shadow:0 1px 0 var(--green-deep)}
  .btn.order{background:var(--navy);box-shadow:0 5px 0 var(--navy-deep);margin-top:10px;padding:13px;font-size:16px}
  .btn.order:active{box-shadow:0 1px 0 var(--navy-deep)}
  .center{max-width:400px;margin:56px auto;text-align:center;padding:0 20px}
  /* 589 — THE TOAST SITS ABOVE EVERY SHEET, because it is the ONLY channel an error has.
     It was z-index:100 and every modal host is a FULL-VIEWPORT scrim (`inset:0`) at 210 — so from
     2026-08 until today every failed action inside a sheet reported its reason UNDERNEATH the sheet,
     for the 2 600 ms it lives (core.js toast()). Bram, 2026-09-19, on the menu cap panel: "it won't
     save when I click" — the app was naming the cause the whole time and painting it out of sight.
     Measured with elementFromPoint at the toast's centre: modal open -> the modal DIV; modal gone -> #toast.
     240 CLEARS EVERY SHEET, AND 220 WOULD NOT HAVE. The hosts are not all at 210: receiving.js:16 is a
     tenth scrim at 230 — and it calls toast() 14 times, more than any other sheet. #imglightbox is 205.
     The only thing left above is #vcalbubble (300), a pointer-events:none calendar tooltip, not a
     blocking layer. The scanner (scanner.js:58, 210) keeps its value and needs no tier of its own: it
     raises ZERO toasts, so nothing it draws has a message to cover.
     test/toast_above_modals.test.js pins this against every scrim literal — a new host above 240 goes red. */
  #toast{position:fixed;top:14px;left:50%;transform:translateX(-50%);background:var(--navy);color:#fff;font-size:13px;font-weight:800;border-radius:999px;padding:10px 18px;display:none;z-index:240;box-shadow:0 12px 32px rgba(0,0,0,.3)}
  /* detail + forms */
  .chev{color:var(--muted);font-size:20px;font-weight:400;line-height:1;flex:none;margin-left:2px}
  .open{flex:1;min-width:0;cursor:pointer}
  .back{border:1.5px solid var(--ib);background:var(--surface);border-radius:12px;padding:9px 14px;font-size:14px;font-weight:800;color:var(--navy);cursor:pointer;font-family:inherit}
  .fab{position:fixed;left:50%;margin-left:224px;bottom:88px;width:56px;height:56px;border-radius:50%;border:0;background:var(--coral);color:#fff;font-size:32px;line-height:1;box-shadow:0 6px 16px rgba(217,81,46,.42);cursor:pointer;z-index:38}
  .fab:active{transform:translateY(2px)}
  @media(max-width:600px){.fab{left:auto;right:20px;margin-left:0}}
  .panel{background:var(--surface);border:1px solid var(--line);border-radius:20px;box-shadow:var(--shadow-card);padding:14px 16px;margin-top:14px}
  .lbl{font-size:11.5px;font-weight:800;text-transform:uppercase;letter-spacing:.07em;color:var(--muted);margin:0 0 8px}
  .dl{font-size:11.5px;font-weight:800;color:var(--navy);margin:0 0 6px;text-transform:uppercase;letter-spacing:.04em}
  .field{margin-bottom:13px}
  .field .inp{width:100%}
  .note{font-size:13.5px;color:var(--muted);font-weight:600;line-height:1.45}
  .urow{display:flex;align-items:center;gap:8px;padding:11px 0;border-bottom:1px solid var(--line);flex-wrap:wrap}
  .urow:last-child{border-bottom:0}
  .urow input[type=radio]{width:22px;height:22px;accent-color:var(--coral);flex:none;cursor:pointer}
  .ulabel{flex:1;min-width:110px}
  .uqty{width:70px;text-align:center}
  .ubar{flex-basis:100%;margin-top:2px}
  .rm{border:0;background:none;color:var(--coral-deep);font-size:22px;font-weight:900;cursor:pointer;padding:0 2px;flex:none}
  /* dashed "ghost" affordance — muted, per handoff */
  .ghost{display:block;width:100%;border:1.5px dashed #E3C6B8;background:rgba(255,255,255,.45);border-radius:14px;padding:12px;font-size:13px;font-weight:700;color:var(--muted);cursor:pointer;margin-top:8px;font-family:inherit;text-align:center}
  /* segmented control — inset blush-deep track, active = white pill + card shadow */
  .seg{display:flex;background:var(--blush-deep);border-radius:999px;padding:4px;gap:4px;margin-bottom:14px}
  .seg button{flex:1;border:0;background:none;border-radius:999px;padding:9px;font-size:13.5px;font-weight:800;color:#97756A;cursor:pointer;font-family:inherit}
  .seg button.on{background:var(--surface);color:var(--navy);box-shadow:var(--shadow-card)}
  /* ROOM STRIP (stock count) — a chip row, NEVER a .seg. The room list is data-driven and unbounded (the
     Berlin bakery has 20), and .seg is built for 2–3 fixed tabs: `button{flex:1}` stretches every room to
     an equal share of its row, and `border-radius:999px` is a pill only while the box is one row tall. At
     20 rooms that drew a page-tall blush ELLIPSE of 3-line-wrapped labels with an ellipse around the
     active one, and pushed the first item below the fold. Chips size to their own text instead. */
  .rooms{display:flex;gap:7px;flex-wrap:nowrap;overflow-x:auto;padding:2px 2px 5px;margin-top:8px;-webkit-overflow-scrolling:touch;scrollbar-width:none}
  .rooms::-webkit-scrollbar{display:none}
  .rooms.open{flex-wrap:wrap;overflow-x:visible}               /* "All N rooms" — every room at once, still content-sized */
  .rooms .chip{flex:none;max-width:100%;overflow:hidden;text-overflow:ellipsis}
  .rooms .chip.done:not(.on){background:var(--coral-tint);border-color:var(--coral);color:var(--coral-deep)}   /* something counted here */
  select.inp{appearance:none;-webkit-appearance:none;background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8'%3E%3Cpath d='M1 1l5 5 5-5' stroke='%238B93A3' stroke-width='2' fill='none'/%3E%3C/svg%3E");background-repeat:no-repeat;background-position:right 14px center}
  /* pills */
  .pill{display:inline-flex;align-items:center;gap:6px;border-radius:999px;padding:5px 11px;font-size:12px;font-weight:800;background:var(--surface);border:1px solid var(--line);white-space:nowrap;color:var(--navy)}
  .pill.pc{background:var(--coral-tint);border-color:var(--coral-soft);color:var(--coral-deep)}
  .pill.pg{background:var(--green-tint);border-color:var(--green-line);color:var(--green-deep)}   /* 161 item 3: byte-identical tokens (were the literal values R16 lifted) */
  .pill.pa{background:var(--amber-tint);border-color:var(--amber-line);color:var(--amber-ink)}
  /* recipes viewer/builder (R2) */
  .rvnum{width:28px;height:28px;flex:none;border-radius:50%;background:var(--coral);color:#fff;font-weight:900;font-size:14px;display:flex;align-items:center;justify-content:center}
  .rvhero{width:100%;aspect-ratio:3/2;max-height:460px;object-fit:cover;border-radius:20px;border:1px solid var(--line);box-shadow:var(--shadow-card);margin-top:12px;display:block;cursor:zoom-in}   /* landscape hero, taller so more of the photo shows; tap to see the full picture */
  .stepimg{width:100%;max-height:300px;object-fit:cover;border-radius:14px;margin-top:10px;border:1px solid var(--line);display:block;cursor:zoom-in}
  /* square photo thumbs (recipe list, catalogue rows, photo editors) — FIXED boxes so rows never jump while
     an image loads or errors; object-fit:cover so nothing ever stretches. .thumbph = the placeholder box. */
  .thumb{width:44px;height:44px;flex:none;border-radius:10px;object-fit:cover;border:1px solid var(--line);background:var(--blush);display:block;cursor:zoom-in}
  .thumbph{width:44px;height:44px;flex:none;border-radius:10px;border:1px solid var(--line);background:var(--blush);display:flex;align-items:center;justify-content:center;font-size:20px}
  .thumb.lg,.thumbph.lg{width:76px;height:76px;border-radius:12px}
  .thumbph.lg{font-size:30px}
  /* .thumbsp = the RESERVED, INVISIBLE thumb slot on the order screen. Half a shop's orderable items have
     a photo and half do not, so the alternative — no element at all — would step the name column sideways
     on every other row. Width only (no height): a photo-less row keeps its natural height instead of being
     padded out to the thumb, while every name still starts at the same x. Deliberately has no border, tint
     or glyph — the rows without a picture should read as rows, not as empty boxes. */
  .thumbsp{width:38px;flex:none}
  /* The order list runs a SMALLER thumb than the Catalogue and recipe lists, and the 6px is measured, not
     taste. This row already spends 152px on the −/qty/+ stepper, so at 375px the name column is 147px with
     no photo column at all and 91px at the standard 44px — enough to throw "Frosch Toilet Cleaner" onto
     three lines and clip the unit picker under it. 38px leaves 95px: most names back on two lines, and of
     the 27 unit pickers in Prenzlauer Berg's widest category only 2 still clip (0 before, 5 at 44px). A
     canister is still recognisable at 38px, and the lightbox is one tap away for when it isn't. Measured
     cost of the column on that category (31 rows, 24 with photos): 2991px -> 3310px, ~11% more scroll. */
  #obody img.thumb,#obody .thumbph{width:38px;height:38px;border-radius:9px}
  /* scanner reticle — orientation-AGNOSTIC square with corner marks (the old wide slot begged users to
     rotate the device; both decoders read sideways barcodes in place, so the frame shouldn't suggest one). */
  #scanreticle{position:relative;z-index:2;width:min(66vw,260px);height:min(66vw,260px);max-height:56vh;border-radius:18px;box-shadow:0 0 0 9999px rgba(0,0,0,.4)}
  #scanreticle b{position:absolute;width:30px;height:30px;border:0 solid rgba(255,255,255,.95)}
  #scanreticle b:nth-child(1){top:-2px;left:-2px;border-top-width:4px;border-left-width:4px;border-top-left-radius:16px}
  #scanreticle b:nth-child(2){top:-2px;right:-2px;border-top-width:4px;border-right-width:4px;border-top-right-radius:16px}
  #scanreticle b:nth-child(3){bottom:-2px;left:-2px;border-bottom-width:4px;border-left-width:4px;border-bottom-left-radius:16px}
  #scanreticle b:nth-child(4){bottom:-2px;right:-2px;border-bottom-width:4px;border-right-width:4px;border-bottom-right-radius:16px}
  .subpill{font-size:9.5px;padding:2px 7px;text-transform:uppercase;letter-spacing:.06em;vertical-align:2px}
  /* recipe city badge — compact B/H letter pill on list rows (both-cities recipes carry none) */
  .citypill{display:inline-flex;align-items:center;justify-content:center;min-width:18px;height:18px;border-radius:6px;padding:0 5px;font-size:10.5px;font-weight:900;margin-left:6px;vertical-align:2px;line-height:1}
  .citypill.cb{background:var(--navy);color:#fff}
  .citypill.ch{background:var(--coral);color:#fff}
  .citypill.cbh{background:var(--surface);color:var(--navy);border:1.5px solid var(--navy);padding:0 4px}
  /* ordering */
  .linkrow{display:flex;justify-content:space-between;align-items:center;margin:2px 4px 0}
  .link{background:none;border:0;color:var(--coral-deep);font-size:13px;font-weight:800;cursor:pointer;padding:4px 0;font-family:inherit}
  .row.picked{background:var(--coral-tint);margin:0 -16px;padding:12px 16px;border-radius:0}
  .parchip{display:inline-block;background:#FBF0D8;border:1px solid #F0DCA6;color:#9A6B12;border-radius:999px;padding:1px 8px;font-size:10.5px;font-weight:800;margin-left:6px;cursor:pointer}
  /* 234 — recent-count detail: one line per pack (count bold, contribution right-aligned) and the
     amount-first right edge (amount big, euro value in small print under it). Tokens only. */
  .cdparts{margin-top:3px;display:grid;gap:2px;max-width:230px}
  .cdpart{display:flex;justify-content:space-between;gap:10px;font-size:12.5px;font-weight:600;color:var(--muted);font-variant-numeric:tabular-nums}
  .cdpart b{font-weight:900;color:var(--navy)}
  .cdamt{text-align:right;flex:none}
  .cdamt .cda{font-size:14.5px;font-weight:900;white-space:nowrap;font-variant-numeric:tabular-nums}
  .cdamt .cdv{font-size:11.5px;color:var(--muted);font-weight:700;white-space:nowrap;font-variant-numeric:tabular-nums}
  .tint{background:var(--coral-tint);border-color:var(--coral-soft)}
  .editban{background:var(--coral-tint);border:1px solid var(--coral-soft);border-radius:14px;padding:10px 14px;display:flex;align-items:center;justify-content:space-between;gap:10px;margin:10px 0 2px;color:var(--coral-deep);font-weight:800;font-size:13.5px}
  .editban button{background:none;border:0;color:var(--coral-deep);font-weight:800;font-size:13.5px;cursor:pointer;text-decoration:underline;font-family:inherit}
  .subbar{position:fixed;left:0;right:0;bottom:0;z-index:35;background:linear-gradient(rgba(251,226,218,0),var(--blush) 34%);padding:14px 16px calc(16px + env(safe-area-inset-bottom))}
  .subbar.hasnav{padding-bottom:calc(74px + env(safe-area-inset-bottom))}
  .subbar .inner{max-width:560px;margin:0 auto}
  .deliver{text-align:center;font-size:12.5px;color:var(--muted);font-weight:600;margin-bottom:8px}
  .deliver button{color:var(--coral-deep);font-weight:800;background:none;border:0;cursor:pointer;font-family:inherit;font-size:12.5px}
  .ostat{display:inline-block;border-radius:999px;padding:2px 9px;font-size:10.5px;font-weight:800;text-transform:capitalize;border:1px solid var(--line)}
  .st-submitted{background:#FBF0D8;border-color:#F0DCA6;color:#9A6B12}
  .st-picking{background:var(--coral-tint);border-color:var(--coral-soft);color:var(--coral-deep)}
  .st-shipped,.st-received{background:#E2F4EA;border-color:#BFE6D0;color:var(--green-deep)}
  /* st-out — a CANCELLED row (transfers.js:35 is the only caller). Grey, not danger: the row is dead, not
     dangerous, and nobody has to act on it. Uses the R16 grey trio the app already spends on inert state
     (a locked day_check, a rebuilt count, .pill.pm) rather than a fifth hand-typed hex. The class had been
     used since transfers shipped and never defined, so it rendered toneless and silently (brief 220). */
  .st-out{background:var(--grey-tint);border-color:var(--grey-line);color:var(--grey-ink)}
  /* ---- SUPPLIER-PO STATUSES (brief 224). The four rules above cover `orders.status` completely; these
     cover `ext_orders.status`, whose members reach CSS by string concatenation ('st-'+esc(o.status) in
     receiving/today/deliveries/pick/order) and so are whatever the DB holds. Its CHECK vocabulary is
     draft/sent/received/partial/cancelled — `received` was already green, the other four had NO rule and
     drew a bare .ostat. Tones argued from the reader's next action, not picked:
       · st-sent    — the resolver renders 'sent' as the WORD "Ordered", exactly like internal 'submitted'.
                      Same word, same tone: amber. Values are the R16 tokens, identical to .st-submitted's
                      legacy hex (the tokens were lifted from it), so the two are indistinguishable.
       · st-partial — "Part received": the ONLY ext status with work outstanding (receive the rest, or raise
                      a backorder). Coral is what this app already spends on part-way-through — .st-picking,
                      and today.js's "not confirmed" crate — so it joins that, not amber's "just waiting".
       · st-cancelled / st-draft — NOT IN FLIGHT, nothing to wait for. Grey, never --danger: danger means
                      "needs you". They deliberately share ONE tone; R12 has the word riding along, which is
                      what separates a PO that was called off from one that was never placed.
     Mirrors .st-out's grey rather than joining its selector, so 220's line stays byte-identical — fold them
     together only if somebody is changing that tone anyway. */
  .st-sent{background:var(--amber-tint);border-color:var(--amber-line);color:var(--amber-ink)}
  .st-partial{background:var(--coral-tint);border-color:var(--coral-soft);color:var(--coral-deep)}
  .st-cancelled,.st-draft{background:var(--grey-tint);border-color:var(--grey-line);color:var(--grey-ink)}
  .big{font-size:52px;line-height:1;margin:24px 0 6px}
  /* floating bottom-nav pill (phone) */
  .nav{position:fixed;left:50%;transform:translateX(-50%);width:calc(100% - 32px);max-width:528px;bottom:calc(14px + env(safe-area-inset-bottom));background:rgba(18,31,52,.93);-webkit-backdrop-filter:blur(8px);backdrop-filter:blur(8px);border-radius:999px;display:flex;padding:6px;box-shadow:0 12px 32px rgba(0,0,0,.35);z-index:40}
  .ni{flex:1;text-align:center;padding:11px 2px;color:rgba(255,255,255,.52);font-weight:800;font-size:12px;letter-spacing:-.01em;white-space:nowrap;border-radius:999px;cursor:pointer;border:0;background:none;font-family:inherit}
  .ni.on{color:#fff;background:rgba(255,255,255,.12)}
  /* role-preview banner (admin "View as role") — fixed, above the nav pill; lives on <body>, survives re-renders */
  #previewbar{position:fixed;left:50%;transform:translateX(-50%);width:calc(100% - 32px);max-width:528px;bottom:calc(70px + env(safe-area-inset-bottom));z-index:45;background:var(--navy);color:#fff;font-size:12.5px;font-weight:800;border-radius:999px;padding:9px 16px;display:flex;align-items:center;justify-content:center;gap:4px;box-shadow:0 10px 28px rgba(0,0,0,.32)}
  #previewbar b{text-transform:capitalize}
  #previewbar button{background:rgba(255,255,255,.16);border:0;color:#fff;font-weight:900;font-size:12.5px;border-radius:999px;padding:5px 12px;margin-left:8px;cursor:pointer;font-family:inherit}
  /* prep batch code — "write this on the label" affordances (success modals + Prep History rows) */
  .preplabel{background:var(--coral-tint);border:1.5px solid var(--coral-soft);border-radius:16px;padding:14px;margin-top:12px;text-align:center}
  .preplabeltag{font-size:11px;font-weight:900;text-transform:uppercase;letter-spacing:.05em;color:var(--coral-deep)}
  .preplabelcode{display:inline-block;margin-top:6px;background:var(--surface);border:1.5px dashed var(--coral);border-radius:12px;padding:8px 18px;font-size:26px;font-weight:900;letter-spacing:.02em;color:var(--coral-deep);cursor:pointer;font-family:inherit;line-height:1.1}
  /* The LABEL TEMPLATE fields (poster: item · date · person · amount · colour dot). The DATE is the big
     line — it is the field people copy wrong (Bram 2026-08-09) — and the dot names its colour in text. */
  .preplabelnm{margin-top:8px;font-size:15px;font-weight:900;color:var(--navy)}
  .preplabeldate{font-size:20px;font-weight:900;color:var(--navy);line-height:1.25}
  .preplabeldot{display:inline-block;margin-top:4px;padding:2px 12px;border-radius:999px;border:1px solid rgba(0,0,0,.15);font-size:13px;font-weight:900;letter-spacing:.03em}
  .preplabelwho{margin-top:5px;font-size:13px;font-weight:700;color:var(--muted)}
  .codechip{margin-top:6px;background:var(--coral-tint);border:1.5px solid var(--coral-soft);border-radius:10px;padding:5px 10px;font-size:12.5px;font-weight:900;color:var(--coral-deep);cursor:pointer;font-family:inherit;display:inline-flex;align-items:center;gap:5px;white-space:nowrap}
  /* ---- MISE EN PLACE: TICK OFF WHAT IS ALREADY IN (2026-08-11) ---------------------------------------
     Bram, relaying the bakery: "It would be nice to be able to cross the ingredients we already added to
     the recipe, like we can do in apicbase when you are in the plan."
     A 30px target with a 44px tap area via padding — this is tapped with a wet hand between two bowls, and
     an ingredient row that is ALSO a sub-recipe tap-through sits underneath it (the handler stops the
     event). Ticked rows go quiet rather than vanishing: struck through and dimmed, so the eye lands on
     what is LEFT while the whole list stays readable — a row that disappeared would take the quantity
     with it, and somebody re-checking their work needs to see what they already put in. */
  .rvtick{flex:none;width:30px;height:30px;margin-right:4px;border:2px solid var(--ib);background:var(--surface);
    border-radius:9px;font-size:16px;font-weight:900;line-height:1;color:#fff;cursor:pointer;font-family:inherit;
    display:flex;align-items:center;justify-content:center;padding:0;transition:background var(--dur-state),border-color var(--dur-state)}
  .rvtick.on{background:var(--green);border-color:var(--green-deep)}
  .rvtick:active{transform:translateY(1px)}
  .row.rvdone .nm,.row.rvdone .rs{text-decoration:line-through;opacity:.5}
  @media print{ .rvtick{display:none} .row.rvdone .nm,.row.rvdone .rs{text-decoration:none;opacity:1} }
  /* ---- THE LABEL, ON A LABEL (2026-08-11) ------------------------------------------------------------
     The bakery asked for a colour label printer with dissolvable stock. The hardware is a separate
     decision (see HANDOVER.md); what the app owes it either way is a page that IS a label — one sticker
     of content at the roll's own size — instead of the tub's details somewhere on an A4.
     Sized in MILLIMETRES against a physical roll. The width/height live in ONE place, prepPrintLabel()
     in prep.js, which writes the @page rule at print time — a different roll is two numbers, not a
     stylesheet edit. Everything here is layout only, so it holds at any of them.
     Printed in COLOUR on purpose: the day dot is the bakery's FIFO signal and a grey circle is not one.
     print-color-adjust:exact is what stops a browser "saving ink" by dropping exactly that. It still
     NAMES the colour in text beside it, so the label survives a mono printer with nothing lost but
     convenience — which is also what makes pre-coloured stock a real option. */
  .lbl-print{display:none}
  @media print{
    body.printlabel .lbl-print{display:block !important;-webkit-print-color-adjust:exact;print-color-adjust:exact}
    /* Nothing else. A label is not a page with a label on it. */
    body.printlabel>*:not(.lbl-print){display:none !important}
    body.printlabel .lbl-name{font-size:13pt;font-weight:900;line-height:1.15;color:#000;margin:0 0 1mm}
    /* The date is the biggest thing on the sticker — it is the field the whole label exists to get right. */
    body.printlabel .lbl-date{font-size:17pt;font-weight:900;line-height:1.1;color:#000}
    body.printlabel .lbl-dot{display:inline-block;margin-top:1.2mm;padding:0.6mm 3mm;border-radius:999px;
      border:0.3mm solid rgba(0,0,0,.35);font-size:11pt;font-weight:900;letter-spacing:.02em}
    body.printlabel .lbl-meta{margin-top:1.5mm;font-size:11pt;font-weight:800;color:#000;line-height:1.25}
    body.printlabel .lbl-code{margin-top:1.5mm;font-size:10pt;font-weight:900;letter-spacing:.04em;color:#000}
  }
  /* global shop switcher (admin) — rides the app shell on every top-level screen.
     >>> 16px IS DELIBERATE, NOT A TYPE CHOICE (see .inp at ~line 51). This <select> sits on EVERY
     >>> top-level screen, so a sub-16px size here auto-zooms the page the moment an admin taps it to
     >>> switch shop — and once zoomed, iOS floats every position:fixed bar (the .subbar/.nav) off the
     >>> bottom and mis-docks the keyboard mid-screen. That is the "keyboard in the middle" the shop iPad
     >>> hit doing inventory (Bram, 2026-08-13) — the same family as the 2026-08-12 donut-count bug, on the
     >>> one focusable control that fix missed. width:96px still truncates the name; the picker shows it in full. */
  .railloc{width:96px;max-width:96px;font-size:16px;font-weight:800;padding:7px 6px;border-radius:12px;border:1px solid var(--line);background:var(--surface);color:var(--navy);font-family:inherit;cursor:pointer;margin:2px 0 2px;text-align:center}
  .phoneloc{display:none}
  /* delivery-date chip (header) — an overlaid native date input opens the picker in view.
     Selector generalized from #deldate so the Prep-plan date chip reuses it (one date input per screen). */
  .delchip{display:inline-flex;align-items:center;gap:6px;position:relative;border-radius:999px;padding:8px 13px;font-size:12.5px;font-weight:800;background:var(--surface);border:1.5px solid var(--ib);color:var(--navy);cursor:pointer;white-space:nowrap}
  /* font-size matters even at opacity:0 — an invisible overlay still triggers iOS's auto-zoom on focus (see .inp) */
  .delchip input[type=date]{position:absolute;inset:0;width:100%;height:100%;opacity:0;border:0;margin:0;padding:0;cursor:pointer;font-size:16px}
  /* SOLD-OUT TIME — the same overlay trick as .delchip, on the donut count's "out HH:MM" pill: a tap opens
     the native time picker so the time can be TYPED rather than only stamped from the clock. Bram,
     2026-08-04: "allow shops to still fill out manual sold out time on donut count sheet, ad also allow to
     edit yesterdays sold out time" — on a past day a clock stamp is not just unhelpful, it is wrong. */
  .dcsochip{position:relative}
  .dcsochip input[type=time]{position:absolute;inset:0;width:100%;height:100%;opacity:0;border:0;margin:0;padding:0;cursor:pointer;font-size:16px}   /* 16px even at opacity:0 — same iOS auto-zoom guard as .delchip date above (this twin was missed) */
  /* The same overlay on the WHITE "out" chip a row with no time yet carries (Bram, 2026-08-12: "just show
     the normal white out button like on a normal day in case someone wants to add a sold out time after the
     fact"). It cannot be a <button> — an <input> inside one is invalid — so it is a span wearing .chip, and
     these three declarations are what a span needs to sit and size like the buttons beside it. */
  .chip.dcsochip{display:inline-flex;align-items:center;vertical-align:middle}
  /* YOUR ORDER cart — phone: top card; iPad: left panel */
  .cth{font-size:20px;font-weight:900;margin:0 0 2px;letter-spacing:-.01em}
  .cartempty{color:var(--muted);font-weight:600;font-size:13px;padding:12px 2px}
  .usel{font:inherit;font-size:16px;font-weight:700;color:var(--navy);border:1.5px solid var(--ib);border-radius:9px;padding:3px 6px;margin-top:2px;max-width:100%;background:var(--card)}   /* 16px: order-line unit <select>; sub-16px focus auto-zooms iOS (see .inp) */
  .uord{display:inline-flex;align-items:center;gap:4px;font-size:12px;font-weight:800;color:var(--muted);white-space:nowrap;cursor:pointer}
  .vsug{font-size:10.5px;font-weight:800;color:var(--coral-deep);background:var(--coral-tint);border-radius:6px;padding:0 4px;white-space:nowrap;flex:none}
  .vsug.thin{color:var(--muted);background:var(--ib)}
  .uord input{width:16px;height:16px;accent-color:var(--navy)}
  .cartfoot{display:flex;justify-content:space-between;align-items:center;gap:10px;margin:12px 2px 10px}
  /* iPad (>=1024) POS layout for the order screen */
  .padwrap{display:flex;align-items:flex-start;min-height:100vh}
  .ocartpanel{width:356px;flex:none;background:var(--surface);border-right:1px solid var(--line);padding:22px 18px;position:sticky;top:0;height:100vh;overflow:auto;display:flex;flex-direction:column}
  .ocartpanel .cartfoot{margin-top:auto}
  .opadmain{flex:1;min-width:0;padding:22px 24px 48px}
  .orail{width:116px;flex:none;display:flex;flex-direction:column;align-items:center;gap:10px;padding:22px 0;position:sticky;top:0;height:100vh}
  .raillogo{width:44px;height:44px;border-radius:999px;background:var(--coral);color:#fff;font-weight:900;font-size:20px;display:flex;align-items:center;justify-content:center}
  .railbrand{display:flex;flex-direction:column;align-items:center;gap:3px;margin-bottom:6px}
  .railword{font-size:10.5px;font-weight:800;letter-spacing:.01em;color:var(--coral-deep);text-align:center;line-height:1}
  .rli{width:86px;padding:11px 4px;text-align:center;border-radius:16px;font-weight:800;font-size:12.5px;line-height:1.15;color:#97756A;background:var(--surface);border:1px solid var(--line);cursor:pointer;font-family:inherit}
  .rli.on{background:var(--coral);color:#fff;border-color:var(--coral);box-shadow:0 4px 0 var(--coral-deep)}
  /* Donut counting table — every row edits INLINE: compact −/+ steppers in the round/leftover cells,
     small out/snooze chips in their column. No expanding editor row. Values stay prominent + tabular. */
  .dctwrap{background:var(--surface);border:1px solid var(--line);border-radius:20px;box-shadow:var(--shadow-card);overflow-x:auto;margin-top:10px}
  .dct{width:100%;border-collapse:separate;border-spacing:0;font-variant-numeric:tabular-nums}
  .dct th{position:sticky;top:0;background:var(--surface);z-index:2;font-size:10.5px;font-weight:800;text-transform:uppercase;letter-spacing:.06em;color:var(--muted);text-align:right;padding:11px 10px 9px;border-bottom:1px solid var(--line);white-space:nowrap}
  .dct td{padding:6px 10px;border-bottom:1px solid var(--line);text-align:right;font-weight:800;font-size:15px;color:var(--navy);height:52px;white-space:nowrap;vertical-align:middle}
  .dct tr:last-child>td{border-bottom:0}
  .dct th:first-child,.dct td:first-child{text-align:left;position:sticky;left:0;background:var(--surface);z-index:1;max-width:130px;overflow:hidden;text-overflow:ellipsis}
  .dctcap{display:block;font-size:10.5px;color:var(--muted);font-weight:700;line-height:1.3;margin-top:1px}
  /* >>> THE CONFIRM TICK RIDES THE CAPTION SLOT AND COSTS NO WIDTH (brief 341). .dcistep is exactly 116px
     >>> and the table is ~688px on a two-round day, so a fourth inline element is +34px on EVERY round
     >>> column — and on a 375px phone it forces the card's two tiles onto separate lines. The caption is
     >>> already absolutely positioned under the value for precisely this reason ("so a wide caption can
     >>> never push the buttons apart"), so the control goes there instead of beside '+'.
     >>> The ::after gives it the finger target the 10.5px glyph cannot have on its own, the same trick
     >>> .step.sm uses — it grows downward into the .dcrq/.dctd padding, never sideways into the stepper. */
  .dctick{display:inline-block;border:0;background:none;padding:0;font:inherit;font-size:11px;font-weight:800;
    color:var(--muted);cursor:pointer;position:relative;white-space:nowrap;font-family:inherit}
  .dctick::after{content:'';position:absolute;top:-4px;bottom:-12px;left:-10px;right:-10px}
  .dctick[data-dctick="on"]{color:var(--green-deep)}
  .dctmut{color:var(--muted);font-weight:700}
  .dcok{background:#EAF7F0}.dcok .dctcap{color:var(--green-deep)}
  .dcshort{background:#FBF3DC}.dcshort .dctcap{color:#9A6B12}
  .dcover{background:var(--coral-tint)}.dcover .dctcap{color:var(--coral-deep)}
  .dctpill{padding:3px 8px;font-size:11px}
  /* compact in-cell stepper — 28px hit targets keep rows slim (the full 40px pills bloated every row).
     ONE flex group per stepper: fixed-width CENTERED value box (tabular-nums) with identical gaps on both
     sides of −/+, so every column's steppers line up; the tray-math/diff caption hangs UNDER the value via
     absolute positioning so a wide caption ("3 tr + 11") can never push the buttons apart. */
  .step.sm{width:28px;height:28px;border-radius:8px;font-size:15px;position:relative}
  /* >>> 28px DRAWN, 40x44 TAPPED. The buttons cannot actually grow: .dcistep is 116px and the table is
     >>> already ~688px on a 2-round day, so widening every stepper widens every round column. An invisible
     >>> ::after gives the finger the Apple-minimum target without moving a single pixel of layout — it eats
     >>> the 6px flex gap horizontally (stopping exactly at the value box, never overlapping it) and 8px of
     >>> the 52px row's slack vertically, which still leaves 4px between one row's target and the next. */
  .step.sm::after{content:'';position:absolute;top:-8px;bottom:-8px;left:-6px;right:-6px}
  .dcistep{display:inline-flex;align-items:center;gap:6px;vertical-align:middle}
  .dcival{position:relative;display:inline-flex;flex:none;width:48px;align-items:center;justify-content:center}
  .dcival .qy{width:100%;border:0;background:none;border-radius:0;padding:0;text-align:center;font-size:16px;font-weight:800;font-variant-numeric:tabular-nums}
  .dcival .qy:focus{outline:none;color:var(--coral-deep)}
  /* An unconfirmed suggestion is readonly, so the tap confirms instead of raising a keyboard (day_check.js
     dcCellStepperHtml). It is a button in all but name — say so to the finger, and never show a caret. */
  .dcival .qy[readonly]{cursor:pointer;caret-color:transparent}
  .dcival .qy[readonly]:focus{color:inherit}
  .dcival .dctcap{position:absolute;top:100%;left:50%;transform:translateX(-50%);margin-top:0;white-space:nowrap;text-align:center}
  /* >>> THE ONE CHIP THAT STAYS SMALL — DRAWN SMALL, TAPPED FULL SIZE (181). dcact is the donut count's
     >>> in-row action (out / zzz / ✕), and that table already carries ~52px rows across a phone-width
     >>> grid: giving it the 44px BOX would add ten pixels to every flavour row on the screen a shop uses
     >>> most, for a control that sits beside a .step.sm which solved this exact problem the other way in
     >>> 2026-08. So it takes the sanctioned trick instead of the box — an invisible ::after carries the
     >>> target while the drawn chip keeps the row's height. R14's drawn-vs-tapped doctrine, applied where
     >>> it was always meant to apply. */
  /* >>> A SMALL PILL THAT KEEPS A FULL-SIZE TARGET (brief 308). Bram, 2026-08-30: "the chips are a bit
     >>> too big but I do think that's one of the most important informations". Both halves are true, and
     >>> they only conflict if the visible pill and the finger target have to be the same box. The label
     >>> was always 11px; the height came from .chip's own min-height:44px. So: shrink the pill, and hang
     >>> a 44px strip off it that nobody can see. Exactly what .chip.dcact below already does for the day
     >>> check's action chips — same trick, same reason, and it is the reason this is two lines and not a
     >>> new component. ~20px back on every row that carries one. */
  .chip.mini{padding:2px 8px;font-size:11px;min-height:0;position:relative}
  .chip.mini::after{content:'';position:absolute;top:50%;left:0;right:0;height:44px;transform:translateY(-50%)}
  .chip.dcact{padding:5px 9px;font-size:11px;min-height:0;position:relative}
  .chip.dcact::after{content:'';position:absolute;top:50%;left:0;right:0;height:44px;transform:translateY(-50%)}
  /* ===== DONUT COUNT ON A PHONE — one CARD per flavour instead of a table wider than the screen =====
     The table is ~688px on a 2-round day; a phone viewport is ~358px, so the round a shop is filling sits
     off the right edge and every entry needs a horizontal scroll to reach and another to read the caption.
     The cards stack the same controls vertically at full width: nothing is dropped, nothing is condensed,
     and every stepper keeps its data-dcstep attributes so the existing wiring is untouched.
     .dcrq carries bottom padding because .dcival's caption hangs UNDER the input by absolute positioning. */
  .dccards{display:flex;flex-direction:column;gap:8px;margin-top:10px}
  .dccard{background:var(--surface);border:1px solid var(--line);border-radius:18px;box-shadow:var(--shadow-card);padding:10px 12px}
  .dccard.dcok,.dccard.dcshort,.dccard.dcover{border-width:1.5px}
  .dccard.dcok{border-color:#9ED8B8}.dccard.dcshort{border-color:#E5C77C}.dccard.dcover{border-color:#EFA79B}
  .dchead{display:flex;align-items:baseline;gap:8px}
  .dcname{font-weight:800;font-size:15.5px;color:var(--navy);flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
  .dcord{font-size:11px;font-weight:700;color:var(--muted);flex:none;white-space:nowrap}
  /* The tiles WRAP rather than stretch: a one-round day puts Round 1 and Left over on the same line and the
     card stays ~110px tall; a three-round day wraps to a second line instead of squeezing the steppers. */
  .dcgrid{display:flex;flex-wrap:wrap;gap:2px 10px;margin-top:4px}
  .dcrq{flex:0 0 auto;padding-bottom:14px;text-align:center;border-radius:10px}
  .dcrl{font-size:10px;font-weight:800;text-transform:uppercase;letter-spacing:.05em;color:var(--muted);margin-bottom:1px}
  /* The website's own snooze, not the shop's: muted, no border emphasis, and explicitly not tappable, so
     it never reads as a control on a screen where everything beside it is one. */
  .dcweb{opacity:.72;font-style:italic;cursor:help}
  /* …and since 2026-08-12 it is an ⓘ INSIDE the sold-out / snoozed pill rather than a chip of its own
     (Bram: "better to add the i to the actually sold out/snooze time and not a seperate button").
     THE PILL IT SITS IN IS USUALLY A .dcsochip, whose transparent <input type=time> is absolutely positioned
     across the WHOLE pill (see .dcsochip above) — an ⓘ in normal flow would sit UNDER it and every tap meant
     for the explanation would open the time picker instead. position:relative + z-index lift it clear, so
     the two controls own disjoint areas. The negative margins buy a thumb-sized hit box out of the pill's
     own padding without making the pill any taller; font-style undoes .dcweb's italic, which a single glyph
     only makes look broken. */
  .dcwebi{position:relative;z-index:1;font-style:normal;padding:5px 4px;margin:-5px -5px -5px -2px;line-height:1}
  /* The out / zzz chips ride in the SAME wrapping row, pushed right. When a one-round day leaves room they
     sit beside the steppers and the flavour is one line; when it does not, flex-wrap drops them to their
     own line and margin-left:auto still right-aligns them. Either way no empty band is reserved for them. */
  .dcouts{display:flex;flex-wrap:wrap;gap:4px;justify-content:flex-end;align-items:center;margin-left:auto;align-self:center;padding-bottom:6px}
  /* prep-plan log-control label ("Prepare" + the kg|batches toggle). This USED to be display:none under
     430px as an overflow band-aid — which silently removed the only control that switches the log unit on
     every phone (Bram, 2026-08-08: "you can['t] switch between kg and batch"). The row now wraps as two
     units (label line, stepper cluster — see prepRowHtml), so it fits at any width and stays visible. */
  /* ===== PRODUCTION ROUNDS (dough / deco / packing) — Phase B Wave 2 (B3) =====
     On-screen tables reuse the app design system; print sheets are plain high-contrast A4 tables. The
     inner cell classes (.lft/.grp/.tot/.z) are shared — only the table shell (.rndt vs .pt) differs, so
     one HTML builder feeds both the screen and the print DOM. */
  .rndtabs{display:flex;gap:8px;margin:14px 0 2px}
  .rndtab{flex:1;text-align:center;border:1.5px solid var(--ib);background:var(--surface);border-radius:12px;padding:11px 6px;font-size:13.5px;font-weight:800;color:var(--navy);cursor:pointer;font-family:inherit}
  .rndtab.on{background:var(--navy);border-color:var(--navy);color:#fff}
  .rndwrap{background:var(--surface);border:1px solid var(--line);border-radius:16px;box-shadow:var(--shadow-card);overflow-x:auto;-webkit-overflow-scrolling:touch;margin-top:12px}
  .rndt{width:100%;border-collapse:separate;border-spacing:0;font-variant-numeric:tabular-nums}
  .rndt th,.rndt td{padding:7px 9px;font-size:13px;font-weight:800;text-align:right;white-space:nowrap;border-bottom:1px solid var(--line)}
  .rndt th{font-size:10px;text-transform:uppercase;letter-spacing:.05em;color:var(--muted);background:var(--surface);position:sticky;top:0;z-index:2}
  .rndt td.lft,.rndt th.lft{text-align:left}
  /* The frozen shop column. `box-shadow` is the seam that tells you the column is frozen rather than
     merely first — without it a 28-column city grid scrolling under a plain white cell reads as a
     rendering bug. The corner needs a z-index above BOTH sticky axes: `.rndt th` already claims 2 for the
     header row, and this rule's higher specificity used to knock the corner back down to 1, so the day
     headings scrolled OVER the word "Shop" (same fix, same reason, as .plant thead th.rowlab). */
  .rndt th.lft,.rndt td.lft:first-child{position:sticky;left:0;background:var(--surface);z-index:1;box-shadow:1px 0 0 var(--line)}
  .rndt thead th.lft{z-index:3}
  .rndt .grp td{background:var(--blush);color:var(--navy-deep);font-size:10.5px;text-transform:uppercase;letter-spacing:.06em;font-weight:900;text-align:left}
  .rndt tr.tot td,.rndt tr.tot th{background:var(--coral-tint);color:var(--navy-deep);font-weight:900}
  .rndt tr.sub td{background:var(--blush);color:var(--navy-deep);font-weight:900}   /* per-type subtotal row (deco waves) */
  .ftp{color:var(--amber-deep)}   /* Flavour totals: a number leaning on a forecast stand-in (≈), amber like every other "this is a guess" mark */
  .cvedited{outline:2px solid var(--coral-soft)}   /* City overview: this shop's total was changed and is pending commit */
  /* City overview, the HISTORY half of the matrix (2026-08-14). A finished column reports realised DEMAND,
     a future one reports a PLAN, and the two must not read as one column of "donuts" — so the past is a
     tinted block, the first future column carries the divider, and the correction has its own mark (↑)
     which is deliberately NOT the forecast's ≈. Colour alone never carries it: every past cell also
     differs by marker and by tooltip, so this survives a greyscale phone and a colour-blind reader. */
  .rndt td.cvpast,.rndt th.cvpast{background:#F7F4F2;color:var(--muted)}
  .rndt td.cvdem{color:var(--amber-deep)}
  .cvdemx{font-weight:900;margin-right:1px}        /* the ↑ that marks a censoring-corrected demand estimate */
  .rndt td.cvnow,.rndt th.cvnow{border-left:2px solid var(--coral)}
  /* Far-ahead columns. The weekday view reaches 28 days out, four weeks past the 10-day weather horizon
     the model is trained against — so those cells are faded rather than sitting in the grid looking as
     solid as tomorrow's. The ceiling is the SERVER's (src/forecast.js capConfidenceForHorizon), never
     re-derived in the browser, and the day heading carries its sentence as a tooltip. */
  .rndt td.cvmid,.rndt th.cvmid{opacity:.72}
  .rndt td.cvfar,.rndt th.cvfar{opacity:.5}
  .rndt tr.tot td.cvpast{background:var(--blush);color:var(--navy-deep)}
  .rndt td.cvedcell{background:var(--coral-tint);color:var(--coral-deep);font-weight:900}   /* …and the matrix cell holding a pending edit (✎) */
  /* ================================================= THE TODAY BOARD (city overview, brief 135)
     Direction A of the design canvas Bram picked: a four-tile estate strip over one card of ranked shop
     rows. ONE DOM, TWO LAYOUTS — the row cells are flex items whose `order` and widths change at 1024px,
     so a phone gets stacked rows and a landscape iPad gets five columns without a resize listener and
     without the markup being rendered twice (public/js/city_view.js's cvTodayShopRow says the same).

     >>> 1024 IS THE APP'S OWN BREAKPOINT AND THAT IS WHY IT IS THE ONE USED. `.wrap` is max-width 560
     >>> below 1024 and only `.wrap.wide` opens up at 1024, so a five-column row has nowhere to live below
     >>> it — a second breakpoint at ~700px would put five columns in a 528px column. Below 1024 the same
     >>> cells stack: shop + signal pill on the first line, the sell-out line, then the leftover line.
     >>> NOTHING SCROLLS SIDEWAYS at any width (the brief's 375px rule): every cell wraps, no min-width
     >>> exceeds the phone column, and the board has no horizontal overflow to hide. */
  .cvtas{font-size:13px;font-weight:600;color:var(--muted);margin:10px 2px 0}
  .cvtwarn{margin-top:10px}
  /* the strip: 2x2 on a phone (the canvas's phone artboard), four across when the wide wrap opens */
  .cvtsum{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:10px;margin-top:10px}
  .cvts{background:var(--surface);border:1px solid var(--line);border-radius:var(--radius-card);
         box-shadow:var(--shadow-card);padding:12px 14px;min-width:0}
  .cvtsl{font-size:10.5px;font-weight:800;text-transform:uppercase;letter-spacing:.06em;color:var(--muted)}
  .cvtsv{font-size:22px;font-weight:900;color:var(--navy-deep);margin-top:1px;font-variant-numeric:tabular-nums}
  .cvtsv small{font-size:13px;font-weight:800;color:var(--muted)}
  .cvtsv.bad{color:var(--coral-deep)} .cvtsv.warn{color:var(--amber-deep)} .cvtsv.good{color:var(--green-deep)}
  .cvtsn{font-size:12px;font-weight:600;color:var(--muted);margin-top:2px}
  /* the board card and its rows */
  .cvtb{margin-top:10px;padding:2px 14px}
  .cvth{display:none}                              /* column headings exist only where there are columns */
  .cvtr{display:flex;flex-wrap:wrap;align-items:center;gap:7px 10px;padding:12px 0;
        border-bottom:1px solid var(--line);cursor:pointer}
  .cvtr:last-child{border-bottom:0}
  .cvtr.shut{cursor:default}
  .cvtc{min-width:0}
  .cvtc.nm{flex:1 1 auto;font-size:15px;font-weight:900;color:var(--navy-deep);order:0}
  .cvtc.sc{flex:0 0 auto;order:1;font-size:13px;font-weight:800;color:var(--muted)}
  .cvtc.sg{flex:0 0 auto;order:2;margin-left:auto;text-align:right}
  .cvtc.so{flex:1 1 100%;order:3;display:flex;align-items:center;gap:8px;flex-wrap:wrap}
  .cvtc.lo{flex:1 1 100%;order:4;font-size:12.5px;font-weight:600;color:var(--muted)}
  .cvtc.note{flex:1 1 100%;order:5;font-size:12.5px;font-weight:600;color:var(--amber-deep)}
  .cvtc.dd{flex:1 1 100%;order:6;display:flex;flex-direction:column;gap:2px}
  .cvtddl{font-size:12.5px;font-weight:600;color:var(--muted)}
  .cvtlab{color:var(--muted);font-weight:600}      /* "left today" etc — the column heading, inlined on a phone */
  .cvtnum{font-size:15px;font-weight:900;color:var(--navy-deep);font-variant-numeric:tabular-nums}
  .cvtnum.bad{color:var(--coral-deep)} .cvtnum.warn{color:var(--amber-deep)} .cvtnum.good{color:var(--green-deep)}
  .cvtmut{color:var(--muted);font-weight:600}
  .cvtdash{color:var(--muted);font-weight:600}
  /* the arrival time / lag note: INLINE on a phone, where the leftover line is one flowing sentence, and
     a block under the number in the wide layout's 180px column */
  .cvtin{font-size:11px;font-weight:700;color:var(--muted);margin-left:4px}
  .cvtnone{font-size:12px;font-weight:700;color:var(--amber-deep)}
  /* the sell-out pill: a clock + coral for a MORNING sell-out (under-supply), a tick otherwise. The
     .alarm class and the icon both carry it, never colour alone. */
  .cvtsell{display:inline-flex;align-items:center;gap:6px;background:var(--surface);border:1.5px solid var(--ib);
           color:var(--navy);border-radius:var(--radius-pill);padding:5px 11px;font-size:12.5px;font-weight:800}
  .cvtsell svg{color:var(--green)}
  .cvtsell.alarm{background:var(--coral-tint);border-color:var(--blush-deep);color:var(--coral-deep)}
  .cvtsell.alarm svg{color:var(--coral-deep)}
  .cvtsell.nodata{background:#FBF0D8;border-color:#F0DCA6;color:#9A6B12;font-weight:800}
  .cvtsellx{font-size:12px;font-weight:700;color:var(--muted)}
  .cvtbar{flex:1 1 60px;max-width:220px;height:8px;background:#F3EAE4;border-radius:var(--radius-pill);overflow:hidden}
  .cvtbar i{display:block;height:100%;background:#C9BBB1;border-radius:var(--radius-pill)}
  .cvtbar.alarm{background:var(--coral-tint)} .cvtbar.alarm i{background:var(--coral)}
  /* the signal pill, in WORDS — the reading itself, never a colour the reader has to decode */
  .cvtsig{display:inline-block;border-radius:var(--radius-pill);padding:4px 10px;font-size:10.5px;font-weight:800;
          white-space:nowrap;background:var(--coral-tint);border:1px solid var(--blush-deep);color:var(--coral-deep)}
  .cvtsig.warn{background:#FBF0D8;border-color:#F0DCA6;color:#9A6B12}
  .cvtnot{display:inline-block;border-radius:var(--radius-pill);padding:3px 9px;font-size:10.5px;font-weight:800;
          background:#FBF0D8;border:1px solid #F0DCA6;color:#9A6B12;white-space:nowrap}
  .cvtok{display:inline-flex;align-items:center;gap:5px;font-size:12px;font-weight:800;color:var(--green-deep)}
  .cvtfoot{font-size:12px;font-weight:600;color:var(--muted);margin:10px 4px 0}
  @media(min-width:1024px){
    .cvtsum{grid-template-columns:repeat(4,minmax(0,1fr));gap:12px}
    .cvtsv{font-size:26px}
    /* FIVE COLUMNS. The widths are the canvas's; `order` returns the cells to reading order (shop, score,
       sold out, leftover, signal) and the signal stops being pushed right by margin so it lines up under
       its own heading. .cvth is the same flex row, which is what keeps headings and cells aligned. */
    .cvth{display:flex;flex-wrap:wrap;align-items:center;gap:12px;padding:12px 0 8px;border-bottom:1px solid var(--line)}
    /* the headings win over the row cells' own type because they come later in the sheet at equal weight */
    .cvth .cvtc{font-size:10.5px;font-weight:800;text-transform:uppercase;letter-spacing:.07em;color:var(--muted)}
    .cvtr{gap:12px}
    .cvth .cvtc.nm,.cvtr .cvtc.nm{flex:0 0 168px;order:0}
    .cvth .cvtc.sc,.cvtr .cvtc.sc{flex:0 0 90px;order:1}
    .cvth .cvtc.so,.cvtr .cvtc.so{flex:1 1 180px;order:2}
    .cvth .cvtc.lo{flex:0 0 180px;order:3} .cvth .cvtc.yd{flex:0 0 108px;order:3}
    .cvth.set .cvtc.lo{flex:0 0 300px}                 /* settled: no yesterday cell, so leftover spans both */
    .cvtr .cvtc.lo{flex:0 0 300px;order:3}
    .cvth .cvtc.sg,.cvtr .cvtc.sg{flex:0 0 128px;order:4;margin-left:0;text-align:right}
    /* The leftover cell holds "left today N · €X" AND "yesterday M" on a phone; at this width they become
       two of the row's own columns, so the wrapper stops being a box (display:contents) and the inline
       separator and the "left today" label — the phone's stand-ins for column headings — go away. */
    .cvtr .cvtc.lo{display:contents}
    .cvtr .cvtcl{flex:0 0 180px;order:3;min-width:0;font-size:12.5px;font-weight:600;color:var(--muted)}
    /* a SETTLED row has no yesterday cell, so its leftover cell alone has to fill the heading's width */
    .cvtr.set .cvtcl{flex:0 0 300px}
    .cvtr .cvtcy{flex:0 0 108px;order:3;min-width:0;font-size:12.5px;font-weight:600;color:var(--muted)}
    .cvtsep{display:none}
    .cvtlab{display:none}
    .cvtin{display:block;margin-left:0}
    /* the wrong-place sentence and the drill-down are the only cells that take a line of their own here */
    .cvtr .cvtc.note,.cvtr .cvtc.dd{flex:1 1 100%;order:9}
  }
  .rndt .colsep{border-left:1px solid var(--line)}
  .rndt .z{color:var(--muted);font-weight:600}
  /* HAND-MOVED tray cell (migration 0093). It has to be obvious at a glance which numbers a person changed
     and which the engine computed — an invisible override is an override nobody can audit. Screen only:
     the PRINT sheets deliberately show the plain number, because what is on the van is the moved number
     and the packers do not need to know who decided it. */
  .rndt td.rndmoved{background:var(--blush);box-shadow:inset 0 0 0 2px var(--coral-deep);border-radius:6px}
  .rndt td.rndmv{cursor:pointer}
  .rndmvhand{font-size:10px;vertical-align:super;opacity:.8}
  /* WHOLE-ROUND move handle, in each shop's packing-sheet header (Bram 2026-07-22: "there should also be a
     way to move all trays from a round to another"). Deliberately small and quiet in the header — it is the
     blunt instrument on this screen, so it does not advertise itself, and it never fires without a
     confirmation naming the trays. Screen only, like every other move affordance. */
  .rndmvall{display:block;margin:3px auto 0;border:1px solid var(--line);background:var(--surface);color:var(--muted);
    border-radius:999px;padding:1px 8px;font-size:10px;font-weight:900;cursor:pointer;letter-spacing:.02em}
  .rndmvall:hover{border-color:var(--coral-deep);color:var(--coral-deep)}
  .rndwavesub{font-size:9px;font-weight:800;color:var(--muted);text-transform:none;letter-spacing:0}   /* deco wave departure/label sub-header */
  .rndamber{background:#FBF3DC;border:1px solid #F0DCA6;color:#9A6B12;border-radius:12px;padding:9px 13px;font-size:12.5px;font-weight:700;margin-top:8px;line-height:1.45}
  .rndchip{display:inline-flex;align-items:center;gap:6px;font-size:12px;font-weight:800;border-radius:999px;padding:5px 11px;margin-top:8px}
  .rndchip.good{background:#EAF7F0;color:var(--green-deep)}
  .rndchip.bad{background:var(--coral-tint);color:var(--coral-deep)}
  .rndnote{font-size:12px;color:var(--muted);font-weight:700;margin:8px 4px 0;line-height:1.5}
  /* NOTE: the dough sheet's old two-unit cell (a big proofing count over a small "N sheet · M donuts" line,
     plus a "+ 18d" remainder) is GONE as of 2026-08-05 — the cell is now one decimal, one unit. Its .rndpf /
     .rndpr rules went with it; see rndProofTrays() for why. */
  /* print sheet — hidden on screen, shown (and app chrome hidden) only in @media print.
     TWO print copies are rendered every time: .rnd-print = the active view (+letters), .rnd-print-all =
     the WHOLE plan (dough + deco×3 + packing + letters). "Print whole plan" just flips body.printall and
     calls print() — it never MUTATES the print DOM, so a re-render mid-print (or an afterprint that fires
     early, as iOS Safari does) can't clobber it: renderRounds rebuilds both copies identically. */
  .rnd-print,.rnd-print-all,.rnd-print-day{display:none}   /* third copy: the production DAY SHEET (body.printday swaps it in) */
  /* PRINT TYPOGRAPHY (Bram 2026-07-18, measured against the bakery's real A4 Google-Sheet pages): the old
     9pt table was the core complaint — "make it as big as that and remove any fluff just make it as easy as
     possible for the bakers to read and check off". Bakers read these at a bench, at arm's length, and tick
     rows off, so the hierarchy is: FULL trays huge (the number they act on) > flavour name > HALF trays
     clearly subordinate (italic, ~half the size — a half-tray is a correction to the full count, never
     confusable with it). Zero cells print BLANK (the on-screen `·` is noise on paper). */
  .psheet{color:#000}
  .psheet .ptitle{background:#fff;color:#000;border-bottom:0.8mm solid #000;font-size:11pt;font-weight:900;padding:1.6mm 3mm;margin:0 0 2.5mm;
    letter-spacing:.02em;display:flex;align-items:baseline;justify-content:space-between;gap:3mm;
    -webkit-print-color-adjust:exact;print-color-adjust:exact}   /* slim dark title bar, date folded in (the old h2 + pmeta subtitle are gone) */
  /* THE DAY, ON EVERY SHEET (Bram 2026-08-10). The whole-plan pack opens on the big day sheet, but it is
     torn apart the moment it lands and its pages go to different benches — so the day has to be legible on
     each loose page, not only on page 1. It was always in this bar; at 11pt at the tail of a sentence it
     read as absent. Now it is its own element, hard against the right edge where the eye goes when a page
     is picked up, and one clear step LARGER than the sheet name. It is still not competing with the numbers.
     Titles that are a bare text node (prep, allergens) become a single anonymous flex item and are unmoved. */
  .psheet .ptitle .pwhat{min-width:0}
  .psheet .ptitle .pwhen{font-size:14pt;white-space:nowrap;letter-spacing:.01em}
  .psheet .pfoot{font-size:12pt;margin:3.5mm 0 0;color:#000;font-weight:800;line-height:1.4}
  .pt{width:100%;border-collapse:collapse;font-variant-numeric:tabular-nums;font-size:16pt;color:#000}
  .pt th,.pt td{border:0.35mm solid #000;padding:1.1mm 2mm;text-align:right;white-space:nowrap;font-weight:700}
  .pt th{background:#dcdcdc;font-size:10pt;text-transform:uppercase;letter-spacing:.02em;font-weight:900;
    -webkit-print-color-adjust:exact;print-color-adjust:exact}
  .pt td.lft,.pt th.lft{text-align:left}
  .pt td.lft{font-size:16pt;font-weight:800;line-height:1.15}          /* flavour / tray-type name column */
  /* Letter-donut NOTES hold a whole customer sentence, so they must WRAP — the table-wide nowrap (right for
     numbers) was truncating them (Bram 2026-07-18). Smaller + normal weight so a long note can't shove the
     Message column off the page. */
  .pt td.note{white-space:normal;font-size:10pt;font-weight:600;line-height:1.3;max-width:78mm}
  .rndt td.note{white-space:normal;font-weight:700;line-height:1.35;max-width:340px;min-width:180px}
  .pt td.num{font-size:28pt;font-weight:900;line-height:1}             /* FULL trays — the number they act on */
  /* HALF trays — subordinate, but BY WEIGHT now rather than by shrinking. Bram, 2026-08-04: "half tray
     numbers in the trays to be bigger and not bold". 15pt bold was small enough to be squinted at across a
     bench and heavy enough to still compete with the full count — the worst of both. 20pt at normal weight
     reads at arm's length while the 28pt/900 full count stays unmistakably the number you act on; italic
     and the grey are what keep saying "this is a correction to the figure beside it". */
  .pt td.half{font-size:20pt;font-style:italic;font-weight:400;color:#333;line-height:1}
  /* A PRESS ROW ON THE DOUGH SHEET (migration 0213). Filled is counted in PRESSES at the bench and in
     PROOFING TRAYS on the rack, so it prints as two rows at the SAME size — the numbers are equally real and
     equally acted on. What this class marks is the one thing size cannot say: a press row is NOT part of the
     "Total trays" sum below it. A hairline under the press block separates it from the tray rows that ARE
     totalled, which is what "make the ones that are actually totalled together" asks for. Deliberately NOT a
     shrink, a grey or an italic — those all read as "less important", and a press is not less important. */
  .pt tr.dpress td,.rndt tr.dpress td{border-bottom:1px solid #bbb}
  .pt tr.dpress td.lft,.rndt tr.dpress td.lft{font-style:italic}
  /* Alternating row shading so a finger tracking across a wide row cannot slip a line. NOTE the `tbody` in
     the grp/sub/tot selectors below: `:nth-child` is a pseudo-CLASS, so the zebra rule scores (0,2,3) and
     would otherwise OUT-SPECIFY a plain `.pt tr.sub td` (0,2,2) and repaint the subtotal band near-white —
     which is exactly the illegibility Bram reported. Matching the element count keeps source order in charge. */
  .pt tbody tr:nth-child(even) td{background:#f6eef2;-webkit-print-color-adjust:exact;print-color-adjust:exact}
  .pt tbody tr.grp td{background:#fff;color:#000;text-align:left;font-weight:900;text-transform:uppercase;font-size:12pt;border-top:0.6mm solid #000;
    -webkit-print-color-adjust:exact;print-color-adjust:exact}
  .pt.coldglaze tbody tr.grp td{background:#fff;color:#000;border-top:0.9mm solid #000;-webkit-print-color-adjust:exact;print-color-adjust:exact} .pt.pack .colsep{border-left-width:0.7mm} .pt.pack tbody td.num:not(.colsep){border-left-width:0.15mm} .pt tbody tr.tot td,.pt tbody tr.tot th{background:#fff;font-weight:900;border-top:1.2mm double #000;-webkit-print-color-adjust:exact;print-color-adjust:exact}
  .pt tbody tr.tot td.lft{font-size:15pt}
  .pt tbody tr.tot td.num{font-size:26pt}
  /* Per-type subtotal row (Total A - Filled / Ring 10 / Bubble 10). THREE DESIGNS, and the history is the
     warning: #ececec (near-white, illegible on paper) -> a dark #3a3a3a band -> this, white delimited by
     0.5mm rules above and below (Bram, 2026-08-17: "isnt there another way of doing this without using
     black background?"). A subtotal does not need a FILL to read as a subtotal, it needs to be SEPARATED,
     and the sheet's hierarchy is now carried entirely in line weight: body 0.35mm, subtotal 0.5mm both
     sides, TOTAL ALL 1.2mm double.
     >>> IF YOU CHANGE THIS BACKGROUND AGAIN, GREP FOR EVERY COLOUR CHOSEN AGAINST IT FIRST. Dropping the
     dark band left `tr.sub td.half` at #e6e6e6 — near-white text picked for the dark fill — which printed
     as invisible half-tray figures until Bram spotted them on paper. Nothing caught it: no test asserts
     colour, these rules are @media print so no screen shows them, and the CSS twin proves only that the
     two copies match, never that the result is legible. */
  .pt tbody tr.sub td{background:#fff;color:#000;font-weight:900;border-top:0.5mm solid #000;border-bottom:0.5mm solid #000;-webkit-print-color-adjust:exact;print-color-adjust:exact}
  .pt tbody tr.sub td.half,.pt tbody tr.tot td.half{color:#000}
  .pt tbody tr.sub td.lft{font-size:14pt}
  .pt tbody tr.sub td.num{font-size:24pt}
  /* THE BOUND TRAILING NOTE (Bram 2026-08-10, "avoid printing pages with just one line of text"). It is the
     LAST ROW of the table's last row group so that `.psheet tbody{break-inside:avoid}` — the same rule that
     already keeps a totals band whole — carries it onto the same page as the rows it explains; see
     rndFootNote() for why CSS `break-before:avoid` could not do that on its own. It is a table cell only for
     the pagination; it must READ as the foot it always was, so it borrows .pfoot's type exactly and drops
     every table treatment: no rule, no zebra, no right alignment, no nowrap. Placed AFTER the zebra/tot/sub
     rules on purpose — those score the same (0,2,3), so source order is what keeps this one in charge. */
  .pt tbody tr.pnote td{border:0;background:none;text-align:left;white-space:normal;font-size:12pt;
    font-weight:800;line-height:1.4;color:#000;padding:3.5mm 0 0}
  .pt.pack tbody tr.pnote td{padding-top:3mm}
  /* The packing sheet's check-off strip is an INSTRUCTION, not a footnote — the bakery's own sheet ends on
     it — so it keeps its box and its size in either binding (a table row here, a .pfoot on a day that has
     per-stop customer orders above it). */
  .pkcheck{font-size:14pt;font-weight:900;text-transform:uppercase;letter-spacing:.03em;
    border:0.6mm solid #000;padding:2mm 3mm;text-align:center}
  /* PACKING is by far the widest sheet — one column-GROUP per shop — and it was printing A4 PORTRAIT, so the
     trailing shops (Steglitz, Markets) fell clean off the right edge and never reached the packers. It now
     prints LANDSCAPE (see @page packing) AND sizes itself: landscape alone is not enough at 11 Berlin shops.
     Names WRAP instead of forcing the table wider than the page. The rule is "as large as fits, but NO shop
     may be dropped" — .pk-s / .pk-xs are set by rndPackSheet.

     SIZED ON THE NUMBER COLUMN COUNT (shops × active rounds), not the shop count: that is what actually
     decides how wide a column can be, and since 2026-07-18 the per-shop TOT column is gone, so 11 shops × 2
     rounds is 22 columns where it used to be 33. Same physical column width per band as before — the bands
     are simply expressed in the unit that drives them, which also means the third ("Rest") round widening a
     day back to 33 columns tightens the type automatically instead of clipping a shop.

     The ROUND cells are the BIG ones now (they were ~8pt `.half` cells beside a big total). They are the
     only numbers anyone packs from, so they get the large `num` treatment and every band is a step larger
     than its pre-rework equivalent — the extra width from dropping TOT is spent entirely on legibility. */
  .pt.pack{font-size:12pt}
  .pt.pack th,.pt.pack td{padding:0.6mm 1mm}
  .pt.pack th{font-size:9pt;white-space:normal;word-break:break-word}
  .pt.pack td.lft,.pt.pack th.lft{white-space:normal;font-size:12pt}
  .pt.pack td.num{font-size:19pt}
  .pt.pack.pk-s{font-size:10pt}                      /* 21–29 number columns (e.g. 11 shops × 2 rounds) */
  .pt.pack.pk-s th{font-size:8pt}
  .pt.pack.pk-s td.lft,.pt.pack.pk-s th.lft{font-size:10.5pt}
  .pt.pack.pk-s td.num{font-size:15pt}
  .pt.pack.pk-s th,.pt.pack.pk-s td{padding:0.5mm 0.8mm}
  .pt.pack.pk-xs{font-size:8.5pt}                    /* 30+ number columns — the tightest that still fits A4 landscape */
  .pt.pack.pk-xs th{font-size:6.5pt}
  .pt.pack.pk-xs td.lft,.pt.pack.pk-xs th.lft{font-size:9pt}
  .pt.pack.pk-xs td.num{font-size:12.5pt}
  .pt.pack.pk-xs th,.pt.pack.pk-xs td{padding:0.35mm 0.5mm}
  /* The packing TOTAL rows are the load checks — keep them clearly totals, but they must not outgrow the
     round cells they sit under (the generic .pt tr.tot/.sub td.num 26/24pt would blow the column width wide
     open). Both bands are sized here: `sub` is the per-ROUND row a packer counts a crate against, `tot` the
     per-SHOP row the driver counts the drop against (Bram 2026-08-10, see rndPackSheet). */
  .pt.pack tbody tr.tot td.num,.pt.pack tbody tr.sub td.num{font-size:17pt}
  .pt.pack.pk-s tbody tr.tot td.num,.pt.pack.pk-s tbody tr.sub td.num{font-size:14pt}
  .pt.pack.pk-xs tbody tr.tot td.num,.pt.pack.pk-xs tbody tr.sub td.num{font-size:12pt}
  .pt.pack tbody tr.tot td.lft,.pt.pack tbody tr.sub td.lft{font-size:11pt}
  /* ROUNDS AFTER THE FIRST RECEDE (Bram, 2026-08-28, brief 247): "please put the 2nd round in a smaller
     font and cursive. there were too many packing mistakes in the last days and I got the feedback that
     that would help." Applied to Rest as well — his call, asked with the numbers: if Rest kept full weight,
     round 1 would compete with Rest instead of with 2nd, the same glance-mistake one column over.

     >>> IT IS SIZED PER DENSITY BAND, AND A SINGLE RULE WOULD HAVE BEEN WORSE THAN NOTHING. This sheet has
     three bands (`.pt.pack` 19pt → `.pk-s` 15pt → `.pk-xs` 12.5pt, chosen by column count), so one
     absolute size would apply at one band and silently not at the others — and `pk-xs` is 30+ number
     columns, i.e. 11 shops × 3 rounds, which is exactly the busy REST day this brief exists for.
     >>> Measured, and it caught a real inversion: borrowing `.half`'s absolute 20pt would have made round 2
     BIGGER than round 1's 19pt base. The step is a RATIO (~0.71, `.half`'s own 20/28) off each band's base.

     >>> THE FLOOR IS THE SHEET'S OWN SMALLEST TEXT, NOT A GUESS: 10.5pt, held at `pk-xs` where the ratio
     would give 9pt. This sheet already prints 6.5pt column headers at that band and the bakery reads them
     off a mono laser, so 10.5pt italic is comfortably above what is already proven legible there — while
     still visibly receding from the 12.5pt beside it. `pk-xs` headers are ALREADY at 6.5pt, so those get
     italic only: cutting the smallest text on the sheet further would trade the reported problem for a
     worse one.

     Full black, not `.half`'s grey — that grey means "a correction to the figure beside it", and the
     complaint being fixed is MISREADING, so dropping contrast on a laser would work against the brief. */
  .pt.pack th.pkr2{font-size:8pt;font-style:italic;font-weight:700}
  .pt.pack td.num.pkr2{font-size:13.5pt;font-style:italic;font-weight:400}
  .pt.pack tbody tr.sub td.num.pkr2{font-size:12pt;font-style:italic;font-weight:400}
  .pt.pack.pk-s th.pkr2{font-size:7pt}
  .pt.pack.pk-s td.num.pkr2{font-size:10.5pt}
  .pt.pack.pk-s tbody tr.sub td.num.pkr2{font-size:10pt}
  .pt.pack.pk-xs th.pkr2{font-size:6.5pt}            /* already the floor — italic carries it alone here */
  .pt.pack.pk-xs td.num.pkr2{font-size:10.5pt}       /* floored, not 9pt: see the note above */
  .pt.pack.pk-xs tbody tr.sub td.num.pkr2{font-size:10pt}
  .pt.pack .rndpred{font-size:5.5pt;padding:0 1px;margin-left:2px}   /* the `predicted` badge stays, but must not widen a shop column */
  /* SPECIAL / MAIL ORDERS sheet — unlike every other rounds sheet this one is mostly PROSE (a free-text,
     multi-line order description per row), not numbers. It therefore drops the big `num` type entirely and
     sizes for reading: the two wrapping cells are the whole point, so they get explicit widths instead of
     the generic `.pt td.note` 78mm cap — two independent 78mm cells would overrun A4 portrait's ~190mm.
     Everything stays well above the old 9pt complaint; the description is the largest wrapping text. */
  .pt.spec{font-size:12pt}
  .pt.spec td.lft{font-size:12pt;line-height:1.25}
  .pt.spec td,.pt.spec th{vertical-align:top;padding:1.4mm 2mm}
  .pt.spec td.note{font-size:11pt;font-weight:600;line-height:1.35;max-width:none}
  .pt.spec th:nth-child(1),.pt.spec td:nth-child(1){width:16mm}   /* Time */
  .pt.spec th:nth-child(2),.pt.spec td:nth-child(2){width:34mm;white-space:normal}   /* Customer (+ contact) */
  .pt.spec th:nth-child(3),.pt.spec td:nth-child(3){width:36mm}   /* Pickup / delivery (+ address) */
  .pt.spec th:nth-child(4),.pt.spec td:nth-child(4){width:26mm;white-space:normal;font-weight:900}   /* donut counts — what the bakery makes */
  /* ---- THE CUSTOMER-ORDERS A4 (Bram 2026-08-10) ------------------------------------------------------
     "the packing printout becomes really small cause it puts the letter donuts and specials orders on the
     same sheet, can you put that on its seperate page and just combine it with the last one, make sure the
     letters are still big letters like on the last one and deal better with long notes."

     Two pages became one: the per-stop block that used to sit at the foot of the LANDSCAPE packing sheet
     (where its height came straight out of the tray matrix) and the konditor's letter list at the back of
     the pack, which repeated most of the same rows. See rndOrdersSheetHtml.

     >>> FIXED LAYOUT IS THE FIX FOR LONG NOTES, and it is the reason the widths below are declared rather
     >>> than left to the browser. The old table was auto-laid-out with a NOWRAP message column, so one
     >>> "QUARTERLY CELEBRATION" took the width it wanted and a 700-character customer note — the one that
     >>> says do NOT pipe a "3", pipe a heart — was left a ribbon a few words wide and forty lines tall.
     >>> Now the message WRAPS inside a fixed 52mm and the note keeps ~68mm whatever else is on the page.
     >>> The three declared widths are content-box (padding + rule add ~4mm each), so 22+52+36 reserves
     >>> ~122mm of the 190mm printable and the notes column absorbs the rest.

     The MESSAGE keeps the konditor's own type — 16pt/800, the same step as a flavour name on every other
     sheet in this pack. It is what gets piped onto the donut; on the packing sheet it had been demoted to
     a 10pt grey line, which is the one thing on the row that must not be misread. */
  .pt.ordp{table-layout:fixed;font-size:12pt}
  .pt.ordp th,.pt.ordp td{vertical-align:top;padding:1.4mm 2mm}
  .pt.ordp col.oc1{width:36mm}   /* Time — sized so a full "18:00–18:15" range stays on ONE line */
  .pt.ordp col.oc2{width:50mm}   /* Order — the message, big */
  .pt.ordp col.oc3{width:36mm}   /* Customer — sized for a long surname; the 4th column takes the rest (~68mm) */
  /* WHITE-SPACE:NORMAL, and it is load-bearing: `.pt td` is nowrap for the number sheets, and under
     table-layout:fixed a nowrap value simply OVERFLOWS its cell instead of widening it — a pickup range
     printed straight through the message beside it ("18:00–18:15LAY DHD 3"). Every wrapping cell on this
     sheet therefore says so explicitly. */
  .pt.ordp td.ordt{font-size:13pt;line-height:1.15;white-space:normal}
  .pt.ordp td.ordw{font-size:16pt;font-weight:800;line-height:1.12;white-space:normal;overflow-wrap:anywhere}
  .pt.ordp td.ordc{font-size:12pt;line-height:1.2;white-space:normal;overflow-wrap:anywhere}
  .pt.ordp .ordk{font-size:8.5pt;font-weight:800;text-transform:uppercase;letter-spacing:.03em;margin-top:0.9mm}
  .pt.ordp .ordmiss{font-size:9.5pt;font-weight:900;line-height:1.2;margin-top:0.9mm}
  /* Notes wrap, keep their line breaks, and break inside a word rather than push the column open — a
     customer's address or a pasted URL is one long token often enough to matter. A note past ~240
     characters steps down a size (`ordlong`) so it stops driving the height of every row beside it;
     nothing is ever truncated, because the long ones are the ones carrying an instruction. */
  .pt.ordp td.note{font-size:10.5pt;font-weight:600;line-height:1.3;max-width:none;overflow-wrap:anywhere}
  .pt.ordp td.note.ordlong{font-size:9pt;line-height:1.25}
  /* THE STOP BAND. It is a <th> in the stop's own <thead> so the engine REPRINTS it wherever that stop's
     orders continue onto another page — a tbody group row does not repeat, and unlabelled paid orders on a
     continuation page is the failure this sheet exists to prevent (see rndOrdersSheetHtml). Styled as the
     grey total band the rest of the pack uses for a group heading, not as a column header. */
  .pt.ordp th.ordg{background:#c6c6c6;font-size:13pt;font-weight:900;text-align:left;white-space:normal;
    letter-spacing:.02em;-webkit-print-color-adjust:exact;print-color-adjust:exact}
  .pt.ordp + .pt.ordp{margin-top:2mm}
  /* …AND THE ROW GROUPS BREAK. `.psheet tbody{break-inside:avoid}` is right for a totals band and wrong
     for a stop: eight long-note orders are taller than an A4, so the engine moves the group to a fresh
     page, breaks it there anyway, and leaves the sheet's first page holding the title bar and one line of
     text — measured at 28 orders, and the exact defect Bram reported on 2026-08-10. Each ORDER row stays
     unbreakable (`.psheet tr`); the stop it belongs to does not need to be. */
  .pt.ordp tbody{break-inside:auto;page-break-inside:auto}
  .pt.ordp thead{break-after:avoid;page-break-after:avoid}   /* a stop band is never the last thing on a page */
  /* The "these ride on top of the trays" line. Same weight as the dough sheet's special-order warning —
     it is the same class of statement: an already-paid order that no tray count implies. */
  .ordwarn{font-size:12.5pt;font-weight:900;line-height:1.35;margin:0 0 3mm}
  /* SPECIAL ORDERS on the DOUGH sheet (Bram 2026-07-18: "since the donuts are not added to the production
     the bakers need to see the special orders they need to make extra dough for"). Special-order donuts are
     made by the KONDITOR and their counts are DELIBERATELY kept out of the tray/round maths, so the tray
     table above this block does NOT contain them. That makes this block the only place the dough bench ever
     learns a 200-donut paid order exists — and it must be impossible to mistake for part of the table above.
     Hence: a heavy box, a black banner, its own totals row in the sheet's big number type, and an explicit
     "NOT included above" line. It is rendered only when the day HAS special orders (no empty furniture). */
  .dspec{margin:5mm 0 0;border:0.9mm solid #000;break-inside:avoid;page-break-inside:avoid}
  .dspec-h{background:#fff;color:#000;border-bottom:0.7mm solid #000;font-size:13pt;font-weight:900;text-transform:uppercase;letter-spacing:.02em;
    padding:1.7mm 3mm;-webkit-print-color-adjust:exact;print-color-adjust:exact}
  .dspec-b{padding:2.5mm 3mm 3mm}
  .dspec-w{font-size:12.5pt;font-weight:900;line-height:1.35;margin:0 0 2.5mm}
  .pt.dspec-t{width:auto;min-width:100mm}
  .pt.dspec-l{margin-top:3mm;font-size:12pt}
  .pt.dspec-l td.lft,.pt.dspec-l th.lft{font-size:12pt;white-space:normal}
  .pt.dspec-l td.num{font-size:18pt}
  /* SCREEN twin of the same block (the dough view), so what the bakery sees on the tablet matches paper. */
  .rnddspec{margin-top:14px;border:2px solid #000;border-radius:12px;overflow:hidden}
  .rnddspec .dh{background:#000;color:#fff;font-weight:900;font-size:13px;text-transform:uppercase;letter-spacing:.03em;padding:9px 13px}
  .rnddspec .db{padding:12px 13px 13px}
  .rnddspec .dw{font-size:13px;font-weight:800;line-height:1.45;margin:0 0 10px}
  /* ===== THE DOUGH A4, 2026-08-05 =====================================================================
     Bram: "can you make sure the letter donuts and the dough stuff is on just one a4?" The page now carries
     FIVE things — the tray table, the early airport run, the special-order extras, tomorrow's dough figure
     and the letter glyphs — where it used to carry two, so the whole sheet is tuned to fit A4 portrait.
       · The tray cells lost their second line entirely (rndProofTrays), which is a 9pt line off every row.
       · Every block below the table is BANDED and break-inside:avoid, so a day big enough to spill spills
         as a whole block rather than tearing a table in half.
       · MEASURED, not guessed. Rendered at 190 × 277 mm (A4 portrait inside the sheet's own 10mm @page
         margin) against a heavy Berlin Wednesday — 11 shops, 18 flavours, an airport run, three special
         orders and 22 letter glyphs — the first cut came to 334mm. Every size below was cut until that
         page measured comfortably inside 277mm, largest block first.
       · What was NOT traded away: the numbers a bench acts on. The tray cells, the extra-dough totals and
         tomorrow's two figures stay large (the 2026-07-18 "make it as big as that" complaint is what
         started all of this). The room came out of headers, prose, padding and the per-ORDER list, which
         is a cross-check — the konditor's own page carries every one of those orders in full.
     `.dough-sheet` scopes every one of these to this sheet — deco and packing are untouched. */
  .dough-sheet .pt td.num{font-size:21pt}
  .dough-sheet .pt tbody tr.tot td.num{font-size:19pt}
  .dough-sheet .pt td.lft{font-size:13pt}
  .dough-sheet .pt tbody tr.tot td.lft{font-size:12pt}
  .dough-sheet .pt th,.dough-sheet .pt td{padding:0.5mm 2mm}
  .dough-sheet .ptitle{font-size:10pt;padding:1.2mm 3mm;margin-bottom:2mm}
  .dough-sheet .ptitle .pwhen{font-size:12pt}   /* the day stays a step up, but this is the MEASURED A4 — the bar must not grow */
  .dough-sheet .pfoot{font-size:9.5pt;margin:2mm 0 0;line-height:1.25}
  /* The banded blocks, tightened as a set: thinner rule, shorter banner, less body padding. */
  .dough-sheet .dspec,.dough-sheet .dearly,.dough-sheet .dprep,.dough-sheet .dletters{margin-top:3mm;border-width:0.5mm}
  .dough-sheet .dspec-h,.dough-sheet .dearly-h,.dough-sheet .dprep-h,.dough-sheet .dletters-h{font-size:10.5pt;padding:1mm 3mm}
  .dough-sheet .dspec-b,.dough-sheet .dearly-b,.dough-sheet .dprep-b,.dough-sheet .dletters-b{padding:1.5mm 3mm 2mm}
  .dough-sheet .dspec-w{font-size:10pt;line-height:1.25;margin-bottom:1.8mm}
  .dough-sheet .dearly-w{font-size:9.5pt;line-height:1.25;margin-top:1.5mm}
  /* The special-orders TOTALS keep the big treatment — they are the extra dough somebody mixes. The
     per-ORDER list under them is a cross-check ("which order needs what"), so it takes the cut. */
  .dough-sheet .pt.dspec-t td.num{font-size:19pt}
  .dough-sheet .pt.dspec-l{margin-top:2mm;font-size:9pt}
  .dough-sheet .pt.dspec-l th{font-size:7.5pt}
  .dough-sheet .pt.dspec-l td.lft,.dough-sheet .pt.dspec-l th.lft{font-size:9pt}
  .dough-sheet .pt.dspec-l td.num{font-size:12pt}
  .dough-sheet .pt.dspec-l th,.dough-sheet .pt.dspec-l td{padding:0.4mm 1.5mm}
  .dough-sheet .pt.dearly-t td.num{font-size:16pt}
  .dough-sheet .pt.dearly-t th{font-size:7.5pt}
  .dough-sheet .pt.dearly-t th,.dough-sheet .pt.dearly-t td{padding:0.4mm 1.5mm}
  /* The three banded blocks share one shell: heavy box, black banner, tight body. They differ only in what
     they hold, so the shell is one rule and each keeps its own body class for its own contents. */
  .dearly,.dprep,.dletters{margin:3.5mm 0 0;border:0.7mm solid #000;break-inside:avoid;page-break-inside:avoid}
  .dearly-h,.dprep-h,.dletters-h{background:#fff;color:#000;border-bottom:0.7mm solid #000;font-size:12pt;font-weight:900;text-transform:uppercase;
    letter-spacing:.02em;padding:1.3mm 3mm;-webkit-print-color-adjust:exact;print-color-adjust:exact}
  .dearly-b,.dprep-b,.dletters-b{padding:2mm 3mm 2.5mm}
  .dearly-w{font-size:11pt;font-weight:800;line-height:1.3;margin:2mm 0 0}
  .pt.dearly-t{width:auto;min-width:90mm}
  /* TOMORROW's two numbers. They are the largest type on the page after the tray cells, because "how many
     doughs" is the one figure somebody carries out of the room — and the ÷850 sits BETWEEN them so the
     arithmetic is legible rather than asserted. */
  .dprep-n{display:flex;align-items:baseline;gap:5mm;flex-wrap:wrap}
  .dprep-v{display:block;font-size:26pt;font-weight:900;line-height:1;font-variant-numeric:tabular-nums}
  .dprep-k{display:block;font-size:8.5pt;font-weight:800;text-transform:uppercase;letter-spacing:.03em;margin-top:0.6mm}
  .dprep-op{font-size:12pt;font-weight:900}
  .dprep-s{font-size:9pt;font-weight:700;line-height:1.25;margin:1.5mm 0 0}
  /* SCREEN twins, so the tablet shows the same page the printer does. */
  .rnddearly,.rnddprep,.rnddletters{margin-top:14px;border:2px solid #000;border-radius:12px;overflow:hidden}
  .rnddearly .dh,.rnddprep .dh,.rnddletters .dh{background:#000;color:#fff;font-weight:900;font-size:13px;
    text-transform:uppercase;letter-spacing:.03em;padding:9px 13px}
  .rnddearly .db,.rnddprep .db,.rnddletters .db{padding:12px 13px 13px}
  .rnddearly .dw{font-size:13px;font-weight:800;line-height:1.45;margin:10px 0 0}
  .rnddprep .dprep-v{font-size:30px}
  .rnddprep .dprep-k{font-size:10.5px;color:var(--muted)}
  .rnddprep .dprep-op{font-size:14px}
  .rnddprep .dprep-s{font-size:12.5px;color:var(--muted)}
  .psheet.pack-sheet{page:packing}
  /* THE PRODUCTION DAY SHEET (Bram 2026-08-09): one A4, the weekday + date big enough to read across a
     bench — the person hand-labelling a container copies THIS. Sizes are fixed, chosen so the LONGEST
     values fit 190mm printable width ("Donnerstag", "28. September 2026") — a sheet that is the same
     size every day beats one that jumps around. The day-dot colour band carries the colour NAME in text:
     on a mono laser it prints as a grey band that still says which marker to grab. */
  .day-sheet .dayc{height:235mm;display:flex;flex-direction:column;align-items:center;justify-content:center;text-align:center;color:#000}
  .day-sheet .dayw{font-size:88pt;font-weight:900;line-height:1.02;letter-spacing:-.01em;white-space:nowrap}
  .day-sheet .dayd{font-size:44pt;font-weight:900;line-height:1.1;margin-top:8mm;white-space:nowrap}
  .day-sheet .dayband{margin-top:14mm;padding:6mm 14mm;border-radius:6mm;font-size:26pt;font-weight:900;letter-spacing:.06em;
    -webkit-print-color-adjust:exact;print-color-adjust:exact}
  @media print{
    #toast{display:none !important}
    #fbbtn,#fbmodal{display:none !important}   /* the floating 💬 feedback button must never print over the rounds sheet (Bram 2026-07-18) */
    .rnd-screen{display:none !important}
    .rnd-print{display:block !important}
    /* "Print whole plan": body.printall swaps which copy prints (set before print(), cleared after). */
    body.printall .rnd-print{display:none !important}
    body.printall .rnd-print-all{display:block !important}
    /* "Print day sheet": a third flag, same doctrine. AFTER the printall rules so that, should both flags
       ever coexist, the day rules win on source order at equal specificity. */
    body.printday .rnd-print,body.printday .rnd-print-all{display:none !important}
    body.printday .rnd-print-day{display:block !important}
    html,body{background:#fff !important;color:#000}
    @page{size:A4 portrait;margin:10mm}
    /* PACKING prints LANDSCAPE (Bram 2026-07-18: portrait CLIPPED the trailing shops — Steglitz + Markets
       simply vanished off the right edge). A NAMED page keeps every other sheet portrait. */
    @page packing{size:A4 landscape;margin:8mm}
    .psheet tbody,.psheet tr{break-inside:avoid;page-break-inside:avoid}
    .rnd-print .psheet + .psheet,
    .rnd-print-all .psheet + .psheet{break-before:page;page-break-before:always}   /* each rounds print sheet on its own A4 page (day/dough/deco×3/packing/letters) */
    /* ---- A TRAILING NOTE BELONGS TO WHAT IT EXPLAINS ---------------------------------------------------
       Bram, 2026-08-10, looking at a whole-plan print: "avoid printing pages with just one line of text".
       The page he was holding carried the deco subtraction note — "− Choc Hazelnut Bomboloni · Cinnamon
       Sugar are subtracted from the Total rows … TOTAL ALL is the full count" — alone on a full A4.

       MECHANISM, measured in a real print run rather than guessed: every printed sheet ends on a note (the
       deco subtraction, the dough sheet's proofing-tray key, the packing check-off strip, the konditor
       pointer) and each was an ordinary block placed AFTER the thing it explains, with nothing binding it
       there. The per-sheet `break-before:page` above is innocent — it fires between sheets, not here. So on
       any day whose content happened to end AT the bottom margin the note simply flowed onto the next page,
       and being last in the sheet it had that page to itself. It is a NARROW BAND of content heights, which
       is why it looks random from the bench: rendering the deco totals sheet at 6…26 flavours, the note
       orphaned at 14 and 15 and at no other count.

       THIS RULE IS HALF THE FIX — the half that covers a note after a BLOCK (the dough key, which always
       follows the tomorrow-prep band; the packing strip on a day with per-stop orders). For a note after a
       TABLE it is not enough on its own: Chrome moves the break back exactly ONE row group and gives up, so
       a sheet whose last group is a single TOTAL ALL row still orphans (reproduced on the deco wave sheet at
       26 flavours). Those notes are bound in the MARKUP instead — see rndFootNote() and `tr.pnote` above.
       Neither half is allowed to be "drop the note" or "shrink it to fit": it explains a subtraction on a
       production number and is read at a bench, at arm's length, like everything else here.

       Both directions of the same instruction are stated because engines implement the two sides unevenly;
       Chrome honours either one alone. */
    .psheet .pfoot{break-before:avoid;page-break-before:avoid}
    .psheet *:has(+ .pfoot){break-after:avoid;page-break-after:avoid}
    /* …AND THE SAME DEFECT AT THE TOP OF A SHEET, found sweeping the rest of the pack for it. The special
       orders sheet keeps every order in ONE row group, so once the day has ~14 of them that group cannot fit
       under the title bar, moves off as a unit, and leaves page 1 holding the title bar and nothing else —
       literally one line of text on an A4, and then a page of paper spent on it. A sheet header that ends a
       page is meaningless wherever it happens, so this is stated for every sheet rather than for that one.
       Measured: 14 and 20 special orders both went from 3 pages to 2, with page 1 properly filled. */
    .psheet .ptitle{break-after:avoid;page-break-after:avoid}
    /* PREP SHEET (Bram 2026-07-29) — a WORKSHEET, so it is built to be marked up rather than read:
       a real tick box on every row, the "make" number large because it is the one they act on, and rows
       already done struck through rather than dropped. A missing done-row is how a batch gets made twice. */
    .prep-print .ptick{width:9mm}
    .prep-print .ptick::before{content:"";display:block;width:5mm;height:5mm;border:0.35mm solid #000;margin:0 auto}
    .prep-print thead .ptick::before{border:0}
    .prep-print .pbig{font-size:22pt;font-weight:900}
    .prep-print tr.pdone td{text-decoration:line-through;color:#555}
    .prep-print tr.pdone .ptick::before{background:#000;-webkit-print-color-adjust:exact;print-color-adjust:exact}
    .prep-print .pwarn{font-size:10pt;font-weight:700;font-style:italic}
  }
  /* ---- MENU POSTER (Feature 2) — a print-friendly A4 poster of a menu's donut flavours: big photo + name.
     Screen preview lives in the .rnd-screen copy; a sibling .rnd-print copy is what the shared @media print
     rules above reveal, so window.print() is scoped to the poster (app chrome hidden). ---- */
  .poster-title{display:none}
  .postergrid{display:grid;grid-template-columns:repeat(auto-fill,minmax(150px,1fr));gap:16px}
  .ptile{display:flex;flex-direction:column;align-items:center;text-align:center;break-inside:avoid;page-break-inside:avoid}
  .ptile-img,.ptile-ph{width:100%;aspect-ratio:1/1;object-fit:cover;border-radius:16px;border:1px solid var(--line);background:var(--blush);display:block}
  .ptile-ph{display:flex;align-items:center;justify-content:center;font-size:52px}
  .ptile-nm{font-size:15px;font-weight:900;line-height:1.2;margin-top:8px;color:var(--navy)}
  @media print{
    .poster{-webkit-print-color-adjust:exact;print-color-adjust:exact}
    .poster-title{display:block;text-align:center;font-size:20pt;font-weight:900;color:#000;margin:0 0 6mm}
    .poster-print .postergrid{grid-template-columns:repeat(3,1fr);gap:6mm}
    .poster-print .ptile-img,.poster-print .ptile-ph{border:0.3mm solid #999;border-radius:3mm}
    .poster-print .ptile-nm{color:#000;font-size:13pt;margin-top:3mm}
    .poster-print .ptile-ph{font-size:40pt}
  }
  /* image lightbox — tap a .thumb photo to see it big (capture-phase delegated click; sits BELOW the
     barcode scanner at 210, above nav/toast). Scrim + centered contain image + round ✕ close. */
  #imglightbox{position:fixed;inset:0;background:var(--scrim);z-index:205;display:flex;align-items:center;justify-content:center;padding:16px;cursor:zoom-out}
  #imglightbox img{max-width:92vw;max-height:92vh;object-fit:contain;border-radius:16px;box-shadow:var(--shadow-lift);background:var(--surface)}
  #imglightbox .lbx{position:absolute;top:calc(14px + env(safe-area-inset-top));right:16px;width:44px;height:44px;border-radius:50%;border:0;background:rgba(18,31,52,.72);color:#fff;font-size:20px;font-weight:900;cursor:pointer;font-family:inherit;line-height:1;display:flex;align-items:center;justify-content:center}
  /* DONUT ORDER (Phase B Wave 3, B1) — week strip of day cells + per-day ½/¼-tray steppers (shop + admin) */
  .dordstrip{display:flex;gap:7px;overflow-x:auto;padding:2px 0 4px;margin-top:10px;-webkit-overflow-scrolling:touch;scrollbar-width:none}   /* 161 item 1: hidden scrollbar, the .rooms/.hscroll precedent (R15) */
  .dordstrip::-webkit-scrollbar{display:none}
  .dordday{flex:0 0 auto;width:58px;border:1.5px solid var(--ib);background:var(--surface);border-radius:14px;padding:8px 0 7px;display:flex;flex-direction:column;align-items:center;gap:1px;cursor:pointer;font-family:inherit;color:var(--navy)}
  .dordday.on{border-color:var(--coral);box-shadow:0 3px 0 var(--coral-deep)}
  .dordday .dw{font-size:10px;font-weight:800;text-transform:uppercase;letter-spacing:.05em;color:var(--muted)}
  .dordday.on .dw{color:var(--coral-deep)}
  .dordday .dn{font-size:18px;font-weight:900;line-height:1.15}
  .dordday .dd{font-size:12px;font-weight:900;line-height:1;height:14px}
  .dordday .dt{font-size:10px;font-weight:800;line-height:1;height:12px;color:var(--muted);font-variant-numeric:tabular-nums}   /* the day's tray total under the date (Bram 2026-07-29) — fixed height so a day with no order doesn't shrink the cell */
  .dordday.on .dt{color:var(--navy)}
  .dordday.st-locked{opacity:.5}
  /* Un-commit (Bram 2026-08-10) — a quiet text action under the big Commit button. Deliberately NOT a .btn:
     the everyday move is re-committing a corrected number, and un-committing is the rare "this shouldn't be
     an order at all". Sized for a thumb anyway (min-height 44) because it sits on a phone. */
  .dordlnk{border:0;background:none;color:var(--coral-deep);font-size:13.5px;font-weight:800;cursor:pointer;font-family:inherit;text-decoration:underline;padding:12px 8px;min-height:44px}
  .dordflav{opacity:.5}
  /* ---- donut FORECAST finalization view (fork branch donut-forecast, Wave 2) ---- */
  .forehero{font-size:56px;font-weight:900;line-height:1;color:var(--navy);font-variant-numeric:tabular-nums}   /* 2026-07-20: the number the whole screen exists for — grown as the rare controls moved behind ⚙ */
  /* The hero DOUBLES AS THE FIELD when the day is editable (2026-08-13, foreHeroHtml): same 56px face, so
     read-only and typed states are the same number in the same place, not two designs. The dashed rule is
     the only difference — the affordance that says "this one you can type into".
     >>> 56px IS ALSO THE iOS GUARD (see .inp, ~line 43): a focused field under 16px auto-zooms iPad Safari,
     >>> and a zoomed page resolves position:fixed against the layout viewport, which tears .subbar off
     >>> mid-list. The type scale clears it by 40px — but if this ever shrinks, 16px is the floor, not a
     >>> preference. Width is set per-value in foreHeroShow(): an <input> will not size to its content. */
  input.forehero{border:0;border-bottom:2px dashed var(--ib);border-radius:0;background:transparent;padding:0;margin:0;text-align:center;font-family:inherit;-webkit-appearance:none;appearance:none}
  input.forehero:focus{outline:none;border-bottom-color:var(--coral)}
  .foresub{font-size:12.5px;color:var(--muted);font-weight:600;margin-top:3px;line-height:1.4}
  .foreconf{display:inline-flex;align-items:center;gap:5px;border-radius:999px;padding:4px 10px;font-size:11px;font-weight:800;cursor:pointer;border:1px solid var(--line);white-space:nowrap}
  .foreconf.hi{background:#E2F4EA;border-color:#BFE6D0;color:var(--green-deep)}
  .foreconf.md{background:#FBF0D8;border-color:#F0DCA6;color:#9A6B12}
  .foreconf.lo{background:var(--blush);border-color:var(--blush-deep);color:var(--navy)}
  /* .st = a READOUT wearing the pill shape, not a control. Same shape so it reads as part of the row, but no
     pointer and a muted face, so nobody taps it looking for the model chooser (which is in ⚙︎ Settings). */
  .foreconf.st{cursor:default;background:transparent;border-color:var(--line);color:var(--muted)}
  .foreslider{-webkit-appearance:none;appearance:none;width:100%;height:8px;border-radius:999px;background:linear-gradient(90deg,var(--coral-soft),var(--coral));outline:none;margin:14px 0 2px}
  .foreslider::-webkit-slider-thumb{-webkit-appearance:none;appearance:none;width:30px;height:30px;border-radius:50%;background:var(--surface);border:3px solid var(--coral);box-shadow:var(--shadow-lift);cursor:pointer}
  .foreslider::-moz-range-thumb{width:30px;height:30px;border-radius:50%;background:var(--surface);border:3px solid var(--coral);box-shadow:var(--shadow-lift);cursor:pointer}
  .forebadge{display:inline-flex;align-items:center;gap:5px;border-radius:999px;padding:5px 11px;font-size:11.5px;font-weight:800;background:var(--surface);border:1px solid var(--line);color:var(--navy);white-space:nowrap}
  .forebadge.up{color:var(--green-deep)} .forebadge.dn{color:var(--coral-deep)}
  /* ROUGH WEATHER (8+ days out) — see foreWxRough(). Forecast skill is NOT flat, so a day-9 chip must not
     read like tomorrow's or people order against it with the same confidence. Degraded, never hidden:
     dashed border + --muted (the same muted the whole app uses for secondary text, so it stays legible in
     any theme treatment — muted must not mean invisible) + a literal "rough outlook" tag. Deliberately a
     DIFFERENT language from "no weather yet", which is a plain solid badge at opacity .6 — absent data and
     unreliable data are two different states and must never look the same. */
  .forebadge.wxrough{color:var(--muted);border-style:dashed;font-weight:700}
  .wxrough{color:var(--muted);font-weight:700}
  .wxroughtag{display:inline-block;font-size:9.5px;font-weight:800;text-transform:uppercase;letter-spacing:.05em;
    color:var(--muted);border:1px dashed var(--line);border-radius:999px;padding:0 5px;margin-left:4px;white-space:nowrap}
  /* CONSUMPTION CHECK (openConsumptionExplain) — the whole point of this screen is that MISSING data never
     looks like a real zero, so absence gets its own visual language: .cexnull ("— no data", nothing was
     there) and .cexunk ("couldn't look", the table itself was unreadable) are BOTH muted+italic and
     obviously not numbers, while a genuine 0 renders as an ordinary bold figure like any other total. */
  .cexnull{color:var(--muted);font-weight:700;font-style:italic;font-size:12.5px;white-space:nowrap}
  .cexunk{color:var(--amber-deep);font-weight:800;font-style:italic;font-size:12.5px;white-space:nowrap}
  .cexval{font-weight:800;font-variant-numeric:tabular-nums;color:var(--navy)}
  .cexbanner{border-radius:16px;padding:12px 14px;margin-top:10px;border:1px solid var(--line);background:var(--surface)}
  .cexbanner.bad{background:var(--coral-tint);border-color:var(--coral-soft)}
  .cexbanner.good{background:#EAF7F0;border-color:#C7E7D5}
  .cexbanner h3{margin:0 0 6px;font-size:15px;font-weight:900;color:var(--navy)}
  .cexbanner.bad h3{color:var(--coral-deep)} .cexbanner.good h3{color:var(--green-deep)}
  .cexbanner ul{margin:6px 0 0;padding-left:18px} .cexbanner li{font-size:12.5px;font-weight:600;color:var(--navy);margin:3px 0}
  .cexwhy{font-size:13px;color:var(--muted);font-weight:600;font-style:italic;margin:0 0 8px}
  .cexgood{font-size:12.5px;font-weight:700;color:var(--green-deep)}
  .cexcap{font-size:11px;font-weight:800;color:var(--amber-deep);margin:6px 0 0}
  .cexsig{display:flex;gap:8px;align-items:flex-start;padding:8px 0;border-bottom:1px solid var(--line)}
  .cexsig:last-child{border-bottom:0}
  .cexcontrib{background:var(--coral-tint)}
  .cexcontrib td{height:auto;padding:5px 10px;font-size:12.5px;font-weight:700}
  /* OPENING-HOURS IMPORT (openHoursImport) — a REVIEW screen for a scrape of a hand-edited website. The
     site mixes 12- and 24-hour notation ("11 – 8" = 11:00–20:00), so a flagged row must not read like an
     ordinary one: .hrirow.risky is tinted, bordered and carries .hriwarn in full sentences. A wrong
     closing time corrupts the donut score's sold-out points, so "loud" here is the feature, not decoration. */
  .hrirow{display:flex;gap:10px;align-items:flex-start;padding:10px 0;border-bottom:1px solid var(--line)}
  .hrirow:last-child{border-bottom:0}
  .hrirow.risky{background:#FFF6E8;border:1.5px solid #F0C97A;border-radius:14px;padding:10px 12px;margin:6px 0}
  .hrirow input[type=checkbox]{width:22px;height:22px;accent-color:var(--coral);flex:none;cursor:pointer;margin:2px 0 0}
  .hridiff{display:flex;gap:8px;align-items:center;flex-wrap:wrap;margin-top:3px;font-variant-numeric:tabular-nums}
  .hricur{font-weight:700;color:var(--muted);font-size:13.5px}
  .hriarr{font-weight:900;color:var(--coral-deep)}
  .hriprop{font-weight:900;color:var(--navy);font-size:15px;background:var(--coral-tint);border:1px solid var(--coral-soft);border-radius:8px;padding:1px 8px}
  .hriunk{font-style:italic;font-weight:700;color:var(--muted);font-size:12.5px}
  .hriwarn{margin-top:6px;font-size:12.5px;font-weight:800;color:#8A5A00;background:#FBF0D8;border:1px solid #F0DCA6;border-radius:10px;padding:7px 10px;line-height:1.4}
  /* the QUIET informational variant (brief 159/R12) — shop-level notes that are context, not a
     flag to check. Was four inline overrides at its one call site in hours_import.js. */
  .hriwarn.info{background:var(--surface);color:var(--muted);border-color:var(--line);font-weight:700}
  .hrisrc{font-size:12px;font-weight:700;color:var(--muted);font-family:ui-monospace,Menlo,Consolas,monospace;word-break:break-word}
  .hribar{position:sticky;bottom:8px;z-index:6;background:var(--surface);border:1.5px solid var(--coral-soft);
    border-radius:16px;padding:12px 14px;margin:14px 0 4px;box-shadow:var(--shadow-lift)}
  @media(max-width:520px){ .hridiff{gap:6px} .hriprop{font-size:14px} }
  /* ---- ALLERGEN LIST (LMIV / EU FIC 1169-2011) — a LEGAL document, so the visual language is built around
     ONE rule: a short or empty allergen list must never LOOK like a clean bill of health. Two thirds of the
     donuts currently resolve incompletely (151 of 447), so "incomplete" is the COMMON case and cannot be
     styled as an alarm — an alarm on 300 rows is wallpaper nobody reads. It is therefore a calm, permanent,
     non-dismissible amber state (.algp.inc: amber left rail + an "at least" label on the list), while the
     things that are genuinely rare and actionable — the per-product GAPS (.algun) — carry the colour.
     The count is stated ONCE, loudly, at the top (.algbanner). ---- */
  .algbanner{border-radius:16px;padding:13px 15px;margin-top:12px;border:1.5px solid #F0C97A;background:#FFF6E8}
  .algbanner h3{margin:0 0 5px;font-size:15.5px;font-weight:900;color:#8A5A00}
  .algbanner p{margin:0;font-size:12.5px;font-weight:700;color:#8A5A00;line-height:1.5}
  .algbanner.good{border-color:#C7E7D5;background:#EAF7F0}
  .algbanner.good h3{color:var(--green-deep)} .algbanner.good p{color:var(--green-deep)}
  .algp{border:1px solid var(--line);border-radius:16px;background:var(--surface);padding:12px 14px;margin-top:10px;border-left:5px solid var(--line)}
  .algp.inc{border-left-color:#E0A93B;background:#FFFCF5}
  .algp.ok{border-left-color:var(--green)}
  .algnm{font-size:16px;font-weight:900;color:var(--navy);line-height:1.25}
  .algsub{font-size:11.5px;font-weight:700;color:var(--muted);margin-top:1px}
  .algtag{display:inline-block;border-radius:999px;padding:2px 9px;font-size:10.5px;font-weight:900;letter-spacing:.02em;margin-left:6px;vertical-align:2px}
  .algtag.inc{background:#F7E2B8;color:#7A4E00;border:1px solid #E0A93B}
  .algtag.ok{background:#EAF7F0;color:var(--green-deep);border:1px solid #BFE6CF}
  /* The word before the list is load-bearing: "Contains" is a statement, "At least" is a floor. */
  .alglead{font-size:11px;font-weight:900;text-transform:uppercase;letter-spacing:.05em;color:var(--muted);margin:9px 0 4px}
  .algp.inc .alglead{color:#8A5A00}
  .algchips{display:flex;gap:6px;flex-wrap:wrap}
  .alga{display:inline-block;border-radius:8px;padding:3px 9px;font-size:12.5px;font-weight:900;background:var(--coral-tint);color:var(--coral-deep);border:1px solid var(--coral-soft)}
  .algt{display:inline-block;border-radius:8px;padding:3px 9px;font-size:12.5px;font-weight:800;background:#F1F1F6;color:#4A4A66;border:1px solid #D8D8E6}
  /* An EMPTY allergens[] — never "allergen free", always a sentence about what we know and don't. */
  .algnone{font-size:12.5px;font-weight:800;font-style:italic;color:var(--muted);line-height:1.45}
  .algp.inc .algnone{color:#8A5A00}
  .algun{margin-top:9px;border-radius:12px;border:1px solid #F0DCA6;background:#FBF0D8;padding:8px 11px}
  .algunh{font-size:12px;font-weight:900;color:#8A5A00;margin-bottom:3px}
  .algunr{font-size:12px;font-weight:700;color:#8A5A00;line-height:1.45;padding:3px 0;border-top:1px dashed #E0C489}
  .algunr:first-of-type{border-top:0}
  .algvia{font-family:ui-monospace,Menlo,Consolas,monospace;font-size:11px;font-weight:700;color:#9A6B12;word-break:break-word}
  /* The shared-bakery traces notice. Rendered on EVERY product, whatever its own traces say. */
  .algnotice{margin-top:9px;font-size:11.5px;font-weight:700;color:var(--muted);line-height:1.45;border-top:1px dashed var(--line);padding-top:7px}
  /* PRINT — the point of the screen. Reuses the .psheet/.pt conventions; the traces notice sits in the
     table's <thead>, which browsers REPEAT on every printed page, so it really is above every product. */
  .pt.alg{font-size:13pt}
  .pt.alg td,.pt.alg th{vertical-align:top;text-align:left;white-space:normal;padding:1.5mm 2.5mm}
  .pt.alg td.lft{font-size:14pt;font-weight:900;line-height:1.2}
  .pt.alg td.al{font-size:13pt;font-weight:900}
  .pt.alg td.tr{font-size:11pt;font-weight:700;color:#222}
  .pt.alg th:nth-child(1),.pt.alg td:nth-child(1){width:58mm}
  .pt.alg th:nth-child(3),.pt.alg td:nth-child(3){width:46mm}
  .pt.alg tr.incrow td{background:#efefef;-webkit-print-color-adjust:exact;print-color-adjust:exact}
  .pt.alg td.al .algfloor{display:block;font-size:9.5pt;font-weight:900;text-transform:uppercase;letter-spacing:.03em}
  .pt.alg td .algpgap{display:block;font-size:9.5pt;font-weight:700;font-style:italic;color:#333;line-height:1.3;margin-top:1mm}
  .algnoticebar{border:0.6mm solid #000;padding:2mm 3mm;font-size:11pt;font-weight:900;line-height:1.35;text-align:left;
    background:#fff;-webkit-print-color-adjust:exact;print-color-adjust:exact}
  .algwarnbar{border:0.9mm solid #000;background:#000;color:#fff;padding:2.2mm 3mm;font-size:12pt;font-weight:900;line-height:1.4;margin:0 0 3mm;
    -webkit-print-color-adjust:exact;print-color-adjust:exact}
  /* ---- PREORDER BREAKDOWN on the rounds screen (screen ONLY — never printed, by instruction). ---- */
  .pretree{margin-top:12px}
  .preshop{border:1px solid var(--line);border-radius:16px;background:var(--surface);margin-top:10px;overflow:hidden}
  .preshoph{display:flex;align-items:center;gap:10px;padding:11px 14px;cursor:pointer}
  .preflav{border-top:1px solid var(--line);padding:9px 14px 9px 26px;cursor:pointer}
  .preord{border-top:1px dashed var(--line);padding:7px 14px 7px 40px;font-size:12.5px;font-weight:700;color:var(--navy);line-height:1.45}
  .prenum{font-variant-numeric:tabular-nums;font-weight:900;color:var(--navy)}
  /* donuts > 0 but addedTrays 0 — a preorder too small to reach the shop's tray step. It adds nothing to
     production, which is CORRECT, but hiding it makes a customer's 5 donuts look lost. */
  .presub{display:inline-block;border-radius:999px;padding:2px 9px;font-size:10.5px;font-weight:900;background:#E7EAF6;color:#3A4A8C;border:1px solid #C9D2EE;margin-left:6px}
  .prewhy{font-size:11.5px;font-weight:700;color:var(--muted);line-height:1.45;margin-top:3px}
  /* ---- ROUND ARRIVALS: the DRIVER-ROUND report + the van plan. The donut count no longer draws an arrival
     card at all (Bram 2026-07-21) — arrivals are inferred from the counting itself, so .arrb/.arrt are gone
     with it and only the provenance tag survives. ---- */
  .arrcorr{display:inline-block;border-radius:999px;padding:1px 8px;font-size:10px;font-weight:900;background:#FBF0D8;color:#8A5A00;border:1px solid #F0DCA6;margin-left:6px}
  /* An 'expected' round value is a SUGGESTION the shop has not confirmed — it must read as pending, never as
     an entered number, because it is never written back (see dcRoundCellHtml). */
  .qy.dcsug{color:var(--muted);font-style:italic;font-weight:700}
  .dcsugcap{font-size:9.5px;font-weight:900;text-transform:uppercase;letter-spacing:.03em;color:var(--muted)}
  .dcdelta{font-size:10px;font-weight:900;font-variant-numeric:tabular-nums}
  .dcdelta.short{color:var(--coral-deep)} .dcdelta.over{color:#3A4A8C}
  .rrpround{border:1px solid var(--line);border-radius:18px;background:var(--surface);margin-top:14px;padding:12px 14px}
  /* The shops are printed in MEDIAN-ARRIVAL order and that order IS the observed driver route, so it is drawn
     as a route (numbered stops down a rail), not as an alphabetical table someone has to re-sort mentally. */
  .rrpstop{display:flex;gap:12px;align-items:flex-start;padding:10px 0;position:relative}
  .rrpstop::before{content:'';position:absolute;left:13px;top:0;bottom:0;width:2px;background:var(--line)}
  .rrpstop:first-of-type::before{top:18px} .rrpstop:last-of-type::before{bottom:calc(100% - 18px)}
  .rrppos{position:relative;z-index:1;flex:none;width:28px;height:28px;border-radius:50%;background:var(--coral-tint);border:1.5px solid var(--coral-soft);
    color:var(--coral-deep);font-size:12px;font-weight:900;display:flex;align-items:center;justify-content:center;font-variant-numeric:tabular-nums}
  .rrpmed{font-size:21px;font-weight:900;font-variant-numeric:tabular-nums;color:var(--navy);line-height:1}
  .rrpstat{font-size:11.5px;font-weight:700;color:var(--muted);line-height:1.5;margin-top:3px}
  /* A THIN cell has no mean/stdev/IQR at all — it shows the actual taps. Never a number that implies a
     confidence three observations cannot carry. */
  .rrpthin{display:inline-block;border-radius:999px;padding:1px 8px;font-size:10px;font-weight:900;background:#F1F1F6;color:#4A4A66;border:1px solid #D8D8E6;margin-left:6px}
  .rrpobs{font-size:12px;font-weight:800;font-variant-numeric:tabular-nums;color:var(--navy);font-style:italic}
  .rrpshift{font-size:11.5px;font-weight:800;font-variant-numeric:tabular-nums;color:var(--navy);padding:3px 0}
  .rrpshift .lt{color:var(--coral-deep)} .rrpshift .er{color:var(--green-deep)}
  /* ---- VAN PLAN (0077). A van is a CARD and its shops are numbered stops down a rail, the same visual
     grammar as the driver-round report above — because it is the same route, planned rather than observed. */
  .vpvan{border:1px solid var(--line);border-radius:18px;background:var(--surface);margin-top:12px;padding:12px 14px}
  .vpvan.over{border-color:var(--coral-soft);background:var(--coral-tint)}
  .vprack{display:inline-flex;gap:3px;align-items:center;flex-wrap:wrap;margin-top:6px}
  /* One pip per RACK — the unit capacity is actually counted in. Racks are never shared between shops, so a
     part-full rack is drawn as a full slot with a lighter fill: it occupies the whole slot regardless. */
  .vppip{width:13px;height:17px;border-radius:3px;background:var(--coral-deep);border:1px solid var(--coral-deep)}
  .vppip.part{background:var(--coral-tint)}
  .vppip.free{background:transparent;border:1px dashed var(--line)}
  .vpstop{display:flex;gap:12px;align-items:flex-start;padding:9px 0;position:relative}
  .vpstop::before{content:'';position:absolute;left:13px;top:0;bottom:0;width:2px;background:var(--line)}
  .vpstop:first-of-type::before{top:18px} .vpstop:last-of-type::before{bottom:calc(100% - 18px)}
  .vppos{position:relative;z-index:1;flex:none;width:28px;height:28px;border-radius:50%;background:var(--coral-tint);border:1.5px solid var(--coral-soft);
    color:var(--coral-deep);font-size:12px;font-weight:900;display:flex;align-items:center;justify-content:center;font-variant-numeric:tabular-nums}
  .vppos.pin{background:#EAF7F0;border-color:#9AD6B5;color:var(--green-deep)}
  .vpeta{font-size:19px;font-weight:900;font-variant-numeric:tabular-nums;color:var(--navy);line-height:1}
  /* >>> THE PROVENANCE BADGE. Every time on this screen says whether it was MEASURED from the shops' own
     arrival taps or ASSUMED from Bram's 10-15 minutes. A screen that showed them identically would be
     inviting somebody to plan a morning on a number nobody ever observed. */
  .vpb{display:inline-block;border-radius:999px;padding:1px 8px;font-size:10px;font-weight:900;border:1px solid;margin-left:6px;white-space:nowrap}
  .vpb.meas{background:#EAF7F0;color:var(--green-deep);border-color:#9AD6B5}
  .vpb.est{background:#FBF0D8;color:#8A5A00;border-color:#F0DCA6}
  .vpb.typ{background:#F1F1F6;color:#4A4A66;border-color:#D8D8E6}
  .vpb.bad{background:var(--coral-tint);color:var(--coral-deep);border-color:var(--coral-soft)}
  .vpmeta{font-size:11.5px;font-weight:700;color:var(--muted);line-height:1.5;margin-top:3px}
  /* ---- DRIVERS (0080). >>> AN UNLINKED DRIVER IS DRAWN AS A PROBLEM, NOT AS A BLANK FIELD. A driver row
     with no login can be planned and printed and still cannot open their own day or mark a delivery — the
     one state on this screen that looks configured and is not. So it gets a coral block with the sentence
     spelled out, never a quiet empty dropdown. `.vdok` is the ordinary linked line, deliberately quiet. */
  .vdwarn{margin-top:6px;padding:7px 10px;border-radius:12px;background:var(--coral-tint);border:1px solid var(--coral-soft);
          color:var(--coral-deep);font-size:11.5px;font-weight:800;line-height:1.5}
  .vdok{margin-top:5px;font-size:11.5px;font-weight:700;color:var(--muted);line-height:1.5}
  .vdrow{border-top:1px solid var(--line);padding:11px 0}
  .vdrow:first-child{border-top:0}
  .vdrow.retired{opacity:.62}
  /* ---- LOGISTICS PLANS (0080). The four proposed variants + the fifth (target state), each shown with the
     ONE thing that decides between them: does it get donuts to the airport before the 5-7 rush, and how much
     of its own timing has actually been measured. A card here must never look finished when the plan is not. */
  .lpcard{border:1px solid var(--line);border-radius:18px;background:var(--surface);margin-top:12px;padding:13px 15px;cursor:pointer}
  .lpcard.cur{border-color:var(--green-deep);box-shadow:0 0 0 2px #EAF7F0}
  .lpcard.open{border-color:var(--coral-soft)}
  /* >>> AN INCOMPLETE PLAN IS DRAWN INCOMPLETE. Dashed, because the shape of the card should say "there is
     more that nobody has written down" before a single word is read. */
  .lpcard.inc{border-style:dashed;border-color:var(--coral-soft)}
  .lprow{display:flex;align-items:baseline;gap:9px;flex-wrap:wrap}
  .lpname{font-size:15px;font-weight:900;color:var(--navy);flex:1;min-width:140px}
  .lpobj{font-size:12px;font-weight:700;color:var(--muted);margin-top:3px;line-height:1.5}
  .lpfacts{display:flex;gap:6px;flex-wrap:wrap;margin-top:8px}
  .lpfact{font-size:11px;font-weight:800;color:var(--navy);background:var(--blush-deep);border-radius:9px;padding:3px 8px}
  /* >>> COVERAGE SITS UNDER EVERY VERDICT. 'ok' at 4% measured and 'ok' at 100% are different claims and
     must never be able to look the same. */
  .lpcov{font-size:11.5px;font-weight:800;color:var(--muted);margin-top:7px;line-height:1.5}
  .lpcovbar{height:5px;border-radius:3px;background:var(--blush-deep);overflow:hidden;margin-top:4px}
  .lpcovbar>i{display:block;height:100%;background:var(--green-deep)}
  .lpleg{display:flex;gap:9px;align-items:baseline;padding:5px 0;font-size:12px;font-weight:700;border-top:1px dashed var(--line)}
  .lplegt{font-variant-numeric:tabular-nums;font-weight:900;color:var(--navy);min-width:42px}
  .lpq{display:flex;gap:9px;align-items:flex-start;padding:7px 0;border-top:1px dashed var(--line)}
  .lpqbox{flex:none;width:17px;height:17px;border-radius:5px;border:1.5px solid var(--coral-soft);background:var(--coral-tint);margin-top:1px}
  .lpqbox.met{background:#EAF7F0;border-color:#9AD6B5}
  /* The driver's own screen: one button, unmissable, and nothing else competing with it. */
  .lpgo{display:block;width:100%;padding:16px;border:0;border-radius:16px;background:var(--coral-deep);color:#fff;
    font-size:17px;font-weight:900;margin-top:10px;cursor:pointer}
  .lpgo:disabled{background:#EAF7F0;color:var(--green-deep);cursor:default}
  /* The escape from the load gate: deliberately NOT the big coral button. Leaving with a crate still on
     the bakery bench is an exception, and an exception that looks exactly like the normal action is one
     people take by accident at 6am. Same downgrade the doorstep escape wears. */
  .lpgo.warn{background:#fff;color:var(--coral-deep);border:2px solid var(--coral-soft);font-size:13.5px;padding:11px}
  /* The per-stop delivery mark: smaller than the round-departure button so it reads as a step within the
     round, not another leave-now. Once marked, a calm green confirmation with the stamped time. */
  .lpstopgo{display:block;width:100%;padding:11px;border:1.5px solid var(--coral-soft);border-radius:12px;
    background:var(--coral-tint);color:var(--coral-deep);font-size:14px;font-weight:900;margin-top:7px;cursor:pointer;font-family:inherit}
  .lpdelivered{margin-top:7px;font-size:13px;font-weight:900;color:var(--green-deep)}
  /* THE DRIVER'S LOAD (2026-07-27). Racks lead, the flavour split is a table, and the forgettable
     extras sit above both in coral. Sized for a phone held one-handed at 6am, not a desk. */
  .lpracks{margin-top:6px;font-size:13.5px;font-weight:900;color:var(--navy);display:flex;gap:8px;align-items:baseline;flex-wrap:wrap}
  .lpxw2{font-size:11px;font-weight:800;color:var(--muted);margin-top:1px}
  .lpmore{margin-left:auto;border:0;background:none;padding:2px 0;font:inherit;font-size:12px;font-weight:800;color:var(--coral-deep);cursor:pointer;white-space:nowrap}
  .lpload{width:100%;border-collapse:collapse;margin-top:5px;font-size:12.5px}
  .lpload td{padding:3px 0;border-bottom:1px solid var(--line);color:var(--navy);font-weight:700}
  .lpload td.n{text-align:right;width:3.5em;font-variant-numeric:tabular-nums}
  .lpload tr:last-child td{border-bottom:0}
  /* Coral, bordered, and ABOVE the donut table — these are the items with a customer attached. */
  .lpextras{margin-top:7px;border:2px solid var(--coral-deep);border-radius:12px;overflow:hidden;background:var(--coral-tint)}
  .lpx{display:flex;gap:9px;align-items:flex-start;padding:8px 10px;border-bottom:1px solid var(--coral-soft)}
  .lpx:last-child{border-bottom:0}
  .lpxi{font-size:16px;line-height:1.2}
  .lpxt{font-size:13px;font-weight:900;color:var(--coral-deep)}
  .lpxw{margin-left:6px;font-size:11.5px;font-weight:800;color:var(--navy)}
  .lpxd{font-size:11.5px;font-weight:600;color:var(--navy);margin-top:1px;line-height:1.45}
  /* COLLECT AT THE BAKERY — the loading view, before the van pulls away. A row that is not ready is
     visually different, not just differently worded: it is the one that loses a shop its delivery. */
  .lppick{margin-top:8px;border:2px solid var(--navy);border-radius:14px;overflow:hidden}
  .lppickh{padding:9px 11px;background:var(--navy);color:#fff;font-size:12.5px;font-weight:900}
  .lppickr{display:flex;gap:9px;align-items:flex-start;padding:9px 11px;border-bottom:1px solid var(--line);background:#fff}
  .lppickr:last-child{border-bottom:0}
  .lppickr.warn{background:var(--coral-tint)}
  .lppickb{flex:0 0 20px;height:20px;border-radius:6px;background:var(--green-deep);color:#fff;display:flex;
           align-items:center;justify-content:center;font-size:12px;font-weight:900}
  .lppickr.warn .lppickb{background:var(--coral-deep)}
  .lppickt{display:block;font-size:13px;font-weight:900;color:var(--navy)}
  .lppickd{display:block;font-size:11.5px;font-weight:700;color:var(--muted);margin-top:1px}
  .lppickw{margin-left:7px;font-size:11px;font-weight:800;color:var(--navy);white-space:nowrap}
  .lppickw.late{color:var(--coral-deep)}      /* the shop has already been waiting */
  .lppickw.unset{color:var(--muted);font-weight:700}
  /* HANDED OVER AT THE STOP. A tickable extra is a button, not a div — it has to look pressable at a
     doorstep, and a ticked one has to be unmistakable from two steps away. */
  .lpx.lpxh{width:100%;text-align:left;border:0;border-bottom:1px solid var(--coral-soft);cursor:pointer;font:inherit}
  .lpxh[data-on="1"]{background:#EAF6F0}
  .lpx.done{background:#EAF6F0}
  /* 565: the collapsed hand-over group. Reuses .lpx for the row shape, .lpxb.on for the tick and
     .lpmore for the expander, so the group reads as one more row of the same list rather than as a
     control bolted on top of it. Tappable in BOTH forms — a supervisor reading a marked stop back
     expands the same way a driver correcting a mis-tick does. */
  .lpx.lpxg{width:100%;text-align:left;border:0;border-bottom:1px solid var(--coral-soft);cursor:pointer;
            font:inherit;background:#EAF6F0;align-items:center;min-height:44px}
  .lpxgt{flex:1;min-width:0;font-size:12.5px;font-weight:800;color:var(--muted)}
  /* 579: the customer's number, tapped one-handed at a doorstep — its own row, never inside the tick
     button. min-height is 444's thumb floor: this is the control a driver reaches for while holding a
     crate, and it carries one short line, so nothing else would make it tall enough. */
  .lpxtel{display:flex;align-items:center;gap:6px;min-height:44px;padding:6px 10px;text-decoration:none;
          font-size:13px;font-weight:900;color:var(--coral-deep);background:#FFF7E8;
          border-bottom:1px solid var(--coral-soft)}
  .lpxnote{font-size:11.5px;font-weight:700;color:var(--navy);margin-top:2px;line-height:1.45}
  /* 567: shown only inside an expanded group on a MARKED stop, where tapping a row is a correction
     rather than the normal path. Not a warning tone — the driver is doing the right thing. */
  .lpxfix{padding:6px 10px;font-size:11.5px;font-weight:700;color:var(--navy);background:#FFF7E8;
          border-bottom:1px solid var(--coral-soft)}
  .lpxb{flex:0 0 20px;height:20px;border-radius:6px;border:2px solid var(--coral-deep);display:flex;
        align-items:center;justify-content:center;font-size:12px;font-weight:900;color:#fff;margin-top:1px}
  .lpxh[data-on="1"] .lpxb,.lpxb.on{background:var(--green-deep);border-color:var(--green-deep)}
  .lpxh[data-on="1"] .lpxt,.lpx.done .lpxt{text-decoration:line-through;color:var(--muted)}
  /* The gate line, and the escape under it. The escape is deliberately quieter than a normal action. */
  .lpgate{margin-top:8px;font-size:12.5px;font-weight:900;color:var(--coral-deep)}
  .lpstopgo.warn{background:#fff;color:var(--coral-deep);border:2px solid var(--coral-soft);font-size:12.5px}
  /* VAN-PLAN TIMELINE (Bram 2026-07-30): time down the side, one column per driver, stops where they land.
     Cards are absolutely positioned at (arrival − window start) × VCAL_PPM; the dotted line between two
     cards is the time the plan leaves for the drive. touch-action:none on cards is what makes iPad drags
     drags instead of scrolls. */
  .vcal{display:flex;background:var(--surface);border:1px solid var(--line);border-radius:16px;margin-top:12px;overflow-x:auto;position:relative}
  .vcalgut{flex:0 0 40px;border-right:1px solid var(--line);position:relative}
  .vcalcol{flex:1;min-width:150px;border-right:1px solid var(--line);display:flex;flex-direction:column}
  .vcalcol:last-child{border-right:0}
  .vcalcol.over{background:var(--blush)}
  .vcalhd{padding:8px 10px 7px;border-bottom:1px solid var(--line);min-height:46px}
  .vcalgrid{position:relative}
  .vcalhour{position:absolute;left:0;right:0;border-top:1px solid var(--blush-deep);pointer-events:none}
  .vcalhour span{position:absolute;top:-7px;left:5px;font-size:10px;font-weight:800;color:var(--muted);background:var(--surface);padding:0 2px}
  .vcalshift{position:absolute;left:0;right:0;background:var(--blush);opacity:.5;pointer-events:none}
  .vcalstop{position:absolute;left:6px;right:6px;height:28px;display:flex;align-items:center;gap:6px;padding:0 9px;
    background:var(--blush);border:1.5px solid var(--ib);border-radius:10px;cursor:grab;touch-action:none;
    user-select:none;-webkit-user-select:none;z-index:2;box-sizing:border-box}
  .vcalstop .vt{font-size:11px;font-weight:900;color:var(--coral-deep);font-variant-numeric:tabular-nums;flex:none}
  .vcalstop .vn{font-size:12px;font-weight:800;color:var(--navy);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
  .vcalstop .vr{flex:none;font-size:9px;font-weight:900;background:var(--navy);color:#fff;border-radius:6px;padding:1px 4px}
  .vcalstop.ret{background:var(--surface);border-style:dashed}
  .vcalstop.ret .vn,.vcalstop.ret .vt{color:var(--muted)}
  /* 549: a stop taken off this day. Struck through and faded rather than removed, so the planner can see
     what they took off and put it back — the driver's screen is where it actually disappears. */
  .vcalstop.skipped{opacity:.55;border-style:dashed}
  .vcalstop.skipped .vn{text-decoration:line-through}
  .vcalstop.drag{opacity:.9;box-shadow:var(--shadow-lift);cursor:grabbing;z-index:40}
  .vcalstop.sel{height:auto;min-height:30px;flex-wrap:wrap;padding:6px 9px;z-index:6;cursor:default}
  .vcalgap{position:absolute;left:0;right:0;pointer-events:none;display:flex;align-items:center;justify-content:center;z-index:1}
  .vcalgap i{position:absolute;top:0;bottom:0;left:50%;border-left:2px dotted var(--blush-deep)}
  .vcalgap span{position:relative;background:var(--surface);border:1px solid var(--line);border-radius:999px;padding:1px 7px;font-size:10px;font-weight:800;color:var(--muted)}
  .vcalgap.warn span{color:#fff;background:var(--coral-deep);border-color:var(--coral-deep)}
  /* THE VAN, at the height of its last mark on the Today board (vptVanNow). pointer-events:none so the
     emoji can never intercept a tap or drag meant for the card it sits on; the words live in the column
     header, which is why it carries no title of its own. */
  .vnow{position:absolute;right:1px;margin-top:-8px;z-index:7;font-size:15px;line-height:1;
        filter:drop-shadow(0 1px 2px var(--scrim));pointer-events:none}
  .vcaltray{border-top:1px dashed var(--line);padding:8px;display:flex;flex-direction:column;gap:6px}
  .vcaltray .vcalstop{position:static;height:28px}
  .vcalfoot{padding:8px;border-top:1px solid var(--line);margin-top:auto}
  #vcalbubble{position:fixed;z-index:300;background:var(--navy);color:#fff;font-size:12px;font-weight:900;border-radius:8px;padding:3px 8px;pointer-events:none;font-variant-numeric:tabular-nums;box-shadow:var(--shadow-lift)}
  /* THE LOAD CHECKLIST. Only appears once the driver has tapped "I'm leaving now" — before that they are
     reading, after it they are loading. A ticked row goes green and stays ticked across a reload. */
  .lpck{margin-top:10px;border:2px solid var(--coral-deep);border-radius:14px;overflow:hidden}
  .lpckh{padding:9px 11px;background:var(--coral-deep);color:#fff;font-size:12.5px;font-weight:900}
  .lpckr{display:flex;gap:10px;align-items:flex-start;width:100%;text-align:left;padding:11px;border:0;
         border-bottom:1px solid var(--line);background:#fff;cursor:pointer}
  .lpckr:last-child{border-bottom:0}
  .lpckr[data-on="1"]{background:#EAF6F0}   /* no --green-tint in the palette; matches its tint weight */
  .lpckb{flex:0 0 22px;height:22px;border-radius:7px;border:2px solid var(--coral-deep);display:flex;
         align-items:center;justify-content:center;font-size:13px;font-weight:900;color:#fff}
  .lpckr[data-on="1"] .lpckb{background:var(--green-deep);border-color:var(--green-deep)}
  .lpckt{font-size:13px;font-weight:900;color:var(--navy)}
  .lpckr[data-on="1"] .lpckt{text-decoration:line-through;color:var(--muted)}
  .lpckd{font-size:11.5px;font-weight:600;color:var(--muted);margin-top:1px}
  .vpsup{font-size:11.5px;font-weight:800;color:#3A4A8C;background:#EEF1FB;border:1px solid #CFD8F5;border-radius:10px;padding:4px 8px;margin-top:5px;display:inline-block}
  .foredots{display:inline-flex;align-items:center;gap:2px;flex-wrap:wrap}
  .forechips{display:flex;gap:6px;flex-wrap:wrap;margin-top:2px}
  .foregrp{font-size:11px;font-weight:900;text-transform:uppercase;letter-spacing:.06em;color:var(--muted);margin:14px 4px 6px}
  .foreavg{font-size:11px;color:var(--muted);font-weight:600}
  .forennum{font-size:14px;font-weight:900;color:var(--navy);font-variant-numeric:tabular-nums}
  .foredel{border:0;background:transparent;color:var(--muted);font-size:14px;cursor:pointer;padding:0 2px;line-height:1}
  /* ---- donut FORECAST long-term "2-week overview" (fork branch donut-forecast, Wave 3) ---- */
  .plangraph{background:var(--blush);border:1px solid var(--line);border-radius:18px;padding:12px 10px 6px;margin-top:10px;overflow:hidden}
  .plangraph svg{display:block;width:100%;height:auto}
  .planwrap{background:var(--surface);border:1px solid var(--line);border-radius:18px;box-shadow:var(--shadow-card);overflow-x:auto;-webkit-overflow-scrolling:touch;margin-top:12px}
  .plant{border-collapse:separate;border-spacing:0;font-variant-numeric:tabular-nums}
  .plant th,.plant td{padding:7px 9px;border-bottom:1px solid var(--line);white-space:nowrap;text-align:right;font-size:13px;font-weight:800;color:var(--navy);vertical-align:middle}
  .plant thead th{position:sticky;top:0;z-index:3;background:var(--surface);border-bottom:1.5px solid var(--line);font-size:11px;line-height:1.25;padding:8px 9px}
  .plant th.rowlab,.plant td.rowlab{position:sticky;left:0;z-index:2;background:var(--surface);text-align:left;font-weight:800;max-width:132px;min-width:104px;overflow:hidden;text-overflow:ellipsis;box-shadow:1px 0 0 var(--line)}
  .plant thead th.rowlab{z-index:4}
  .plant tbody tr:last-child td,.plant tbody tr:last-child th{border-bottom:0}
  .plant td.today,.plant thead th.today{background:var(--coral-tint)}
  .plant td.wknd,.plant thead th.wknd{background:#FBF3EF}
  .plant td.today.wknd,.plant thead th.today.wknd{background:var(--coral-tint)}
  .plant .planday .wd{font-size:10px;font-weight:800;text-transform:uppercase;letter-spacing:.04em;color:var(--muted);display:block}
  .plant .planday.on .wd{color:var(--coral-deep)}
  .plant .planday .dn{font-size:15px;font-weight:900;color:var(--navy)}
  .plant tr.plangrp th{background:var(--blush);color:var(--navy-deep);font-size:10px;text-transform:uppercase;letter-spacing:.06em;font-weight:900;text-align:left}
  .plant tr.plangrp td{background:var(--blush)}
  .plant tr.plermeta td,.plant tr.plermeta th.rowlab{background:#FCF6F2}
  .plant td.planfaint{opacity:.42}
  .plant td.planmut{color:var(--muted);font-weight:700}
  /* NOT ON THE MENU THAT DAY — a different fact from "predicted nothing" (·) and from "no history, type it
     in" (—), so it gets its own look rather than a third dash nobody can tell apart. Hatched and empty: in a
     14-column grid an absent cell reads instantly, a glyph has to be decoded. The title says which days the
     flavour IS available, so a blank never looks like a broken grid. */
  .plant td.planoff{background-image:repeating-linear-gradient(135deg,transparent,transparent 4px,var(--line) 4px,var(--line) 5px);opacity:.5}
  .plant .plersales{color:var(--green-deep)}
  .plant .plerinfo{font-size:10.5px;font-weight:700;line-height:1.35;text-align:right;white-space:normal;min-width:120px}
  .plerbadge{display:inline-block;border-radius:999px;padding:1px 6px;font-size:10px;font-weight:800;margin:1px 0 1px 3px}
  .plerbadge.up{background:#E2F4EA;color:var(--green-deep)} .plerbadge.dn{background:var(--coral-tint);color:var(--coral-deep)}
  .plerchip{display:inline-block;border-radius:999px;padding:1px 6px;font-size:10px;font-weight:800;background:var(--surface);border:1px solid var(--line);margin:1px 0 1px 3px;color:var(--navy)}
  .plerbtn{border:1.5px solid var(--ib);background:var(--surface);border-radius:10px;padding:6px 8px;font-size:11px;font-weight:800;color:var(--navy);cursor:pointer;font-family:inherit;white-space:nowrap;display:block;width:100%;margin:2px 0}
  .plerbtn.acc{border-color:var(--coral-soft);background:var(--coral-tint);color:var(--coral-deep)}
  .plerbtn:disabled{opacity:.5;cursor:default}
  .plercmp{border:0;background:none;color:var(--coral-deep);font-size:10.5px;font-weight:800;cursor:pointer;font-family:inherit;padding:2px 0;text-decoration:underline}
  .planlegend{display:flex;flex-wrap:wrap;gap:10px;margin:8px 4px 0;font-size:11px;font-weight:700;color:var(--muted)}
  .planlegend span{display:inline-flex;align-items:center;gap:5px}
  .planlegend i{width:11px;height:11px;border-radius:3px;display:inline-block}
  /* app-shell rail (iPad) — hidden on phone; replaces the bottom pill on iPad */
  .apprail{display:none}
  /* phone (<1024) — floating global shop switcher (admin), top-right, above the screen chrome */
  @media(max-width:1023px){
    .phoneloc{display:block;position:fixed;top:10px;right:12px;z-index:46}
    .phoneloc select{font-size:16px;font-weight:800;padding:7px 12px;border-radius:999px;border:1.5px solid var(--coral-soft);background:rgba(255,255,255,.96);color:var(--navy);font-family:inherit;box-shadow:0 6px 18px rgba(0,0,0,.16);max-width:150px}   /* 16px: phone twin of .railloc — sub-16px focus auto-zooms iOS (see .inp / .railloc) */
    /* Header lines wrap clear of the fixed switcher instead of running under it (admin at 375px: the
       pill sat on Today's Sign out link, and later on the prep/donut-order header pills). Generalised
       from .sub-only 2026-08-08 — any header row may carry it; the class renders only when the pill
       does (phoneLocClearCls). */
    /* flex-wrap too: padding alone cannot defend a row whose content is WIDER than the space beside the
       switcher — flex children overflow into the padding and slide under the pill anyway (seen on the
       Inventory links and the Production-rounds title). Wrapped content lands BELOW the switcher's
       vertical band, which is just as clear and costs nothing on rows that fit. */
    .phonelocclear{padding-right:152px;flex-wrap:wrap}
  }
  /* iPad (>=1024) — right-rail app shell; content stays a SINGLE centered column (Pick-queue = a card grid) */
  @media(min-width:1024px){
    .wrap,header,.subbar .inner{max-width:760px}
    .nav{display:none}                                    /* phone bottom pill hidden on iPad — the full rail replaces it */
    /* ...so stop reserving room for it. .subbar.hasnav pads 74px below itself to clear the bottom pill, and
       that padding was never undone here: on iPad the submit bar of every screen that uses it (donut count,
       order, count) floated a pill-height above the bottom edge with nothing underneath, clipping the last
       row of the list. Visible at rest in Bram's 2026-08-12 iPad video, before any of the zoom/fixed
       drifting started. Back to the plain .subbar padding — the rail is on the RIGHT and costs no height. */
    .subbar.hasnav{padding-bottom:calc(16px + env(safe-area-inset-bottom))}
    .apprail{display:flex;flex-direction:column;align-items:center;gap:7px;position:fixed;top:0;right:0;width:116px;height:100vh;padding:18px 0;overflow:auto;background:var(--blush);z-index:41}
    #pqgrid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:14px}   /* Pick queue: 2-col order cards inside the centered column */
  }
  /* ---- PER-CITY RECIPE BODIES (migrations 0075/0076) — the recipe manager's sketch: shared header on top,
     Berlin left, Hamburg right. NARROW SCREENS GET A CITY TOGGLE, not two squeezed columns and not a
     horizontal scroll: the bakery follows a method on a tablet held upright while their hands are in dough,
     so the ONE column they actually cook from has to be full width and readable. Comparing the two cities
     is Bram's desk job, and that happens at >=900px where both fit. The toggle is pure CSS off
     .rvcols[data-sel] — no resize listener, so rotating the iPad can never leave the DOM out of sync. */
  .rvcols{display:grid;gap:14px;margin-top:8px}
  .rvcol{border:1px solid var(--line);border-radius:16px;padding:12px 14px;background:var(--surface);min-width:0}
  .rvcolh{display:flex;align-items:baseline;gap:8px;flex-wrap:wrap;margin:0 0 10px}
  .rvcolh b{font-size:15px;font-weight:900;color:var(--navy)}
  .rvcolh .rs{margin-left:auto;text-align:right}
  .rvcol .row{padding-left:0;padding-right:0}
  .rvstep{display:flex;gap:10px;align-items:flex-start;padding:9px 0;border-top:1px solid var(--line)}
  .rvstep:first-of-type{border-top:0}
  .rvsh{border-style:dashed;color:var(--muted)}                  /* the "shared" marker — dashed = belongs to BOTH columns */
  .rvcoltabs{display:flex;gap:8px;margin:12px 0 0}
  @media(min-width:900px){
    /* TWO columns only when there ARE two (Bram 2026-07-20: a city user's single column was rendering at
       half width with dead space beside it). A city-scoped read returns ONE column, and one recipe body is
       not half a page — it is the whole thing that bakery cooks from, so it gets the full width. */
    .rvcols[data-cols="2"]{grid-template-columns:repeat(2,minmax(0,1fr))}
    .rvcoltabs{display:none}                                     /* both columns visible -> nothing to toggle */
  }
  @media(max-width:899px){
    .rvcols[data-sel="Berlin"] .rvcol[data-rvcol="Hamburg"]{display:none}
    .rvcols[data-sel="Hamburg"] .rvcol[data-rvcol="Berlin"]{display:none}
  }
  /* ==================================================== SHARED PRIMITIVES (brief 154) =====
     The design-language foundation: docs/design/DESIGN-LANGUAGE.md holds the rulings
     (R1–R16), docs/design/INVENTORY.md the per-screen lines that adopt these. Everything
     here is ADDITIVE — no existing class changed meaning; 46 classic scripts share one
     global scope, so class names are API surface and are never renamed or removed. */
  /* .btn.danger — the destructive commit (R2): delete, force-apply, close-N-shops. Same
     geometry and hard shadow as .btn so it reads as "a primary you must mean"; --danger
     red so it can never be mistaken for the everyday coral action beside it. Replaces the
     hand-invented Material red at catalogue_sync.js:76. */
  .btn.danger{background:var(--danger);box-shadow:0 5px 0 var(--danger-deep)}
  .btn.danger:active{box-shadow:0 1px 0 var(--danger-deep)}
  /* Tinted state cards (R3) — the classes the ~9 hand-hexed card palettes converge on.
     Variants set surface + border only; content keeps its own type. `color` is set on
     .warn/.info because their hand-rolled predecessors inked ALL text (worklists, notices);
     .good/.alarm predecessors kept navy text. .alarm is for DOMAIN alarms (missing orders,
     left-behind crates) — a failed LOAD renders loadErrorHtml, which owns the same shape. */
  .card.warn{background:var(--amber-tint);border-color:var(--amber-line);color:var(--amber-ink)}
  .card.good{background:var(--green-tint);border-color:var(--green-line)}
  .card.info{background:var(--info-tint);border-color:var(--info-line);color:var(--info-ink)}
  /* 161 item 14, ruling REVISED on evidence: the alarm border is --danger, not --coral.
     Agent/2 measured the coral conversion at dE76 20.4 on the card that says a PAID
     CUSTOMER ORDER IS MISSING — an alarm in the brand accent reads CALMER than the red it
     replaced, inverting severity. And every pre-pass author had independently reached for
     a non-brand red at these sites (#C62828 day_check, #A32020 prep) — three hands making
     the same point. Coral stays the ATTENTION border (loadErrorHtml's "couldn't load");
     danger red is the DOMAIN-ALARM border. This restores each site's pre-pass severity. */
  .card.alarm{border:2px solid var(--danger)}
  /* .pill.pi / .pill.pm (R12) — the info-navy and muted-grey pill families that existed
     only as inline hex (special_orders source badges, stock_overview basis pills, rrpthin). */
  .pill.pi{background:var(--info-tint);border-color:var(--info-line);color:var(--info-ink)}
  .pill.pm{background:var(--grey-tint);border-color:var(--grey-line);color:var(--grey-ink)}
  /* .tap44 (R14) — the .step.sm trick as a utility: an invisible ::after grows the hit
     area to the 44px Apple minimum without moving a pixel of layout. Opt-in per control.
     DO NOT stack it on elements whose ::after already means something (.step.sm, .sw).
     >>> IT USED TO BE `inset:-8px` AND THAT DELIVERED 29px, NOT 44 (444). On the ~13px line these
     >>> inline Change/Edit/Undo links use, -8px top and bottom gives 13+16 = 29 — correct across
     >>> (66px) and 15px short down, on exactly the controls somebody taps in a hurry. A fixed
     >>> negative inset can only ever be right for one line height, and the class is used on eight
     >>> screens with several. min-height/min-width are a FLOOR that does not care about the line;
     >>> the calc keeps the old ±8px bleed so a wide link is not shrunk to 44, and the centring
     >>> puts the extra reach around the text rather than below it. Nothing paints: no content,
     >>> no background, and the element is still out of flow, so no layout moves. */
  .tap44{position:relative}
  .tap44::after{content:'';position:absolute;left:50%;top:50%;transform:translate(-50%,-50%);
    width:calc(100% + 16px);height:calc(100% + 16px);min-width:44px;min-height:44px}
  /* .hscroll (R15) — the .rooms scroller for non-chip content: sized-to-content children,
     hidden scrollbar, momentum scroll. The page body itself never scrolls sideways. */
  .hscroll{display:flex;gap:8px;overflow-x:auto;-webkit-overflow-scrolling:touch;scrollbar-width:none}
  .hscroll::-webkit-scrollbar{display:none}
  /* .chip:disabled (R2) — the rule vans.js:2013 had to fake inline per site. */
  .chip:disabled{opacity:.35;cursor:default}
  /* A checkbox in a .urow matches the radio beside it (R5) — users.js's location list and
     close_days' shop list rendered native ~14px boxes because only [type=radio] had a rule. */
  .urow input[type=checkbox]{width:22px;height:22px;accent-color:var(--coral);flex:none;cursor:pointer}
  /* ---- THE CLASSLESS-FIELD BACKSTOP (R5). Safari auto-zooms the page when a focused
     field's font-size is under 16px, and once zoomed, iOS resolves position:fixed against
     the layout viewport — the .subbar tears off mid-list (see .inp, ~line 43: that comment
     is the incident report). Eleven+ fields shipped with NO class at all (special_orders'
     whole form, close_days' date/reason, invoices' picker) and sat at the ~13px UA default
     in the UA's own font. :where() keeps specificity at ZERO, so any classed rule — .inp,
     .parv (17px), .qy, .dcival .qy — still wins exactly as before; only fields nothing
     else styles are caught. This is a seatbelt, not the fix: every field still wants its
     class (INVENTORY lines say which). */
  :where(input:not([type=checkbox]):not([type=radio]):not([type=range]):not([type=file]),select,textarea){font-size:16px;font-family:inherit}
  /* ==================================================== 161 SHARED-FILE FOLLOW-UPS =====
     Items the five design passes carried to the shared-file owner (docs/tasks/161). */
  /* item 5 — the tick/info circle recipes tasks.js and pick.js each carried as inline
     style strings ("no new CSS" was stated as a feature; five copies later it is a class).
     Byte-faithful to the shipped inline recipes; adoption is each territory's own line. */
  .tickc{width:28px;height:28px;flex:none;border-radius:50%;display:flex;align-items:center;justify-content:center;font-size:15px;font-weight:900;cursor:pointer;font-family:inherit;padding:0;line-height:1;border:2px solid var(--ib);background:var(--surface);color:transparent}
  .tickc.on{border:0;background:var(--green);color:#fff}
  .infoc{width:26px;height:26px;flex:none;border-radius:50%;border:2px solid var(--ib);background:var(--surface);color:var(--muted);font-weight:900;font-size:13px;cursor:pointer;font-family:inherit;padding:0;line-height:1}
  /* ==================================================== 375 THE FLAVOUR LIBRARY =====================
     The approved mockup (Claude Design "Flavour Library", options 1a/1b/1c/1f) was drawn in this
     sheet's own vocabulary, so everything below is LAYOUT only: no new colour, no new radius, no new
     shadow — every value is a var() that already existed.

     >>> EVERY CLASS IS `flv`-PREFIXED, AND THAT IS NOT TIDINESS. The mockup names its boxes `.two`,
     >>> `.sec`, `.cell`, `.card`, `.add`, `.ph` — and `.two`, `.sec` and `.card` ALREADY MEAN SOMETHING
     >>> ELSE in this file (2, 1 and 7 rules respectively, measured). There is one global stylesheet and
     >>> one global scope; a second `.sec` would not fail here, it would fail three screens away, months
     >>> from now, on whichever rule lost the specificity coin toss. */
  .flvhd{ display:flex; align-items:center; gap:10px; flex-wrap:wrap; margin-top:8px }
  .flvgrp{ margin:6px 0 18px }
  .flvgh{ display:flex; align-items:baseline; gap:10px; margin:16px 4px 8px }
  .flvgh .t{ font-size:11px; font-weight:800; text-transform:uppercase; letter-spacing:.07em; color:var(--muted) }
  .flvgh .n{ font-size:12px; color:var(--muted); font-weight:600 }

  /* ---- 1a PHOTOBOOK ---------------------------------------------------------------------------- */
  .flvgrid{ display:grid; grid-template-columns:repeat(5,minmax(0,1fr)); gap:12px }
  .flvcard{ background:var(--surface); border:1px solid var(--line); border-radius:20px;
            box-shadow:var(--shadow-card); overflow:hidden; display:flex; flex-direction:column;
            text-align:left; width:100%; padding:0; font:inherit; color:inherit }
  .flvph{ aspect-ratio:1/1; background:repeating-linear-gradient(135deg,var(--blush-deep) 0 10px,var(--blush) 10px 20px);
          display:flex; align-items:flex-end; padding:8px; position:relative }
  .flvph img{ position:absolute; inset:0; width:100%; height:100%; object-fit:cover }
  .flvph .need{ position:absolute; inset:0; display:flex; align-items:center; justify-content:center;
                font-size:11px; font-weight:800; color:var(--coral-deep) }
  .flvph .dt{ position:absolute; top:8px; left:8px }
  .flvph .vlp{ position:absolute; top:8px; right:8px }
  .flvfb{ padding:11px 12px 12px; display:flex; flex-direction:column; gap:8px; flex:1 }
  .flvnm{ font-size:15px; font-weight:700; line-height:1.2 }
  .flvmeta{ display:flex; align-items:center; justify-content:space-between; gap:6px; margin-top:auto }
  .flvpr{ font-size:14px; font-weight:900; white-space:nowrap }
  .flvadd{ border:1.5px dashed var(--ib); border-radius:20px; display:flex; align-items:center;
           justify-content:center; color:var(--coral-deep); font-weight:900; font-size:13.5px;
           background:rgba(255,255,255,.5); min-height:120px; width:100% }
  .flvvl{ display:inline-flex; align-items:center; justify-content:center; width:24px; height:24px;
          border-radius:999px; background:var(--green-tint); border:1px solid var(--green-line);
          color:var(--green-deep); font-size:11px; font-weight:900 }

  /* ---- 1b BOARD -------------------------------------------------------------------------------- */
  .flvbwrap{ overflow-x:auto }
  .flvb{ border-collapse:collapse; width:100%; min-width:900px }
  .flvb th{ font-size:11px; letter-spacing:.07em; text-transform:uppercase; color:var(--muted);
            font-weight:800; text-align:left; padding:10px 7px; border-bottom:1px solid var(--line);
            background:var(--coral-tint); white-space:nowrap }
  .flvb td{ padding:8px 7px; border-bottom:1px solid var(--line); font-size:13.5px;
            vertical-align:middle; white-space:nowrap }
  .flvb td.nm{ white-space:normal; min-width:130px; font-size:15px; font-weight:700 }
  .flvb td.note{ color:var(--muted); font-weight:600; max-width:160px; overflow:hidden; text-overflow:ellipsis }
  .flvb tr.g td{ background:var(--coral-tint); font-weight:800; color:var(--coral-deep); font-size:11px;
                 text-transform:uppercase; letter-spacing:.07em }
  .flvb .ed{ display:inline-block; border:1.5px solid transparent; border-radius:8px; padding:3px 6px;
             min-width:40px; font:inherit; color:inherit; background:transparent; text-align:left }
  .flvb .ed:hover{ border-color:var(--ib); background:var(--surface) }
  .flvb input.ed, .flvb select.ed{ border-color:var(--ib); background:var(--surface); min-height:32px }
  .flvb .tk{ color:var(--green-deep); font-weight:900 }

  /* ---- 1c YEAR --------------------------------------------------------------------------------- */
  .flvcalwrap{ overflow-x:auto }
  .flvcal{ display:grid; grid-template-columns:96px repeat(12,minmax(64px,1fr)); gap:6px;
           align-items:stretch; min-width:900px }
  .flvcal .mh{ font-size:12px; font-weight:900; text-align:center; padding:6px 0; color:var(--muted) }
  .flvcal .mh.now{ color:var(--coral-deep) }
  .flvcal .rh{ font-size:11px; font-weight:800; text-transform:uppercase; letter-spacing:.07em;
               color:var(--muted); padding:10px 4px; display:flex; align-items:flex-start }
  .flvcell{ background:var(--surface); border:1px solid var(--line); border-radius:12px; padding:6px;
            display:flex; flex-direction:column; gap:5px; min-height:96px; min-width:0; text-align:left;
            font:inherit; color:inherit; width:100% }
  .flvcell.now{ border-color:var(--coral-soft); box-shadow:0 0 0 2px var(--coral-tint) }
  .flvcell.empty{ background:transparent; border-style:dashed }
  .flvmini{ display:flex; align-items:center; gap:5px; font-size:10px; font-weight:800; line-height:1.15; min-width:0 }
  .flvmini span{ min-width:0; overflow-wrap:anywhere }
  .flvmini .d{ font-size:9px; color:var(--muted); font-weight:600 }
  .flvmore{ font-size:10px; color:var(--coral-deep); font-weight:900 }

  /* ---- 1f PHONE -------------------------------------------------------------------------------- */
  /* Two columns, per the approved 1f. The board is READ-ONLY below 700px: an inline field inside a
     900px-wide table on a 375px screen is a horizontal drag to reach and a mis-tap to edit, and this
     screen's write path (PATCH on blur) has no undo. */
  @media (max-width:700px){
    .flvgrid{ grid-template-columns:repeat(2,minmax(0,1fr)); gap:10px }
    .flvnm{ font-size:14px }
    .flvb .ed{ border-color:transparent; background:transparent }
  }
  @media (min-width:701px) and (max-width:1100px){
    .flvgrid{ grid-template-columns:repeat(3,minmax(0,1fr)) }
  }

  /* ---- PRINT: ONE MONTH PER SHEET, CARDS 3-WIDE ------------------------------------------------ */
  /* >>> EVERY RULE LIVES INSIDE @media print — the estate's rule, and the reason is that a print-only
     >>> declaration written outside one is a SCREEN change nobody notices until it ships.
     >>> >>> AND THE GRID IS NARROWED, NOT SCALED. The screen grid is 5-wide; a fit pass that shrinks
     >>> >>> the page to fit 5 columns on A4 makes every card smaller and the photo useless — the
     >>> >>> photobook exists to be LOOKED at. 3-wide at full size is the same sheet count either way. */
  @media print{
    .flvgrid{ grid-template-columns:repeat(3,minmax(0,1fr)); gap:10px }
    .flvcard{ break-inside:avoid; box-shadow:none; border-color:#ddd }
    .flvgrp{ break-inside:avoid-page }
    .flvadd, .flvnoprint{ display:none !important }
    .flvph{ background:#f4f4f4 }
  }

  /* ============================ THE STRIPS SCROLL WITH A MOUSE AGAIN (brief 166, URGENT) ====
     Bram, 2026-08-22, photo of the Ordering screen on a bakery desktop, Katja's words:
     "Ich kann die weisse Liste mit den Suppliers nicht mehr bewegen."
     >>> A HIDDEN SCROLLBAR IS A TOUCH IDIOM, AND WE SHIPPED IT TO PEOPLE HOLDING A MOUSE.
     >>> R15 hides the bar on .rooms/.hscroll/.dordstrip so a chip strip does not grow a grey
     >>> band on an iPad — right there, and wrong the moment the same screen opens on a desktop:
     >>> there is no bar to drag, and a wheel scrolls the PAGE, not a horizontal strip. The strip
     >>> is then immovable and everything past the right edge is unreachable. 157 moved the
     >>> Ordering supplier tabs onto this family, so the worst case landed on the heaviest user —
     >>> the bakery orders on desktops and has the most suppliers.
     >>> `pointer:fine` is the honest test: it matches mice AND trackpads (a trackpad Mac can
     >>> two-finger-scroll horizontally and does not need the bar, but a thin bar costs it
     >>> nothing). Do not try to separate them — it is not reliably detectable, and the failure
     >>> mode of over-cleverness here is Katja, again.
     >>> Placed at the END of the sheet deliberately: a media query adds NO specificity, so this
     >>> only beats the base `display:none` by source order. */
  @media (pointer: fine){
    .rooms, .hscroll, .dordstrip{ scrollbar-width:thin; scrollbar-color:var(--ib) transparent; padding-bottom:8px }
    .rooms::-webkit-scrollbar, .hscroll::-webkit-scrollbar, .dordstrip::-webkit-scrollbar{ display:block; height:8px }
    .rooms::-webkit-scrollbar-track, .hscroll::-webkit-scrollbar-track, .dordstrip::-webkit-scrollbar-track{ background:transparent }
    .rooms::-webkit-scrollbar-thumb, .hscroll::-webkit-scrollbar-thumb, .dordstrip::-webkit-scrollbar-thumb{
      background:var(--ib); border-radius:999px }
    .rooms::-webkit-scrollbar-thumb:hover, .hscroll::-webkit-scrollbar-thumb:hover, .dordstrip::-webkit-scrollbar-thumb:hover{
      background:var(--blush-deep) }
  }
  /* .rooms.wrap — the opt-in that makes scrolling moot where there is room to wrap (brief 167
     opts the supplier strips in; INERT here, nothing wears it yet). Wrapping beats a scroller on
     a wide screen: every chip is visible at once and nothing has to be dragged into view. */
  .rooms.wrap{ flex-wrap:wrap; overflow-x:visible; scrollbar-width:auto }
  .rooms.wrap::-webkit-scrollbar{ display:none }

/* ============================================ THE BAKERY PACKING BOARD (brief 372, design 249)
   An iPad on a bench, read at arm's length, tapped with floury hands between batches. Two rules drive
   every number here and neither is a preference:
     · 44px is the app's floor for a target; the WALK is bigger, because the walk is what you tap while
       carrying a tray. 372 says to measure the sizes rather than eyeball them, so they are stated once,
       here, and the sandbox test asserts against these declarations.
     · ONE SHOP PER LINE in the walk (Linda Hansack, who packs). Never wrap it to fit more on screen —
       two shops on a line is how a tray goes into the neighbouring rack, the error she named first. */
  .pb-top{display:flex;flex-wrap:wrap;gap:10px;justify-content:space-between;align-items:center;margin:8px 4px 12px}
  .pb-rounds,.pb-tools{display:flex;gap:8px;flex-wrap:wrap}
  .pb-round,.pb-tool{min-height:44px;padding:0 16px;border-radius:12px;border:1px solid var(--line);
    background:var(--surface);color:var(--ink);font-weight:800;font-size:15px;cursor:pointer}
  .pb-roundon,.pb-toolon{background:var(--coral);border-color:var(--coral);color:#fff}

  /* The grid scrolls sideways INSIDE its own box — the page body must never scroll horizontally, or the
     rack line moves under the packer's finger while they are reaching for a column. */
  .pb-scroll{overflow-x:auto;-webkit-overflow-scrolling:touch;border:1px solid var(--line);border-radius:16px;background:var(--surface)}
  .pb-grid{border-collapse:separate;border-spacing:0;width:100%}
  .pb-grid th,.pb-grid td{padding:0}
  /* 432, Bram: "horizontal rows between the flavours" — so a flavour can be followed across eleven racks
     without the eye dropping a line. 1px, the weight of the rack header rule; blush rather than --line
     because it separates without competing with the wave breaks (.pb-wave) that carry real meaning.
     `:not(:last-child)` puts a rule BETWEEN rows and never under the last one. The walk rows already
     carry their own 1px separator and are left alone. */
  .pb-grid tbody tr:not(:last-child)>*{border-bottom:1px solid var(--blush)}
  .pb-h{min-width:64px;padding:8px 4px;text-align:center;border-bottom:1px solid var(--line);vertical-align:bottom}
  /* The deco-wave break is a LINE, not a gap: a gap in a row of racks reads as a missing rack. */
  .pb-wave{border-left:3px solid var(--coral-soft)}
  .pb-hdone{background:var(--coral-tint)}
  .pb-short{font-size:12px;font-weight:800;letter-spacing:.04em;text-transform:uppercase;color:var(--ink)}
  .pb-indue{font-size:12px;font-weight:700;color:var(--muted);font-variant-numeric:tabular-nums}
  .pb-rowh{position:sticky;left:0;background:var(--surface);z-index:1;text-align:left;border-right:1px solid var(--line)}
  .pb-rowbtn{min-height:48px;width:100%;padding:6px 12px;border:0;background:none;text-align:left;
    font-weight:800;font-size:15px;color:var(--ink);cursor:pointer}
  .pb-rdone{color:var(--muted);text-decoration:line-through}
  .pb-namedrow .pb-rowbtn{color:var(--coral-deep)}
  .pb-c{border-top:1px solid var(--line)}
  .pb-cell{min-width:56px;min-height:48px;width:100%;border:0;background:none;cursor:pointer;
    font-weight:800;font-size:16px;color:var(--muted);font-variant-numeric:tabular-nums}
  .pb-part{color:var(--coral-deep)}
  .pb-full{background:var(--coral);color:#fff}
  /* Dimmed, NEVER hidden: hiding a rack shifts every column left and the grid stops matching the line of
     racks in front of the packer, which is the one thing this screen exists to match. */
  .pb-off{background:repeating-linear-gradient(45deg,var(--coral-tint),var(--coral-tint) 6px,transparent 6px,transparent 12px);opacity:.5}
  /* 548: an empty cell is tappable — half a tray a tap. It stays visibly OFF-PLAN (the hatch above shows
     through) because that is what it is: an addition the packer made, not a tray the plan asked for. The
     '+' is quiet until something has been added, then the cell carries the number like any other. */
  .pb-add{color:var(--muted);opacity:.75;font-size:18px}
  .pb-added{opacity:1;color:var(--coral-deep);text-decoration:underline;text-underline-offset:3px}
  /* 548: the "+ Shop" picker — one row of shop names under the round bar, only while it is open. */
  .pb-pick{display:flex;flex-wrap:wrap;gap:6px;padding:8px 0 0}
  /* 552: a reprint moved the plan under the packer. Louder than the other warnings on this board,
     because it is the only one that means the trays in front of them are now wrong. */
  .pb-moved{border-left:4px solid var(--coral-deep)}
  .pb-moved b{font-weight:800}

  /* THE WALK — bigger than the floor, one shop per line. */
  .pb-walk{background:var(--surface);border:1px solid var(--line);border-radius:20px;overflow:hidden}
  .pb-walkhead{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:14px 16px;border-bottom:1px solid var(--line)}
  .pb-walktitle{font-size:22px;font-weight:800;color:var(--ink)}
  .pb-done{min-height:56px;padding:0 22px;border-radius:14px;border:0;background:var(--coral);color:#fff;font-weight:800;font-size:16px;cursor:pointer}
  .pb-stop{display:flex;align-items:center;gap:16px;width:100%;min-height:72px;padding:12px 18px;
    border:0;border-bottom:1px solid var(--line);background:none;cursor:pointer;text-align:left;
    white-space:nowrap}
  .pb-stopname{flex:1;min-width:0;font-size:24px;font-weight:800;letter-spacing:.04em;text-transform:uppercase;color:var(--ink);
    overflow:hidden;text-overflow:ellipsis}
  /* >>> THE AMOUNT WAS CLIPPED AT THE RIGHT EDGE (407) — "1.5 /" with nothing after it. `.pb-stopname` is
     flex:1 and the two bumpers are 54px each, so the amount was the only child that could shrink, and it
     did. `flex:none` plus a min-width sized for the widest real value ("12.5 / 12.5") stops it, and the
     NAME keeps the ellipsis — a truncated shop name is legible, a truncated number is a wrong number. */
  .pb-stopqty{flex:none;min-width:104px;text-align:right;padding-right:4px;
    font-size:20px;font-weight:800;color:var(--muted);font-variant-numeric:tabular-nums;white-space:nowrap}
  .pb-stoptick{flex:none;width:32px;text-align:center;font-size:22px;color:var(--coral)}
  .pb-stopdone .pb-stopname{color:var(--muted)}
  .pb-stopdone{background:var(--coral-tint)}
  .pb-rackbtn{width:100%;min-height:44px;border:0;background:none;cursor:pointer;padding:4px 2px}
  .pb-longname{text-transform:none;letter-spacing:0;font-size:20px}
  /* 372 — the magnet colour, the named waves, and making the two axes look different.
     The swatch is the rack's physical magnet; in the WALK it sits beside the name you press (249's
     finding), in the grid it can only head the column, which is the trade the grid accepts. */
  .pb-swatch{height:6px;border-radius:3px;margin:0 4px 4px}
  .pb-dot{flex:none;width:26px;height:26px;border-radius:13px;border:1px solid rgba(0,0,0,.12)}
  .pb-waveh{padding:6px 4px;text-align:center;font-size:11px;font-weight:800;text-transform:uppercase;
    letter-spacing:.07em;color:var(--muted);border-bottom:1px solid var(--line)}
  /* Which dimension is the door: the tappable one is emphasised, the other recedes. */
  .pb-axis-flavour .pb-rowbtn{color:var(--coral-deep)}
  .pb-axis-rack .pb-rackbtn .pb-short{color:var(--coral-deep)}
  .pb-axis-rack .pb-rowbtn{color:var(--muted);font-weight:700}
  .pb-air{border-color:var(--coral-soft)}
  /* 372 follow-up (Bram, on the bench): the columns were all white and ran together; the walk needs a way
     to record MORE than the sheet asked for; and a tick threw away the number it replaced. */
  .pb-c{border-left:1px solid var(--line)}
  .pb-of{font-size:11px;font-weight:700;opacity:.75}
  /* 432: why this cell's due is not its plan ("plan 5 · R1 +1"). Quiet — it explains, never competes. */
  /* 425: how far a cell has moved since the sheet was printed. Coral, small, superscript-ish — it must be
     findable while scanning for a number that changed, and must not be mistaken for the number itself. */
  .rndchg{margin-left:4px;font-size:10px;font-weight:900;color:var(--coral-deep);vertical-align:super;font-variant-numeric:tabular-nums}
  .pb-barwhy{font-size:12px;font-weight:700;color:var(--muted);font-variant-numeric:tabular-nums}
  .pb-over{background:var(--coral-deep);color:#fff}
  .pb-stop{display:flex;align-items:center;gap:8px;width:100%;min-height:72px;padding:0 10px 0 0;
    border-bottom:1px solid var(--line);background:none}
  .pb-stoptap{flex:1;min-width:0;display:flex;align-items:center;gap:16px;min-height:72px;padding:12px 18px;
    border:0;background:none;cursor:pointer;text-align:left;white-space:nowrap}
  /* The bumpers clear the 44px floor with room to spare: they are pressed with floury hands, and the one
     that matters (+) is the only way to say "more went in than you asked for". */
  .pb-bump{flex:none;width:54px;height:54px;border-radius:14px;border:1px solid var(--line);
    background:var(--surface);font-size:24px;font-weight:800;color:var(--ink);cursor:pointer;line-height:1}
  .pb-stopover{background:var(--coral-tint)}
  .pb-stopover .pb-stoptick{color:var(--coral-deep);font-weight:800;font-size:18px}
  /* 372 — the two doors, said and shown; and v9's bumper bar in one fixed place. */
  /* A chevron and an underline on the name: the header is a door, so it has to look like one. */
  .pb-go{flex:none;color:var(--coral);font-weight:800;font-size:16px;opacity:.85}
  .pb-rowbtn{display:flex;align-items:center;justify-content:space-between;gap:8px}
  .pb-rowname{text-decoration:underline;text-decoration-style:dotted;text-underline-offset:3px;
    text-decoration-color:var(--coral-dust)}
  .pb-rackbtn .pb-short{text-decoration:underline;text-decoration-style:dotted;text-underline-offset:2px;
    text-decoration-color:var(--coral-dust)}
  .pb-rackbtn .pb-go{display:block;font-size:13px;line-height:1}
  /* The last cell tapped stays outlined, and the bar below points at it. */
  .pb-sel{box-shadow:inset 0 0 0 3px var(--coral-deep)}
  /* >>> z-index IS LOAD-BEARING HERE. `.pb-rowh` is a sticky column with z-index:1, and it painted OVER
     the bar's left half — the swatch and "flavour · rack", which is the only part that says WHICH cell
     the bumpers will change. A bar that cannot tell you what it points at is worse than no bar. */
  .pb-bar{position:sticky;bottom:0;z-index:3;display:flex;align-items:center;gap:10px;flex-wrap:wrap;
    margin-top:10px;padding:10px 12px;background:var(--surface);border:1px solid var(--line);
    border-radius:18px;box-shadow:var(--shadow-card)}
  .pb-barwhat{flex:1;min-width:0;font-size:15px;color:var(--ink);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
  .pb-barqty{font-size:15px;font-weight:800;color:var(--muted);font-variant-numeric:tabular-nums;white-space:nowrap}
  .pb-barend{min-height:54px;padding:0 16px;border-radius:14px;border:1px solid var(--line);
    background:var(--surface);font-weight:800;font-size:15px;color:var(--ink);cursor:pointer}
  .pb-barall{background:var(--coral);border-color:var(--coral);color:#fff}
  /* 372 — the walk's header: a way back, and the bulk action that button used to only imply. */
  /* ‹ Back rides the date's line rather than costing a title bar of its own — the board wants the pixels,
     but the way OUT still has to exist. */
  .pb-top .pb-back{min-height:44px;min-width:44px;padding:0 12px;font-size:20px;line-height:1;margin-right:2px}
  .pb-back{min-height:54px;padding:0 16px;border-radius:14px;border:1px solid var(--line);
    background:var(--surface);font-weight:800;font-size:15px;color:var(--ink);cursor:pointer;white-space:nowrap}
  .pb-walkhead{gap:12px}
  .pb-walktitle{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
  .pb-doneoff{background:var(--surface);border:1px solid var(--line);color:var(--muted)}
  .pb-warn{margin:0 0 8px;padding:10px 14px;border-radius:14px;background:var(--coral-tint);
    border:1px solid var(--coral-soft);color:var(--coral-deep);font-size:13.5px;font-weight:700}
  /* 393 — lift-and-place reordering, and the state that says a tap now means something different. */
  .pb-liftbar{display:flex;align-items:center;justify-content:space-between;gap:12px;margin:0 0 8px;
    padding:10px 14px;border-radius:14px;background:var(--coral);color:#fff;font-weight:800;font-size:14px}
  .pb-liftbar .pb-barend{min-height:44px;background:rgba(255,255,255,.16);border-color:transparent;color:#fff}
  .pb-lifted{outline:3px solid var(--coral-deep);outline-offset:-3px;background:var(--coral-tint)}
  .pb-target{background:var(--coral-tint)}
  /* 404 — the walk's own All, at the end of its stop list. Full width because it is the last thing
     in a list of full-width rows, and a packer reaching it has both hands full. */
  .pb-allplanned{display:block;width:100%;min-height:64px;border:0;border-top:1px solid var(--line);
    background:var(--coral);color:#fff;font-weight:800;font-size:17px;cursor:pointer}
  .pb-allplanned.pb-doneoff{background:var(--surface);color:var(--muted);cursor:default}
  /* 571: the round's bulk button under the resting grid. Same control as the walk's, one scope wider,
     so the same look — only the edge differs, because it is not inside a card. */
  .pb-packround{margin-top:10px;border-top:0;border-radius:16px}

/* 388 — the month board's two gap states. One class each, additive: a row with neither renders exactly as
   it did before. Muted rather than alarming — these are jobs to do, not errors, and a board where most
   rows are red teaches people to stop reading the colour. */
.flvgap{font-size:11px;padding:1px 6px;border-radius:7px;white-space:nowrap}
.flvgap-norecipe{background:#fdeceb;color:var(--coral-deep)}
.flvgap-nomenu{background:var(--ib);color:var(--muted)}
