/* SHOPS design system v2 — SERVD storefront idiom (light, Just-Eat/Deliveroo class),
   adapted from the SERVD pixel spec. Per-tenant accent via CSS vars (Ambas: red/green).
   Surfaces: storefront (light SERVD idiom) · staff/owner (same light system) ·
   hygiene (.hyg dark dashboard, blue accent — deliberately separate language, per spec). */

@font-face { font-family: 'Fraunces'; src: url('/fonts/fraunces-2.woff2') format('woff2');
  font-weight: 100 900; font-display: swap; }
@font-face { font-family: 'Fraunces'; src: url('/fonts/fraunces-0.woff2') format('woff2');
  font-weight: 100 900; font-style: italic; font-display: swap; }
@font-face { font-family: 'Hanken'; src: url('/fonts/hanken-3.woff2') format('woff2');
  font-weight: 100 900; font-display: swap; }

:root {
  /* SERVD light tokens */
  --ink: #F7F7F8; --ink-2: #FFFFFF; --ink-3: #F1F1F3;
  --line: #E7E7EB; --line-2: #D6D6DC;
  --text: #1A1A1F; --muted: #63636B; --muted-2: #8A8A92; --surface: #FFFFFF;
  /* Tenant accent (Ambas: logo red; ember = deepened red; fresh = logo green) */
  --saffron: #E2000A; --ember: #9E0007; --on-accent: #FFFFFF; --accent-strong: #B00008;
  --fresh: #7DC821; --card-from: #FDECEA; --card-to: #F7F7F8;
  --glass-top: rgba(255,255,255,.92); --glass-bottom: rgba(255,255,255,.72);
  --good: #17924E; --bad: #C4322F; --warn: #B87A17;
  --good-bg: #E9F5EE; --bad-bg: #FBEAE8; --amber-bg: #FDF3E0;
  --toast-bg: #1A1A1F; --toast-fg: #FFFFFF;
  --radius: 20px; --maxw: 480px;
  --sh-sm: 0 1px 2px rgba(16,17,33,.06);
  --sh-md: 0 6px 20px -8px rgba(16,17,33,.12);
  --sh-lg: 0 20px 48px -24px rgba(16,17,33,.22);
  --font: 'Hanken', -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
  --font-display: 'Fraunces', Georgia, 'Times New Roman', serif;
  --fs-xs: clamp(.72rem, .69rem + .14vw, .78rem);
  --fs-sm: clamp(.82rem, .79rem + .16vw, .9rem);
  --fs-md: clamp(.95rem, .9rem + .28vw, 1.06rem);
  --fs-lg: clamp(1.16rem, 1.04rem + .62vw, 1.42rem);
  --fs-xl: clamp(1.4rem, 1.18rem + 1.1vw, 1.92rem);
}
* { box-sizing: border-box; margin: 0; }
/* 🚨 BIF-290 "you zoom in and out of it": iPad Safari reads a cashier's rapid taps as
   double-taps and ZOOMS the till. `manipulation` disables double-tap zoom ONLY —
   pinch-zoom (accessibility) survives, unlike a maximum-scale viewport hack. */
button, [role="button"], input, select, textarea, label, a { touch-action: manipulation; }

/**
 * TOUCH SURFACES DON'T SELECT TEXT. On the counter till a tap-drag was
 * HIGHLIGHTING tile names and prices like a document (operator, 31 Aug) —
 * a till is operated, not read, and blue selection under a working thumb
 * reads as the screen misbehaving. Scoped to body.pos (till/KDS/picking set
 * it at boot) so the Office keeps copyable text — an owner copying an order
 * ref or a phone number off an order is a real workflow this must not break.
 * Real text-entry fields win it back explicitly, or PIN/search/note inputs
 * would refuse the caret. touch-action stays PER ELEMENT above — the html/body
 * pin broke fixed bottom nav scrolling once already (see the 2136 comment).
 */
/**
 * COLLAPSED ORDER CARDS (KDS + picking) — his ask, 31 Aug: a wall of full
 * orders wastes the screen; a compact row that expands on a tap shows the
 * full order only when someone is working it. Native <details>: no state
 * machinery of its own, keyboard/reader friendly — the boards persist the
 * open set across their polls in JS. The safety rows never fold: notes, the
 * rider code, ID/CASH chips, and the unmatched warning all live OUTSIDE the
 * fold or ON the summary, so nothing a handover depends on can be hidden.
 */
/* The APPLIED voucher row (his photo, 31 Aug): the code is a chip, never a
   headline — it inherited the total's type scale, wrapped mid-code and pushed
   the money off its column. Chip stays one piece on one line. */
.vapplied { align-items: center; gap: 8px; }
.vapplied-label { display: flex; align-items: center; gap: 6px; font-size: 14px; min-width: 0; }
.vcode-chip { font-family: ui-monospace, Menlo, monospace; font-size: 13px; font-weight: 700;
  background: rgba(148,163,184,.15); border-radius: 6px; padding: 3px 8px; white-space: nowrap;
  overflow: hidden; text-overflow: ellipsis; max-width: 180px; }

/**
 * WHAT AM I TYPING INTO? (his ask, 31 Aug — "the same red outline that the
 * online checkout has"). On a till, typing happens through the on-screen
 * Keys, so the FIELD holding focus is the only sign of where letters will
 * land. Every text field on a POS surface shows the brand ring on focus —
 * voucher, mobile, search, the note box — same treatment everywhere, so
 * "selected" always looks like selected.
 */
body.pos input.text:focus, body.pos .field input:focus, body.pos input[type="search"]:focus,
body.pos #search:focus, body.pos input:focus-visible {
  outline: 2px solid var(--brand, #E2000A); outline-offset: 1px;
  border-color: var(--brand, #E2000A);
}

/* The till picker's registers sit SIDE BY SIDE (his screenshot, 31 Aug: a
   vertical string of single buttons on a till-sized screen). Wrap on narrow
   phones; equal tiles, one row on the counter hardware. */
.regpick { display: flex; flex-wrap: wrap; gap: 12px; justify-content: center; }
.regpick .stbtn { flex: 0 1 150px; min-height: 110px; }
/* The offer analytics heads — three numbers an owner manages by, above the
   hand-drawn 30-day chart. */
.va-heads { display: flex; gap: 14px; margin: 12px 0 10px; flex-wrap: wrap; }
.va-head { display: flex; flex-direction: column; }
.va-head b { font-size: 20px; }
.va-head span { font-size: 12px; color: var(--muted); }
/* Voucher rows, phone-first (his screenshot, 31 Aug): the code owns its full
   line — it is the payload an owner reads out loud, so it must never truncate —
   and the actions drop below as a compact chip row. End is quiet-destructive:
   visible, never louder than the code it would kill. */
.vrow .vcode { word-break: break-word; }
.vrow .vacts { display: flex; gap: 8px; margin-top: 8px; }
.vrow .vacts .btn { flex: 0 0 auto; padding: 6px 14px; min-height: 36px; font-size: 13px; }
.vrow .vacts .danger-quiet { color: #b42318; border-color: rgba(180,35,24,.35); }
/* The smart tender chips — the two amounts a customer plausibly hands over
   for this remainder. Visually distinct from the notes: these are DERIVED,
   and a cashier should read them as "suggested" not "denomination". */
.notebtn.smart { border-style: dashed; font-weight: 800; }
/* The customer's note on a picking card — amber like the KDS's, NEVER inside
   the fold ("extra spicy" and "allergy" must not need a tap). .pk-note was
   already an empty-state style; this is its own class on purpose. */
.pk-ordnote { margin: 6px 0; padding: 6px 8px; border-radius: 8px; font-size: 13px;
  background: rgba(245,158,11,.12); color: #b45309; }
/**
 * THE SYNC INDICATOR (his ask, 31 Aug — "like the SERVD till"): a header
 * refresh icon that SPINS while a fetch is genuinely in flight, and is also
 * the manual refresh-now button. The spin is truth, not theatre: the class
 * rides the real in-flight state (KDS board fetch / till price-book sync),
 * so a spinner that never stops is a fetch that never returned — a symptom,
 * exactly like SERVD's. Reduced motion switches the spin off.
 */
.refr svg { transition: transform .2s; }
.refr.spinning svg { animation: refrspin .9s linear infinite; }
@keyframes refrspin { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) { .refr.spinning svg { animation: none; opacity: .5; } }
/* The reprint button on an Office order row — compact so the row stays one
   line; the row itself is the tap target for opening the order. */
.orow-print { flex: 0 0 auto; padding: 6px 10px; font-size: 12px; min-height: 34px; }
.pk-fold, .kc-fold { margin: 6px 0; }
.pk-foldsum, .kc-foldsum { display: flex; align-items: center; gap: 8px; cursor: pointer;
  list-style: none; padding: 8px 10px; border: 1px solid var(--line); border-radius: 10px;
  font-weight: 700; font-size: 13px; }
.pk-foldsum::-webkit-details-marker, .kc-foldsum::-webkit-details-marker { display: none; }
.pk-foldhint, .kc-foldhint { margin-left: auto; transition: transform .15s; }
details[open] > .pk-foldsum .pk-foldhint, details[open] > .kc-foldsum .kc-foldhint { transform: rotate(180deg); }
.kc-foldsum { border-color: rgba(148,163,184,.3); }

/* The chosen VAT on a typed price — unmissable at arm's length: filled,
   ringed, and ticked. A till operator glances, never reads. */
.vat-chip { position: relative; }
.vat-chip.selected { background: var(--brand, #E2000A); border-color: var(--brand, #E2000A); color: #fff; }
.vat-chip.selected .muted { color: rgba(255,255,255,.85); }
.vat-chip.selected::after { content: '✓'; position: absolute; top: 4px; right: 8px; font-weight: 800; }
body.pos { -webkit-user-select: none; user-select: none; -webkit-touch-callout: none; }
body.pos input, body.pos textarea, body.pos [contenteditable="true"] {
  -webkit-user-select: text; user-select: text;
}
html { -webkit-text-size-adjust: 100%; overflow-x: clip; overscroll-behavior: none; }
/* 🚨 REVERTED, and the reason is worth keeping. Pinning overscroll-behavior and
   touch-action on `html, body` TOGETHER (copying SERVD) broke the fixed bottom nav
   on mobile: `nav.tabs` is position:fixed, and constraining the body's scroll
   changed how the browser handles its collapsing toolbar, so the bar rendered
   mid-page with content below it. On the ordering site that trade might be
   arguable; on the Office, where the tab bar IS the navigation, it is not.
   Overscroll gets solved by the JS lock + per-container containment instead —
   neither of which touches document-level layout. */
/* The document must NEVER scroll sideways — only the intentional horizontal
 * strips (category cards, hero, deals rail) scroll inside their own overflow-x.
 * A stray full-bleed child was dragging the whole page; clamp it at the root.
 * `clip` (not `hidden`) so the sticky header keeps working. */
body { font-family: var(--font); background: var(--ink); color: var(--text);
  line-height: 1.5; letter-spacing: -.002em; font-optical-sizing: auto;
  overflow-x: clip; max-width: 100%; }
img { max-width: 100%; display: block; }
button, input, select, textarea { font: inherit; }
a { color: var(--text); text-decoration: none; }
h1, h2, h3, .display { font-family: var(--font-display); font-weight: 600; letter-spacing: -.01em; line-height: 1.12; }
.tnum { font-family: var(--font-display); font-feature-settings: 'tnum' 1, 'lnum' 1; font-weight: 500; }

/* ---------- shared shell (staff/owner) ---------- */
.wrap { max-width: 1100px; margin: 0 auto; padding: 0 16px; }
.narrow { max-width: 560px; }
header.app { position: sticky; top: 0; z-index: 20; color: var(--text);
  background: linear-gradient(var(--glass-top), var(--glass-bottom));
  -webkit-backdrop-filter: blur(18px); backdrop-filter: blur(18px);
  border-bottom: 1px solid var(--line);
  padding: max(10px, env(safe-area-inset-top)) 16px 10px; display: flex; align-items: center; gap: 10px; }
header.app h1 { font-size: 17px; flex: 1; }
header.app .who { font-size: 13px; color: var(--muted); }
main { padding: 18px 0 90px; }

.card { background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius);
  box-shadow: var(--sh-sm); padding: 16px; margin-bottom: 14px; }
.card h2 { font-family: var(--font); font-size: 12.5px; margin-bottom: 10px; color: var(--muted);
  font-weight: 600; text-transform: uppercase; letter-spacing: .06em; }
.row { display: flex; align-items: center; gap: 12px; padding: 11px 0; border-bottom: 1px solid var(--line); }
.row:last-child { border-bottom: 0; }
.row .grow { flex: 1; min-width: 0; }
.row .name { font-weight: 500; font-size: 15px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.row .sub { font-size: 13px; color: var(--muted); }
.thumb { width: 46px; height: 46px; border-radius: 12px; object-fit: cover; background: var(--ink-3); flex: none; }

/* admin — vertical section header (Bifrost group shaping) */
.vertical-head { font-family: var(--font); font-size: 12.5px; margin: 22px 2px 10px; color: var(--muted);
  font-weight: 600; text-transform: uppercase; letter-spacing: .06em; }

/* staff stock check stepper — tap the number to type a full recount */
.qty { display: flex; align-items: center; gap: 4px; flex: none; }
.qty button { min-width: 40px; min-height: 40px; border: 1.5px solid var(--line); background: var(--surface);
  border-radius: 12px; font-size: 18px; font-weight: 700; color: var(--text); cursor: pointer; }
.qty .qn { min-width: 34px; text-align: center; font-weight: 700; font-variant-numeric: tabular-nums;
  padding: 8px 2px; border-radius: 10px; text-decoration: underline dotted var(--muted); cursor: pointer; }
.qty .qset { width: 72px; min-height: 40px; text-align: center; font-weight: 700; }
.qty .qok { background: var(--saffron); color: var(--on-accent); border: 0; }

.btn { display: inline-flex; align-items: center; justify-content: center; gap: 8px; border: 0; cursor: pointer;
  border-radius: 16px; padding: 14px 20px; font-size: 16px; font-weight: 600; min-height: 52px;
  background: var(--saffron); color: var(--on-accent); width: 100%; transition: transform .12s; }
.btn:active { transform: scale(.98); }
.btn.secondary { background: var(--ink-3); color: var(--text); }
.btn.gold { background: var(--fresh); color: #14260a; }
.btn.danger { background: var(--bad); color: #fff; }
.btn.small { min-height: 38px; padding: 6px 14px; font-size: 14px; width: auto; border-radius: 12px; }
.btn:disabled { opacity: .5; cursor: default; }
.btn-row { display: flex; gap: 10px; }
.btn-row .btn { flex: 1; }

.pill { display: inline-block; padding: 3px 10px; border-radius: 999px; font-size: 12px; font-weight: 600; }
.pill.good { background: var(--good-bg); color: var(--good); }
.pill.amber { background: var(--amber-bg); color: var(--warn); }
.pill.bad { background: var(--bad-bg); color: var(--bad); }
.pill.plain { background: var(--ink-3); color: var(--muted); }
/* Orders rows — the text column must never be crushed into a one-character
   ribbon by the buttons beside it (his 1 Sep screenshot: the date wrapped
   vertically). min-width:0 lets .grow actually shrink; the sub line then
   ellipsises instead of wrapping per character. */
.orow .grow { min-width: 0; }
.orow .name { display: flex; flex-wrap: wrap; gap: 4px 8px; align-items: center; }
.orow .sub { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.pill.src { background: var(--ink-3); color: var(--muted); }
.pill.src-uber_eats { background: #e6f7ef; color: #0e8345; }
.pill.src-deliveroo { background: #e5f8fb; color: #007e9e; }
.pill.src-just_eat, .pill.src-justeat { background: #fff1e8; color: #c94f16; }
.pill.src-web { background: #eef2ff; color: #4552c7; }
.pill.src-till { background: var(--ink-3); color: var(--muted); }

label.field { display: block; margin-bottom: 12px; }
label.field span { display: block; font-size: 13px; font-weight: 600; color: var(--muted); margin-bottom: 5px; }
input.text, select.text, textarea.text { width: 100%; padding: 13px 14px; border: 1.5px solid var(--line);
  border-radius: 13px; font-size: 16px; background: var(--surface); color: var(--text); }
input.text:focus, select.text:focus { outline: 2px solid var(--accent-strong); border-color: transparent; }
.pin { letter-spacing: 12px; text-align: center; font-size: 26px !important; font-variant-numeric: tabular-nums; }

/* ---------- premium-white SERVD-style bottom selector (ops apps) ---------- */
nav.tabs { position: fixed; bottom: 0; left: 50%; transform: translateX(-50%);
  width: 100%; max-width: 640px; /* web: centred pill; PWA/phone: full width */
  border-top: 1px solid var(--line);
  background: rgba(255, 255, 255, .88);
  -webkit-backdrop-filter: saturate(180%) blur(20px); backdrop-filter: saturate(180%) blur(20px);
  display: flex; padding: 4px 6px calc(4px + env(safe-area-inset-bottom)); z-index: 40;
  box-shadow: 0 -1px 24px -8px rgba(16, 17, 33, .14); }
nav.tabs button { flex: 1; background: none; border: 0; padding: 6px 4px; font-size: 11px; font-weight: 650;
  letter-spacing: .01em; color: var(--muted, #63636b); cursor: pointer; display: flex; flex-direction: column;
  align-items: center; gap: 4px; min-height: 56px; justify-content: center; border-radius: 14px;
  transition: color .15s; -webkit-tap-highlight-color: transparent; }
nav.tabs button .ico { display: grid; place-items: center; width: 46px; height: 28px; border-radius: 999px; transition: background .18s, transform .12s; }
nav.tabs button .ico svg { width: 22px; height: 22px; }
nav.tabs button.active { color: var(--accent, #E2000A); }
nav.tabs button.active .ico { background: #FCE9E9; transform: translateY(-1px); }
nav.tabs button:active .ico { transform: scale(.9); }
/* dark-mode aware (some EPOS/phones force dark) */
@media (prefers-color-scheme: dark) {
  nav.tabs { background: rgba(24, 24, 28, .9); border-top-color: #2a2a30; }
  nav.tabs button.active .ico { background: rgba(226, 0, 10, .18); }
}

/* ---------- admin: a shop's own web address (collapsed by default) ----------
   Optional for most shops, so it must not shout on a list of forty of them. */
.pc-domains, .pc-printers { margin-top: 10px; border-top: 1px solid var(--line); padding-top: 8px; }
.pc-domains > summary, .pc-printers > summary { cursor: pointer; font-size: 13px; list-style: revert; }
.dombox { margin-top: 8px; }
/* The DNS records an operator pastes to whoever runs the shop's DNS. Monospace
   and selectable — a mistyped CNAME is a shop that is down and nobody knows why. */
.dombox .dns { margin-top: 6px; line-height: 1.7; word-break: break-all; user-select: all; }
.dombox .dns code { background: var(--ink-3); border-radius: 6px; padding: 2px 6px; font-size: 12px; }
.dberr { color: #c0392b; font-size: 12.5px; margin-top: 6px; }

/* ---------- the allergen declaration ----------
   Two separate grids, on purpose: "contains" is a fact and "may contain" is a
   cross-contamination warning, and a customer with a peanut allergy is entitled
   to the difference. Collapsed until it matters. */
.alrg { border-top: 1px solid var(--line); margin-top: 10px; padding-top: 8px; }
.alrg > summary { cursor: pointer; font-size: 13px; list-style: revert; }
.alrg-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 4px 10px; margin-top: 4px; }
.alrg-pick { display: flex; align-items: center; gap: 7px; font-size: 13px; min-height: 34px; }
.alrg-pick input { width: 18px; height: 18px; flex: none; accent-color: var(--accent, #E2000A); }
/* The legal line. Loud on purpose — it appears the moment a shop says it packs
   the food itself, which is the moment the labelling duty starts. */
.alrg-law { background: #FFF6E5; border-left: 3px solid #9A5B00; color: #6B3E00;
  padding: 8px 10px; border-radius: 8px; font-size: 12.5px; margin: 0 0 10px; }
/* ---------- Products: filter chips + inline edits ----------
   The chips scroll horizontally rather than wrapping to four rows on a phone,
   and each carries its own count so "No cost price 12" is the label. */
.fchips { display: flex; gap: 7px; overflow-x: auto; padding: 10px 0 2px;
  -webkit-overflow-scrolling: touch; scrollbar-width: none; }
.fchips::-webkit-scrollbar { display: none; }
.fchip { flex: none; border: 1.5px solid var(--line); background: var(--surface);
  color: var(--muted); border-radius: 999px; padding: 7px 13px; font-size: 13px;
  font-weight: 600; cursor: pointer; white-space: nowrap; font-family: inherit;
  -webkit-tap-highlight-color: transparent; }
.fchip b { font-weight: 700; color: var(--accent, #E2000A); }
.fchip.active { background: var(--text); border-color: var(--text); color: #fff; }
.fchip.active b { color: #fff; }
/* Bulk-select tick. 20px minimum — a 14px checkbox on a shop-floor phone with
   cold hands is a mis-tap that silently selects the wrong product. */
.ptick { width: 20px; height: 20px; flex: none; accent-color: var(--accent, #E2000A); }
/* THE PRODUCT ROW WRAPS. A tick, a photo, the name, two number boxes and Edit do
   not fit one line on a 390px phone — the name was squeezed to ~50px and vanished
   behind its own ellipsis while the line beneath it wrapped one word per line. The
   name owns the first line; the numbers drop below and align under it. */
.prow { flex-wrap: wrap; }
.prow .grow { min-width: 7rem; }
.prow .name { white-space: normal; }
/* Edit stays on the FIRST line, hard right, so the thumb-reachable action is in
   the same place on every row whether or not the numbers have wrapped below. */
.pedit { flex: none; align-self: flex-start; }
.pnums { display: flex; gap: 10px; width: 100%; order: 5; padding-left: 78px; margin-top: 2px; }
.pnums .pnum { flex: 1; width: auto; }
@media (min-width: 560px) {
  .pnums { width: auto; order: 0; padding-left: 0; margin-top: 0; }
  .pnums .pnum { flex: none; width: 86px; }
  .prow .name { white-space: nowrap; }
}

/* Inline price / count. Narrow, tabular, and labelled so a column of numbers
   cannot be mistaken for the other column of numbers. */
.pnum { display: flex; flex-direction: column; gap: 2px; flex: none; width: 86px; }
.pnum span { font-size: 10px; text-transform: uppercase; letter-spacing: .04em; color: var(--muted); }
.pnum input.text { padding: 8px 9px; font-size: 14px; text-align: right;
  font-variant-numeric: tabular-nums; }

/* ---------- the Office's More screen: every section, named ----------
   Replaces the anonymous person-icon menu. A row is a full-width tap target
   with the section NAME and a line saying what is behind it, because the old
   menu's failure was never that it was hard to tap — it was that nothing on it
   said what it did, so an owner could not tell which item held his banners. */
.seclist { background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius);
  overflow: hidden; box-shadow: var(--sh-sm); margin-bottom: 14px; }
/* href rows (doors to other apps, e.g. the Counter) render as anchors — same
   look as the buttons or the row reads as an odd one out. */
a.secrow { text-decoration: none; color: inherit; box-sizing: border-box; }
.secrow { display: flex; align-items: center; gap: 13px; width: 100%; padding: 14px 15px;
  background: none; border: 0; border-bottom: 1px solid var(--line); text-align: left;
  cursor: pointer; color: var(--text); font: inherit; -webkit-tap-highlight-color: transparent; }
.secrow:last-child { border-bottom: 0; }
.secrow:active { background: var(--ink-3); }
.secrow .grow { flex: 1; min-width: 0; display: block; }
.secrow .name { display: block; font-weight: 600; font-size: 15.5px; }
.secrow .sub { display: block; font-size: 13px; color: var(--muted); margin-top: 2px;
  white-space: normal; }
.secrow .chev { width: 18px; height: 18px; color: var(--line-2); flex: none; }
/* BRANDED, not a black square. Same treatment the bottom nav already gives the
   active tab — TILLD red on its own tint — so the two agree instead of the
   sections looking like disabled chrome. */
.secico { display: grid; place-items: center; width: 40px; height: 40px; flex: none;
  border-radius: 13px; background: var(--card-from); color: var(--saffron); }
.secico svg { width: 21px; height: 21px; }
.secrow:active .secico { transform: scale(.94); }
.secico, .secrow:active .secico { transition: transform .12s; }
/* Who am I signed in as — answered on the screen, not inside a menu. */
.whoami { display: flex; align-items: center; gap: 12px; }
.snap { border: 2.5px dashed var(--saffron); background: var(--card-from); border-radius: 22px;
  padding: 34px 20px; text-align: center; cursor: pointer; }
.snap .ico { font-size: 44px; }
.snap .t { font-size: 18px; font-weight: 600; margin-top: 8px; color: var(--saffron); font-family: var(--font-display); }
.snap .s { font-size: 13.5px; color: var(--muted); margin-top: 4px; }

.line-review { border: 1.5px solid var(--line); border-radius: 14px; padding: 12px; margin-bottom: 10px; }
.line-review.auto { border-color: var(--good); background: var(--good-bg); }
.line-review.amber { border-color: var(--warn); background: var(--amber-bg); }
.line-review .raw { font-weight: 600; font-size: 14.5px; }
.line-review .meta { font-size: 13px; color: var(--muted); margin: 3px 0 8px; }

.stats { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 12px; margin-bottom: 14px; }
.stat { background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius); padding: 14px; box-shadow: var(--sh-sm); }
.stat .n { font-size: 26px; font-family: var(--font-display); font-weight: 600; font-feature-settings: 'tnum' 1,'lnum' 1; }
.stat .l { font-size: 12.5px; color: var(--muted); font-weight: 600; }
.stat.alert .n { color: var(--bad); }
.gauge { height: 10px; border-radius: 999px; background: var(--ink-3); overflow: hidden; margin-top: 8px; }
.gauge i { display: block; height: 100%; background: linear-gradient(90deg, var(--bad), var(--warn) 45%, var(--good) 75%); }
.chart { display: flex; align-items: flex-end; gap: 4px; height: 120px; margin-top: 14px; padding-top: 6px; }
.chart .bar { flex: 1; display: flex; flex-direction: column; align-items: center; justify-content: flex-end; height: 100%; min-width: 0; }
.chart .bar i { display: block; width: 100%; max-width: 26px; background: var(--saffron); border-radius: 5px 5px 0 0; }
/* BIF-9 (item 5): a bar chart with no axis reads as bars floating in space. The day
   labels sit under the bars, so a hairline on top of each label draws one continuous
   baseline across the chart — every bar stands on the same line. */
.chart .bar span { font-size: 9.5px; color: var(--muted-2); margin-top: 4px; font-variant-numeric: tabular-nums;
  width: 100%; text-align: center; border-top: 1px solid var(--line); padding-top: 3px; }

.muted { color: var(--muted); font-size: 13.5px; }
/* 🚨 BIF-159 — THIS RULE DID NOT EXIST, AND FOUR SHIPPED CALLERS ALREADY USED IT.
   till.js (BIF-153) and owner.js (the BIF-159 owner cut) both render a blind
   lookup as `class="warn"` so it cannot be mistaken for an ordinary empty result.
   Only COMPOUND selectors were defined (.notebox.warn, .wl-days.warn, …), so a
   bare `warn` inherited body text and looked EXACTLY like the "Nothing found."
   it was written to be distinguishable from. The message copy differed, so the
   defect was mitigated and invisible — the visual half was decorative.
   🔑 A guard whose stylesheet half is missing still reviews as present. */
.warn { color: var(--warn); font-size: 13.5px; font-weight: 700; }
.center { text-align: center; }
.mt { margin-top: 14px; }
.hidden { display: none !important; }
/* A message drops from the TOP like a native banner — it must never sit over the
   pay button the customer just tapped (which a bottom pill did). Dark glass,
   soft double shadow, a spring-in and a clean fade-out. */
.toast { position: fixed; left: 50%; top: calc(env(safe-area-inset-top) + 12px);
  transform: translateX(-50%) translateY(-10px); max-width: min(90vw, 400px); text-align: center;
  background: var(--toast-bg); color: var(--toast-fg); padding: 12px 20px; border-radius: 15px;
  font-size: 14px; font-weight: 600; line-height: 1.32; letter-spacing: -0.006em;
  -webkit-backdrop-filter: blur(12px) saturate(1.4); backdrop-filter: blur(12px) saturate(1.4);
  box-shadow: 0 10px 34px rgba(16,17,33,.30), 0 2px 8px rgba(16,17,33,.18);
  z-index: 200; opacity: 0; animation: toast-in .36s cubic-bezier(.22,1,.36,1) forwards; }
.toast.out { animation: toast-out .26s cubic-bezier(.4,0,1,1) forwards; }
@keyframes toast-in { to { opacity: 1; transform: translateX(-50%) translateY(0); } }
@keyframes toast-out { to { opacity: 0; transform: translateX(-50%) translateY(-10px); } }
@media (prefers-reduced-motion: reduce) {
  .toast { animation-duration: .01ms; opacity: 1; transform: translateX(-50%) translateY(0); }
}

/* A missing required field points at ITSELF — a red ring and a small shake —
   instead of a message blob obscuring the button. */
.gfield.field-err .ginput { border-color: var(--brand, #E2000A);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--brand, #E2000A) 20%, transparent); }
.gfield.field-err { animation: field-shake .4s cubic-bezier(.36,.07,.19,.97); }
@keyframes field-shake {
  10%, 90% { transform: translateX(-1px); } 20%, 80% { transform: translateX(2px); }
  30%, 50%, 70% { transform: translateX(-4px); } 40%, 60% { transform: translateX(4px); }
}
@media (prefers-reduced-motion: reduce) { .gfield.field-err { animation: none; } }

/* =========================================================================
   STOREFRONT — the SERVD ordering-site idiom
   ========================================================================= */
.store { max-width: var(--maxw); margin: 0 auto; min-height: 100vh; position: relative; padding-bottom: 110px; }
@media (min-width: 768px) { :root { --maxw: 640px; } }

/* topbar: sticky glass */
.topbar { position: sticky; top: 0; z-index: 40; padding: max(12px, env(safe-area-inset-top)) 16px 10px;
  background: linear-gradient(var(--glass-top), var(--glass-bottom));
  -webkit-backdrop-filter: blur(18px); backdrop-filter: blur(18px); border-bottom: 1px solid var(--line); }
.topbar-row { display: flex; align-items: center; gap: 12px; }
.brand { display: flex; align-items: center; gap: 11px; flex: 1; min-width: 0; }
.brandmark { width: 52px; height: 52px; border-radius: 15px; background: #fff; border: 1px solid var(--line);
  box-shadow: var(--sh-sm); display: grid; place-items: center; overflow: hidden; flex: none; }
.brandmark img { width: 100%; height: 100%; object-fit: contain; padding: 3px; }
.brandtext .nm { font-family: var(--font-display); font-weight: 600; font-size: var(--fs-lg);
  line-height: 1.1; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
.brandtext .sub { font-size: var(--fs-xs); color: var(--muted); margin-top: 2px;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
.iconbtn .hbadge { position: absolute; top: -6px; right: -6px; min-width: 20px; height: 20px; padding: 0 5px;
  border-radius: 999px; background: var(--saffron); color: var(--on-accent); font-size: 11.5px; font-weight: 700;
  display: grid; place-items: center; }
/* fulfilment toggle */
.fulfil { display: flex; flex-direction: column; align-items: stretch; gap: 8px; margin-top: 10px; }
.fulfil .toggle { justify-content: center; }
.fulfil .toggle button { flex: 1; text-align: center; }
.toggle { display: flex; background: var(--ink-3); border-radius: 999px; padding: 3px; }
.toggle button { border: 0; background: none; padding: 7px 16px; border-radius: 999px; font-size: 13.5px;
  font-weight: 600; color: var(--muted); cursor: pointer; }
.toggle button[aria-pressed="true"] { background: var(--saffron); color: var(--on-accent); }
.fmeta { display: flex; justify-content: center; }
.fpill { display: inline-flex; align-items: center; gap: 7px; font-size: 12.5px; font-weight: 650;
  padding: 7px 14px; border-radius: 999px; letter-spacing: .01em; }
.fp-open { background: #F0FAF4; border: 1px solid #CDEBD9; color: #17924E; }
.fp-online { background: #FFF8EC; border: 1px solid #F2E2C4; color: #7A5A18; }
.fp-online .dot { background: #E8A33D !important; }
.fmeta .dot { width: 8px; height: 8px; border-radius: 999px; background: var(--good); animation: breathe 2.2s infinite; }
@keyframes breathe { 50% { opacity: .45; } }

/* hero badges */

/* search + category rail */
.searchwrap { padding: 12px 16px 0; }
.searchbar { display: flex; align-items: center; gap: 9px; background: var(--surface); border: 1.5px solid var(--line);
  border-radius: 15px; padding: 12px 14px; }
.searchbar input { border: 0; outline: 0; flex: 1; font-size: 16px; background: none; color: var(--text); }
.searchbar .mag { color: var(--muted-2); }
.cat-h { display: flex; align-items: center; gap: 10px; padding: 20px 16px 10px; }
.cat-h h2 { font-size: var(--fs-lg); }
.catclear { border: 1px solid var(--line); background: var(--surface); border-radius: 999px;
  padding: 6px 13px; font: inherit; font-size: 12px; font-weight: 600; color: var(--accent-strong);
  cursor: pointer; flex: none; }
.cat-h .ln { flex: 1; height: 1px; background: var(--line); }
.cat-h .ct { font-size: var(--fs-xs); color: var(--muted-2); font-feature-settings: 'tnum' 1; }
.grid { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; padding: 0 16px 8px; }
@media (min-width: 768px) { .grid { grid-template-columns: repeat(3, 1fr); } }
.item { position: relative; background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius);
  box-shadow: var(--sh-sm); overflow: visible; display: flex; flex-direction: column; transition: transform .18s, box-shadow .18s; }
@media (hover:hover) { .item:hover { transform: translateY(-3px); box-shadow: var(--sh-md); } .item:hover .imgbox img { transform: scale(1.05); } }
.imgbox { aspect-ratio: 1/1; overflow: hidden; border-radius: var(--radius) var(--radius) 0 0; background: #fff; }
.imgbox img { width: 100%; height: 100%; object-fit: contain; padding: 10px; transition: transform .35s; }
.imgbox .glyph { width: 100%; height: 100%; display: grid; place-items: center; font-size: 42px; opacity: .6; }
.item .badge2 { position: absolute; top: 8px; left: 8px; z-index: 2; background: rgba(20,20,26,.72); color: #fff;
  font-size: 10px; font-weight: 700; text-transform: uppercase; letter-spacing: .04em; padding: 4px 8px; border-radius: 999px; }
.item .badge3 { position: absolute; top: 8px; right: 8px; z-index: 2; background: var(--saffron); color: var(--on-accent);
  font-size: 10px; font-weight: 700; text-transform: uppercase; letter-spacing: .04em; padding: 4px 8px; border-radius: 999px; }
.item .meta { position: relative; padding: 13px 14px 14px; display: flex; flex-direction: column; gap: 4px; flex: 1; }
.imgbox { position: relative; }
.add-mini { position: absolute; left: 8px; bottom: 8px; /* BIF-290: his call — '+ on the left', everything else untouched */ z-index: 3; border: 0; width: 30px; height: 30px; border-radius: 10px;
  background: var(--saffron); color: var(--on-accent); cursor: pointer;
  box-shadow: 0 2px 10px -2px rgba(16,17,33,.35); display: grid; place-items: center; transition: transform .12s; }
.add-mini:active { transform: scale(.92); }
.gqty { position: absolute; left: 8px; bottom: 8px; /* BIF-290: same corner as the + it replaces */ z-index: 3; display: flex; align-items: center; background: var(--saffron); padding: 0 2px;
  color: var(--on-accent); border-radius: 13px; box-shadow: var(--sh-md); }
/* compact once armed — the visual shrinks, an invisible ::before keeps the
   44px finger target (same trick as the drawer steppers) */
.gqty button { border: 0; background: none; color: inherit; width: 26px; height: 30px;
  font-size: 15px; font-weight: 700; cursor: pointer; position: relative; }
.gqty button::before { content: ""; position: absolute; top: 50%; left: 50%; width: 44px;
  height: 44px; transform: translate(-50%,-50%); }
.gqty span { min-width: 13px; text-align: center; font-weight: 700; font-size: 12.5px;
  font-variant-numeric: tabular-nums; }
.item .nm { font-size: var(--fs-sm); font-weight: 500; letter-spacing: -.02em; line-height: 1.3;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; min-height: 2.5em; }
.item .pr { font-size: var(--fs-sm); font-weight: 500; margin-top: auto; font-family: var(--font-display);
  font-feature-settings: 'tnum' 1,'lnum' 1; }
.item .unit { font-size: 10.5px; color: var(--muted-2); }
.oos-tag { font-size: var(--fs-xs); color: var(--muted-2); font-weight: 600; }

/* buy-again strip (supermarket pattern #1) */
.usuals { padding: 14px 0 0; }
.usuals-h { padding: 0 16px 8px; display: flex; align-items: center; gap: 8px; }
.usuals-h h2 { font-size: var(--fs-lg); }
.usuals-strip { display: flex; gap: 10px; overflow-x: auto; padding: 2px 16px 10px; scrollbar-width: none; }
.usuals-strip::-webkit-scrollbar { display: none; }
.ucard { flex: none; width: 128px; background: var(--surface); border: 1px solid var(--line); border-radius: 16px;
  box-shadow: var(--sh-sm); overflow: visible; position: relative; }
.ucard .imgbox { border-radius: 16px 16px 0 0; aspect-ratio: 1/1; background: #fff; }
.ucard .b { padding: 8px 10px 10px; position: relative; }
.ucard .nm { font-size: 11.5px; font-weight: 500; line-height: 1.25; display: -webkit-box; -webkit-line-clamp: 2;
  -webkit-box-orient: vertical; overflow: hidden; min-height: 2.4em; }
.ucard .pr { font-size: 12.5px; font-family: var(--font-display); font-feature-settings: 'tnum' 1; margin-top: 2px; }
.ucard .add-mini { width: 26px; height: 26px; font-size: 14px; top: -34px; left: 7px; border-radius: 9px; }
.ucard .add-mini svg { width: 13px; height: 13px; }

/* bottom basket bar */
.cartbar { position: fixed; left: 0; right: 0; bottom: 0; z-index: 50; padding: 10px 16px calc(12px + env(safe-area-inset-bottom));
  transform: translateY(110%); transition: transform .3s cubic-bezier(.22,1,.32,1); }
.cartbar.show { transform: translateY(0); }
.cartbar button { width: 100%; max-width: calc(var(--maxw) - 32px); margin: 0 auto; display: flex; align-items: center;
  justify-content: space-between; border: 0; background: linear-gradient(135deg, var(--saffron), var(--ember));
  color: var(--on-accent); border-radius: 17px; padding: 15px 18px; font-size: 16px; font-weight: 700;
  box-shadow: var(--sh-lg); cursor: pointer; }
.cartbar .ct { background: rgba(255,255,255,.22); border-radius: 999px; padding: 2px 10px; font-size: 13.5px; }
.cartbar .tot { font-family: var(--font-display); font-feature-settings: 'tnum' 1,'lnum' 1; }
/* minimum-order progress (supermarket pattern) */
.minbar { max-width: calc(var(--maxw) - 32px); margin: 0 auto 8px; background: var(--surface); border: 1px solid var(--line);
  border-radius: 12px; padding: 8px 12px; font-size: var(--fs-xs); color: var(--muted); box-shadow: var(--sh-md); }
.minbar .track { height: 6px; border-radius: 999px; background: var(--ink-3); margin-top: 6px; overflow: hidden; }
.minbar .track i { display: block; height: 100%; background: var(--fresh); border-radius: 999px; transition: width .3s; }

/* scrim + drawer (z: 60 < 62 — load-bearing) */
.scrim { position: fixed; inset: 0; z-index: 60; background: rgba(16,17,33,.45);
  -webkit-backdrop-filter: blur(4px); backdrop-filter: blur(4px); opacity: 0; pointer-events: none; transition: opacity .3s; }
.scrim.show { opacity: 1; pointer-events: auto; }
/* ---- "Your order" drawer — SERVD anatomy: sticky top / ONE scroll region /
   PINNED pay footer (Total + CTA never scroll away) ---- */
.drawer { position: fixed; left: 0; right: 0; bottom: 0; z-index: 62; max-width: var(--maxw);
  /* 🚨 A CONSTANT, and it must stay one — SERVD's exact value. Any unit that
     Safari can change under us (dvh for the URL bar, --vvh for the keyboard)
     makes the drawer resize while the customer is typing. That was the bug.

     🚨 BIF-75 (his own P1, 12 Aug): `92vh` IS a constant and it is the WRONG one.
     `vh` measures the LARGE viewport — the page as if browser chrome were hidden.
     Chrome for iOS puts its URL bar at the TOP, so a drawer pinned to the bottom
     at 92vh runs its header, and the ✕, ABOVE the visible viewport. The only
     remaining exit was "Clear basket", which DESTROYS the customer's basket.
     Safari is unaffected because its URL bar is at the bottom — which is why he
     reported it as "this however is safari, it's like this good!".

     🔑 `svh` SATISFIES THE CONSTRAINT ABOVE RATHER THAN BREAKING IT. The comment
     forbids units that CHANGE UNDER US — `dvh` (retracting URL bar) and `--vvh`
     (keyboard). `svh` is the SMALL viewport: chrome fully shown, and STATIC. It
     never changes while the customer types, so the resize bug does not return;
     it is simply the correct constant instead of the wrong one.

     📌 This lane already knew. `.signin-stage` below carries the same fix and its
     comment names THIS ELEMENT as the motivating example — "the same unit trap
     that put the basket drawer's close button off-screen" — and the drawer was
     never updated. A law written down in the place it applies is still not a
     search. */
  margin: 0 auto; max-height: 92vh; max-height: 92svh; background: var(--ink);
  border-radius: 28px 28px 0 0; border: 1px solid var(--line); border-bottom: none;
  box-shadow: var(--sh-lg); transform: translateY(102%);
  transition: transform .42s cubic-bezier(.22,1,.32,1);
  display: flex; flex-direction: column; overflow: hidden; }
/* THE OPEN DRAWER CARRIES NO TRANSFORM — SERVD 116b8e4, ported 14 Sep 2026. His
   iPhone-Chrome recording of the SERVD checkout: the drawer unpainted for two
   frames as the keyboard resized the viewport, and the caret drawn ~33 pt below
   the mobile field for the whole entry while nothing on screen moved. A
   bottom-anchored fixed element that is ALSO a composited transform layer is
   repositioned by the compositor apart from layout under a viewport resize, and
   the caret is placed from layout's coordinates. The identity translateY(0)
   still made the open drawer a transform layer; `none` does not — and `none`
   interpolates as the identity, so the .42s slide is unchanged both ways (the
   closed state keeps its translateY(102%)). ⚠️ Precondition, measured on the live
   drawer in the browser gate: it has NO position:fixed descendant (a fixed child
   would change its containing block when the transform goes). */
.drawer.open { transform: none; }
/* TYPING MODE — SERVD b4dd85c, ported 14 Sep 2026 (his ask). While a text field
   inside the drawer has focus, /js/drawer-typing.js puts `.typing` on the drawer:
   the pinned summary collapses to the Total line (the CTA, the fold body and the
   counter link step aside — the strip gets the space the keyboard took), the
   header gives up its padding, and every field keeps a 16px margin inside the
   strip (scroll-margin on the field, scroll-padding on the scroller) so a
   floating label or a focus ring is never the part that is clipped. Focus
   events only; nothing here or in the module reads the keyboard. */
.drawer.typing .drawer-top { padding: 8px 20px; }
.drawer.typing .summary { padding: 8px 20px calc(8px + env(safe-area-inset-bottom)); }
.drawer.typing .summary .checkout-cta, .drawer.typing .summary .paylink,
.drawer.typing .summary .ordfold-body { display: none; }
.drawer.typing .summary .srow.tot { padding-top: 6px; margin-top: 0; border-top: none; }
.drawer.typing .drawer-scroll { scroll-padding: 16px; }
.drawer .ginput, .drawer .lqn { scroll-margin: 16px; }
/* 🚨 BIF-164 — THE CLIPPED-TITLE DEFECT ON EVERY TILL SHEET. #drawerbody had NO
   RULE AT ALL: content rendered flush against the container border, and the 28px
   corner radius sliced the first line's ascenders and first characters — "ash —
   £14.72" on his iPad (14 Aug), the W of "Weigh" (17 Aug), the C of "Cash due"
   under our own eyes tonight. Same family as the ghost token: a style that was
   never written produces no error, just a wrong screen. The scroll lives HERE
   (not on .drawer, whose overflow:hidden does the corner clipping) so a tall
   sheet scrolls inside its padding instead of sliding under the radius. */
#drawerbody { padding: 18px 22px calc(20px + env(safe-area-inset-bottom));
  overflow-y: auto; min-height: 0; -webkit-overflow-scrolling: touch; }
/* the grab handle was an unstyled, invisible div — now it is the pill it meant to be */
.drawer > .grab { flex: none; width: 44px; height: 5px; border-radius: 999px;
  background: var(--line-2); margin: 10px auto 0; }
.drawer-top { padding: 17px 20px; border-bottom: 1px solid var(--line); display: flex;
  align-items: center; justify-content: space-between; background: var(--ink); flex: none; }
.drawer-top h3 { font-size: var(--fs-lg); letter-spacing: -.03em; }
.drawer-top .x { background: none; border: none; color: var(--muted); cursor: pointer;
  display: grid; place-items: center; width: 40px; height: 40px; font-size: 21px; }
.drawer-scroll { flex: 1; min-height: 0; overflow-y: auto; -webkit-overflow-scrolling: touch;
  /* 🚨 `none`, NOT `contain`. `contain` stops the scroll CHAINING to the page
     behind, but it does NOT suppress this scroller's own rubber-band. With the
     pinned footer eating half the drawer, the remaining port is small enough
     that a flick hits one of its two ends nearly every time — so the bounce
     fired constantly, and every pixel the footer grew made it worse. Declared
     once; it was written twice, which is how long nobody looked at it. */
  overscroll-behavior: none; }
.lines { padding: 8px 20px; }
.summary-fields { padding: 4px 20px 12px; }
.line { display: flex; gap: 13px; padding: 15px 0; border-bottom: 1px solid var(--line); }
.line:last-child { border-bottom: 0; }
.le { width: 48px; height: 48px; border-radius: 13px; object-fit: contain; background: #fff;
  border: 1px solid var(--line); flex: none; }
.le.ph { display: grid; place-items: center; color: var(--muted-2); }
.line .mid { flex: 1; min-width: 0; }
.line .lnm { font-size: var(--fs-sm); font-weight: 500; }
.line .lpr { font-size: var(--fs-xs); color: var(--muted); margin-top: 5px; font-weight: 500; }
.lq { display: flex; align-items: center; gap: 10px; height: fit-content; }
.lq button { width: 34px; height: 34px; border-radius: 11px; border: 1px solid var(--line);
  background: var(--ink-2); color: var(--text); font-size: 1.05rem; display: grid;
  place-items: center; cursor: pointer; line-height: 1; position: relative; }
.lq button::before { content: ""; position: absolute; top: 50%; left: 50%; width: 44px;
  height: 44px; transform: translate(-50%,-50%); }
.lq span { min-width: 14px; text-align: center; font-weight: 500; font-variant-numeric: tabular-nums; }
/* BIF-14: the quantity is typeable. Sized to fit three digits so 100 does not
   clip, and 16px so iOS does not zoom the page when it takes focus. */
.lqn { width: 42px; min-width: 42px; height: 34px; padding: 0 4px; text-align: center;
  font: inherit; font-size: 16px; font-weight: 500; font-variant-numeric: tabular-nums;
  color: var(--text); background: var(--ink-2); border: 1px solid var(--line);
  border-radius: 11px; -moz-appearance: textfield; }
.lqn:focus { outline: 2px solid var(--saffron); outline-offset: 1px; }
/* BIF-13: present but quiet — emptying the basket is not a thing to invite. */
.clearbasket { display: block; margin: 4px auto 2px; background: none; border: none;
  color: var(--muted); font: inherit; font-size: var(--fs-sm); text-decoration: underline;
  text-underline-offset: 3px; padding: 10px 12px; cursor: pointer; }
.clearbasket.arm { color: #c0392b; font-weight: 600; text-decoration: none; }
/* guest fields — SERVD .ginputs grid + floating .gfield labels */
.ginputs { display: grid; grid-template-columns: 1fr 1fr; gap: 9px; margin-top: 11px; }
.gfield { position: relative; }
.ginput { width: 100%; background: var(--ink-3); border: 1px solid var(--line); border-radius: 13px;
  padding: 12px 14px; color: var(--text); font: inherit; font-size: var(--fs-sm); min-height: 44px; }
.gfield > .ginput { padding-top: 19px; padding-bottom: 5px; }
/* BIF-9 (item 6): the focus ring used to be --accent-strong (#B00008), a red one shade
   off --bad (#C4322F) — a field you had just tapped INTO looked like a field you had
   got WRONG. Focus is "this one is active", never "this one is bad"; the hygiene forms
   already use this blue for exactly that. */
.ginput:focus { outline: none; border-color: #2E7DD2; }
.gfield > label { position: absolute; left: 15px; top: 50%; transform: translateY(-50%);
  transform-origin: left center; font-size: var(--fs-sm); color: var(--muted); pointer-events: none;
  transition: top .16s, transform .16s, color .16s; white-space: nowrap; }
.gfield > .ginput:focus + label, .gfield > .ginput:not(:placeholder-shown) + label {
  top: 7px; transform: translateY(0) scale(.82); }
.gfield > .ginput:focus + label { color: #2E7DD2; } /* BIF-9: the label follows the focus colour, not the error colour */
.gwide { grid-column: 1 / -1; }
/* address autocomplete panel (SERVD parity) — sits under the address field */
.addrwrap { position: relative; }
.ginput.haslocbtn { padding-right: 48px; }
.locbtn { position: absolute; right: 6px; top: 6px; width: 38px; height: 38px; border-radius: 11px;
  border: 0; background: none; color: var(--accent-strong); display: grid; place-items: center;
  cursor: pointer; }
.locbtn svg { width: 20px; height: 20px; }
.locbtn.busy svg { animation: locspin 1s linear infinite; }
@keyframes locspin { to { transform: rotate(360deg); } }
.addrpanel { overscroll-behavior: contain; position: absolute; left: 0; right: 0; top: calc(100% + 5px); z-index: 5;
  max-height: 210px; overflow-y: auto;
  background: var(--ink-2); border: 1px solid var(--line); border-radius: 13px;
  /* 🚨 NO `overflow: hidden` HERE. The shorthand comes AFTER `overflow-y: auto`
     above and resets it, so the panel stopped scrolling and every address
     suggestion past 210px was clipped and unreachable — on the one field a
     delivery customer cannot skip. Live since 44c6596. If the corner radius
     ever needs clipping, do it on a wrapper, never on the scroller itself. */
  box-shadow: var(--sh-lg); }
.addrpanel.hidden { display: none; }
.addrrow { display: block; width: 100%; text-align: left; border: 0; background: none;
  padding: 11px 14px; cursor: pointer; border-bottom: 1px solid var(--line); font: inherit; }
.addrrow:last-child { border-bottom: 0; }
.addrrow:hover { background: var(--ink-3); }
.addrrow b { display: block; font-size: var(--fs-sm); font-weight: 500; color: var(--text); }
.addrrow span { display: block; font-size: var(--fs-xs); color: var(--muted); margin-top: 2px; }
.codok { padding: 10px 13px; border-radius: 12px; background: var(--good-bg); color: var(--good);
  font-size: var(--fs-xs); font-weight: 600; }
/* delivery slot picker (Asda/Tesco pattern, drawer language) */
.slotpick { margin-top: 11px; }
.slot-lab { display: block; font-size: var(--fs-xs); font-weight: 600; color: var(--muted); margin-bottom: 7px; }
.slot-row { display: flex; gap: 7px; flex-wrap: wrap; }
.slotchip { border: 1px solid var(--line); background: var(--ink-2); border-radius: 999px;
  padding: 9px 14px; font: inherit; font-size: 13px; font-weight: 600; color: var(--text); cursor: pointer; }
.slotchip.on { background: var(--saffron); color: var(--on-accent); border-color: var(--saffron); }
.slotgrid { margin-top: 10px; border: 1px solid var(--line); border-radius: 13px; padding: 11px;
  background: var(--ink-2); display: flex; flex-direction: column; gap: 10px; }
.slot-day > b { display: block; font-size: 12px; text-transform: uppercase; letter-spacing: .06em;
  color: var(--muted); margin-bottom: 6px; }
.slot-wins { display: flex; flex-wrap: wrap; gap: 7px; }

/* closed-state (RJS parity): red status dot + scheduled-orders banner */
.fmeta .dot.closed { background: var(--bad); animation: none; }
.closedbar { display: flex; gap: 11px; align-items: flex-start; margin: 10px 16px 2px;
  padding: 13px 15px; border-radius: 15px; background: var(--amber-bg);
  border: 1px solid color-mix(in srgb, var(--warn) 30%, transparent); }
.closedbar .cb-ic { flex: none; width: 34px; height: 34px; border-radius: 10px; display: grid;
  place-items: center; background: color-mix(in srgb, var(--warn) 14%, transparent); color: var(--warn); }
.closedbar .cb-ic svg { width: 19px; height: 19px; }
.closedbar b { display: block; font-size: var(--fs-sm); font-weight: 600; color: var(--text); }
.closedbar span { display: block; font-size: var(--fs-xs); color: var(--muted); margin-top: 2px; line-height: 1.45; }
/* PINNED pay footer */
.summary { flex: none; padding: 15px 20px calc(18px + env(safe-area-inset-bottom));
  border-top: 1px solid var(--line); background: var(--ink-2); }
.srow { display: flex; justify-content: space-between; font-size: var(--fs-sm);
  color: var(--muted); padding: 5px 0; }
.srow.tot { color: var(--text); font-weight: 500; font-size: var(--fs-lg); letter-spacing: -.03em;
  padding-top: 13px; margin-top: 6px; border-top: 1px solid var(--line);
  font-family: var(--font-display); font-feature-settings: 'tnum' 1,'lnum' 1; }
.checkout-cta { width: 100%; margin-top: 15px; border: none; border-radius: 17px;
  background: var(--saffron); color: var(--on-accent); font-weight: 500; font-size: var(--fs-md);
  padding: 15px 17px; cursor: pointer; box-shadow: var(--sh-md); min-height: unset; display: block; }
.checkout-cta:disabled { opacity: .5; cursor: not-allowed; box-shadow: none; }
.checkout-cta .ck-sub { display: block; font-size: 11px; font-weight: 400; opacity: .78; margin-top: 2px; }
.pay-notice { display: flex; align-items: flex-start; gap: 9px; margin-top: 15px; padding: 11px 13px;
  border-radius: 13px; background: var(--ink-2); border: 1px solid var(--line);
  font-size: var(--fs-xs); line-height: 1.4; color: var(--muted); }
.pay-notice + .checkout-cta { margin-top: 11px; }
.delgate { margin-top: 15px; padding: 11px 13px; border-radius: 13px; background: var(--bad-bg);
  color: var(--bad); font-size: var(--fs-xs); font-weight: 600; }
.paycounter2 { width: 100%; margin-top: 10px; border: 1px solid var(--line); border-radius: 16px;
  background: transparent; color: var(--muted); font-weight: 500; font-size: var(--fs-sm);
  padding: 13px; cursor: pointer; }
.guestnote { text-align: center; font-size: var(--fs-xs); color: var(--muted); margin-top: 12px; }
.guestnote b { color: var(--text); font-weight: 500; }
.empty { padding: 66px 30px; text-align: center; color: var(--muted); }
.empty .ic { font-size: 3rem; display: block; margin-bottom: 14px; opacity: .7; }
.empty b { color: var(--text); display: block; font-size: var(--fs-lg); margin-bottom: 6px;
  font-weight: 500; letter-spacing: -.02em; font-family: var(--font-display); }
.empty p { font-size: var(--fs-sm); }

/* ---- live order tracker — SERVD anatomy: full-screen staged overlay +
   collapsed "Your order" pill (servd-tracker-css, adapted to TILLD tokens) ---- */
.strk-overlay { position: fixed; inset: 0; z-index: 80; color: var(--text);
  background: radial-gradient(120% 90% at 50% -10%, color-mix(in srgb, var(--saffron) 8%, var(--ink)), var(--ink) 55%);
  display: flex; flex-direction: column; overflow-y: auto; overscroll-behavior: contain;
  opacity: 0; pointer-events: none; transform: translateY(3%);
  transition: opacity .3s, transform .34s cubic-bezier(.2,.8,.2,1); }
.strk-overlay.on { opacity: 1; pointer-events: auto; transform: none; }
.strk-top { display: flex; align-items: center; justify-content: space-between;
  padding: calc(14px + env(safe-area-inset-top)) 18px 6px; }
.strk-top .t { font-size: 11px; letter-spacing: .16em; text-transform: uppercase; color: var(--muted); }
.strk-min { width: 44px; height: 44px; border-radius: 13px; border: 1px solid var(--line);
  background: var(--ink-2); color: var(--text); display: grid; place-items: center; cursor: pointer;
  box-shadow: var(--sh-sm); }
.strk-min svg { width: 20px; height: 20px; }
.strk-body { flex: 1; display: flex; flex-direction: column; align-items: center; justify-content: center;
  text-align: center; padding: 12px 26px 8px; min-height: 0; }
.strk-emblem { position: relative; width: 118px; height: 118px; margin-bottom: 22px; }
.strk-emblem .ring { position: absolute; inset: 0; border-radius: 50%;
  border: 2px solid color-mix(in srgb, var(--saffron) 45%, transparent);
  animation: strk-ring 2.4s ease-out infinite; }
.strk-emblem .ring.r2 { animation-delay: 1.2s; }
.strk-emblem .core { position: absolute; inset: 10px; border-radius: 50%;
  background: radial-gradient(120% 120% at 32% 22%, var(--saffron), var(--ember) 82%);
  display: grid; place-items: center;
  box-shadow: 0 18px 44px -12px color-mix(in srgb, var(--saffron) 45%, transparent); }
.strk-emblem .core svg { width: 46px; height: 46px; color: var(--on-accent); }
.strk-emblem.pop .core { animation: strk-pop .5s cubic-bezier(.34,1.56,.64,1); }
@keyframes strk-ring { 0% { transform: scale(.82); opacity: .9; } 100% { transform: scale(1.28); opacity: 0; } }
@keyframes strk-pop { 0% { transform: scale(.72); } 55% { transform: scale(1.1); } 100% { transform: scale(1); } }
.strk-h { font-family: var(--font-display); font-size: var(--fs-xl); font-weight: 600;
  letter-spacing: -.02em; line-height: 1.1; text-wrap: balance; }
.strk-sub { margin-top: 9px; font-size: var(--fs-sm); color: var(--muted); line-height: 1.5; max-width: 340px; }
.strk-eta { margin-top: 16px; display: inline-flex; align-items: center; gap: 9px;
  border: 1px solid color-mix(in srgb, var(--saffron) 40%, transparent);
  background: color-mix(in srgb, var(--saffron) 12%, var(--ink-2));
  border-radius: 999px; padding: 9px 16px; font-size: var(--fs-sm); font-weight: 500; }
.strk-eta .d { width: 7px; height: 7px; border-radius: 50%; background: var(--saffron);
  animation: strk-blink 1.6s ease-in-out infinite; }
@keyframes strk-blink { 0%, 100% { opacity: 1; } 50% { opacity: .35; } }
.strk-paid { margin-top: 12px; display: inline-flex; align-items: center; gap: 7px; font-size: 12px;
  font-weight: 500; letter-spacing: .06em; text-transform: uppercase; color: var(--good);
  background: color-mix(in srgb, var(--good) 12%, var(--ink-2));
  border: 1px solid color-mix(in srgb, var(--good) 35%, transparent); border-radius: 999px; padding: 6px 13px; }
.strk-paid svg { width: 13px; height: 13px; }
.strk-line { width: 100%; max-width: 430px; margin: 34px auto 0; padding: 0 6px; }
.strk-track { position: relative; height: 4px; border-radius: 2px; background: var(--line); margin: 0 22px; }
.strk-fill { position: absolute; inset: 0; border-radius: 2px; transform-origin: left center;
  background: linear-gradient(90deg, var(--ember), var(--saffron)); transform: scaleX(0);
  transition: transform .8s cubic-bezier(.22,1,.32,1); }
.strk-nodes { display: flex; justify-content: space-between; margin-top: -15px; position: relative; }
.strk-node { width: 44px; display: flex; flex-direction: column; align-items: center; gap: 8px; }
.strk-node .dot { width: 26px; height: 26px; border-radius: 50%; background: var(--ink-2);
  border: 2px solid var(--line); display: grid; place-items: center; color: var(--muted);
  transition: border-color .3s, background .3s, color .3s; }
.strk-node .dot svg { width: 13px; height: 13px; }
.strk-node.done .dot { background: var(--saffron); border-color: var(--saffron); color: var(--on-accent); }
.strk-node.now .dot { background: var(--saffron); border-color: var(--saffron); color: var(--on-accent);
  animation: strk-nodepop .5s cubic-bezier(.34,1.56,.64,1);
  box-shadow: 0 0 0 6px color-mix(in srgb, var(--saffron) 16%, transparent),
    0 6px 18px -6px color-mix(in srgb, var(--saffron) 50%, transparent); }
@keyframes strk-nodepop { 0% { transform: scale(.5); } 60% { transform: scale(1.22); } 100% { transform: scale(1); } }
.strk-node .lb { font-size: 10px; font-weight: 500; letter-spacing: .03em; text-transform: uppercase;
  color: var(--muted); white-space: nowrap; transition: color .3s; }
.strk-node.done .lb, .strk-node.now .lb { color: var(--text); }
.strk-foot { padding: 18px 22px calc(20px + env(safe-area-inset-bottom));
  display: flex; flex-direction: column; align-items: center; gap: 12px; }
.strk-hint { font-size: 12px; color: var(--muted); text-align: center; }
.strk-done { border: none; border-radius: 14px; background: var(--saffron); color: var(--on-accent);
  font-weight: 500; font-size: var(--fs-md); padding: 14px 38px; cursor: pointer; box-shadow: var(--sh-md); }
.strk-overlay.ready { background: radial-gradient(130% 100% at 50% -10%, color-mix(in srgb, var(--good) 10%, var(--ink)), var(--ink) 58%); }
.strk-overlay.ready .strk-emblem .core { background: radial-gradient(120% 120% at 32% 22%, var(--good), #0F6B39 82%);
  box-shadow: 0 18px 48px -12px color-mix(in srgb, var(--good) 45%, transparent); }
.strk-overlay.ready .strk-emblem .core svg { color: #fff; }
.strk-overlay.ready .strk-emblem .ring { border-color: color-mix(in srgb, var(--good) 45%, transparent); }
.strk-overlay.cancelled .strk-emblem .core { background: var(--muted-2); box-shadow: none; }
.strk-overlay.cancelled .strk-emblem .core svg { color: #fff; }
.strk-overlay.cancelled .strk-emblem .ring { display: none; }
.strk-confetti { position: absolute; inset: 0; overflow: hidden; pointer-events: none; }
.strk-confetti i { position: absolute; top: 34%; left: 50%; width: 8px; height: 12px; border-radius: 2px;
  background: var(--c, #E2000A); opacity: 0;
  animation: strk-conf 1.05s cubic-bezier(.22,.9,.36,1) var(--dl, 0s) 1 both; }
@keyframes strk-conf { 0% { transform: translate(0,0) rotate(0); opacity: 0; } 8% { opacity: 1; }
  100% { transform: translate(var(--dx,0px), var(--dy,-180px)) rotate(var(--rt,180deg)); opacity: 0; } }
.strk-pill { position: fixed; left: 50%; transform: translateX(-50%); bottom: calc(14px + env(safe-area-inset-bottom));
  z-index: 56; display: inline-flex; align-items: center; gap: 10px;
  border: 1px solid color-mix(in srgb, var(--saffron) 40%, var(--line));
  background: rgba(255,255,255,.94); -webkit-backdrop-filter: blur(12px); backdrop-filter: blur(12px);
  color: var(--text); border-radius: 999px; padding: 11px 18px; font-size: var(--fs-sm); font-weight: 500;
  cursor: pointer; box-shadow: var(--sh-lg); animation: strk-pill-in .45s cubic-bezier(.2,.8,.2,1);
  white-space: nowrap; max-width: 92vw; overflow: hidden; text-overflow: ellipsis; }
.strk-pill .d { width: 8px; height: 8px; border-radius: 50%; background: var(--saffron); flex: none;
  animation: strk-blink 1.6s ease-in-out infinite; }
.strk-pill.ready-pill .d { background: var(--good); }
.strk-pill .st { color: var(--accent-strong); }
.strk-pill.ready-pill .st { color: var(--good); }
.strk-pill svg { width: 15px; height: 15px; color: var(--muted); flex: none; }
@keyframes strk-pill-in { 0% { transform: translateX(-50%) translateY(16px); opacity: 0; }
  100% { transform: translateX(-50%) translateY(0); opacity: 1; } }
.strk-pill.above-cart { bottom: calc(96px + env(safe-area-inset-bottom)); }
@media (min-width: 1024px) { .strk-pill, .strk-pill.above-cart { bottom: 28px; } }
@media (min-width: 768px) { .strk-line { max-width: 470px; } }
@media (prefers-reduced-motion: reduce) {
  .strk-overlay, .strk-overlay *, .strk-pill { transition-duration: .01ms !important;
    animation-duration: .01ms !important; animation-iteration-count: 1 !important; }
  .strk-confetti { display: none; }
}

.imgph { width: 100%; aspect-ratio: 1; display: grid; place-items: center; font-size: 40px;
  background: radial-gradient(120% 120% at 30% 20%, var(--card-from), var(--card-to)); }

/* =========================================================================
   HYGIENE — dark dashboard (SERVD FSMS language: slate + #2E7DD2 blue)
   ========================================================================= */
.hyg { background: #0f172a; color: #f1f5f9; border: 1px solid #1e293b; border-radius: var(--radius);
  padding: 16px; margin-bottom: 14px; }
.hyg h2 { font-family: var(--font); color: #94a3b8; font-size: 12.5px; text-transform: uppercase;
  letter-spacing: .06em; margin-bottom: 10px; }
.hyg .row { border-color: #1e293b; }
.hyg .name { color: #f1f5f9; }
.hyg .sub { color: #94a3b8; }
.hyg input.text { background: #111827; border-color: #374151; color: #f3f4f6; }
.hyg input.text::placeholder { color: #4b5563; }
.hyg input.text:focus { outline-color: #2E7DD2; }
.hyg .btn { background: #2E7DD2; color: #fff; }
.hyg .btn.secondary { background: #1e293b; color: #e2e8f0; }
.hyg .pass, .hyg .fail { font-size: 11px; padding: 5px 12px; border-radius: 10px; font-weight: 700; cursor: pointer; }
.hyg .pass { background: rgba(16,185,129,.15); color: #6ee7b7; border: 1px solid rgba(16,185,129,.4); }
.hyg .fail { background: rgba(244,63,94,.15); color: #fda4af; border: 1px solid rgba(244,63,94,.4); }
.hyg .pass.on { background: #10b981; color: #06281c; }
.hyg .fail.on { background: #f43f5e; color: #2d060d; }
.hyg .statgrid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 8px; margin-bottom: 12px; }
@media (min-width: 560px) { .hyg .statgrid { grid-template-columns: repeat(4, 1fr); } }
.hyg .hstat { background: rgba(15,23,42,.6); border: 1px solid #1e293b; border-radius: 12px; padding: 10px; }
.hyg .hstat .v { font-size: 18px; font-weight: 700; font-variant-numeric: tabular-nums; }
.hyg .hstat .k { font-size: 11px; color: #94a3b8; }
.hyg .templog { display: flex; gap: 8px; align-items: center; }
.hyg .templog input { width: 72px; text-align: center; }
.hyg .good-t { color: #6ee7b7; } .hyg .bad-t { color: #fda4af; }


/* ---- OOS: keep the colour, lose the funeral ---- */
.item.oos .imgbox { filter: none; opacity: .82; }
.item.oos .nm, .item.oos .pr { color: var(--muted); }
.oospill { position: absolute; left: 50%; top: 42%; transform: translate(-50%, -50%); z-index: 2;
  background: rgba(255,255,255,.96); color: var(--warn); border: 0;
  font-size: 11px; font-weight: 700; padding: 6px 13px; border-radius: 999px;
  box-shadow: 0 2px 10px -2px rgba(16,17,33,.18); }

/* ---- promos: offer badges, was/now, deals rail, voucher row ---- */
.offerpill { position: absolute; top: 8px; left: 8px; z-index: 2; background: var(--saffron);
  color: var(--on-accent); font-size: 10px; font-weight: 800; letter-spacing: .06em;
  padding: 4px 9px; border-radius: 999px; box-shadow: var(--sh-sm); }
.offerpill.inline { position: static; margin-left: 6px; vertical-align: middle; }
.pr .was, .pr1 .was { color: var(--muted-2); text-decoration: line-through; font-weight: 400;
  margin-right: 3px; font-size: .88em; }
.pr .now { color: var(--accent-strong); }
/* member (Clubcard-style) pricing */
.pr .now.member, .pr1 .member { color: #1f7a4d; }
.mtag { display: inline-block; background: #1f7a4d; color: #fff; font-size: 9.5px; font-weight: 800;
  letter-spacing: .05em; padding: 2px 6px; border-radius: 999px; vertical-align: middle; text-transform: uppercase; }
.mtag.sm { font-size: 8.5px; padding: 1px 5px; margin-left: 4px; }
.mhint { display: block; font-size: 11px; font-weight: 700; color: #1f7a4d; margin-top: 1px; }
.offerends.member { color: #1f7a4d; }
/* till: weighed-goods sheet + remove button */
.weighrow { display: flex; gap: 8px; }
.weighrow .text { flex: 1; }
.lqx { width: 34px; height: 34px; border-radius: 8px; border: 1px solid var(--line, #ddd);
  background: transparent; font-size: 20px; line-height: 1; cursor: pointer; color: var(--muted-2, #888); }
.lqx:hover { border-color: #c0392b; color: #c0392b; }
/* owner feature switch (iOS-style) */
.fswitch { flex: 0 0 auto; width: 46px; height: 28px; border-radius: 999px; border: 0; padding: 0;
  background: var(--ink-3, #d7d7d7); position: relative; cursor: pointer; transition: background .18s ease; }
.fswitch span { position: absolute; top: 3px; left: 3px; width: 22px; height: 22px; border-radius: 50%;
  background: #fff; box-shadow: 0 1px 3px rgba(0,0,0,.25); transition: transform .18s ease; }
.fswitch.on { background: #1f7a4d; }
.fswitch.on span { transform: translateX(18px); }
/* driver stop tags: when (scheduled/asap) + pay (cash/paid) */
.sc-tags { display: flex; flex-wrap: wrap; gap: 6px; margin: 8px 0; }
.whenpill { font-size: 12px; font-weight: 700; padding: 4px 10px; border-radius: 999px; letter-spacing: .01em; }
.whenpill.sched { background: #eef2ff; color: #3730a3; }
.whenpill.asap { background: #fff4e5; color: #B4530A; }
.whenpill.cash { background: #fde8e8; color: #9b1c1c; }
.whenpill.paid { background: #e7f6ec; color: #1f7a4d; }
/* KDS aggregator chip (Uber Eats / Deliveroo / Just Eat provenance) */
.kc-chip.agg { background: #1c1c1e; color: #fff; }
.offerends.member .linkbtn { color: #1f7a4d; display: inline; border: 0; background: none; padding: 0;
  font: inherit; font-weight: 800; text-decoration: underline; cursor: pointer; }
.dealsrail { margin-top: 14px; }
.dealsrail .usuals-h { display: flex; align-items: center; gap: 9px; padding: 0 16px 8px; }
.dealsrail h2 { font-size: var(--fs-lg); }
.dealflame { background: var(--saffron); color: var(--on-accent); font-size: 10px; font-weight: 800;
  letter-spacing: .08em; padding: 4px 10px; border-radius: 999px; }
.dealsrail .usuals-strip { display: flex; gap: 10px; overflow-x: auto; padding: 2px 16px 6px;
  scrollbar-width: none; }
.dealsrail .usuals-strip::-webkit-scrollbar { display: none; }
.dealcard { position: relative; }
.dealcard .pr .now { font-weight: 700; }
.vrow { display: flex; gap: 8px; align-items: stretch; }
.vapply { flex: none; border: 1px solid var(--line); border-radius: 13px; background: var(--ink-2);
  color: var(--accent-strong); font: inherit; font-size: 13px; font-weight: 700; padding: 0 18px;
  cursor: pointer; }
.srow.voff span { color: var(--good); font-weight: 600; }

/* ---- SERVD topbar icon row (share · save · info · bag) ---- */
.icons { display: flex; gap: 8px; align-items: center; flex: none; }
.iconbtn { width: 44px; height: 44px; border-radius: 14px; overflow: visible; border: 1px solid var(--line);
  background: var(--surface); color: var(--text); display: grid; place-items: center;
  position: relative; cursor: pointer; box-shadow: var(--sh-sm); transition: border-color .18s; }
.iconbtn:hover { border-color: var(--line-2, #D6D6DC); }
.iconbtn svg { width: 20px; height: 20px; }
.inforow { display: flex; gap: 14px; padding: 12px 0; border-bottom: 1px solid var(--line);
  font-size: var(--fs-sm); }
.inforow:last-of-type { border-bottom: 0; }
.inforow b { flex: none; width: 84px; font-weight: 600; color: var(--muted); }
.inforow span, .inforow a { color: var(--text); line-height: 1.45; }
.inforow a { font-weight: 600; text-decoration: none; color: var(--accent-strong); }
/* mobile squeeze: logo + 4 icons must share 430px */
@media (max-width: 540px) {
  .brandwide { height: 44px; }
  .brandtext { display: none; }
  .icons { gap: 6px; }
  .iconbtn { width: 40px; height: 40px; border-radius: 12px; }
}

/* ---- AI assistant: "Ask us" entry + bottom-sheet chat ---- */
/* BIF-290 — the pill used to float 122px up the screen AT ALL TIMES, which put
 * it on top of the product grid (it covered a card image). The 122px only ever
 * existed to clear the cartbar — so it now applies only WHILE the cartbar is
 * shown; the rest of the time the pill hugs the bottom edge, inside .store's
 * bottom padding where no card can be. */
.askus { position: fixed; left: 14px; bottom: calc(14px + env(safe-area-inset-bottom)); z-index: 55;
  display: inline-flex; align-items: center; gap: 7px; border: 1px solid var(--line);
  background: rgba(255,255,255,.94); -webkit-backdrop-filter: blur(12px); backdrop-filter: blur(12px);
  color: var(--text); border-radius: 999px; padding: 11px 16px; font: inherit; font-size: var(--fs-sm);
  font-weight: 600; cursor: pointer; box-shadow: var(--sh-lg); }
.askus svg { width: 16px; height: 16px; color: var(--saffron); }
/* Scoped BELOW the desktop breakpoint: at ≥1120px the cartbar is display:none
 * but keeps its .show class, and an unscoped :has() would lift the pill over
 * a bar that is not there. */
@media (max-width: 1119px) {
  body:has(.cartbar.show) .askus { bottom: calc(122px + env(safe-area-inset-bottom)); }
  /* And the grid must be able to scroll CLEAR of both bar and pill — 110px of
   * clearance covered the cartbar alone, so the lifted pill still sat on the
   * last row of cards at full scroll. */
  body:has(.cartbar.show) .store { padding-bottom: 176px; }
}
@media (min-width: 1024px) { .askus { bottom: 28px; } }
.chat { position: fixed; left: 0; right: 0; bottom: 0; z-index: 64; max-width: var(--maxw);
  margin: 0 auto; height: 72vh; height: 72dvh; background: var(--ink); border-radius: 28px 28px 0 0;
  border: 1px solid var(--line); border-bottom: none; box-shadow: var(--sh-lg);
  transform: translateY(102%); transition: transform .42s cubic-bezier(.22,1,.32,1);
  display: flex; flex-direction: column; overflow: hidden; }
.chat.open { transform: translateY(0); }
.chat-top { padding: 13px 20px; border-bottom: 1px solid var(--line); display: grid;
  grid-template-columns: 1fr auto 1fr; align-items: center; flex: none; }
.chat-top .grab { grid-column: 1 / -1; justify-self: center; margin-bottom: 8px; }
.chat-top h3 { grid-column: 2; font-size: var(--fs-lg); letter-spacing: -.03em; }
.chat-top .x { grid-column: 3; justify-self: end; background: none; border: none; color: var(--muted);
  cursor: pointer; display: grid; place-items: center; width: 40px; height: 40px; }
.chat-top .x svg { width: 22px; height: 22px; }
.chat-log { flex: 1; min-height: 0; overflow-y: auto; overscroll-behavior: contain;
  padding: 14px 16px; display: flex; flex-direction: column; gap: 10px; }
.cnotice { font-size: 11.5px; color: var(--muted); background: var(--ink-2); border: 1px solid var(--line);
  border-radius: 12px; padding: 10px 13px; line-height: 1.45; }
.cmsg { max-width: 86%; padding: 11px 14px; border-radius: 16px; font-size: var(--fs-sm);
  line-height: 1.5; white-space: pre-wrap; }
.cmsg.me { align-self: flex-end; background: var(--saffron); color: var(--on-accent);
  border-bottom-right-radius: 5px; }
.cmsg.bot { align-self: flex-start; background: var(--ink-2); border: 1px solid var(--line);
  color: var(--text); border-bottom-left-radius: 5px; }
.cmsg.typing { display: inline-flex; gap: 4px; padding: 14px 16px; }
.cmsg.typing i { width: 7px; height: 7px; border-radius: 50%; background: var(--muted-2);
  animation: cdot 1.2s ease-in-out infinite; }
.cmsg.typing i:nth-child(2) { animation-delay: .15s; }
.cmsg.typing i:nth-child(3) { animation-delay: .3s; }
@keyframes cdot { 0%, 100% { opacity: .35; transform: translateY(0); } 50% { opacity: 1; transform: translateY(-3px); } }
.ccards { display: flex; gap: 10px; overflow-x: auto; padding: 2px 0 4px; scrollbar-width: none; }
.ccards::-webkit-scrollbar { display: none; }
.ccard { flex: 0 0 128px; background: var(--surface); border: 1px solid var(--line); border-radius: 14px;
  padding: 8px; display: flex; flex-direction: column; gap: 6px; }
.ccimg { height: 84px; border-radius: 10px; background: #fff; display: grid; place-items: center; overflow: hidden; }
.ccimg img { width: 100%; height: 100%; object-fit: contain; padding: 4px; }
.ccnm { font-size: 12px; font-weight: 500; line-height: 1.3; display: -webkit-box;
  -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; min-height: 31px; }
.ccrow { display: flex; align-items: center; justify-content: space-between; margin-top: auto; }
.ccrow .tnum { font-size: 13px; font-weight: 600; }
.ccadd { width: 30px; height: 30px; border-radius: 10px; border: 0; background: var(--saffron);
  color: var(--on-accent); display: grid; place-items: center; cursor: pointer; }
.cbasket { background: var(--surface); border: 1px solid var(--line); border-radius: 14px;
  padding: 6px 13px 12px; }
.cb-line { display: flex; justify-content: space-between; gap: 12px; padding: 8px 0;
  border-bottom: 1px solid var(--line); font-size: 13px; }
.cb-line:last-of-type { border-bottom: 0; }
.cb-line b { color: var(--muted); font-weight: 600; }
.cb-addall { width: 100%; margin-top: 8px; border: 0; border-radius: 13px; background: var(--saffron);
  color: var(--on-accent); font: inherit; font-size: 14px; font-weight: 700; padding: 12px;
  cursor: pointer; box-shadow: var(--sh-md); }
.cb-addall:disabled { opacity: .55; cursor: default; box-shadow: none; }
.cchips { display: flex; flex-wrap: wrap; gap: 7px; }
.cchip.droprecipe { border-style: dashed; border-color: var(--accent-strong); color: var(--accent-strong);
  font-weight: 600; align-self: flex-start; }
.recipedrop { background: var(--surface); border: 1.5px dashed var(--line-2, #D6D6DC);
  border-radius: 14px; padding: 13px; display: flex; flex-direction: column; gap: 9px; }
.recipedrop b { font-size: 13px; }
.recipedrop textarea { border: 1px solid var(--line); border-radius: 11px; padding: 10px 12px;
  font: inherit; font-size: 14px; resize: vertical; min-height: 96px; background: var(--ink-3); }
.recipedrop textarea:focus { outline: none; border-color: var(--accent-strong); }
.cchip { border: 1px solid color-mix(in srgb, var(--saffron) 35%, var(--line)); background: var(--ink-2);
  color: var(--text); border-radius: 999px; padding: 8px 13px; font: inherit; font-size: 12.5px;
  font-weight: 500; cursor: pointer; }
.chat-bar { flex: none; display: flex; gap: 8px; padding: 10px 14px calc(12px + env(safe-area-inset-bottom));
  border-top: 1px solid var(--line); background: var(--ink-2); }
.chat-bar input { flex: 1; border: 1px solid var(--line); border-radius: 999px; padding: 12px 16px;
  font: inherit; font-size: 16px; background: var(--ink-3); }
.chat-bar input:focus { outline: none; border-color: var(--accent-strong); }
.chat-bar button { width: 46px; height: 46px; border-radius: 50%; border: 0; background: var(--saffron);
  color: var(--on-accent); display: grid; place-items: center; cursor: pointer; flex: none; }
.chat-bar button svg { width: 19px; height: 19px; }

/* ---- admin partners view ---- */
.statgrid4 { display: grid; grid-template-columns: repeat(2, 1fr); gap: 10px; margin-bottom: 14px; }
@media (min-width: 640px) { .statgrid4 { grid-template-columns: repeat(4, 1fr); } }
.partner-card { margin-bottom: 12px; }
.pc-top { display: flex; align-items: flex-start; justify-content: space-between; gap: 10px; }
.pc-stats { display: flex; flex-wrap: wrap; gap: 8px 18px; margin-top: 12px; font-size: var(--fs-sm);
  color: var(--muted); }
.pc-stats b { color: var(--text); }
.pc-addons { display: flex; flex-wrap: wrap; gap: 7px; margin-top: 12px; }
.chip.togglable { cursor: pointer; font: inherit; font-size: 12px; }
.pc-pipe { display: flex; align-items: center; flex-wrap: wrap; gap: 6px; margin-top: 12px;
  font-size: 12px; font-weight: 600; color: var(--muted); }
.pp-step { display: inline-flex; align-items: center; gap: 5px; }
.pp-step i { width: 8px; height: 8px; border-radius: 50%; background: var(--line-2, #D6D6DC); }
.pp-step.done { color: var(--text); }
.pp-step.done i { background: var(--good); }
.pp-sep { color: var(--muted-2); }
.chip { border: 1px solid var(--line); background: var(--surface); border-radius: 999px;
  padding: 6px 12px; font-size: 12px; font-weight: 500; color: var(--muted); }
.chip.on { background: var(--good-bg); color: var(--good); border-color: color-mix(in srgb, var(--good) 35%, transparent); }

/* ---- KDS: the dark pack-screen board (SERVD KdsBoard language) ---- */
.kds { min-height: 100vh; background: #0f172a; color: #f1f5f9; }
.kds-top { display: flex; align-items: center; justify-content: space-between;
  padding: 12px 18px; border-bottom: 1px solid #1e293b; }
.kds-top .platbrand { color: #f1f5f9; }
/* auto-fit, NOT a fixed 3: when a platform cancellation raises the STOP column
   there are FOUR tracks, and a fixed 3 wrapped Ready/Out below the fold — on a
   wall-mounted screen nobody scrolls, finished orders just vanished
   (ultra-review 2026-07-30). */
.kds-cols { display: grid; grid-template-columns: repeat(auto-fit, minmax(230px, 1fr)); gap: 12px; padding: 14px;
  align-items: start; }
@media (max-width: 760px) { .kds-cols { grid-template-columns: 1fr; } }
.kcol h2 { font-family: var(--font); font-size: 13px; text-transform: uppercase;
  letter-spacing: .08em; color: #94a3b8; margin: 4px 2px 10px; display: flex;
  align-items: center; gap: 8px; }
.kcount { background: #1e293b; border-radius: 999px; padding: 2px 9px; font-size: 12px;
  color: #e2e8f0; }
/* SERVD parity: the three stages carry the three tones (sky / violet /
   emerald), so the lanes read as a flow at a glance instead of three grey
   headings. Same palette as SERVD's board, on the same dark ground. */
.kcol-new > h2 { color: #38bdf8; }
.kcol-new .kcount { background: #0c4a6e; color: #bae6fd; }
.kcol-prep > h2 { color: #a78bfa; }
.kcol-prep .kcount { background: #3b2d6e; color: #ddd6fe; }
.kcol-ready > h2 { color: #34d399; }
.kcol-ready .kcount { background: #064e3b; color: #a7f3d0; }
/* SERVD parity: a Ready ticket fades once it has sat on the pass a while —
   the eye goes to what still needs hands, not what is waiting for a door. */
.kcard.kc-dim { opacity: .55; }
/* The prep strip — totals across every unfinished ticket, biggest first. */
.kprep { display: flex; align-items: center; gap: 10px; overflow-x: auto;
  padding: 6px 10px; margin: 0 0 8px; background: #16213a; border-radius: 10px;
  -webkit-overflow-scrolling: touch; white-space: nowrap; }
.kprep-t { font-size: 11px; text-transform: uppercase; letter-spacing: .08em;
  color: #94a3b8; flex: none; }
.kprep-i { font-size: 14px; color: #e2e8f0; flex: none; }
.kprep-i b { color: #a78bfa; }
/* The quiet-printer banner — the kitchen learns paper stopped BEFORE the
   driver arrives asking for a ticket nobody printed. */
.kquiet { background: #451a03; color: #fdba74; border: 1px solid #9a3412;
  border-radius: 10px; padding: 8px 12px; margin: 0 0 8px; font-size: 14px; }
/* Booked for later — deliberately MUTED and visually unlike a ticket (SERVD's
   rule): it is awareness, not work, so it must never compete with the lanes. */
.kup { display: flex; align-items: center; gap: 8px; overflow-x: auto;
  padding: 6px 10px; margin: 0 0 8px; background: #0f172a; border: 1px solid #1e293b;
  border-radius: 10px; -webkit-overflow-scrolling: touch; white-space: nowrap; }
.kup-t { font-size: 11px; text-transform: uppercase; letter-spacing: .08em;
  color: #64748b; flex: none; }
.kup-i { font-size: 13px; color: #94a3b8; background: #111c33; border: 1px solid #1e293b;
  border-radius: 8px; padding: 3px 9px; flex: none; }
.kup-i b { color: #cbd5e1; }
.profbtn.kbook { position: relative; font-size: 18px; }
.kbook-n { position: absolute; top: -3px; right: -3px; background: #a78bfa; color: #1e1b4b;
  border-radius: 999px; font-size: 11px; font-weight: 700; min-width: 17px; padding: 0 4px; line-height: 17px; }
.bookedmenu { position: fixed; top: calc(58px + env(safe-area-inset-top)); right: 14px; z-index: 60;
  background: #0f172a; border: 1px solid #1e293b; border-radius: 14px; padding: 12px;
  box-shadow: 0 18px 50px -14px rgba(0,0,0,.6); max-width: min(92vw, 420px);
  max-height: 70vh; overflow-y: auto; }
.bookedmenu.hidden { display: none; }
.bm-h { font-size: 11px; text-transform: uppercase; letter-spacing: .08em; color: #64748b; margin-bottom: 8px; }
.bm-row { border-top: 1px solid #1e293b; padding: 10px 0; }
.bm-row:first-of-type { border-top: 0; }
.bm-when { color: #cbd5e1; font-size: 14px; }
.bm-ref { color: #94a3b8; font-size: 13px; margin: 2px 0; }
.bm-lines { color: #64748b; font-size: 12px; margin-bottom: 6px; }
.kempty { color: #475569; font-size: 13px; padding: 8px 2px; }
.kcard { background: #111c33; border: 1px solid #1e293b; border-radius: 14px; padding: 13px;
  margin-bottom: 10px; }
.kcard.late { border-color: #f43f5e; box-shadow: 0 0 0 1px #f43f5e inset; }
.kc-top { display: flex; justify-content: space-between; align-items: center; }
.kc-top b { font-size: 17px; font-variant-numeric: tabular-nums; }
.kc-age { color: #94a3b8; font-size: 12.5px; font-variant-numeric: tabular-nums; }
.kcard.late .kc-age { color: #fda4af; font-weight: 700; }
.kc-meta { display: flex; flex-wrap: wrap; gap: 6px; align-items: center; margin-top: 8px; }
.kc-chip { font-size: 11px; font-weight: 700; padding: 3px 9px; border-radius: 999px;
  background: #1e293b; color: #e2e8f0; }
.kc-chip.del { background: rgba(46,125,210,.2); color: #93c5fd; }
/* order SOURCE — branded so staff clock provenance instantly */
.kc-chip.src { text-transform: uppercase; letter-spacing: .03em; }
.kc-chip.src-uber_eats { background: #06C167; color: #04220f; }
.kc-chip.src-deliveroo { background: #00CCBC; color: #003c37; }
.kc-chip.src-just_eat { background: #FF8000; color: #331a00; }
.kc-chip.src-tiktok { background: #fe2c55; color: #fff; }
.kc-chip.src-web { background: rgba(59,130,246,.25); color: #93c5fd; }
/* The shop's own mark on its own orders. White plate on purpose: the wordmark
   is full-colour artwork on transparency, and a dark board would swallow it —
   the plate is the paper it was designed for. Height-capped so a wide logo
   cannot outgrow the chip row it sits in. */
.kc-srclogo { height: 24px; max-width: 96px; object-fit: contain; background: #fff; border-radius: 999px; padding: 2px 10px; }
.kc-chip.src-till { background: rgba(148,163,184,.22); color: #cbd5e1; }
/* owner analytics: source dots share the platform liveries */
.srcdot { width: 10px; height: 10px; border-radius: 999px; flex: none; background: #64748b; }
.srcdot.src-uber_eats { background: #06C167; }
.srcdot.src-deliveroo { background: #00CCBC; }
.srcdot.src-just_eat { background: #FF8000; }
.srcdot.src-tiktok_shop, .srcdot.src-tiktok { background: #fe2c55; }
.srcdot.src-web { background: #3b82f6; }
.srcdot.src-till { background: #94a3b8; }
.kc-chip.cod { background: rgba(244,180,0,.16); color: #fcd34d; }
.kc-chip.paid { background: rgba(16,185,129,.15); color: #6ee7b7; }
.kc-chip.slot { background: rgba(212,160,60,.18); color: #fcd34d; }
.kc-name { color: #94a3b8; font-size: 12.5px; }
.kc-lines { margin-top: 10px; border-top: 1px solid #1e293b; padding-top: 8px; }
.kc-line { display: flex; gap: 9px; padding: 4px 0; font-size: 14px; line-height: 1.35; align-items: center; }
.kc-line b { color: #7dd3fc; font-variant-numeric: tabular-nums; flex: none; }
/* The picture IS the pick. Six near-identical atta sacks differ only by brand;
   a photo settles in a glance what a name row gets wrong. Every line reserves
   the slot — missing images get an empty tile, so quantities and names stay
   COLUMN-ALIGNED down the ticket instead of ragged where photos are absent. */
.kc-thumb { width: 42px; height: 42px; min-width: 42px; border-radius: 8px; object-fit: cover;
  background: #1e293b; display: block; }
.kc-thumb-none { display: block; }
.kc-go { width: 100%; margin-top: 11px; border: 0; border-radius: 12px; background: #2E7DD2;
  color: #fff; font: inherit; font-size: 15px; font-weight: 700; padding: 13px; cursor: pointer; }
.kc-go:disabled { opacity: .5; }
.kc-done { margin-top: 11px; text-align: center; color: #64748b; font-size: 12.5px; }

/* ---- driver app ---- */
.secttl { font-size: var(--fs-lg); margin: 6px 2px 10px; }
.stopcard { margin-bottom: 12px; }
.sc-top { display: flex; align-items: center; justify-content: space-between; }
.sc-name { margin-top: 6px; font-size: var(--fs-sm); color: var(--muted); }
.sc-addr { display: block; margin-top: 10px; font-size: var(--fs-md); font-weight: 500;
  color: var(--text); text-decoration: none; line-height: 1.4; }
.codchip { margin-top: 10px; padding: 9px 13px; border-radius: 11px; background: var(--amber-bg);
  color: var(--warn); font-size: var(--fs-xs); font-weight: 700; }
.paidchip { margin-top: 10px; padding: 9px 13px; border-radius: 11px; background: var(--good-bg);
  color: var(--good); font-size: var(--fs-xs); font-weight: 600; }

/* ---- branded login hero (servd.ai-style touch on every sign-in card) ---- */
.loginhero { text-align: center; padding: 10px 0 16px; }
.loginhero img { height: 38px; display: block; margin: 0 auto; }
.loginhero p { margin-top: 9px; font-size: 13px; color: var(--muted); }

/* ---- the sign-in stage: one job, centred ----
   A destination reached from an email, not a screen inside an app. `svh` rather than `vh`
   so the mobile browser's own chrome is subtracted — with `vh` the card is pushed below
   the fold by the URL bar, which is the same unit trap that put the basket drawer's close
   button off-screen. min-height, never height: the card must still be able to grow past
   the viewport and scroll on a small screen. */
.signin-stage { min-height: 88svh; display: flex; flex-direction: column;
  justify-content: center; padding-top: 12px; padding-bottom: 28px; }
@supports not (min-height: 1svh) { .signin-stage { min-height: 88vh; } }

/* ---- a button that reads as a link ----
   🚨 THIS BASE RULE DID NOT EXIST. `.linkbtn` was only ever styled SCOPED — inside
   `.offerends.member` and `.codinfo` — so bare `.linkbtn` markup rendered as a default
   grey browser button. The repo's css-classes-exist gate passed it, because the class IS
   in this stylesheet; the gate answers "is this class defined anywhere", which is a
   different question from "does a rule reach this element". Caught by looking at a
   screenshot, not by any check. */
.linkbtn { display: inline; border: 0; background: none; padding: 0; margin: 0;
  font: inherit; color: var(--accent-strong); font-weight: 600;
  text-decoration: underline; text-underline-offset: 3px; cursor: pointer; }
.linkbtn:hover { text-decoration-thickness: 2px; }
.linkbtn:disabled { color: var(--muted); text-decoration: none; cursor: default; }

/* ---- platform brand lockup (staff/owner/till headers) ---- */
.platbrand { display: flex; align-items: center; gap: 9px; font-family: var(--font);
  font-weight: 700; letter-spacing: .02em; }
.platbrand img { width: 30px; height: 30px; display: block; }
.platbrand span { color: var(--muted); font-weight: 500; }

/* ---- hero banner slideshow — the ambas.store top pics, swipe + auto-advance ---- */
.heroslides { margin: 12px 16px 0; position: relative; }
.heroslip { display: flex; overflow-x: auto; scroll-snap-type: x mandatory;
  border-radius: var(--radius); box-shadow: var(--sh-md); cursor: grab;
  scrollbar-width: none; -webkit-overflow-scrolling: touch; }
.heroslip::-webkit-scrollbar { display: none; }
.heroslip:active { cursor: grabbing; }
/* Design-native slides: clean generated 16:9 imagery full-bleed, gradient scrim,
   LIVE overlaid type + a tappable department CTA. Ken Burns drift on the image. */
.heroslide { flex: 0 0 100%; scroll-snap-align: center; position: relative;
  aspect-ratio: 16 / 9; overflow: hidden; background: var(--ink-3); }
.heroslide .hs-img { position: absolute; inset: 0; width: 100%; height: 100%;
  object-fit: cover; user-select: none; -webkit-user-drag: none; }
.heroslide.active .hs-img { animation: kenburns 16s ease-in-out infinite alternate; }
@keyframes kenburns { from { transform: scale(1.02) translateX(-1%); } to { transform: scale(1.1) translateX(1%); } }
.heroslide .hs-scrim { position: absolute; inset: 0;
  background: linear-gradient(100deg, rgba(10,10,18,.72) 0%, rgba(10,10,18,.38) 46%, transparent 72%); }
.heroslide .hs-copy { position: absolute; left: 18px; right: 30%; top: 50%;
  transform: translateY(-50%); color: #fff; }
.hs-eyebrow { display: block; font-size: 10.5px; font-weight: 700; letter-spacing: .16em;
  text-transform: uppercase; opacity: .85; }
.hs-title { font-family: var(--font-display); font-size: clamp(20px, 5.6vw, 30px);
  font-weight: 600; letter-spacing: -.02em; line-height: 1.08; margin-top: 5px;
  text-shadow: 0 2px 14px rgba(0,0,0,.35); }
.hs-sub { font-size: 12.5px; opacity: .88; margin-top: 6px; line-height: 1.4; }
.hs-cta { margin-top: 12px; border: 0; border-radius: 999px; background: #fff;
  color: #101121; font: inherit; font-size: 13px; font-weight: 700; padding: 12px 20px;
  cursor: pointer; box-shadow: 0 4px 16px rgba(0,0,0,.3); }
@media (prefers-reduced-motion: reduce) { .heroslide .hs-img { animation: none; } }
.herodots { position: absolute; left: 0; right: 0; bottom: 10px; display: flex;
  justify-content: center; gap: 6px; pointer-events: none; }
.herodots button { width: 7px; height: 7px; border-radius: 50%; border: 0; padding: 0;
  background: rgba(255,255,255,.55); cursor: pointer; transition: background .25s, transform .25s;
  pointer-events: auto; box-shadow: 0 1px 4px rgba(16,17,33,.25); }
.herodots button.on { width: 20px; border-radius: 4px; background: #fff; }

/* ---- department slider — swipeable strip with gentle auto-drift ---- */
.slideband { margin: 12px 0 0; overflow-x: auto; position: relative; cursor: grab;
  scrollbar-width: none; -webkit-overflow-scrolling: touch;
  -webkit-mask-image: linear-gradient(90deg, transparent, #000 6%, #000 94%, transparent);
  mask-image: linear-gradient(90deg, transparent, #000 6%, #000 94%, transparent); }
.slideband::-webkit-scrollbar { display: none; }
.slideband:active { cursor: grabbing; }
.slidetrack { display: flex; gap: 10px; width: max-content; padding: 4px 16px; }
.slidecard { flex: none; width: 108px; border: 1px solid var(--line); border-radius: 16px;
  background: var(--surface); padding: 10px 8px 9px; display: flex; flex-direction: column;
  align-items: center; gap: 7px; cursor: pointer; box-shadow: var(--sh-sm); font: inherit;
  transition: transform .15s, border-color .15s; }
.slidecard:active { transform: scale(.96); }
.slidecard:hover { border-color: color-mix(in srgb, var(--saffron) 45%, var(--line)); }
.slideimg { width: 74px; height: 74px; border-radius: 50%; overflow: hidden; display: grid;
  place-items: center; background: radial-gradient(120% 120% at 30% 20%, var(--card-from), var(--card-to)); }
.slideimg img { width: 100%; height: 100%; object-fit: cover; }
.slidelb { font-size: 12px; font-weight: 600; color: var(--text); }

/* ---- storefront footer: real socials + contact + TILLD mark ---- */
.shopfoot { margin: 26px 16px 0; padding: 26px 16px calc(30px + env(safe-area-inset-bottom));
  border-top: 1px solid var(--line); text-align: center; }
.sf-brand img { height: 44px; margin: 0 auto; display: block; }
.sf-strap { font-size: 12.5px; color: var(--muted); margin-top: 8px; }
.sf-socials { display: flex; justify-content: center; gap: 12px; margin-top: 16px; }
.sf-so { width: 42px; height: 42px; border-radius: 13px; display: grid; place-items: center;
  box-shadow: var(--sh-md); transition: transform .12s; }
.sf-so:active { transform: scale(.92); }
.sf-so svg { width: 22px; height: 22px; }
.sf-meta { display: flex; flex-direction: column; gap: 4px; margin-top: 16px;
  font-size: 12.5px; color: var(--muted); }
.sf-meta a { color: var(--accent-strong); font-weight: 600; text-decoration: none; }
/* The shop's own legal links: quiet, but never hidden — a privacy notice a
   customer cannot find is not a privacy notice (UK GDPR Art. 13). */
.sf-legal { margin-top: 16px; font-size: 12px; color: var(--muted-2); }
.sf-legal a { color: var(--muted); text-decoration: none; border-bottom: 1px solid var(--line); }
.sf-pw { margin-top: 18px; font-size: 10.5px; letter-spacing: .16em; color: var(--muted-2); }
.sf-pw b { color: var(--text); letter-spacing: .08em; }

/* ---- premium touches ---- */
.store::before { content: ''; position: absolute; top: -140px; left: 50%; transform: translateX(-50%);
  width: 560px; height: 340px; border-radius: 50%; pointer-events: none;
  background: radial-gradient(closest-side, color-mix(in srgb, var(--saffron) 9%, transparent), transparent); }
.brandband h2 { font-size: var(--fs-xl); color: #fff; position: relative; z-index: 1; }
.brandband p { font-size: var(--fs-xs); opacity: .92; margin-top: 4px; position: relative; z-index: 1; }
.icb { width: 21px; height: 21px; display: block; margin: 0 auto; }
.icb path { stroke: currentColor; stroke-width: 1.8; fill: none; stroke-linecap: round; stroke-linejoin: round; }

/* ---- the TILL ---- */
/* minmax(0,…): a bare fr track refuses to shrink below its content's minimum,
   so the letter board's key rows shoved the sale column past the viewport and
   overflow-x:clip ate the prices and the total (his photo, 30 Aug fullscreen). */
.till { display: grid; grid-template-columns: minmax(0, 1fr); gap: 14px; }
@media (min-width: 900px) { .till { grid-template-columns: minmax(0, 1.2fr) minmax(0, .8fr); align-items: start; } }
.scanline { display: flex; align-items: center; gap: 8px; font-size: 13px; font-weight: 600;
  color: var(--good); margin-bottom: 10px; }
.dot-scan { width: 9px; height: 9px; border-radius: 999px; background: var(--good); animation: breathe 1.6s infinite; }
.till-lines { max-height: 44vh; overflow-y: auto; overscroll-behavior: contain; }
.row-flash { animation: rowflash 0.9s ease-out; }
/* The receipt programme is the only offer that gives money away unattended —
   it reads as a running STATE, not a form (his ask: "its own cta, and green
   going through it"). The bar is the whole signal; it appears only while the
   programme is actually printing codes. */
.prog-card.prog-live { border-left: 4px solid var(--good, #16a34a); }
.prog-state { font-weight: 600; margin-top: 4px; }
.prog-card.prog-live .prog-state { color: var(--good, #16a34a); }
.prog-summary { margin: 2px 0 10px; }
/* The loyalty pad lives INSIDE the tender sheet — the drawer is above the
   scrim, so a pad on the page behind it cannot be tapped at all. Compact:
   it shares the sheet with the money keypad and the Complete CTA. */
.loypad { margin-top: 10px; }
.loypad .kpad-key { min-height: 48px; font-size: 20px; }
.vcount { margin: -4px 0 12px; font-weight: 600; }
@keyframes rowflash { 0% { background: #fef3c7; } 100% { background: transparent; } }
.till-total { display: flex; justify-content: space-between; align-items: baseline; border-top: 2px solid var(--text);
  margin-top: 10px; padding-top: 10px; }
.till-total b { font-size: 30px; }
.till-change { font-size: 17px; margin-top: 10px; min-height: 24px; }
.till-change b { font-size: 24px; color: var(--good); }

/* ---- receipt (80mm print) ---- */
.receipt { display: none; }
/**
 * 🚨 SCOPED TO A PAGE THAT ACTUALLY HAS A RECEIPT ON IT.
 *
 * These are the 80mm till-receipt rules, and app.css is linked on EVERY page
 * (build.js). Unscoped, `body * { visibility: hidden }` plus a global
 * `@page { size: 80mm }` meant that printing — or Saving as PDF — any page on
 * the site produced an empty 80mm slip. That included /privacy and /terms:
 * the notice existed on screen and in no other format, and paper is the one
 * format a complaint file or an ICO request actually uses.
 *
 * `.receipt` is injected only by till.js, so :has() is true exactly on the
 * till surface. If a browser were ever to not support :has(), it fails in the
 * safe direction — the legal pages print correctly and a receipt prints as a
 * full page, rather than every page printing blank.
 */
@media print {
  body:has(.receipt) * { visibility: hidden; }
  body:has(.receipt) .receipt, body:has(.receipt) .receipt * { visibility: visible; }
  .receipt { display: block; position: absolute; left: 0; top: 0; width: 72mm;
    font: 11px/1.45 "Courier New", monospace; color: #000; }
  .r-head { text-align: center; font-weight: 700; font-size: 13px; }
  .r-head span { font-weight: 400; font-size: 10px; }
  .r-item { display: flex; justify-content: space-between; }
  .r-total { font-weight: 700; font-size: 13px; }
  .r-line { overflow: hidden; white-space: nowrap; }
  .r-foot { text-align: center; margin-top: 6px; font-size: 10px; }
}
/**
 * An 80mm slip is a RECEIPT size, not a page size, and `@page` takes no
 * selector — so the receipt gets a NAMED page it opts into via `page:`, and
 * everything else (privacy, terms, an order confirmation) keeps A4 with a
 * readable margin. Previously the bare `@page { size: 80mm auto }` applied to
 * every document in the site.
 */
@page { margin: 12mm; }
@page receipt { size: 80mm auto; margin: 2mm; }
.receipt { page: receipt; }

/* ---- Clerk widget: fit our phone column + match the design system ---- */
#clerkmount .cl-rootBox, #clerkmount .cl-card, #clerkmount .cl-cardBox {
  width: 100% !important; max-width: 100% !important; box-shadow: none !important; }
#clerkmount .cl-card { border: 0 !important; padding: 4px 0 0 !important; background: transparent !important; }
#clerkmount .cl-headerTitle { font-family: var(--font-display) !important; }
#clerkmount .cl-formButtonPrimary { border-radius: 14px !important; min-height: 46px !important; }
#clerkmount .cl-socialButtonsBlockButton { border-radius: 12px !important; }
#clerkmount .cl-footer, #clerkmount .cl-logoBox { margin-top: 4px !important; }

/* ---- SERVD item sheet (tap a product → bottom sheet, spec §1.8) ---- */
.sheet { position: fixed; left: 0; right: 0; bottom: 0; z-index: 63; max-width: var(--maxw);
  margin: 0 auto; max-height: 88vh; max-height: 88dvh;
  background: var(--ink); border-radius: 28px 28px 0 0; transform: translateY(102%);
  transition: transform .5s cubic-bezier(.22,1,.32,1); display: flex; flex-direction: column; overflow: hidden; }
.sheet.open { transform: translateY(0); }
.sheet-hero { height: 240px; flex: none; position: relative; background: #fff;
  border-bottom: 1px solid var(--line); }
.sheet-hero img { width: 100%; height: 100%; object-fit: contain; padding: 14px; }
.sheet-hero .glyph { width: 100%; height: 100%; display: grid; place-items: center; font-size: 52px; opacity: .55; }
.sheet-close { position: absolute; top: 12px; right: 12px; width: 40px; height: 40px; border-radius: 999px;
  border: 0; background: rgba(255,255,255,.85); -webkit-backdrop-filter: blur(8px); backdrop-filter: blur(8px);
  font-size: 18px; cursor: pointer; box-shadow: var(--sh-sm); }
.sheet-body { padding: 16px 18px; overflow-y: auto; flex: 1; }
.sheet-body h3 { font-size: var(--fs-xl); margin-bottom: 6px; }
.sheet-body .ds { color: var(--muted); font-size: var(--fs-sm); }
.sheet-body .pr1 { font-family: var(--font-display); font-feature-settings: 'tnum' 1,'lnum' 1;
  font-size: var(--fs-lg); font-weight: 600; margin-top: 8px; }
.stepper { display: inline-flex; align-items: center; gap: 0; border: 1.5px solid var(--line);
  border-radius: 14px; background: var(--surface); margin-top: 14px; }
.stepper .qbtn { width: 44px; height: 44px; border: 0; background: none; font-size: 20px; font-weight: 700;
  cursor: pointer; color: var(--text); }
.stepper span { min-width: 38px; text-align: center; font-weight: 700; font-size: 17px; font-variant-numeric: tabular-nums; }
.sheet-foot { flex: none; padding: 12px 18px calc(14px + env(safe-area-inset-bottom));
  border-top: 1px solid var(--line); background: var(--ink); }
.addcta { width: 100%; display: flex; align-items: center; justify-content: space-between; border: 0;
  background: var(--saffron); color: var(--on-accent); border-radius: 16px; padding: 15px 18px;
  font-size: 16px; font-weight: 700; cursor: pointer; transition: transform .12s; }
.addcta:active { transform: scale(.98); }
.addcta .qbubble { font-family: var(--font-display); font-feature-settings: 'tnum' 1,'lnum' 1; }
.oos-sheet { background: var(--amber-bg); color: var(--warn); border-radius: 12px; padding: 10px 13px;
  font-size: var(--fs-xs); font-weight: 600; margin-top: 12px; }

/* add feedback: badge pop (SERVD .hbadge count pop) + bigger hit target */
@keyframes hpop { 0% { transform: scale(1); } 45% { transform: scale(1.45); } 100% { transform: scale(1); } }
.hbadge.pop { animation: hpop .38s cubic-bezier(.22,1,.32,1); }
/* 🚨 TOUCH TARGETS — THE FINGER, NOT THE PAINT (BIF-9, WCAG 2.2 §2.5.8).
   Measured in a real browser at 390×844, not read from source: `.add-mini` is
   the most-repeated control on the storefront (48 instances) and its box is
   30×30 — 26×26 inside a `.ucard`. `.iconbtn` is declared 44×44 and then
   SHRUNK TO 40×40 by the max-width:540px rule, i.e. on phones exactly, which is
   the only place it is ever touched by a thumb.

   🔑 The house pattern keeps the VISUAL small and expands the HIT AREA with an
   invisible ::before — already used by `.gqty button` and `.lq button`. What was
   wrong here was the ARITHMETIC, not the idea: `inset: -6px` on a 30px button is
   42px, two short, and on the 26px variant it is 38px.

   So this centres a fixed 44×44 box on the control instead of insetting by a
   guess. One rule is then correct for every size variant, and stays correct if
   a variant is resized later — which is what let the old one drift out of
   compliance silently. */
/* 🪤 .add-mini is DELIBERATELY ABSENT from this rule. It is corner-anchored with
   `position: absolute` (its own rule), and the ::before hit-ring below centres
   correctly inside ANY positioned element — absolute included. The first version
   listed it here, and `relative` (same specificity, later in file) silently
   UN-ANCHORED the storefront's most-repeated control: it fell into text flow,
   the product name painted over it, and the 4h edge cache showed the regression
   to different devices at different times — which is why it was reported as
   "the add to basket ctas have moved". A rule written for a hit ring changed
   LAYOUT, and nothing errored. */
.iconbtn, .locbtn, .lqx { position: relative; }
.add-mini::before, .iconbtn::before, .locbtn::before, .lqx::before {
  content: ''; position: absolute; top: 50%; left: 50%;
  width: 44px; height: 44px; transform: translate(-50%, -50%);
}

/* ---- live suggestions panel (search-first) ---- */
.searchwrap { position: relative; }
.suggest { position: absolute; left: 16px; right: 16px; top: calc(100% + 6px); z-index: 45;
  background: var(--surface); border: 1px solid var(--line); border-radius: 16px;
  box-shadow: var(--sh-lg); overflow: hidden; }
.srow-s { display: flex; align-items: center; gap: 10px; padding: 10px 12px; cursor: pointer;
  border-bottom: 1px solid var(--ink-3); }
.srow-s:active { background: var(--ink-3); }
.srow-s img, .srow-s .sph { width: 34px; height: 34px; border-radius: 9px; object-fit: cover;
  background: var(--ink-3); flex: none; display: grid; place-items: center; color: var(--muted-2); }
.srow-s .snm { flex: 1; min-width: 0; font-size: 13.5px; font-weight: 500; overflow: hidden;
  text-overflow: ellipsis; white-space: nowrap; }
.srow-s .susual { font-style: normal; font-size: 10px; font-weight: 700; color: var(--fresh);
  background: color-mix(in srgb, var(--fresh) 14%, transparent); border-radius: 999px; padding: 1px 7px; margin-left: 4px; }
.srow-s .spr { font-size: 13px; color: var(--muted); flex: none; }
.srow-s .sadd { flex: none; border: 0; width: 30px; height: 30px; border-radius: 10px;
  background: var(--saffron); color: var(--on-accent); cursor: pointer; display: grid; place-items: center; }
.srow-s .soos { font-size: 11px; color: var(--muted-2); font-weight: 600; }
.sall { width: 100%; border: 0; background: var(--ink-3); padding: 11px; font-size: 13px;
  font-weight: 600; color: var(--text); cursor: pointer; }

/* ---- SERVD add-to-basket juice: fly-dot + cartbar bump ---- */
.flydot { position: fixed; z-index: 80; width: 14px; height: 14px; border-radius: 999px;
  background: var(--saffron); box-shadow: var(--sh-md); pointer-events: none;
  transition: transform .55s cubic-bezier(.3,.9,.4,1), opacity .55s; }
@keyframes cbump { 0% { transform: scale(1); } 40% { transform: scale(1.03); } 100% { transform: scale(1); } }
.cartbar button.bump { animation: cbump .32s cubic-bezier(.22,1,.32,1); }

/* wide logo lockup (tenants with a real wordmark logo) */
.brandwide { height: 64px; width: auto; max-width: 210px; flex: none; display: flex; align-items: center; }
.brandwide img { height: 100%; width: auto; max-width: 100%; object-fit: contain; }

/* ---------- loyalty (promos phase 4) ---------- */
.lchip { position: absolute; top: -7px; right: -10px; background: var(--accent2, #7DC821); color: #fff;
  font-size: 10px; font-weight: 800; padding: 2px 6px; border-radius: 999px; letter-spacing: .02em; box-shadow: 0 1px 4px rgba(0,0,0,.25); }
.ltoggle { display: flex; align-items: center; gap: 9px; width: 100%; border: 1.5px solid var(--line);
  background: var(--card, #fff); border-radius: 14px; padding: 13px 14px; font-size: 14px; font-weight: 700;
  color: inherit; cursor: pointer; transition: border-color .15s, background .15s; }
.ltoggle .lt-star { color: #B9760A; font-size: 16px; }
.ltoggle .lt-knob { margin-left: auto; width: 40px; height: 24px; border-radius: 999px; background: #D8D8DE; position: relative; transition: background .18s; flex: 0 0 auto; }
.ltoggle .lt-knob::after { content: ''; position: absolute; top: 3px; left: 3px; width: 18px; height: 18px; border-radius: 50%; background: #fff; transition: left .18s; box-shadow: 0 1px 3px rgba(0,0,0,.25); }
.ltoggle.on { border-color: #17924E; background: #F0FAF4; }
.ltoggle.on .lt-knob { background: #17924E; }
.ltoggle.on .lt-knob::after { left: 19px; }
.lnote { font-size: 13px; color: var(--muted, #63636B); }
.lnote .lsignin { border: 0; background: none; color: inherit; font-size: 13px; font-weight: 700; text-decoration: underline; cursor: pointer; padding: 0; }
.lbal { display: flex; align-items: baseline; gap: 10px; padding: 18px 20px 6px; font-size: 15px; }
.lbal b { font-size: 34px; letter-spacing: -.02em; }
.lbal-star { color: #B9760A; font-size: 22px; }
.lrate, .lphone { padding: 4px 20px; font-size: 14px; line-height: 1.5; color: var(--muted, #63636B); }
.llink { padding: 10px 20px 0; font-size: 14px; }
.llink p { margin-bottom: 8px; }

/* Marketing opt-in. Deliberately quiet: it sits UNDER the thing they came to do
   (save a number for the till), never above it, and never as a barrier. A
   consent people feel tricked into is worth less than no consent at all — they
   unsubscribe on the first message and the number is burnt. */
.lconsent { margin-top: 12px; display: flex; flex-direction: column; gap: 10px; }
.lcopt { display: flex; align-items: flex-start; gap: 10px; cursor: pointer; line-height: 1.35; }
/* 20px+ target and a real accent so the state is unmistakable at a glance —
   a checkbox the customer cannot tell is ticked is a consent dispute later. */
.lcopt input { width: 20px; height: 20px; min-width: 20px; margin-top: 1px; accent-color: var(--accent, #1f7a3d); }
.lcopt span { font-size: 14px; }
.lcnote { font-size: 12.5px; opacity: .72; margin: 0; line-height: 1.4; }
.lsignout { margin: 18px 20px 22px; border: 1.5px solid var(--line); background: none; border-radius: 12px;
  padding: 10px 16px; font-size: 13px; font-weight: 700; color: var(--muted, #63636B); cursor: pointer; }

/* ---------- desktop (≥1120px): full-width shop + pinned Asda-style basket ----------
 * Mobile is untouched — these rules only ADD above the breakpoint. The bottom
 * drawer becomes a permanent right-hand sidebar; the cartbar disappears. */
@media (min-width: 1120px) {
  .store { max-width: none; margin: 0 400px 0 0; padding-bottom: 48px; }
  .topbar { padding-left: 32px; padding-right: 32px; }
  .heroslides { margin: 18px 32px 0; }
  .heroslide { height: 340px; }
  .slideband, .dealsrail, .usuals { padding-left: 16px; padding-right: 16px; }
  .grid { grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); gap: 16px; padding: 0 32px 8px; }
  .sectionh, .usuals-h { padding-left: 16px; padding-right: 16px; }
  .cartbar { display: none !important; }

  /* the drawer IS the page's right column now — always present, never closes.
     SCOPED to .store: the till/KDS/driver reuse .drawer as a bottom sheet and
     must NOT get this sidebar treatment (it blocked the till on wide EPOS). */
  .store ~ .drawer, .store .drawer, body:has(.store) .drawer { left: auto; right: 0; top: 0; bottom: 0; width: 400px; max-width: 400px;
    height: 100%; max-height: none; border-radius: 0; border: none; border-left: 1px solid var(--line);
    transform: none; transition: none; z-index: 45; box-shadow: none; }
  body:has(.store) .drawer .drawer-top .x { display: none; }
  .askus { left: 24px; bottom: 24px; }
}
@media (min-width: 1500px) {
  .store { margin-right: 430px; }
  body:has(.store) .drawer { width: 430px; max-width: 430px; }
  .grid { grid-template-columns: repeat(auto-fill, minmax(210px, 1fr)); }
}

/* national delivery: unpostable-line chip */
.localonly { display: inline-block; vertical-align: 1px; margin-left: 6px; background: #FBEAEA; color: #B42318;
  border: 1px solid #F0C4C0; font-size: 10px; font-weight: 800; letter-spacing: .04em; text-transform: uppercase;
  padding: 2px 7px; border-radius: 999px; }

/* out-of-area address: informative, never a red shout */
.codinfo { background: #FFF8EC; border: 1px solid #F2E2C4; color: #7A5A18; border-radius: 12px;
  padding: 11px 14px; font-size: 13px; line-height: 1.5; }
.codinfo .linkbtn { display: inline; border: 0; background: none; padding: 0; color: #B4530A;
  font-weight: 800; font-size: 13px; text-decoration: underline; cursor: pointer; }

/* ---------- the butchers: cut-to-order ---------- */
.butcherband { position: relative; margin: 22px 16px 6px; border-radius: 22px; overflow: hidden;
  background: var(--ink-3, #14141A); min-height: 210px; display: flex; align-items: flex-start; }
.bb-img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }
.bb-scrim { position: absolute; inset: 0; background: linear-gradient(180deg, rgba(10,10,14,.88) 22%, rgba(10,10,14,.20) 82%); }
.bb-copy { position: relative; padding: 22px 20px; color: #fff; }
.bb-eyebrow { font-size: 10.5px; font-weight: 800; letter-spacing: .14em; color: #e0362c; }
.bb-copy h2 { font-family: var(--font-display); font-size: 26px; letter-spacing: -.02em; margin: 6px 0 4px; }
.bb-copy p { font-size: 13.5px; line-height: 1.5; color: rgba(255,255,255,.85); max-width: 420px; }
.bb-ctas { display: flex; gap: 10px; margin-top: 14px; flex-wrap: wrap; }
.bb-cta { border: 0; background: #fff; color: #14141A; font-weight: 800; font-size: 14px;
  padding: 11px 18px; border-radius: 999px; cursor: pointer; }
.bb-tt { display: inline-flex; align-items: center; gap: 8px; border: 1.5px solid rgba(255,255,255,.4);
  color: #fff; font-weight: 700; font-size: 13px; padding: 10px 16px; border-radius: 999px; text-decoration: none; }
.bb-tt svg { width: 15px; height: 15px; }
@media (min-width: 1120px) { .butcherband { margin: 26px 32px 8px; min-height: 250px; } }

/* butcher's prep chips (item sheet) */
.prepwrap { margin-top: 16px; border-top: 1px solid var(--line); padding-top: 14px; }
.preph { font-size: 12px; font-weight: 800; letter-spacing: .08em; text-transform: uppercase; color: var(--muted); margin-bottom: 10px; }
.prepg { display: flex; gap: 7px; flex-wrap: wrap; margin-bottom: 8px; }
.pchip { border: 1.5px solid var(--line); background: var(--surface); color: inherit; font-size: 13px;
  font-weight: 600; padding: 8px 13px; border-radius: 999px; cursor: pointer; transition: border-color .12s, background .12s; }
.pchip.on { border-color: var(--saffron); background: var(--saffron); color: var(--on-accent); font-weight: 700; }
.prepnotef { margin-top: 6px; }
.lprep { font-size: 12px; color: #B4530A; font-weight: 600; margin-top: 2px; }
.kc-prep { display: block; font-style: normal; font-size: 12px; color: #E8A33D; margin-top: 1px; }

/* allergen line on the item sheet — information, never decoration */
.allg { margin-top: 10px; background: #FFF8EC; border: 1px solid #F2E2C4; border-radius: 11px;
  padding: 10px 13px; font-size: 12.5px; line-height: 1.5; color: #7A5A18; }
.allg b { font-weight: 800; }
.allg span { color: #9A7A38; }
.allg.muted2 { background: var(--surface-2, #F4F4F6); border-color: var(--line); color: var(--muted); }

/* desktop: toggle + status pill share the row again (space exists) */
@media (min-width: 1120px) {
  .fulfil { flex-direction: row; align-items: center; justify-content: space-between; }
  .fulfil .toggle button { flex: none; }
}

/* offer urgency line on the sheet */
.offerends { margin-top: 4px; font-size: 12.5px; font-weight: 700; color: #B4530A; }

/* courier tracking chip on the national tracker */
.strk-trackref { margin-top: 12px; }
.strk-trackref a, .strk-trackref { font-size: 13px; font-weight: 700; color: var(--saffron); }
.strk-trackref a { text-decoration: underline; text-underline-offset: 3px; }

/* recent-search chips */
.rchips { display: flex; gap: 7px; flex-wrap: wrap; padding: 8px 2px 0; }
.rchip { border: 1px solid var(--line); background: var(--surface); color: var(--muted); font-size: 12.5px;
  font-weight: 600; padding: 6px 12px; border-radius: 999px; cursor: pointer; }
.rchip:active { background: var(--ink-3); }

/* points star-burst toast */
.startoast { background: #14141A; color: #fff; font-weight: 700; overflow: visible; }
.startoast .stars { position: absolute; inset: 0; pointer-events: none; }
.startoast .stars i { position: absolute; left: 50%; top: 50%; font-style: normal; color: #E8A33D;
  animation: starfly .9s ease-out forwards; }
.startoast .stars i:nth-child(1) { --sx: -60px; --sy: -34px; animation-delay: 0s; }
.startoast .stars i:nth-child(2) { --sx: 52px; --sy: -40px; animation-delay: .05s; }
.startoast .stars i:nth-child(3) { --sx: -30px; --sy: -52px; animation-delay: .1s; }
.startoast .stars i:nth-child(4) { --sx: 38px; --sy: -24px; animation-delay: .15s; }
.startoast .stars i:nth-child(5) { --sx: 0px; --sy: -58px; animation-delay: .2s; }
@keyframes starfly { from { transform: translate(0, 0) scale(.6); opacity: 1; } to { transform: translate(var(--sx), var(--sy)) scale(1.2); opacity: 0; } }
.lchip.pop { animation: chippop .8s cubic-bezier(.22,1.4,.4,1); }
@keyframes chippop { 0% { transform: scale(1); } 40% { transform: scale(1.55); } 100% { transform: scale(1); } }

/* skeleton shimmer grid cards */
.skel { border: 1px solid var(--line); border-radius: 18px; overflow: hidden; background: var(--surface); }
.skel .skimg { aspect-ratio: 1/1; }
.skel .skln { height: 12px; border-radius: 6px; margin: 10px 14px 0; }
.skel .skln.short { width: 55%; margin-bottom: 14px; }
.skel .skimg, .skel .skln { background: linear-gradient(100deg, var(--ink-3) 40%, var(--surface-2, #ECECEF) 50%, var(--ink-3) 60%); background-size: 200% 100%; animation: shimmer 1.4s infinite; }
@keyframes shimmer { to { background-position: -200% 0; } }
@media (prefers-reduced-motion: reduce) { .skel .skimg, .skel .skln, .startoast .stars i, .lchip.pop { animation: none; } }

/* tracker: delivery-updates opt-in */
.strk-notify { width: 100%; border: 1.5px solid var(--line); background: var(--surface); color: inherit;
  font-weight: 700; font-size: 14px; padding: 12px 16px; border-radius: 13px; cursor: pointer; margin-bottom: 10px; }
.strk-notify:disabled { opacity: .75; }

/* ---------- account sheet: orders, receipt, issue chat ---------- */
.acct-h { font-size: 12px; font-weight: 800; letter-spacing: .08em; text-transform: uppercase; color: var(--muted); padding: 14px 20px 6px; }
.acct-orders { padding: 0 14px; display: flex; flex-direction: column; gap: 8px; }
.acct-order { display: flex; justify-content: space-between; align-items: center; width: 100%; border: 1px solid var(--line);
  background: var(--surface); border-radius: 13px; padding: 11px 14px; cursor: pointer; font-size: 13.5px; color: inherit; }
.acct-order .ao-l, .acct-order .ao-r { display: flex; flex-direction: column; gap: 2px; }
.acct-order .ao-l { align-items: flex-start; } .acct-order .ao-r { align-items: flex-end; }
.acct-order i { font-style: normal; font-size: 11.5px; color: var(--muted); }
.acct-order i.st-dispatched, .acct-order i.st-paid, .acct-order i.st-picking { color: #B4530A; font-weight: 700; }
.acct-order i.st-collected { color: #17924E; font-weight: 700; }
.acct-help { margin: 14px 20px 20px; width: calc(100% - 40px); border: 1.5px solid var(--line); background: var(--surface);
  font-weight: 700; font-size: 14px; padding: 13px 16px; border-radius: 13px; cursor: pointer; color: inherit; }
.rcpt-lines { padding: 6px 20px 10px; border-bottom: 1px solid var(--line); margin-bottom: 8px; }
.rcpt-line { display: flex; justify-content: space-between; gap: 12px; padding: 7px 0; font-size: 13.5px; }
.rcpt-line .lprep { display: block; }
.sheet-body .srow { padding: 4px 20px; }
.sheet-body .srow.tot { padding-top: 8px; }
.btnrow { display: block; margin: 12px 20px 0; width: calc(100% - 40px); border: 0; background: var(--saffron); color: var(--on-accent);
  font-weight: 800; font-size: 14.5px; padding: 13px 16px; border-radius: 13px; cursor: pointer; }
.btnrow.secondary { background: var(--surface); border: 1.5px solid var(--line); color: inherit; margin-bottom: 20px; }
.chat-state { padding: 8px 20px; font-size: 12.5px; font-weight: 700; color: var(--muted); background: var(--ink-3); }
.chat-state.live { color: #17924E; }
.chat-log2 { display: flex; flex-direction: column; gap: 8px; padding: 14px 16px; min-height: 200px; }
.smsg { max-width: 82%; padding: 10px 14px; border-radius: 15px; font-size: 14px; line-height: 1.45; white-space: pre-wrap; }
.smsg.customer { align-self: flex-end; background: var(--saffron); color: var(--on-accent); border-bottom-right-radius: 5px; }
.smsg.staff { align-self: flex-start; background: var(--ink-3); border-bottom-left-radius: 5px; }
.chat-compose { display: flex; gap: 8px; padding: 10px 14px calc(12px + env(safe-area-inset-bottom)); border-top: 1px solid var(--line); }
.chat-compose .ginput { flex: 1; }
.csend { border: 0; background: var(--saffron); color: var(--on-accent); width: 46px; border-radius: 12px; font-size: 16px; cursor: pointer; }

/* owner: support inbox */
.srow-thread { border: 1px solid var(--line); border-radius: 12px; padding: 11px 14px; margin-bottom: 8px; cursor: pointer; }
.srow-thread.awaiting { border-color: #E8A33D; background: #FFF8EC; }
.st-top { font-size: 13.5px; } .st-await { color: #B4530A; font-weight: 800; font-size: 11px; text-transform: uppercase; letter-spacing: .05em; }
.st-last { font-size: 12.5px; color: var(--muted); margin-top: 3px; }
.st-msgs { display: flex; flex-direction: column; gap: 6px; margin-top: 10px; max-height: 240px; overflow-y: auto; overscroll-behavior: contain; }

/* sign-in polish: star busy + passkey offer */
.iconbtn.busy svg { animation: starspin 1s linear infinite; opacity: .6; }
@keyframes starspin { to { transform: rotate(360deg); } }
/* Face ID enrol prompt — a proper bottom banner card, NOT the pill's 999px
 * radius (which balloons into a black ellipse once the text wraps). Title on
 * its own row, the two actions sharing the row beneath it. */
.pk-offer { display: flex; flex-wrap: wrap; align-items: center; gap: 10px;
  border-radius: 18px; padding: 15px 17px; bottom: 24px;
  max-width: min(420px, calc(100vw - 28px)); text-align: left; }
.pk-offer b { flex: 1 1 100%; font-size: 14.5px; font-weight: 700; line-height: 1.3; }
.pk-offer button { flex: 1; border: 0; border-radius: 12px; padding: 11px 14px; font-weight: 700; font-size: 13.5px; cursor: pointer; }
.pk-offer #pkyes { background: var(--saffron); color: var(--on-accent); }
.pk-offer #pkno { background: rgba(255,255,255,.16); color: #fff; }


/* ops apps clear the fixed selector */
/* Clear the fixed bottom bar. 84px left the last card tucked under it on a phone
   with a home-indicator inset; the storefront already uses 110px for the same job. */
.ownerscroll, main.wrap { padding-bottom: calc(112px + env(safe-area-inset-bottom)); }
/* web: centre the ops shell like a phone frame on big screens */
@media (min-width: 700px) { main.wrap:not(.till) { max-width: 640px; margin-left: auto; margin-right: auto; } }


/* profile menu (ops apps) */
.app { position: relative; }
.profbtn { width: 40px; height: 40px; border-radius: 50%; border: 1px solid var(--line); background: var(--surface);
  color: var(--text, #1a1a1f); display: grid; place-items: center; cursor: pointer; }
.profbtn svg { width: 21px; height: 21px; }
/* gap, not adjacency: with none, the round buttons sat flush against the
   station name and each other — "Butchers" read half-covered on the iPad. */
.hbtns { display: flex; align-items: center; gap: 8px; }
.fshead { margin-right: 8px; }
.profmenu { position: fixed; top: calc(58px + env(safe-area-inset-top)); right: 14px; z-index: 60;
  background: var(--surface, #fff); border: 1px solid var(--line); border-radius: 14px; box-shadow: 0 18px 50px -14px rgba(16,17,33,.28);
  padding: 14px; display: flex; flex-direction: column; gap: 10px; min-width: 180px; }
.profmenu.hidden { display: none; }
.pm-name { font-weight: 700; font-size: 14px; }
/* pass identity: every signed-in menu names the person AND their pass type */
.passchip { display: inline-flex; align-items: center; justify-content: center; font-size: 10.5px;
  font-weight: 800; letter-spacing: .04em; text-transform: uppercase; padding: 2px 8px;
  border-radius: 999px; vertical-align: 1px; }
.passchip.pass-owner { background: rgba(212,160,60,.2); color: #b7791f; }
.passchip.pass-staff { background: rgba(59,130,246,.16); color: #2563eb; }
.passchip.pass-driver { background: rgba(16,185,129,.16); color: #059669; }
.passchip.pass-operator { background: rgba(100,116,139,.18); color: #475569; }
/* team sheet: initial disc reuses the same liveries */
.orow2 .passchip { width: 30px; height: 30px; padding: 0; border-radius: 999px; font-size: 13px; margin-right: 10px; flex: none; }
.oview[hidden] { display: none; }
.oview { animation: viewfade .18s ease; }
@keyframes viewfade { from { opacity: .4; transform: translateY(4px); } to { opacity: 1; transform: none; } }


/* goods-in: scan / upload options */
.gi-hero { background: var(--surface, #fff); border: 1px solid var(--line); border-radius: 20px; padding: 26px 20px; text-align: center; }
.gi-title { font-family: var(--font-display, inherit); font-size: 20px; font-weight: 700; letter-spacing: -.01em; }
.gi-sub { color: var(--muted, #63636b); font-size: 13.5px; line-height: 1.5; margin: 6px auto 18px; max-width: 320px; }
.gi-actions { display: flex; gap: 10px; }
.gi-btn { flex: 1; display: flex; flex-direction: column; align-items: center; gap: 8px; padding: 18px 10px; border-radius: 15px;
  border: 1.5px solid var(--line); background: var(--surface, #fff); color: inherit; font-weight: 700; font-size: 14px; cursor: pointer; transition: border-color .15s, transform .1s; }
.gi-btn svg { width: 30px; height: 30px; }
.gi-btn:active { transform: scale(.97); }
.gi-btn.primary { background: var(--accent, #E2000A); color: #fff; border-color: var(--accent, #E2000A); }


/* analytics breakdown toggle */
.brk-head { display: flex; align-items: center; justify-content: space-between; gap: 10px; }
.brk-t { font-size: 12px; font-weight: 800; letter-spacing: .06em; text-transform: uppercase; color: var(--muted); }
.seg { display: inline-flex; background: var(--ink-3, #f0f0f3); border-radius: 999px; padding: 3px; }
.segbtn { border: 0; background: none; padding: 6px 12px; border-radius: 999px; font-size: 12.5px; font-weight: 700; color: var(--muted); cursor: pointer; }
.segbtn.active { background: var(--surface, #fff); color: var(--accent, #E2000A); box-shadow: 0 1px 3px rgba(16,17,33,.12); }

.gi-drop { margin-top: 14px; font-size: 12px; color: var(--muted); }
.gi-hero.dragging { border-color: var(--accent, #E2000A); background: #FEF6F6; box-shadow: 0 0 0 3px rgba(226,0,10,.12); }
.gi-hero { transition: border-color .15s, background .15s, box-shadow .15s; }

/* per-category delivery uplift — one card per department, with that department's
   own worked lines and its own Deliveroo cash warning */
.upl-cat { border: 1px solid var(--line, #E7E7EC); border-radius: 12px; padding: 10px 12px; margin-top: 8px; }
.upl-cat-hd { display: flex; align-items: center; gap: 10px; margin-bottom: 6px; }
.upl-cat .field { margin-bottom: 0; }
.upl-cat .btn-row { gap: 8px; }
.upl-eg { font-size: 12.5px; color: var(--muted); margin: 6px 0 0; }
.upl-warn { font-size: 12.5px; color: #c0392b; margin: 6px 0 0; }
.upl-pct, .upl-cap { width: 100%; }

/* driver paid/cash badge — unmissable */
.paybadge { display: flex; align-items: center; gap: 6px; font-size: 13px; font-weight: 600; padding: 9px 12px; border-radius: 11px; margin-top: 8px; }
.paybadge b { font-weight: 800; }
.paybadge.cash { background: #FFF4E5; color: #9A5B00; border: 1px solid #F3D9A8; }
.paybadge.paid { background: #F0FAF4; color: #17924E; border: 1px solid #CDEBD9; }

/* owner order-detail (god-mode) sheet */
.orow { cursor: pointer; } .orow .chev { width: 18px; height: 18px; color: var(--muted-2, #b0b0b8); flex: none; }
.oscrim { position: fixed; inset: 0; background: rgba(10,10,14,.4); z-index: 62; opacity: 0; pointer-events: none; transition: opacity .2s; }
.oscrim.show { opacity: 1; pointer-events: auto; }
.osheet { position: fixed; left: 50%; bottom: 0; transform: translate(-50%, 102%); width: 100%; max-width: 560px; z-index: 63;
  background: var(--surface, #fff); border-radius: 22px 22px 0 0; box-shadow: var(--sh-lg, 0 -10px 50px -12px rgba(16,17,33,.3));
  /* --kb = live keyboard height (ui-sheet.js) — the sheet yields to the keys */
  max-height: 88vh; max-height: max(35dvh, calc(88dvh - var(--kb, 0px)));
  display: flex; flex-direction: column; transition: transform .32s cubic-bezier(.22,1,.32,1); }
.osheet.open { transform: translate(-50%, 0); }
.osheet-top { display: flex; align-items: center; gap: 10px; padding: 16px 18px; border-bottom: 1px solid var(--line); }
.osheet-top b { font-size: 17px; } .osheet-top .x { margin-left: auto; background: none; border: 0; font-size: 20px; color: var(--muted); cursor: pointer; }
.osheet-body { overflow-y: auto; padding: 4px 18px; flex: 1; overscroll-behavior: contain; }
.osheet-meta { padding: 12px 0; color: var(--muted); font-size: 13.5px; line-height: 1.6; border-bottom: 1px solid var(--line); }
.orow2 { display: flex; align-items: center; gap: 10px; padding: 12px 0; border-bottom: 1px solid var(--line); }
.orow2 .name { font-size: 14px; } .orow2 .lprep { display: block; color: #B4530A; font-size: 12px; }
.osheet-foot { padding: 14px 18px calc(16px + env(safe-area-inset-bottom)); display: flex; flex-direction: column; gap: 8px; border-top: 1px solid var(--line); }

/* KDS station picker + waiting chip */
.stationpick { display: flex; gap: 12px; }
.stbtn { flex: 1; display: flex; flex-direction: column; align-items: center; gap: 4px; padding: 26px 14px; border-radius: 18px;
  border: 1.5px solid var(--line); background: var(--surface, #fff); color: inherit; cursor: pointer; transition: border-color .15s, transform .1s; }
.stbtn:active { transform: scale(.97); }
.stbtn .stico { display: grid; place-items: center; height: 44px; }
.stbtn .stico svg { width: 40px; height: 40px; }
.stbtn b { font-size: 17px; } .stbtn i { font-style: normal; font-size: 12.5px; color: var(--muted); }
.kc-chip.wait { background: rgba(232,163,61,.18); color: #E8A33D; }

/* staff picking cards */
.pkcard { border: 1px solid var(--line); border-radius: 15px; padding: 14px; margin-bottom: 10px; }
.pk-top { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; font-size: 14px; }
.pk-lines { margin: 10px 0; display: flex; flex-direction: column; gap: 5px; font-size: 14px; }
.pk-line .lprep { display: block; color: #B4530A; font-size: 12px; }

/* Picking thumbnails — a picker matches a photo faster than a name, and 2,000
   lines of Asian groceries contain a lot of near-identical sacks. Sized for a
   thumb-held tablet in a cold aisle, not for a desk. */
.pk-line { display: flex; align-items: center; gap: 10px; }
.pk-txt { flex: 1; min-width: 0; }
.pk-img {
  flex: none; width: 46px; height: 46px; border-radius: 8px; object-fit: cover;
  background: rgba(127,127,127,.10); border: 1px solid rgba(127,127,127,.16);
}
/* No photo yet — the initial still gives the eye something stable to lock onto,
   and keeps every row the same height so the list does not jump as images load. */
.pk-img-none {
  display: grid; place-items: center; font-weight: 700; font-size: 17px;
  color: #8a8a8a; letter-spacing: -.02em;
}
.pkcard .btn { width: 100%; }
/**
 * The picking card's action row. `.pkcard .btn` is width:100% for the
 * single-button case, which two buttons in a flex row would each try to
 * honour — so the row resets it and lets flex do the sizing. The primary
 * action keeps twice the width: on a wall-mounted screen, "done" and
 * "reprint" being the same size is how a picker hits the wrong one.
 */
.pk-actions { display: flex; gap: 8px; align-items: stretch; }
.pk-actions .btn { width: auto; flex: 1 1 0; margin: 0; }
.pk-actions .btn:first-child { flex: 2 1 0; }

/* team-push enable banner — pinned at the top, dismisses on grant */
.pushbanner { display: flex; align-items: center; gap: 10px; justify-content: space-between;
  background: #1c1c1e; color: #fff; border-radius: 14px; padding: 10px 14px; margin: 4px 0 12px;
  font-size: 13.5px; font-weight: 600; }
.pushbanner .btn { flex: 0 0 auto; }

/* tracker: receipt-grade order screen */
.strk-ref { font-size: 13px; color: var(--muted-2, #888); margin: 2px 0 6px; }
.strk-ref b { font-size: 16px; color: inherit; }
.strk-paid.cashdue { background: #fff4e5; color: #B4530A; }
.strk-rcpt { width: 100%; max-width: 420px; margin: 12px auto 0; text-align: left;
  background: rgba(127,127,127,.07); border-radius: 14px; padding: 10px 14px; }
.strk-rcpt summary { cursor: pointer; font-weight: 700; font-size: 14px; list-style: none; }
.strk-rcpt summary::-webkit-details-marker { display: none; }
.strk-rcpt summary::after { content: '▾'; float: right; opacity: .55; }
.strk-rcpt[open] summary::after { content: '▴'; }
.rc-lines { margin-top: 10px; }
.rc-line { display: flex; align-items: center; gap: 8px; padding: 5px 0; font-size: 13.5px; }
.rc-line img { width: 28px; height: 28px; border-radius: 6px; object-fit: cover; }
.rc-line .nm { flex: 1; }
.rc-tot { display: flex; justify-content: space-between; font-size: 13px; padding: 3px 0; color: var(--muted-2, #777); }
.rc-tot.save { color: #1f7a4d; }
.rc-tot.grand { font-weight: 800; font-size: 15px; color: inherit; border-top: 1px solid rgba(127,127,127,.25); margin-top: 6px; padding-top: 8px; }
.strk-call { display: inline-block; margin-top: 10px; font-weight: 700; font-size: 14px; text-decoration: none;
  color: inherit; border: 1.5px solid rgba(127,127,127,.35); border-radius: 999px; padding: 8px 18px; }

/* the kind review ask — one nudge, never needy */
.strk-review { max-width: 420px; margin: 14px auto 0; text-align: center;
  background: rgba(127,127,127,.07); border-radius: 16px; padding: 14px 18px; }
.rv-stars { color: #f5a623; font-size: 18px; letter-spacing: 3px; }
.rv-copy { font-size: 13.5px; margin: 8px 0 12px; color: inherit; opacity: .85; }
.rv-actions { display: flex; gap: 12px; justify-content: center; align-items: center; }
.rv-later { border: 0; background: none; color: var(--muted-2, #888); font-size: 13px; cursor: pointer; text-decoration: underline; }

/* Order search — the owner's way back to a customer on the phone. */
.osearch { width: 100%; margin: 2px 0 12px; }
.oref { font-variant-numeric: tabular-nums; letter-spacing: .01em; }
#olist[aria-busy="true"] { opacity: .55; transition: opacity .12s; }

/* ---- printer setup: status chips, a copyable credential, a checkbox row ----
   Three classes, on the existing token set. `st-*` reuses .chip rather than
   inventing a second badge, so a printer's state reads the same as every other
   status in the app. */
.chip.st-good { background: var(--good-bg); color: var(--good);
  border-color: color-mix(in srgb, var(--good) 35%, transparent); }
.chip.st-warn { background: color-mix(in srgb, var(--warn) 12%, transparent); color: var(--warn);
  border-color: color-mix(in srgb, var(--warn) 35%, transparent); }
.chip.st-bad  { background: var(--bad-bg); color: var(--bad);
  border-color: color-mix(in srgb, var(--bad) 35%, transparent); }

.notebox { border: 1px solid var(--line); border-radius: 10px; padding: 10px 12px;
  font-size: var(--fs-sm); line-height: 1.5; background: var(--ink-3); }
.notebox.warn { background: color-mix(in srgb, var(--warn) 10%, transparent);
  border-color: color-mix(in srgb, var(--warn) 30%, transparent); }

/* A credential shown once. Monospace so an O and a 0 are distinguishable when
   someone is retyping it into a printer's settings page by hand. */
.codebox { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 12px;
  line-height: 1.6; background: var(--ink-3); border: 1px solid var(--line);
  border-radius: 10px; padding: 10px 12px; }

.chkrow { display: flex; align-items: center; gap: 10px; margin-top: 10px;
  font-size: var(--fs-sm); cursor: pointer; }
.chkrow input { width: 18px; height: 18px; flex: none; accent-color: var(--accent, #E2000A); }

/* ---- integrations: a group heading and one status dot per row.
   A DOT, not a coloured pill per row: nine pills is a fairground, and the eye
   stops finding the one that matters. Only 'needs you' is warm-coloured — an
   integration the shop has chosen not to use is not a fault and must not read
   like one, or the screen becomes a nag and the real problem hides in it. */
.intgroup { font-size: 12px; font-weight: 600; letter-spacing: .04em; text-transform: uppercase;
  color: var(--muted); margin: 18px 0 6px; }
.intgroup:first-child { margin-top: 4px; }
.stdot { width: 9px; height: 9px; border-radius: 50%; flex: none; margin: 6px 10px 0 0;
  background: var(--line-2, #D6D6DC); }
.stdot.st-good { background: var(--good); }
.stdot.st-bad  { background: var(--bad); }
.stdot.st-warn { background: var(--warn); }
.stdot.st-idle { background: var(--line-2, #D6D6DC); }

/* ---- the bell ---- */
.bellbtn { position:relative; background:none; border:0; font-size:19px; line-height:1;
  padding:6px 8px; cursor:pointer; }
.bellbadge { position:absolute; top:0; right:0; min-width:17px; height:17px; padding:0 4px;
  border-radius:999px; background:var(--bad); color:#fff; font:700 10px/17px ui-sans-serif,system-ui;
  text-align:center; }

/* ---- two channels, DELIBERATELY not alike ------------------------------------
   Visual consistency is the wrong goal here. Two message lists that look the
   same, one tap apart, is how a staff note reaches a customer. The tint, the
   lock line and the different send colour all exist to make the mistake feel
   wrong before it is made. */
.chanswitch { display:flex; gap:6px; padding:10px 16px 0; }
.chan { flex:1; border:1px solid var(--line); background:var(--surface); border-radius:9px;
  padding:9px 10px; font:600 13px ui-sans-serif,system-ui; color:var(--muted); cursor:pointer; }
.chan.active { background:var(--text); color:var(--surface); border-color:var(--text); }
.chanbadge { display:inline-block; min-width:16px; height:16px; margin-left:5px; padding:0 4px;
  border-radius:999px; background:var(--bad); color:#fff; font:700 10px/16px ui-sans-serif,system-ui; }
/* 🚨 An author `display` beats the UA's [hidden]{display:none}, so the badge
   ignored its own hidden attribute and every shop carried a permanently red,
   permanently EMPTY dot on the Team tab. A badge that is always red is a badge
   nobody reads, and then the one message that mattered goes unseen. */
.chanbadge[hidden] { display:none; }

.teampane { margin:12px 16px; border:1px solid #cfd8e6; border-radius:12px; overflow:hidden;
  background:#f4f7fc; }
.teamnote { background:#e6edf8; color:#25405f; font-size:12.5px; padding:9px 13px;
  border-bottom:1px solid #cfd8e6; }
.pinned { padding:8px 13px; border-bottom:1px solid #cfd8e6; background:#fffbe9; }
.pinrow { display:flex; gap:8px; align-items:baseline; font-size:13.5px; margin:3px 0; }
.pinrow small { color:var(--muted); margin-left:auto; flex:none; }
.teamlog { max-height:46vh; overflow:auto; padding:12px 13px; }

/* Empty channel — the first thing every shop sees, so it explains itself
   rather than reading as a page that failed to load. */
.teamempty { text-align:center; padding:26px 16px 30px; }
.teamempty-ic { font-size:30px; line-height:1; margin-bottom:10px; opacity:.85; }
.teamempty h3 { margin:0 0 5px; font-size:16px; font-weight:700; }
.teamempty p { margin:0 auto; max-width:30ch; font-size:13.5px; line-height:1.5; color:var(--muted,#6b7280); }
.teamempty-eg { list-style:none; margin:16px auto 0; padding:0; max-width:34ch;
  display:grid; gap:7px; text-align:left; }
.teamempty-eg li { font-size:12.5px; line-height:1.45; color:var(--muted,#6b7280);
  background:rgba(127,127,127,.09); border-radius:9px; padding:8px 11px; }
.tmsg { max-width:82%; margin-bottom:11px; }
.tmsg.me { margin-left:auto; text-align:right; }
.twho { font-size:11px; font-weight:700; color:#25405f; margin-bottom:2px; }
.tbody { display:inline-block; text-align:left; background:#fff; border:1px solid #d8e1ee;
  border-radius:12px; padding:8px 11px; font-size:14px; white-space:pre-wrap; word-break:break-word; }
.tmsg.me .tbody { background:#25405f; color:#fff; border-color:#25405f; }
.twhen { font-size:10.5px; color:var(--muted); margin-top:3px; }
/* Stacked, not side by side. As a flex row the textarea hit its min width and
   the button pushed straight out of the panel — visible on the owner screen at
   full desktop width, never mind a phone. Every phone chat stacks for a reason. */
.teamcompose { display:flex; flex-direction:column; gap:8px; padding:10px 13px;
  border-top:1px solid #cfd8e6; background:#fff; }
.teamcompose textarea { width:100%; }
.btn.teamsend { width:100%; }
.teamcompose textarea { flex:1; border:1px solid var(--line); border-radius:9px; padding:8px 10px;
  font:inherit; font-size:14px; resize:none; }
.btn.teamsend { background:#25405f; color:#fff; border-color:#25405f; flex:none; }

/* ---- waste: the split that matters, and a form usable with cold hands ---- */
.wsplit { display:grid; grid-template-columns:1fr 1fr; gap:10px; margin:10px 0 6px; }
.wstat { border:1px solid var(--line); border-radius:10px; padding:11px 13px; background:var(--ink-3); }
.wstat b { display:block; font-size:22px; letter-spacing:-.02em; }
.wstat span { display:block; font-size:12.5px; font-weight:600; }
.wstat small { display:block; font-size:11.5px; color:var(--muted); margin-top:3px; line-height:1.4; }
.wtable { width:100%; border-collapse:collapse; margin-top:10px; font-size:13.5px; }
.wtable td { padding:6px 0; border-bottom:1px solid var(--line); }
.wtable td:last-child, .wtable td:nth-child(3) { text-align:right; white-space:nowrap; }

.lblish { display:block; font-size:13px; font-weight:600; color:var(--muted); margin:12px 0 5px; }
/* Six big targets. A dropdown at a chiller with cold wet hands is a log nobody keeps. */
.wreasons { display:grid; grid-template-columns:1fr 1fr; gap:7px; }
.wreason { display:flex; align-items:center; gap:7px; border:1px solid var(--line);
  background:var(--surface); border-radius:10px; padding:12px 11px; font:600 13px ui-sans-serif,system-ui;
  color:var(--text); cursor:pointer; text-align:left; }
.wreason.on { border-color:var(--bad); background:var(--bad-bg); color:var(--bad); }
.wr-ic { font-size:15px; }
.wresults { display:flex; flex-direction:column; gap:4px; }
.wpick { text-align:left; border:1px solid var(--line); background:var(--surface); border-radius:9px;
  padding:9px 11px; font:inherit; font-size:13.5px; cursor:pointer; }
.wpick small { color:var(--muted); float:right; }

/* ---- tilld.ai: the private-release landing ------------------------------------
   Not a SaaS template. The product's strength is one sentence, so the page is
   built around it and the form sits IN the hero — a shopkeeper arriving from a
   conversation already knows they want it, and making them scroll past three
   sections of persuasion is friction applied to the likeliest customer. */
.lp-top { display:flex; align-items:center; justify-content:space-between;
  padding:20px 22px; max-width:940px; margin:0 auto; }
.lp-logo { height:26px; }
.lp-status { font-size:11px; font-weight:700; letter-spacing:.08em; text-transform:uppercase;
  color:var(--muted); border:1px solid var(--line); border-radius:999px; padding:5px 11px; }
.lp { max-width:940px; margin:0 auto; padding:0 22px 60px; }
.lp-hero { padding:46px 0 34px; }
.lp-hero h1 { font-size:clamp(30px,6.2vw,52px); line-height:1.06; letter-spacing:-.035em;
  margin:0 0 16px; font-family:var(--font-display,inherit); }
.lp-hero h1 em { font-style:normal; color:var(--accent,#E2000A); }
.lp-sub { font-size:clamp(15px,2.2vw,18px); line-height:1.6; color:var(--muted); max-width:38em; margin:0 0 26px; }
.lp-form { max-width:32em; }
.lp-row { display:flex; gap:8px; margin-bottom:8px; }
.lp-in { flex:1; min-width:0; border:1px solid var(--line); border-radius:11px; padding:14px 15px;
  font:inherit; font-size:16px; background:var(--surface); }
.lp-form > .lp-in { width:100%; margin-bottom:8px; }
.lp-in:focus { outline:2px solid var(--accent,#E2000A); outline-offset:1px; border-color:transparent; }
.lp-go { flex:none; border:0; border-radius:11px; background:var(--accent,#E2000A); color:#fff;
  font:700 15px ui-sans-serif,system-ui; padding:14px 20px; cursor:pointer; }
.lp-go[disabled] { opacity:.6; cursor:default; }
.lp-go-wide { display:inline-block; text-decoration:none; margin-top:14px; }
/* The honeypot. Off-screen rather than display:none — some bots skip hidden
   fields, and none of them scroll. */
.lp-hp { position:absolute; left:-9999px; width:1px; height:1px; opacity:0; }
.lp-note { font-size:13px; color:var(--muted); margin:8px 0 0; line-height:1.5; }
.lp-note.good { color:var(--good); font-weight:600; }
.lp-note.bad { color:var(--bad); font-weight:600; }

.lp-loop, .lp-three, .lp-honest { padding:34px 0; border-top:1px solid var(--line); }
.lp h2 { font-size:20px; letter-spacing:-.02em; margin:0 0 14px; }
.lp-steps { list-style:none; padding:0; margin:0; display:grid; gap:14px; }
.lp-steps li { padding-left:0; line-height:1.6; font-size:15px; }
.lp-steps b { color:var(--text); }
.lp-three { display:grid; grid-template-columns:repeat(auto-fit,minmax(230px,1fr)); gap:18px; }
.lp-card h3 { font-size:15px; margin:0 0 6px; color:var(--accent,#E2000A); }
.lp-card p { font-size:14px; line-height:1.6; color:var(--muted); margin:0; }
.lp-honest p { font-size:15px; line-height:1.65; max-width:44em; }
.lp-foot { border-top:1px solid var(--line); padding:24px 22px 40px; max-width:940px; margin:0 auto;
  font-size:13px; color:var(--muted); }
.lp-foot a { color:var(--muted); }

/* ---- legal pages ---- */
.legal { padding:34px 0 60px; }
.legal h1 { font-size:30px; letter-spacing:-.03em; margin:0 0 4px; }
.legal h2 { font-size:18px; margin:30px 0 8px; letter-spacing:-.015em; }
.legal h3 { font-size:15px; margin:20px 0 6px; color:var(--accent,#E2000A); }
.legal p, .legal li { line-height:1.7; font-size:15px; }
.legal table { width:100%; border-collapse:collapse; margin:14px 0; font-size:14px; }
.legal th { text-align:left; padding:8px 10px; border-bottom:2px solid var(--line);
  font-size:12px; text-transform:uppercase; letter-spacing:.05em; color:var(--muted); }
.legal td { padding:9px 10px; border-bottom:1px solid var(--line); vertical-align:top; }
.legal .small { font-size:13px; }
/* Impossible to miss, impossible to ship by accident. */
.legal-draft { border:2px solid var(--warn); background:color-mix(in srgb, var(--warn) 12%, transparent);
  border-radius:10px; padding:12px 14px; margin-bottom:22px; font-size:14px; line-height:1.55; }
.legal-note { border:1px solid var(--line); background:var(--ink-3); border-radius:10px;
  padding:12px 14px; margin-bottom:20px; font-size:14px; line-height:1.55; }

/* 🚨 MOBILE OVERFLOW — found by screenshotting the live page, not by testing.
   .lp-row is a flex row of a growing input and a flex:none button. Below about
   520px the input's intrinsic minimum plus the button exceeds the viewport, the
   row forces the whole page wider than the screen, and EVERYTHING clips — the
   "Join the list" button was half off-screen and the body copy was cut mid-word.
   On the one page whose only job is that button, on the device shopkeepers
   actually use.
   Stack it instead: a full-width button under a full-width input is bigger,
   easier to hit with a thumb, and cannot overflow. */
@media (max-width: 520px) {
  .lp-row { flex-direction: column; }
  .lp-go { width: 100%; padding: 15px 20px; }
  .lp-hero h1 { font-size: clamp(27px, 8vw, 40px); }
}
/* Belt and braces: nothing on this page may ever cause a sideways scroll. */
.lp, .lp-top, .lp-foot { max-width: 100%; overflow-wrap: break-word; }
/* 🚨 `clip`, NEVER `hidden`, on html/body. `overflow-x: hidden` here (added for
   the landing page, scoped to the whole document) made iOS Safari treat <body>
   as the scroll container, and `nav.tabs` — position: fixed — then scrolled WITH
   the page and sat mid-screen with content underneath it. The comment at the
   top of this file already records that exact symptom from a different cause.
   `clip` refuses sideways overflow without creating a scroll container. */
html, body { overflow-x: clip; }

/* ── shop-front banner editor ──────────────────────────────────────────────
   A banner is a picture with words on it, so the row leads with the picture.
   A list of filenames tells a shopkeeper nothing about what customers see. */
.herolist { display: grid; gap: 12px; margin-top: 12px; }
.herorow {
  display: grid; grid-template-columns: 108px 1fr; gap: 12px; align-items: start;
  background: var(--card, #fff); border: 1px solid rgba(127,127,127,.18);
  border-radius: 12px; padding: 11px;
}
.heroth {
  width: 108px; height: 72px; object-fit: cover; border-radius: 8px;
  background: rgba(127,127,127,.10);
}
.herofields { display: grid; gap: 7px; min-width: 0; }
.herofields .text { width: 100%; }
.heroacts { grid-column: 1 / -1; display: flex; gap: 7px; justify-content: flex-end; }
@media (max-width: 460px) {
  .herorow { grid-template-columns: 84px 1fr; }
  .heroth { width: 84px; height: 60px; }
}

/* ── banner editor: the controls ───────────────────────────────────────────
   The empty state IS the upload button — "add your first one below" made
   people hunt for a control that was smaller than the sentence describing it. */
.herodrop {
  display: grid; place-items: center; gap: 4px; text-align: center;
  padding: 30px 18px; cursor: pointer;
  border: 2px dashed rgba(127,127,127,.34); border-radius: 14px;
  background: rgba(127,127,127,.04); transition: border-color .15s, background .15s;
}
.herodrop:hover, .herodrop:focus-within { border-color: var(--accent-strong, #9E0007); background: rgba(127,127,127,.08); }
.herodrop-ic { font-size: 28px; line-height: 1; }
.herodrop-t { font-weight: 700; font-size: 16px; }
.herodrop-s { font-size: 13px; color: var(--muted, #6b7280); max-width: 34ch; }

.heroadd { display: block; width: 100%; text-align: center; margin-top: 12px; cursor: pointer; }
.herobar { margin-top: 14px; }
.herosave { display: block; width: 100%; }
.herohint { text-align: center; font-size: 12.5px; margin: 7px 0 0; }
.herocount { font-size: 13px; color: var(--muted, #6b7280); margin: 10px 0 0; }
.herocount b { color: var(--ink, #1b1214); }

/* Hidden banners stay visible to the OWNER but read as switched off — a shop
   keeps last Eid's banner and hides it rather than deleting and re-uploading. */
.herorow.is-off { opacity: .55; }
.herorow.is-off .heroth { filter: grayscale(1); }
.heroacts { align-items: center; }
.herospacer { flex: 1; }
.heroshow { font-weight: 700; }
.heroshow.on { color: #2E6A4E; border-color: rgba(46,106,78,.4); }

/* ── the picture picker ─────────────────────────────────────────────────────
   The shop's own catalogue comes first: 2,000 photos they already own beat a
   blurry phone shot of a shelf, and beat stock photography on relevance. */
/* 🚨 ABOVE .osheet (63) and its scrim (62), not below them. The picker is
   opened FROM the banner sheet, so it is always the newer thing on screen —
   at z-index 60 it opened behind the sheet that launched it, leaving the
   product grid visible but untouchable underneath. */
.pscrim { z-index: 64; }
.psheet {
  position: fixed; left: 50%; bottom: 0; transform: translate(-50%, 100%);
  width: min(680px, 100%); max-height: 88vh; z-index: 65;
  background: var(--card, #fff); border-radius: 18px 18px 0 0;
  box-shadow: 0 -12px 40px rgba(0,0,0,.24);
  display: flex; flex-direction: column; transition: transform .22s ease;
}
.psheet.open { transform: translate(-50%, 0); }
@media (prefers-reduced-motion: reduce) { .psheet { transition: none; } }

.pktabs { display: flex; gap: 6px; padding: 0 14px 10px; border-bottom: 1px solid rgba(127,127,127,.16); }
.pktab {
  flex: 1; text-align: center; padding: 9px 6px; border-radius: 9px; cursor: pointer;
  border: 1px solid rgba(127,127,127,.22); background: transparent;
  font: inherit; font-size: 13.5px; font-weight: 600; color: var(--muted, #6b7280);
}
.pktab.on { background: var(--accent-strong, #9E0007); border-color: var(--accent-strong, #9E0007); color: #fff; }
.pktab.pkup { flex: none; padding-inline: 14px; }

.pksearch { padding: 11px 14px 4px; }
.pksearch .text { width: 100%; }
.pkbody { overflow: auto; padding: 12px 14px 24px; }
.pk-note { text-align: center; padding: 26px 10px; }

.pkgrid { display: grid; grid-template-columns: repeat(auto-fill, minmax(104px, 1fr)); gap: 10px; }
.pkcell {
  padding: 0; border: 1px solid rgba(127,127,127,.2); border-radius: 11px; overflow: hidden;
  background: rgba(127,127,127,.06); cursor: pointer; display: grid; gap: 0;
}
.pkcell:hover, .pkcell:focus-visible { border-color: var(--accent-strong, #9E0007); }
.pkcell img { width: 100%; aspect-ratio: 4 / 3; object-fit: cover; display: block; }
.pkcap {
  font-size: 11.5px; line-height: 1.3; padding: 5px 6px; color: var(--muted, #6b7280);
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
}

/* the thumbnail in the banner row is itself the "change picture" control */
.herothbtn { position: relative; padding: 0; border: 0; background: none; cursor: pointer; display: block; }
.herothedit {
  position: absolute; left: 0; right: 0; bottom: 0; font-size: 10.5px; font-weight: 700;
  letter-spacing: .04em; text-transform: uppercase; color: #fff;
  background: rgba(0,0,0,.55); padding: 3px 0; border-radius: 0 0 8px 8px;
}
button.herodrop { width: 100%; font: inherit; color: inherit; }

/* Nothing to save: inert, not a washed-out red that still invites a press. */
.herosave:disabled {
  background: transparent; color: var(--muted, #6b7280);
  border: 1px solid rgba(127,127,127,.28); opacity: 1;
}

/* Product photo: a shopkeeper points a camera, they do not paste a URL. */
.fphoto { display: flex; align-items: center; gap: 11px; margin-top: 4px; }
.fphoto-img { width: 62px; height: 62px; object-fit: cover; border-radius: 9px;
  border: 1px solid rgba(127,127,127,.2); background: rgba(127,127,127,.08); }
.fphoto-none { flex: 1; font-size: 13px; color: var(--muted, #6b7280); }

/* ── where the order CAME FROM, on the picking card ─────────────────────────
   The KDS already brands these; the picking screen did not, and picking is
   where a grocery order is actually handled. A Deliveroo order and a counter
   order are picked differently — one leaves with a rider who is already
   outside — so the source has to be the first thing read, not buried.

   Own colour set rather than reusing .kc-chip: those are tuned for the KDS's
   dark screen and several would be unreadable on this white card. */
.pksrc {
  display: inline-block; font-size: 10.5px; font-weight: 800; letter-spacing: .04em;
  text-transform: uppercase; padding: 2px 7px; border-radius: 5px; margin-right: 7px;
  vertical-align: 1px; white-space: nowrap;
}
.pksrc-uber_eats { background: #06C167; color: #04220f; }
.pksrc-deliveroo { background: #00CCBC; color: #00302c; }
.pksrc-just_eat  { background: #FF8000; color: #2b1500; }
.pksrc-tiktok    { background: #fe2c55; color: #fff; }
.pksrc-web       { background: #1f3a8a; color: #fff; }   /* the shop's own site */
.pksrc-till      { background: #e5e7eb; color: #374151; }
.pksrc-agg       { background: #4b5563; color: #fff; }

/* ── LOADING THAT LOOKS LIKE LOADING ─────────────────────────────────────────
   🚨 Reported as "the CTAs take a good 10 secs" and "not all work". The server
   was never slow (~225ms, measured): tapping a filter only set aria-busy, which
   a sighted person cannot see, so the OLD list sat there and the button read as
   dead. These rows breathe in the shape of the rows that are coming, so the tap
   has a visible beginning and the list does not jump when the data lands.
   Shares the `breathe` keyframes already defined above. */
.pskel { display: flex; flex-direction: column; gap: 10px; }
.pskel-row { display: flex; align-items: center; gap: 10px; }
.pskel-thumb { width: 46px; height: 46px; border-radius: 12px; background: var(--ink-3); flex: none; animation: breathe 1.6s infinite; }
.pskel-lines { display: flex; flex-direction: column; gap: 6px; flex: 1; min-width: 0; }
.pskel-lines i { display: block; height: 11px; border-radius: 6px; background: var(--ink-3); animation: breathe 1.6s infinite; }
.pskel-lines i.short { width: 45%; }
/* Someone who has asked for less motion still needs to know it is working, so
   the shapes stay and only the animation stops. */
@media (prefers-reduced-motion: reduce) {
  .pskel-thumb, .pskel-lines i { animation: none; opacity: .6; }
}

/* ── ADDRESS ACTIONS (SERVD parity) ──────────────────────────────────────────
   🚨 These replace a 38px crosshair button that lived inside the input and
   carried only an aria-label. A sighted customer cannot read an aria-label, so
   the most useful control on the checkout — "don't type it, use my GPS" — was
   invisible, and there was no way at all to say "stop suggesting, I'll type it".
   Words, both of them, at a thumb-sized target. */
.addracts { display: flex; flex-wrap: wrap; gap: 6px 18px; margin: 8px 2px 2px; }
.addract { display: inline-flex; align-items: center; gap: 7px; min-height: 34px;
  background: none; border: 0; padding: 4px 0; cursor: pointer;
  color: var(--accent-strong); font: inherit; font-size: var(--fs-sm); font-weight: 600; }
.addract svg { width: 15px; height: 15px; flex: none; }
.addract:disabled { opacity: .6; cursor: default; }
.addract.busy svg { animation: locspin 1s linear infinite; }
/* Manual entry latched on: the suggestions have been dismissed for this
   checkout, and the control shows that rather than looking untouched. */
.addract.on { color: var(--ink); text-decoration: underline; text-underline-offset: 3px; }

/* 🚨 The item sheet's own scroll container had overflow-y:auto with NO
   containment, unlike the basket drawer above — so a drag that reached the end
   of the sheet carried on into the shop behind it. This is the CSS half of the
   fix; the JS half pins overscroll-behavior on html+body while any overlay is
   open, because iOS Safari ignores overflow:hidden on body for touch. */
.sheet-body { overscroll-behavior: contain; -webkit-overflow-scrolling: touch; }

/* ── KDS: the STOP column ─────────────────────────────────────────────────────
   🚨 A platform cancellation used to make the ticket VANISH from the board,
   possibly mid-prep. HubRise's audit requires "a very clear alert... so that the
   merchant knows they should stop preparing", so cancels stay on the board for 30
   minutes in a column that reads first and does not look like the others. */
.kcol-stop { background: #fff1f0; border: 2px solid #c0392b; border-radius: 14px; padding: 6px; }
.kcol-stop > h2 { color: #c0392b; }
.kstop-note { color: #c0392b; font-weight: 600; font-size: 13px; margin: 0 4px 8px; line-height: 1.35; }

/* ── storefront overscroll (BIF-12) ─────────────────────────────────────────
   🚨 `body.shopfront { overscroll-behavior-y: none }` LIVED HERE AND MADE THE
   STOREFRONT UNSCROLLABLE ON TOUCH. Not "janky" — the page did not move at all,
   anywhere, in Chrome AND Firefox on Android. Customers could not get past the
   first screen of the shop. The one place a finger DID work was the "Ask us"
   button, because it is position:fixed and sits outside the body's scroll box —
   which is exactly how the bug was reported.

   PROVEN, not reasoned, on the live site with a synthesised touch gesture:
       as shipped ......................................... scrolled 0px
       overriding ONLY this one declaration to `auto` ..... scrolled 403px
       setting html's overscroll-behavior to `auto` ....... still 0px
   The `html` rule at the top of this file is innocent and stays: it is what
   actually suppresses pull-to-refresh, and it does not block scrolling. This
   body-level pin was redundant on top of it, and load-bearing in the worst way.

   The comment that used to sit here cited a "SERVD-proven case for a body-level
   pin" and warned that pinning touch-action alongside overscroll on html+body
   broke the Office's fixed nav. It was half right: the danger was real, and the
   body-level pin was the half that was already dangerous on its own.

   DO NOT REINSTATE. If overscroll containment is ever needed beyond the html
   rule, contain it on the scrolling CHILD (see .sheet-body above), never on the
   element that owns the page's scroll. */

/* ── mobile typing (2026-07-29 audit) ────────────────────────────────────────
   🚨 iOS Safari force-zooms the page when a focused input's font-size is under
   16px — that zoom is the "broken scrolling while typing" every phone felt.
   Every text control is pinned to 16px; nothing may undercut this. */
input.text, select.text, textarea.text, .ginput, .pin, .qset { font-size: 16px !important; }
/* SERVD's constant. The keyboard does NOT shrink dvh on iOS, so a fixed sheet
   keeps its full height under the keys and Safari pans to chase the caret —
   which is the behaviour we want and the behaviour SERVD ships. Sizing it from
   the visual viewport instead is what made it move while being typed into. */
body.shopfront .sheet { max-height: 90vh; }
.sheet-body { scroll-padding: 96px; }
/* The PIN fold: closed by default now email is the front door (2026-07-29) —
   clerk-auth.js springs it open whenever Clerk cannot mount. The class is the
   JS hook; the style keeps the summary tap-friendly and unselectable. */
details.pinfold summary { user-select: none; -webkit-user-select: none; padding: 4px 0; }

/* ── camera scanner (scan-camera.js) ─────────────────────────────────────────
   Fullscreen, single-shot. The reticle cutout is the 100vmax shadow trick; on a
   clean read it flashes green (scanhit) for the 180ms before the sheet opens. */
.scanview { position: fixed; inset: 0; z-index: 92; background: #000; display: flex; align-items: center; justify-content: center; }
.scanvid { width: 100%; height: 100%; object-fit: cover; }
.scanret { position: absolute; width: min(78vw, 420px); height: min(34vh, 260px); border: 3px solid rgba(255,255,255,.92); border-radius: 18px; box-shadow: 0 0 0 100vmax rgba(0,0,0,.45); transition: border-color .12s, box-shadow .12s; }
.scanview.scanhit .scanret { border-color: #2ecc71; box-shadow: 0 0 0 100vmax rgba(35,120,70,.35); }
.scanbar { position: absolute; bottom: calc(20px + env(safe-area-inset-bottom)); left: 0; right: 0; display: flex; gap: 12px; justify-content: center; }
.scanbtn { background: rgba(255,255,255,.14); color: #fff; border: 1.5px solid rgba(255,255,255,.35); border-radius: 999px; padding: 12px 22px; font-size: 16px; font-weight: 700; }

/* ── the scan FAB (scan-camera.js callers) ───────────────────────────────────
   The quick-draw: TILLD red, floated above the tab bar, one thumb from
   anywhere. z-index 56 keeps it UNDER every scrim/sheet (60+), so an open
   sheet naturally covers it — no juggling required. */
.scanfab { position: fixed; right: 16px; bottom: calc(88px + env(safe-area-inset-bottom)); z-index: 56;
  width: 58px; height: 58px; border-radius: 999px; border: none; background: #E2000A; color: #fff;
  display: grid; place-items: center; cursor: pointer;
  box-shadow: 0 6px 20px -4px rgba(226,0,10,.55), 0 2px 6px rgba(16,17,33,.2);
  transition: transform .12s; }
.scanfab:active { transform: scale(.93); }
.scanfab svg { width: 28px; height: 28px; display: block; pointer-events: none; /* the tap belongs to the button, not the glyph */ }
.pscanbtn svg, .btn svg { width: 18px; height: 18px; vertical-align: -4px; }

/* manual entry — the iPhone path until the phase-2 decoder lands */
.scanview.scanmanual { background: rgba(16,17,33,.62); backdrop-filter: blur(3px);
  /* --kb = live keyboard height: the card rises above the keys instead of hiding
     its own Find-it button under them (ultra-review 2026-07-30) */
  align-items: flex-start; padding: max(12vh, 60px) 0 var(--kb, 0px); overflow-y: auto; }
.scanentry { max-height: calc(100dvh - var(--kb, 0px) - 90px); overflow-y: auto; overscroll-behavior: contain; }
.scanentry { background: var(--surface, #fff); color: var(--text, #1A1A1F); border-radius: 22px;
  padding: 22px 20px; width: min(88vw, 420px); box-shadow: 0 18px 60px -12px rgba(16,17,33,.45); }
.scanentry-t { font-weight: 800; font-size: 19px; }
.scanentry-s { color: #6B6B75; font-size: 13.5px; margin: 4px 0 14px; }
.scaninput { width: 100%; font-size: 18px; letter-spacing: .06em; padding: 13px 14px;
  border: 1.5px solid #E3E3E8; border-radius: 14px; font-variant-numeric: tabular-nums; }
.scanbar-m { display: flex; gap: 10px; justify-content: flex-end; margin-top: 14px; }
.scanbar-m .scanbtn { background: #F2F2F5; color: #1A1A1F; border: none; }
.scanbar-m .scanbtn-go { background: #E2000A; color: #fff; font-weight: 800; }

/* ⚡ PHONE SCROLL PERFORMANCE — remove work, don't add hints.
   Owner report (Android Chrome PWA, day one): product tabs juddering on
   scroll, taps feeling dead. backdrop-filter on a STICKY surface forces the
   browser to recomposite everything scrolling BENEATH it every single frame —
   a mid-range Android spends its whole frame budget on the blur. Phones trade
   the glass for near-solid paint; ≥900px (tablets, desks, the KDS screen)
   keeps the look. Blanket on purpose: a decorative blur is never worth a
   dropped frame on the device the shop actually runs on. */
@media (max-width: 899px) {
  *, *::before, *::after { -webkit-backdrop-filter: none !important; backdrop-filter: none !important; }
  /* the two sticky bars were translucent-over-blur; without the blur they need
     to be (nearly) solid or the list reads through them */
  /* nav.tabs BELONGS HERE: it was translucent-over-blur by design, and killing
     the blur left the list showing through the bar (operator screenshot). Solid
     also paints FASTER than translucent, so the perf fix keeps its win. */
  header.app, .topbar, nav.tabs { background: #fff; }
}

/* Kill the tap-delay class of jank: `manipulation` removes double-tap-zoom
   listening from interactive elements, so a tap fires immediately. Applied
   PER ELEMENT on purpose — setting touch-action on html/body together is the
   documented trap that broke the fixed bottom nav (see the note near the
   scroll-lock rules). */
a, button, input, select, textarea, label, [role="button"] { touch-action: manipulation; }

/* ── Your apps (the More screen's doors) — owner request 2026-07-30 ───────── */
.appdoors .ad-head { font-size: 12px; font-weight: 800; letter-spacing: .08em;
  text-transform: uppercase; color: var(--muted); margin-bottom: 10px; }
.ad-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 8px; }
.ad-btn { display: flex; flex-direction: column; align-items: center; gap: 6px;
  padding: 12px 4px; border: 1px solid var(--line); border-radius: 14px;
  background: var(--card, #fff); text-decoration: none; color: inherit;
  font-size: 12.5px; font-weight: 700; }
.ad-btn:active { transform: scale(.97); }
.ad-ico { width: 25px; height: 25px; display: block; color: var(--accent, #E2000A); }
.ad-ico svg { width: 100%; height: 100%; display: block; }
@media (max-width: 360px) { .ad-grid { grid-template-columns: repeat(2, 1fr); } }

/* ── domain self-serve: the DNS record display ────────────────────────────── */
.dnsbox { width: 100%; margin-top: 10px; padding: 10px 12px; border: 1px dashed var(--line);
  border-radius: 12px; background: color-mix(in srgb, var(--line) 12%, transparent); }
.dnsrec { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; padding: 4px 0;
  font-size: 13px; }
.dnsrec .mono { font-family: ui-monospace, 'SF Mono', Menlo, monospace; font-size: 12.5px;
  word-break: break-all; }
.btn.tiny { padding: 5px 10px; font-size: 12px; border-radius: 9px; }
.btn.ghost { background: none; border: 1px solid var(--line); color: var(--muted); }

/* ── opening hours editor (Office → Settings) ─────────────────────────────── */
.oh-day { margin-bottom: 10px; padding-bottom: 8px; border-bottom: 1px solid color-mix(in srgb, var(--line) 55%, transparent); }
.oh-day:last-of-type { border-bottom: none; }
.oh-slots { display: flex; flex-direction: column; }
.oh-slot { display: flex; align-items: center; gap: 8px; margin-top: 6px; }
.oh-slot .text { flex: 1; min-width: 0; }
.oh-closed { font-size: 12.5px; }
/* The day header: the app's .btn is deliberately full-width and 52px tall for
   thumb targets — here it flattened the day name underneath it (prod screenshot,
   2026-08-03). Scoped override: auto width, compact height, never grows. */
.oh-day-head { border-bottom: 0; padding: 6px 0; }
.oh-day-head .btn, .oh-slot .btn { width: auto; min-height: 34px; flex: 0 0 auto; padding: 6px 12px; font-size: 13px; }

/* KDS: the customer's note + the platform's door/access code — must be seen,
   never buried (a dropped gate code strands a driver at a door). */
.kc-note { margin-top: 6px; padding: 6px 8px; border-radius: 8px; font-size: 13px;
  background: color-mix(in srgb, var(--saffron) 12%, transparent); }
/* an aggregator line we couldn't match to a product: paid for, must be seen —
   the amber the API always promised the screen would show (ultra-verify 08-03) */
.kc-line.kc-unm { background: color-mix(in srgb, #E8A33D 18%, transparent);
  border-radius: 8px; padding: 4px 6px; }

/* ── the HQ prospect pipeline board ───────────────────────────────────────── */
.wl-board { display: flex; gap: 10px; overflow-x: auto; padding-bottom: 6px;
  -webkit-overflow-scrolling: touch; overscroll-behavior-x: contain; }
.wl-col { flex: 0 0 220px; min-width: 220px; background: color-mix(in srgb, var(--line) 14%, transparent);
  border: 1px solid var(--line); border-radius: 14px; padding: 8px; }
.wl-col.drop { outline: 2px solid var(--accent, #E2000A); outline-offset: -2px; }
.wl-col.wl-lost { opacity: .75; }
.wl-colhead { font-size: 12px; font-weight: 800; letter-spacing: .06em; text-transform: uppercase;
  color: var(--muted); padding: 2px 4px 8px; display: flex; justify-content: space-between; }
.wl-count { font-variant-numeric: tabular-nums; }
.wl-card { background: var(--card, #fff); border: 1px solid var(--line); border-radius: 12px;
  padding: 10px; margin-bottom: 8px; cursor: grab; }
.wl-card:active { cursor: grabbing; }
.wl-name { font-weight: 800; font-size: 14px; }
.wl-sub { font-size: 12.5px; color: var(--muted); margin-top: 2px; word-break: break-all; }
.wl-note { font-size: 12px; color: var(--muted); margin-top: 5px; font-style: italic; }
.wl-foot { display: flex; align-items: center; gap: 6px; margin-top: 8px; }
.wl-chip { font-size: 10.5px; font-weight: 700; padding: 2px 7px; border-radius: 999px;
  background: color-mix(in srgb, var(--line) 30%, transparent); }
.wl-days { font-size: 11px; color: var(--muted); font-variant-numeric: tabular-nums; }
.wl-days.warn { color: #b45309; font-weight: 800; }
.wl-arr { width: 28px; height: 28px; border-radius: 8px; border: 1px solid var(--line);
  background: none; font-size: 14px; cursor: pointer; }
.wl-empty { text-align: center; color: var(--muted); padding: 14px 0; }

/* ── the polite "new version" bar (replaces the mid-sale forced reload) ──── */
.swbar { position: fixed; left: 12px; right: 12px; bottom: calc(76px + env(safe-area-inset-bottom));
  z-index: 90; display: flex; align-items: center; justify-content: space-between; gap: 12px;
  padding: 11px 14px; border-radius: 14px; background: #17171c; color: #fff;
  font-size: 13.5px; font-weight: 600; box-shadow: 0 8px 28px rgba(0,0,0,.28); }
.swbar button { border: 0; border-radius: 9px; padding: 7px 14px; font-weight: 800;
  font-size: 13px; background: #fff; color: #17171c; cursor: pointer; }

/**
 * 🚨 ROUND 8 — THE KEYBOARD COMPENSATION IS GONE. DO NOT REINTRODUCE IT.
 *
 * What used to live here: `.drawer, body.shopfront .sheet { padding-bottom:
 * var(--kb); max-height: calc(var(--vvh) + var(--kb) - 10px) }` plus a
 * `html.kb-typing` block that swapped overflow and flex on three elements
 * while the keys were up. Seven rounds tuned those values.
 *
 * They were the bug. --vvh + --kb == innerHeight - offsetTop, and offsetTop is
 * SAFARI PANNING TO CHASE THE CARET — so the drawer’s height was a live
 * function of the pan position and it resized on every pan frame. That is the
 * "move". The kb-typing reflow, landing mid-animation, is the "flash".
 *
 * SERVD was the counter-example: same position:fixed, bottom:0, overflow:hidden
 * flex column with the same pinned .summary footer — the container was never
 * the difference. The keyboard opens, nothing recomputes, Safari pans the field
 * into view, and the pay button waits under the keys until you dismiss them.
 *
 * ⚠️ THE SERVD FACTS ABOVE WERE MEASURED AT ROUND 8 AND HAVE SINCE CHANGED.
 * Do not re-derive anything from them. Corrected 2026-08-29 by Alpha, who owns
 * that lane and read it live:
 *   · "max-height is simply the constant 92vh" is FALSE at SERVD's live tip —
 *     it now reads calc(100dvh - max(8px, env(safe-area-inset-top,0px))), a
 *     DYNAMIC unit. The 92vh reading predates a change made when the operator
 *     asked the sheet to reach the top.
 *   · "10/10 on the same phone" is FALSE — the operator photographed a SERVD
 *     storefront with the fulfilment toggle sliced through the middle, today.
 * ✅ STILL TRUE, and it is the half this comment actually rests on: SERVD's
 * storefront carried ZERO visualViewport code (0 refs, verified at that tip).
 *
 * 🔑 THE ARGUMENT BELOW DOES NOT DEPEND ON THE STALE HALF. Round 8's finding is
 * that --vvh + --kb == innerHeight - offsetTop and offsetTop is Safari's caret
 * pan, so the drawer resized every pan frame. That is a fact about OUR code,
 * measured on OUR phone across seven rounds. It stands on its own.
 *
 * 🪤 The back-office keeps its own --kb/--vvh via ui-sheet.js (.osheet,
 * .scanentry, the till tender sheet). shop.js does not import ui-sheet.js, so
 * the storefront is genuinely free of it. Those surfaces are not broken —
 * do not "finish the job" and take the till with you. Locked by a test.
 */

/* 200-row product list: rows off-screen skip layout entirely — every filter
   tap laid out all 200 wrap-flex rows on the main thread of a mid-range
   Android (UX audit 2026-07-30). intrinsic-size keeps the scrollbar honest. */
.prow { content-visibility: auto; contain-intrinsic-size: auto 84px; }

/* ── the marketplace connect wizard (Get listed) ──────────────────────────── */
.mkt { border: 1px solid var(--line); border-radius: 12px; margin-bottom: 8px; }
.mkt summary { display: flex; align-items: center; justify-content: space-between; gap: 10px;
  padding: 11px 13px; cursor: pointer; list-style: none; }
.mkt summary::-webkit-details-marker { display: none; }
.mkt[open] summary { border-bottom: 1px solid var(--line); }
.mkt-body { padding: 11px 13px; font-size: 13.5px; }
.mkt-steps { margin: 0 0 8px 18px; display: flex; flex-direction: column; gap: 6px; }
.mkt-body .mono { font-family: ui-monospace, 'SF Mono', Menlo, monospace; }

/* "Short" — the picker's not-on-the-shelf button, quiet until needed. */
.pk-short { flex: none; margin-left: auto; border: 1px solid var(--line); background: none;
  color: var(--muted); border-radius: 9px; padding: 5px 10px; font-size: 12px; font-weight: 700;
  cursor: pointer; }
.pk-short:active { transform: scale(.96); }

/* ── the packing bench: the spec a packer follows ─────────────────────────── */
.pksrc-chill { background: #E0F2FE; color: #075985; }
.packspec { margin-top: 10px; padding: 10px 12px; border: 1px dashed var(--line);
  border-radius: 12px; background: color-mix(in srgb, var(--line) 12%, transparent); }
.ps-head { font-weight: 800; font-size: 14px; }
.ps-steps { margin: 7px 0 0 17px; display: flex; flex-direction: column; gap: 4px;
  font-size: 13px; line-height: 1.4; }
.ps-labels { display: flex; gap: 6px; margin-top: 9px; flex-wrap: wrap; }
.ps-labels span { font-size: 10.5px; font-weight: 800; letter-spacing: .06em;
  padding: 3px 8px; border-radius: 6px; background: #FFF1F2; color: #C0182A;
  border: 1px solid #FFD3D7; }

/* ── Deliveries tab: the funnel reads left to right, work order ───────────── */
.delfunnel { display: flex; align-items: stretch; gap: 4px; margin-top: 4px; overflow-x: auto;
  overscroll-behavior-x: contain; }
.delstage { flex: 1; min-width: 78px; text-align: center; padding: 12px 6px;
  border: 1px solid var(--line); border-radius: 14px; background: #fff; }
.delstage.empty { opacity: .5; }
.ds-n { font-size: 26px; font-weight: 800; letter-spacing: -.02em; line-height: 1.1; }
.ds-l { font-size: 12.5px; font-weight: 800; margin-top: 2px; }
.ds-s { font-size: 11px; color: var(--muted); margin-top: 2px; line-height: 1.3; }
.ds-arrow { align-self: center; color: var(--muted); font-size: 18px; flex: none; }
.ontime { display: flex; align-items: center; gap: 14px; }
.ot-big { font-size: 40px; font-weight: 800; letter-spacing: -.03em; }
.ot-big.good { color: #1E7F3C; } .ot-big.warn { color: #b45309; } .ot-big.bad { color: #B0303A; }
.ot-txt { font-size: 13.5px; line-height: 1.45; }
.ot-txt .muted, .ot-txt .ot-late { display: block; margin-top: 3px; font-size: 12.5px; }
.ot-late { color: #B0303A; font-weight: 700; }

/* ── till quick-picks: what a scanner cannot help with ────────────────────── */
.qpgrid { display: grid; grid-template-columns: repeat(auto-fill, minmax(96px, 1fr)); gap: 8px; }
/* BIF-143 — the category rail on the till. Chips rather than tiles: a category
   is a WORD, and a cashier scanning a row of words finds one faster than a grid
   of same-sized boxes. Sized for a thumb on the iPad the counter actually uses
   (44px minimum target, WCAG 2.2 target-size). */
/* ── BIF-290: the tender sheet, rebuilt to his spec ─────────────────────────
   "the cta … should be middle of the screeen" — one dominant confirm.
   "cash given … should have notes on there" — the notes ARE the controls.
   "manual numbers … small dropdown arrow to make it big" — the fold below. */
.tender-cta { width: 100%; min-height: 72px; font-size: 20px; font-weight: 800;
  border-radius: 18px; margin: 14px 0 4px; }
.tender-cta:disabled { opacity: .45; }
.notegrid { display: grid; grid-template-columns: repeat(5, 1fr); gap: 10px; margin-top: 14px; }
.notebtn { min-height: 64px; border-radius: 14px; border: 1.5px solid var(--line-2);
  background: var(--surface); color: var(--text); font-size: 19px; font-weight: 750; cursor: pointer;
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 2px; }
.notebtn:active { transform: scale(.97); }
.notebtn.exact { border-color: var(--text); }
.notebtn.exact span { font-size: 12.5px; font-weight: 600; color: var(--muted); }
.till-given { text-align: center; margin-top: 10px; min-height: 22px; font-size: 15px; }
/* The three till .linklike operations (float/paid-outs door, reprint, given-
   reset) are pressed with wet or gloved hands mid-queue - they keep the quiet
   underlined look at the house 44px floor (audit, 30 Aug). */
#cashdrawer, #reprint, #givenreset { min-height: 44px; padding: 12px 16px; display: inline-flex; align-items: center; justify-content: center; }
.linklike { border: 0; background: none; color: var(--muted); text-decoration: underline;
  cursor: pointer; font-size: 13px; }
.padfold { margin-top: 8px; }
.padfold > summary { cursor: pointer; color: var(--muted); font-size: 14px; text-align: center;
  list-style: none; padding: 8px; }
.padfold > summary::before { content: '⌄  '; }
.padfold[open] > summary::before { content: '⌃  '; }

.catchips { display: flex; flex-wrap: wrap; gap: 8px; }
/* 🚨 BIF-290: these chips shipped referencing var(--card) — a token this stylesheet
   has NEVER defined. The browser dropped the background and painted --ink (#F7F7F8,
   near-white) text onto the white panel: 25 invisible categories on his iPad.
   The tokens below are ones the file actually declares. */
.catchip { display: inline-flex; align-items: center; gap: 6px; min-height: 44px;
  padding: 8px 14px; border-radius: 999px; border: 1.5px solid var(--line-2);
  background: var(--surface); color: var(--text); font-size: 15px; font-weight: 650; cursor: pointer; }
.catchip:active { transform: scale(.97); }
.catchip.on { background: var(--text); color: var(--surface); border-color: var(--text); }
.catchip .catn { font-size: 12.5px; font-weight: 600; opacity: .65; }
.catchip.on .catn { opacity: .8; }
.qptile { display: flex; flex-direction: column; align-items: center; gap: 5px;
  padding: 9px 6px 10px; border: 1px solid var(--line); border-radius: 14px;
  background: #fff; cursor: pointer; text-align: center; }
.qptile:active { transform: scale(.97); }
.qptile img, .qp-none { width: 46px; height: 46px; border-radius: 10px; object-fit: cover;
  background: color-mix(in srgb, var(--line) 40%, transparent); display: block; }
.qp-name { font-size: 12px; font-weight: 700; line-height: 1.25;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
.qp-price { font-size: 12px; font-weight: 800; font-variant-numeric: tabular-nums; }

/* ── product editor: Basic | Advanced tabs + the SEO panel (PLAN §4) ─────── */
.otabs { display: flex; gap: 4px; padding: 10px 18px 0; border-bottom: 1px solid var(--line); }
.otab { flex: 1; padding: 10px 12px 11px; border: 0; background: none; cursor: pointer;
  font-family: var(--font); font-size: 14px; font-weight: 700; color: var(--muted);
  border-bottom: 2.5px solid transparent; }
.otab.active { color: var(--text); border-bottom-color: var(--accent-strong); }
.seo-panel { margin-top: 12px; padding-top: 4px; border-top: 1px solid var(--line); }
.seo-count { font-weight: 600; color: var(--muted-2); margin-left: 6px; font-variant-numeric: tabular-nums; }
.seo-count.over { color: #B0303A; }
.seo-note { margin: -4px 0 8px; font-size: 12.5px; }
/* the Google-style snippet card — a SERP result, not an app widget */
.serp { border: 1px solid var(--line); border-radius: 12px; padding: 12px 14px; margin: 6px 0 10px; background: var(--surface); }
.serp-title { color: #1a0dab; font-size: 16px; line-height: 1.3; overflow: hidden;
  display: -webkit-box; -webkit-line-clamp: 1; -webkit-box-orient: vertical; }
.serp-url { color: #1E7F3C; font-size: 12.5px; margin: 2px 0 3px; word-break: break-all; }
.serp-desc { color: var(--muted); font-size: 13px; line-height: 1.45; overflow: hidden;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; }
.seo-score { display: flex; align-items: baseline; gap: 2px; margin: 4px 0 6px; }
.seo-score b { font-size: 26px; font-weight: 800; letter-spacing: -.02em; }
.seo-score span { font-size: 13px; color: var(--muted); }
.seo-checks { display: flex; flex-direction: column; gap: 4px; margin-bottom: 8px; }
.seo-check { display: flex; gap: 8px; font-size: 12.5px; line-height: 1.35; color: var(--muted); }
.seo-check b { flex: none; width: 16px; font-weight: 800; }
.seo-check.pass b { color: #1E7F3C; }
.seo-check.warn b { color: #b45309; }
.seo-check.fail b { color: #B0303A; }
/* CANNIBALISATION WARNING — amber, not red: two of your own products chasing
   one search is a thing to fix, not a thing that is broken. */
.seo-clash { margin: -4px 0 8px; font-size: 12.5px; line-height: 1.4; color: #b45309; }

/* ── the review queue: machine drafts waiting on a human ─────────────────── */
/* The draft itself has to be READABLE — the whole card exists so somebody reads
   it before it publishes, so it gets prose sizing, not caption sizing. */
.seoq { align-items: flex-start; }
.seoq-draft { margin: 6px 0 2px; font-size: 13.5px; line-height: 1.5; color: var(--text);
  overflow: hidden; display: -webkit-box; -webkit-line-clamp: 6; -webkit-box-orient: vertical; }
.seoq-meta { font-size: 12px; opacity: .8; }
.seoq-clash { color: #b45309; }
/* Keep / throw away / edit stack on a phone rather than squeezing to nothing. */
.seoq-acts { display: flex; flex-direction: column; gap: 6px; flex: none; margin-left: 10px; }
@media (max-width: 380px) { .seoq-acts { gap: 5px; } }

/* ── REPORTS: the Office's own section ───────────────────────────────────────
   The screen's job is to be BELIEVED, so the styling stays out of the way: the
   numbers are the only thing with weight on the page, and every qualification
   the server sent is drawn as prose the eye actually stops on rather than as a
   footnote in caption grey. */
.rep-win { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; }
.rep-date { width: auto; min-width: 140px; flex: 1 1 140px; }
/* The report picker scrolls sideways rather than wrapping — six tabs wrapping
   to two lines moves the whole page down every time one is tapped. */
.rep-tabs { display: flex; max-width: 100%; overflow-x: auto; -webkit-overflow-scrolling: touch;
  scrollbar-width: none; }
.rep-tabs::-webkit-scrollbar { display: none; }
.rep-tabs .segbtn { white-space: nowrap; }
.rep-period { font-size: 12.5px; font-weight: 700; letter-spacing: .04em; text-transform: uppercase;
  color: var(--muted); margin: 0 0 10px 2px; }
/* THE HONESTY NOTE. Amber-edged and set in readable prose, because every one of
   these says "here is what this number does NOT cover" — the sentence that
   stops a report being trusted further than it deserves. */
.rep-note { margin-top: 10px; padding: 9px 11px; border-left: 3px solid var(--warn);
  background: var(--amber-bg); border-radius: 0 8px 8px 0; font-size: 12.5px; line-height: 1.5;
  color: var(--text); }
.rep-warn { color: var(--bad); }
.rep-bar { height: 5px; border-radius: 999px; background: var(--ink-3); overflow: hidden; margin-top: 6px; }
.rep-bar i { display: block; height: 100%; background: var(--saffron); }

/* The Search tab's learning loop: "Teach it" expands an inline product finder
   under the missed query, and the picks render as tappable product names. */
.srch-teach { flex-shrink: 0; }
.srch-teach-zone { margin-top: 4px; }
.srch-teach-zone .text { max-width: 280px; }
.srch-find { flex-shrink: 0; }
.srch-picks { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 8px; }
/* Product names can run long — the pick buttons wrap their text rather than
   forcing the row wide on a phone. */
.srch-attach { white-space: normal; text-align: left; line-height: 1.3; }
.rep-tot td { border-top: 1.5px solid var(--line-2); font-weight: 700; }
/* A shop that has switched alerts on and has nobody to send them to. Amber, not
   red: nothing is broken — the shop simply has not finished the job, and it
   would otherwise believe it had. */
.alrt-warn { margin: 8px 0 0; padding: 9px 11px; border-left: 3px solid var(--warn);
  background: var(--amber-bg); border-radius: 0 8px 8px 0; font-size: 12.5px; line-height: 1.5; }

/* A tile whose image 404'd. The label sits directly under it, so a neutral
   tinted panel still reads as a category — a broken-image icon reads as a
   broken shop. See the delegated error listener in shop.js. */
.imgfell { background: linear-gradient(160deg, #F1EFEC 0%, #E7E4DF 100%); }
.imgfell img { display: none; }

/* ── The order fold ────────────────────────────────────────────────────────
   The Total IS the toggle: byte-for-byte the height of the .srow.tot it
   replaces, so folding the fees away costs nothing and buys ~106px back. The
   money stays at the BOTTOM, which is where the operator wants it — it just
   stops occupying the drawer when nobody is reading it. */
.ordfold { border: 0; }
.ordfold > summary {
  list-style: none; cursor: pointer; -webkit-tap-highlight-color: transparent;
  display: flex; align-items: baseline; justify-content: space-between;
}
.ordfold > summary::-webkit-details-marker { display: none; }
/* A caret on the LABEL, not the number: the total must never shift sideways
   when the fold opens — a moving price reads as a changing price. */
.ordfold > summary > span:first-child::after {
  content: ''; display: inline-block; margin-left: 7px; width: 6px; height: 6px;
  border-right: 2px solid var(--muted-2); border-bottom: 2px solid var(--muted-2);
  transform: rotate(45deg) translateY(-2px); transition: transform .16s ease;
}
.ordfold[open] > summary > span:first-child::after { transform: rotate(-135deg) translateY(-1px); }
.ordfold-body { padding-top: 6px; max-height: 34dvh; overflow-y: auto; overscroll-behavior: none; }

/* The min-order banner, flashed when a customer presses a button that cannot
   yet submit. One pulse — enough to find it, not enough to nag. */
.delgate.flash { animation: gateflash .9s ease; }
@keyframes gateflash {
  0%, 100% { box-shadow: 0 0 0 0 rgba(226, 0, 10, 0); }
  25%      { box-shadow: 0 0 0 4px rgba(226, 0, 10, .18); }
}

/* ── The pay-at-counter escape hatch ───────────────────────────────────────
   Card is the outcome the shop wants; this is for the customer who actively
   goes looking for it. It used to be a full-width bordered button sitting
   directly ABOVE the pay button, same width, same family — which reads as an
   equal choice, and the research is consistent that equal weight splits intent.
   A quiet text link is the accepted way to keep a secondary path available
   without competing.

   🚨 SUBTLE IS NOT THE SAME AS FIDDLY. Still a real <button>, still a 44px tap
   target via padding, still meets contrast against the white footer — an
   escape hatch nobody can hit is worse than one nobody notices. */
.paylink {
  display: block; width: auto; margin: 10px auto 0; padding: 12px 16px;
  min-height: 44px; border: 0; background: none; cursor: pointer;
  font-size: var(--fs-xs); font-weight: 500; color: var(--muted);
  text-decoration: underline; text-underline-offset: 3px;
  text-decoration-color: var(--line); -webkit-tap-highlight-color: transparent;
}
.paylink:active { color: var(--text); }

/* Server-rendered content that lives inside #app purely for crawlers and for
   customers with JavaScript off. shop.js replaces #app on boot, so painting
   this first means a full screen of department chips flashing on EVERY page
   load before the shop appears. Hidden here, revealed by a <noscript> style in
   the page head — so no-JS visitors keep it and everyone else never sees it.
   An inline handler could not do this: script-src is 'self' with no
   'unsafe-inline'. style-src does allow inline, which is why noscript works. */
.prehydrate { display: none; }

/* ---- loyalty card (scan at the till instead of reciting a phone number) ----
   White plate with real quiet zones around the bars: a scanner needs the clear
   margin as much as the bars, and a barcode flush to a coloured edge is one
   that fails at the counter with a queue watching. Fixed height rather than a
   percentage so it stays a scannable size on every phone. */
.loycard-wrap { margin-top: 16px; padding: 14px; border-radius: 16px;
  background: var(--surface); border: 1px solid var(--line); box-shadow: var(--sh-sm); text-align: center; }
.loycard-h { font-size: var(--fs-sm); color: var(--muted); margin-bottom: 10px; }
.loycard-h b { color: var(--text); font-weight: 600; }
.loycard { display: block; width: 100%; max-width: 320px; margin: 0 auto; height: 68px; }
.loycard-n { margin-top: 8px; font-family: var(--font-display); font-feature-settings: 'tnum' 1;
  font-size: var(--fs-sm); letter-spacing: .12em; color: var(--muted); }

/* ── Settings, state-first (operator 2026-08-08: "easy and simple") ─────────
   Each card is a folded <details>: the closed screen reads like a receipt of
   the shop's setup — title left, current state right, tap to change. */
.stcard { border: 1px solid var(--line); border-radius: 14px; background: var(--surface); margin-bottom: 8px; }
.stcard > summary { display: flex; align-items: center; justify-content: space-between; gap: 10px;
  padding: 13px 15px; cursor: pointer; list-style: none; min-height: 48px; }
.stcard > summary::-webkit-details-marker { display: none; }
.stcard > summary::after { content: "›"; color: var(--muted-2); font-size: 18px; transform: rotate(90deg); transition: transform .18s; flex: none; }
.stcard[open] > summary::after { transform: rotate(-90deg); }
.stcard[open] > summary { border-bottom: 1px solid var(--line); }
.stcard .stsum { color: var(--muted); font-size: 12.5px; margin-left: auto; text-align: right;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 55%; }
.stcard .stbody { padding: 11px 15px 14px; }

/* Marketing-consent badge on the Customers row (operator 2026-08-08): green =
   textable, grey = not, the WHY on tap. Never red — "no consent yet" is a
   normal state, not an error. */
.cbadge { display: inline-block; margin-top: 6px; font-size: 11.5px; font-weight: 600;
  padding: 2px 9px; border-radius: 999px; letter-spacing: -.01em; }
.cbadge.yes { background: rgba(125,200,33,.16); color: #4d7d0e; }
.cbadge.no { background: var(--ink-3, #ececef); color: var(--muted, #75757f); }

/* ── THE TILL KEYPAD (BIF-143/154/164/168) ────────────────────────────────────
 * One numeric behaviour for price, cash given and quantity. Sized for a thumb
 * at a counter, not for a mouse: 64px minimum targets, because the alternative
 * is a mis-key on the last screen before money changes hands.
 * The display never animates — an amount that moves while it is being read is
 * an amount that can be misread.
 */
.kpad { display: flex; flex-direction: column; gap: 10px; }
.kpad-label { font-size: 13px; font-weight: 700; letter-spacing: .04em;
  text-transform: uppercase; color: var(--muted-2, #888); }
.kpad-display { font-size: 40px; font-weight: 800; line-height: 1.1; text-align: right;
  padding: 10px 14px; border-radius: 10px; background: var(--ink-1, #f4f4f6);
  font-variant-numeric: tabular-nums; min-height: 62px; overflow-x: auto; }
.kpad-display[data-placeholder] { color: var(--muted-2, #999); }
.kpad-keys { display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px; }
.kpad-key { min-height: 64px; min-width: 0; padding: 0; font-size: 26px; font-weight: 700; border-radius: 10px;
  border: 1px solid var(--line, #ddd); background: var(--card-from, #fff);
  color: inherit; cursor: pointer; font-variant-numeric: tabular-nums;
  touch-action: manipulation; -webkit-tap-highlight-color: transparent; }
.kpad-key:active { background: var(--ink-1, #ececf0); }
/* The two edits read as secondary: they are corrections, not the main path,
 * and neither is destructive enough to borrow the red that Clear sale needs. */
.kpad-clear, .kpad-back { font-size: 15px; font-weight: 600; color: var(--muted-2, #777); }
.kpad-commit { min-height: 56px; font-size: 18px; }
.kpad-commit:disabled { opacity: .5; cursor: default; }
@media (prefers-reduced-motion: reduce) { .kpad-key { transition: none; } }

/* BIF-154 — the VAT choice on the typed-price sheet: two big chips (No VAT /
 * 20% VAT) with the shopkeeper's examples underneath; nothing pre-selected. */
.vat-chip { text-align: center; line-height: 1.3; white-space: normal; padding: 8px 10px; }

/* ── THE TENDER HEADING (BIF-164) ─────────────────────────────────────────────
 * 🚨 It rendered as "ash — £14.72" on his iPad: a single centred line, wider
 * than its box, clipped at BOTH ends — and the two things that must never be
 * ambiguous on this screen are the £ and the amount.
 *
 * Fixed structurally rather than by nudging a width: the amount gets its OWN
 * line, so no label length can ever push it out of view, and the block cannot
 * overflow horizontally whatever the label says or however narrow the device.
 * ⚠️ UNPROVEN ON DEVICE — this addresses the cause by inspection. It is not
 * verified until the screen is photographed on the iPad it failed on.
 */
.tender-head { display: flex; flex-direction: column; gap: 2px; margin: 0 0 12px;
  padding-inline: max(4px, env(safe-area-inset-left)) max(4px, env(safe-area-inset-right));
  font-size: 15px; font-weight: 600; color: var(--muted-2, #888);
  text-align: left; overflow-wrap: anywhere; min-width: 0; }
.tender-head b { display: block; font-size: 34px; line-height: 1.1; font-weight: 800;
  color: inherit; font-variant-numeric: tabular-nums; }

/* The basket quantity is tappable now (BIF-168): the stepper is good at ±1 and
 * useless for a case of twelve, where it was twelve taps and a mis-tap could
 * only be tapped back. It reads as a number, not a button, until touched. */
.lq-num { min-width: 40px; min-height: 34px; padding: 0 8px; border: 1px solid transparent;
  border-radius: 8px; background: transparent; color: inherit; font: inherit;
  font-variant-numeric: tabular-nums; cursor: pointer; }
.lq-num:hover, .lq-num:focus-visible { border-color: var(--line, #ddd); }

/* ── BIF-287: Clear sale reads as PRESENT and DESTRUCTIVE ────────────────────
 * It was .btn.secondary.small — --ink-3 (#F1F1F3) on the white sale card, a
 * near-white chip on white; he asked for a clear-basket CTA that was already
 * on his screen. Outline red, not solid: visible and unmistakably destructive
 * without shouting over the gold Cash CTA it sits beneath. Armed = solid, so
 * the confirming second tap looks different from the first. */
.btn.dangerline { background: transparent; border: 1.5px solid var(--bad); color: var(--bad); }
.btn.dangerline.armed { background: var(--bad); border-color: var(--bad); color: #fff; }
.btn.dangerline:disabled { border-color: var(--line); color: var(--muted-2, #999);
  background: transparent; opacity: .55; cursor: default; }

/* His ask, 18 Aug: "from the search when products appear … the gap needs to be
 * about 0.5cm more". 0.5cm ≈ 19px at CSS 96dpi; only when results exist, so
 * the empty pane never pushes the browse cards down. */
#results:not(:empty) { margin-top: 19px; }

/* ── The LEGAL pages read like a document, not a dump (his ask, 19 Aug:
 * "there isnt enhough space each side and top"). A true reading column with
 * real margins; the 3-col GDPR table becomes stacked cards on phones, each
 * cell naming its column — squeezing three columns onto 390px was the cramp. */
main.legal { max-width: 640px; padding: clamp(44px, 9vw, 76px) 24px calc(90px + env(safe-area-inset-bottom)); }
.legal h1 { font-size: clamp(30px, 7vw, 38px); margin-bottom: 6px; }
.legal h2 { margin-top: 36px; }
.legal p, .legal li { line-height: 1.75; }
@media (max-width: 600px) {
  .legal table, .legal tbody, .legal tr, .legal td { display: block; width: 100%; }
  .legal tr:first-child { display: none; }             /* the header row — each card names its own */
  .legal tr { border: 1px solid var(--line); border-radius: 12px; padding: 10px 14px; margin: 12px 0; }
  .legal td { border: 0; padding: 6px 0; }
  .legal td::before { content: attr(data-th); display: block; font-size: 11px; font-weight: 700;
    text-transform: uppercase; letter-spacing: .06em; color: var(--muted-2, #888); margin-bottom: 2px; }
}

/* ── TEAM: people, with their permissions inside them (rebuilt 25 Aug 2026) ──
 *
 * The old screen rendered permissions as one row per PERSON × CAPABILITY, so a
 * name repeated five times and the page grew with the shop. People and their
 * permissions are now ONE list: a person is a row, their settings live inside it.
 *
 * Two failures in the recording were pure CSS and are fixed here:
 *   · the "no email" pill sat in a flex row with no min-width, so on a phone it
 *     wrapped ONE WORD PER LINE into a tall red column — nowrap + allowing the
 *     identity column to shrink is the fix.
 *   · the three-way segmented control was squeezed by a long label beside it and
 *     rendered as a squashed pill with the words stacked. Below ~430px the label
 *     goes ABOVE and the control takes the full width, which is the only honest
 *     way to fit three touch targets on a narrow screen.
 */
.tm-p { border-bottom: 1px solid var(--line, #e6e6e6); }
.tm-p:last-of-type { border-bottom: 0; }
.tm-p > summary {
  display: flex; align-items: center; gap: 10px;
  padding: 11px 2px; cursor: pointer; list-style: none;
}
.tm-p > summary::-webkit-details-marker { display: none; }
.tm-p-id { flex: 1 1 auto; min-width: 0; }        /* min-width:0 is what lets it shrink instead of wrap */
.tm-p-name { display: block; font-weight: 650; font-size: 15px; }
.tm-p-meta {
  display: block; margin-top: 2px; font-size: 12.5px; color: var(--muted, #6b7280);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;   /* an email truncates, never wraps */
}
.tm-nowrap { white-space: nowrap; }
.tm-p-chev { flex: none; color: var(--muted, #9aa0a6); font-size: 20px; transition: transform .15s ease; }
.tm-p[open] > summary .tm-p-chev { transform: rotate(90deg); }
.tm-p.is-off > summary .tm-p-name { color: var(--muted, #6b7280); text-decoration: line-through; }
.tm-p-body { padding: 2px 2px 14px; }
.tm-perm-h {
  margin: 14px 0 6px; font-size: 12px; font-weight: 700; letter-spacing: .04em;
  text-transform: uppercase; color: var(--muted, #6b7280);
}
.tm-perm {
  display: flex; align-items: center; gap: 10px;
  padding: 9px 0; border-top: 1px solid var(--line-soft, #f0f0f0);
}
.tm-perm-l { flex: 1 1 auto; min-width: 0; }
.tm-perm-l .name { display: block; font-size: 14px; font-weight: 600; }
.tm-perm-l .sub { display: block; font-size: 12px; color: var(--muted, #6b7280); }
.tm-perm .seg { flex: none; }
.tm-add > summary { cursor: pointer; font-size: 13px; font-weight: 650; padding: 6px 0; }

/* Below this width three touch targets cannot share a row with a label. */
@media (max-width: 430px) {
  .tm-perm { flex-direction: column; align-items: stretch; gap: 6px; }
  .tm-perm .seg { width: 100%; display: flex; }
  .tm-perm .seg .segbtn { flex: 1 1 0; text-align: center; }
}

/* 🚨 THE ROW THAT DRAGGED THE WHOLE PAGE WIDE.
 * `.btn-row` is `display:flex` with NO wrap and `.btn{flex:1}`, so four actions
 * on one person could not shrink below their own text and forced the document
 * wider than the phone — which is why every card's text was clipped at the right
 * edge, not just this row. Scoped to the team body so no other screen's rows
 * change shape. */
.tm-p-body .btn-row { flex-wrap: wrap; }
.tm-p-body .btn-row .btn { flex: 1 1 auto; min-width: 0; white-space: nowrap; }

/* ── DISCOUNTS & VOUCHERS: two paths, made visibly different ──────────────────
 *
 * "Create voucher" and "Print a batch" sat in one undivided block and read as
 * one feature with two buttons. They are not: one makes a SINGLE code everybody
 * shares, the other mints a RUN where every slip differs. An owner who printed
 * the shared one twice got the same code twice — correctly — and concluded
 * printing was broken. The sections carry that difference visually.
 *
 * And every input has a real LABEL now. They were placeholder-only, and a
 * placeholder vanishes the moment you type: the one moment you want to check
 * what a box was for is the one moment it cannot tell you.
 */
.vsec { padding: 14px 0; border-top: 1px solid var(--line, #e6e6e6); }
.vsec:first-of-type { border-top: 0; padding-top: 4px; }
.vsec-h { font-size: 15px; font-weight: 700; margin-bottom: 2px; }
.vsec-s { margin: 0 0 12px; font-size: 13px; }
.vsec-alt { background: var(--tint, #fafafa); margin: 0 -14px; padding-left: 14px; padding-right: 14px; }
.vhint { margin: 6px 0 0; font-size: 12.5px; line-height: 1.45; }
.vhint code {
  font-size: 12px; background: var(--chip, #f0f0f2); border-radius: 4px;
  padding: 1px 5px; white-space: nowrap;
}
.vchk { flex-direction: row; align-items: center; gap: 8px; margin-top: 12px; }
.vchk input { width: auto; margin: 0; }
.vchk span { font-size: 14px; }
/* A label's own text sits above its control; without this the field collapses
 * to the bare input and we are back to placeholder-as-label. */
.vsec .field > span { display: block; font-size: 12.5px; font-weight: 650; margin-bottom: 4px; }
/* The checkbox row is the one field whose text belongs BESIDE the control, not
 * above it — the block rule above would otherwise push the tick onto its own
 * line and orphan its own sentence. */
.vsec .vchk > span { display: inline; font-weight: 500; margin-bottom: 0; }
.vchk { align-items: flex-start; }
.vchk input { margin-top: 2px; flex: none; }

/* The person's sign-in address, shown IN FULL inside their row. The summary
 * line truncates it — it must, or a long address wraps the whole row on a
 * phone — so this is the one place an owner can actually read which email a
 * person has. "Change email" without showing the current one asks them to
 * remember it. */
.tm-signin {
  display: flex; flex-wrap: wrap; align-items: baseline; gap: 4px 8px;
  padding: 8px 10px; margin: 0 0 10px;
  background: var(--tint, #fafafa); border-radius: 8px;
}
.tm-signin-k { font-size: 11.5px; font-weight: 700; letter-spacing: .04em; text-transform: uppercase; color: var(--muted, #6b7280); }
.tm-signin-v { font-size: 13.5px; word-break: break-all; }
.tm-signin-v.tm-none { color: #b3261e; }
/* The row-level Invite: small, and it must not push the name column around. */
.tm-p > summary .tm-inv { flex: none; margin-left: auto; padding: 5px 12px; font-size: 12.5px; }

/* ── Messages: the Customers / Team selector ──────────────────────────────────
 * The two tabs are DELIBERATELY NOT symmetrical in colour. Customers is the
 * shop's own accent; Team is the blue the team channel already uses everywhere
 * else (#25405f). Someone glancing down at the composer should be able to tell
 * which audience they are typing to from the colour alone, without reading.
 * See the note in owner.js above .msgsel before making these consistent.
 */
.msgsel { display: flex; gap: 8px; margin: 12px 16px 0; }
.msgtab {
  flex: 1 1 0; min-width: 0; display: flex; flex-direction: column; gap: 2px;
  padding: 10px 12px; text-align: left; cursor: pointer;
  background: var(--surface); border: 1.5px solid var(--line); border-radius: 12px;
  color: var(--muted); font: inherit;
}
.msgtab-t { font-weight: 650; font-size: 14.5px; color: var(--text); display: flex; align-items: center; gap: 6px; }
.msgtab-s { font-size: 11.5px; color: var(--muted-2); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.msgtab[data-chan="customers"].active { border-color: var(--saffron); background: var(--card-from); }
.msgtab[data-chan="customers"].active .msgtab-t { color: var(--ember); }
.msgtab[data-chan="team"].active { border-color: #25405f; background: #e6edf8; }
.msgtab[data-chan="team"].active .msgtab-t { color: #25405f; }

/* The whole section carries the tint too, so the audience is legible even when
 * the tabs have scrolled off the top of a phone screen. */
.oview.chan-is-team { background: #f4f7fc; }

@media (max-width: 380px) {
  .msgtab { padding: 9px 10px; }
  .msgtab-s { display: none; }   /* the title still names the audience */
}

/* The till-receipt code section. Distinct from the two above it because it is the
 * only one that gives money away AUTOMATICALLY — an owner should be able to tell
 * at a glance that this one runs on its own. */
.vsec.vsec-till { border-left: 3px solid var(--fresh); }

/* ═══ REFUND A SALE — pick the sale, then pick what is coming back (18 Sep) ═══
   Counter hardware first: every target is at least 44px, the quantity is the only
   input, and a chosen line is obvious from across the counter. It widens on a
   desktop screen rather than staying a phone column in the middle of a monitor. */
.rf-sales { display: grid; gap: 8px; max-height: 46vh; overflow: auto; margin-top: 4px; }
.rf-sale {
  display: grid; grid-template-columns: 1fr auto; align-items: center; gap: 4px 12px;
  width: 100%; min-height: 56px; padding: 10px 14px; text-align: left;
  border: 1.5px solid var(--line); border-radius: 12px; background: var(--surface);
  color: var(--text); font: inherit; cursor: pointer;
}
.rf-sale:hover:not(:disabled) { border-color: var(--line-2); background: var(--ink-3); }
.rf-sale:disabled { opacity: .5; cursor: not-allowed; }
.rf-sale-ref { font-weight: 650; }
.rf-sale-amt { font-weight: 700; }
.rf-sale-note { grid-column: 1 / -1; color: var(--warn); font-size: 13px; }

.rf-lines { display: grid; gap: 8px; max-height: 44vh; overflow: auto; margin-top: 4px; }
.rf-line {
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  padding: 10px 12px; border: 1.5px solid var(--line); border-radius: 12px; background: var(--surface);
}
/* A chosen line is marked by BORDER AND BACKGROUND, never by colour alone — the
   counter screen is read at an angle in daylight. */
.rf-line.on { border-color: var(--fresh); background: var(--good-bg); }
.rf-line-name { display: grid; gap: 2px; font-weight: 600; min-width: 0; }
.rf-line-name .muted { font-weight: 400; font-size: 13px; }
.rf-step { display: flex; align-items: center; gap: 6px; flex: none; }
.rf-step button, .rf-minus, .rf-plus {
  min-width: 44px; min-height: 44px; font-size: 22px; line-height: 1;
  border: 1.5px solid var(--line); border-radius: 10px; background: var(--surface);
  color: var(--text); cursor: pointer;
}
.rf-step button:hover, .rf-minus:hover, .rf-plus:hover { background: var(--ink-3); }
.rf-qty { min-width: 28px; text-align: center; font-size: 18px; font-weight: 700; }

@media (min-width: 700px) {
  /* Room to read a long product name without wrapping every line. */
  .rf-sales, .rf-lines { max-height: 52vh; }
  .rf-line { padding: 12px 16px; }
}
