/* --- The kitchen screen's chrome, as tokens (T4 slice 1, 2026-08-30) ---

   Jamal chose a light kitchen screen through multi; dark stays the default.
   The tickets themselves are already paper — white cards, dark ink, set on
   `#kds-board .kt`. What is still dark is everything around them, and this is
   that everything: nine or so surfaces, four borders and a handful of inks.

   Nothing here changes what is on screen. Every value below is the colour the
   rule already carried; the point is that the light theme can now be a second
   block of values rather than a hunt through 353 rules.

   What is deliberately NOT a token: the age states, parked green, cancelled
   red, rush, the delivery band, buzzer chips and any colour a venue set. Those
   are semantic — an order going late has to shout across a pass in either
   theme — so they stay written where they are read.

   tests/kdstokens.test.js fails if a new hard-coded surface colour lands in a
   chrome rule, so this does not silently re-harden between slices. */
:root {
  --k-ground: #0c0e13;     /* the board behind the paper tickets */
  --k-bar: #14161b;        /* the one bar of chrome, and the strips that match it */
  --k-panel: #14171d;      /* the bridge and the prep strip */
  --k-tile: #171a21;       /* a station or a state button, unpicked */
  --k-control: #1a1d24;    /* a round control: filter, sound, push */
  --k-lane: #101218;       /* a lane column */
  --k-track: #191c23;      /* the scrollbar track */
  --k-oncontrast: #0d0f14; /* ink on a picked state button */
  --k-edge: #2a2e38;       /* the ordinary border */
  --k-edge-2: #23262e;     /* the bar and its strips */
  --k-edge-3: #1e222b;     /* a lane head */
  --k-chipedge: #3a3f4c;   /* an unpicked filter chip */
  --k-thumb: #3a4150;      /* the scrollbar thumb */
  --k-thumb-2: #4a5364;    /* the scrollbar thumb, hovered */
  --k-ink: #c8ceda;        /* ordinary chrome text */
  --k-ink-2: #cdd3dd;      /* chrome text a shade brighter */
  --k-ink-3: #e8eaee;      /* the brightest chrome text */
  --k-muted: #8b93a1;      /* a label beside a control */
  --k-muted-2: #8b90a0;    /* a control that is off */
  --k-dim: #6c7482;        /* an empty lane */
  --k-dim-2: #6b7080;      /* a control that cannot work here */
  --k-nav: #14161c;        /* the bottom nav, shared with the till */
  /* A ticket's own edge. On a near-black board a white card separates itself
     and this is barely visible; on a light board it is the only thing holding
     the card off the ground, which is why it is a token and not a constant. */
  --k-card-edge: #dfe3ea;
  --k-card-shadow: none;
  /* The scaled design stage the KDS and the register both live on, and the
     letterbox around it. Shared with the till, which is why they are tokens
     with the till's own values as the default rather than anything new. */
  --k-stage: #0c0e12;
  --k-stage-ink: #e8eaee;
  --k-letterbox: #000;
}

/* --- The same board, in daylight (T4 slice 2, 2026-08-30) ------------------

   Jamal, through multi: build a real light theme, dark stays the default.
   A pass under a roller door in July reads nothing off a near-black screen.

   Only the chrome moves. Every value below answers a token defined above, and
   the ROLES are what carry across — `--k-oncontrast` is the ink on a filled
   chip, which is near-black on dark (the chip fills light) and white here (the
   chip fills dark). That flip is exactly what a find-and-replace would have got
   wrong, and it is why this is a second set of values rather than a second set
   of rules.

   `--k-ground` is deliberately a clear grey rather than white: the tickets are
   white, and a white card on a white board is a card with no edge. The ground
   does half the separating and `--k-card-edge` does the other half.

   Nothing semantic appears here. The age states, parked green, cancelled red,
   rush, the delivery band and any venue-set source colour keep their hue in
   both themes — a chef reads those across a room, and a preference does not
   get to repaint them. */
/* The preview box in the settings sheet and in back-office Configuration
   carries the attribute itself, so somebody choosing the theme sees the theme
   rather than a description of it — and sees it inside a back office that is
   still dark. */
body[data-kds-theme="light"], .kexample[data-kds-theme="light"] {
  --k-ground: #dfe3e9;     /* the board — grey, so the paper tickets read as paper */
  --k-bar: #ffffff;        /* the one bar of chrome */
  --k-panel: #f4f6f9;      /* the bridge and the prep strip */
  --k-tile: #ffffff;       /* a station or state button, unpicked */
  --k-control: #ffffff;    /* a round control: filter, sound, push */
  --k-lane: #eef1f5;       /* a lane column */
  --k-track: #d7dbe2;      /* the scrollbar track */
  --k-oncontrast: #ffffff; /* ink on a picked state button — the role flips here */
  --k-edge: #ccd2dc;       /* the ordinary border */
  --k-edge-2: #dbe0e8;     /* the bar and its strips */
  --k-edge-3: #e2e6ec;     /* a lane head */
  --k-chipedge: #b9c1cd;   /* an unpicked filter chip */
  --k-thumb: #aeb6c2;      /* the scrollbar thumb */
  --k-thumb-2: #8f98a6;    /* the scrollbar thumb, hovered */
  --k-ink: #3d4351;        /* ordinary chrome text */
  --k-ink-2: #333947;      /* chrome text a shade darker */
  --k-ink-3: #1c2029;      /* the strongest chrome text */
  --k-muted: #5b6472;      /* a label beside a control */
  --k-muted-2: #5f6875;    /* a control that is off */
  --k-dim: #7b8492;        /* an empty lane */
  --k-dim-2: #7f8794;      /* a control that cannot work here */
  --k-nav: #ffffff;        /* the bottom nav */
  /* The card needs a real edge, and a shadow it never needed on the dark board. */
  --k-card-edge: #b9c2cf;
  --k-card-shadow: 0 1px 2px rgba(16, 22, 34, .08), 0 2px 8px rgba(16, 22, 34, .06);
  /* The stage follows the board, or a light board sits in a black picture
     frame — which is what the first cut of this looked like. */
  --k-stage: #dfe3e9;
  --k-stage-ink: #1c2029;
  --k-letterbox: #c9cfd8;
}

:root {
  --bg: #0f1115;
  --surface: #171a21;
  --surface-2: #1e222b;
  --border: #2a2f3a;
  --text: #e6e9ef;
  --muted: #9aa3b2;
  --primary: #4f8cff;
  --primary-ink: #101418;   /* picked to read on --primary, not fixed white — see branding.js inkOn() */
  --green: #3ecf8e;
  --amber: #f5a623;
  /* The note pill on a ticket. Dark enough to pass AA on white (6.33) and on
     its own pale ground (5.78) — both measured, not eyeballed. */
  --tk-note-ink: #8f5000;
  --tk-note-bg: #fff3e0;
  --red: #ff5c5c;
  --radius: 10px;
  /* The till: one radius for every box, one for small badges. Pills read as
     a different design language sitting next to square tiles. */
  --pos-r: 10px;
  --pos-r-s: 6px;
  --shadow: 0 6px 24px rgba(0,0,0,.35);
  font-synthesis: none;
}

* { box-sizing: border-box; }
html, body { height: 100%; }
body {
  margin: 0;
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  background: var(--bg);
  color: var(--text);
  font-size: 14px;
  line-height: 1.5;
}

/* **A link with no class is still a link** (v5.518.0). Every anchor in this
   app took its colour from a class selector, and there was no rule for a bare
   `a` — so the moment somebody wrote a plain `<a>` it fell through to the
   browser's own #0000EE, which on this dark surface measures 1.85:1 and is
   effectively unreadable. That is how the venue's public QR address ended up
   illegible on the one card whose entire job is to show it. Every class rule
   below still wins (a class beats an element selector at any order), so this
   only reaches the links nothing else claimed. */
a { color: var(--primary); }

#app { display: flex; min-height: 100vh; }

/* Sidebar */
.sidebar {
  width: 232px;
  flex: 0 0 232px;
  background: var(--surface);
  border-right: 1px solid var(--border);
  display: flex;
  flex-direction: column;
  padding: 18px 14px;
  position: sticky;
  top: 0;
  height: 100vh;
}
.brand { display: flex; align-items: center; gap: 10px; padding: 6px 8px 18px; }
.brand-mark { font-size: 26px; }
.brand-name { font-weight: 700; font-size: 16px; }
.brand-sub { color: var(--muted); font-size: 12px; }
.site-switch { padding: 4px 8px 14px; margin-bottom: 6px; border-bottom: 1px solid var(--border); }
.site-switch label { font-size: 10px; letter-spacing: .05em; }
.site-switch select { font-weight: 600; }
.nav { display: flex; flex-direction: column; gap: 2px; overflow-y: auto; flex: 1; min-height: 0; scrollbar-width: thin; }
.nav a {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 10px 12px;
  border-radius: 8px;
  color: var(--muted);
  text-decoration: none;
  font-weight: 500;
}
/* The icon holds its size whatever the label does — a long one like
   "Purchase orders" must not squeeze the glyph out of alignment with the
   rows above and below it. */
.nav-i { flex: 0 0 18px; opacity: .85; }
.nav a.active .nav-i { opacity: 1; }
.nav a:hover { background: var(--surface-2); color: var(--text); }
.nav a.active { background: var(--primary); color: var(--primary-ink); }
/* **The screens under the hub you are in** (QA round 3, #6: "no sub-module
   navigation for Menu"). Drawn by drawNavSub() under the ACTIVE hub item only —
   it is where-you-are, not a dropdown you open, so the StoreKit rule of eight
   plain items stands for every hub you are not in. */
.nav-sub { display: flex; flex-direction: column; gap: 1px; margin: 2px 0 6px 20px;
  padding-left: 8px; border-left: 1px solid var(--border); }
.nav-sub-h { font-size: 10.5px; letter-spacing: .06em; text-transform: uppercase;
  color: var(--muted); padding: 8px 8px 3px; opacity: .8; }
.nav .nav-sub a { padding: 5px 8px; font-size: 13px; border-radius: 6px; }
.nav .nav-sub a.on { color: var(--text); font-weight: 600; background: var(--surface-2); }
/* The Gratuity screen's 1-of-3 mode cards — the POS's own selectable cards,
   left-aligned because they sit in a card body rather than a settings row. */
.kset-cards.gr-cards { justify-content: flex-start; margin-top: 10px; }
.gr-cards .kset-card { min-width: 150px; align-items: flex-start; text-align: left; padding: 10px 14px; }
.gr-cards .kset-card small { font-weight: 400; opacity: .8; font-size: 11.5px; }
.gr-amounts { display: flex; gap: 8px; flex-wrap: wrap; margin-top: 8px; }
.gr-amounts label { display: flex; align-items: center; gap: 4px; font-size: 13px; }
.gr-amounts input { width: 84px; }
.gr-preview { display: flex; gap: 8px; flex-wrap: wrap; margin-top: 6px; }
.gr-chip { border: 1px solid var(--border); border-radius: 999px; padding: 6px 12px;
  font-size: 13px; font-weight: 600; }
.gr-chip.pencil { padding: 6px 10px; color: var(--muted); }
/* Store details — the logo well. */
.sd-logo { display: flex; align-items: center; gap: 14px; margin-top: 8px; }
.sd-logo-well { width: 72px; height: 72px; border-radius: 12px; border: 1px dashed var(--border);
  display: flex; align-items: center; justify-content: center; overflow: hidden; background: var(--surface-2); }
.sd-logo-well img { max-width: 100%; max-height: 100%; object-fit: contain; }
/* **The bottom-left block (StoreKit's layout, 2026-08-31).** The venue selector
   and the admin code used to sit ABOVE the nav, so every screen began with two
   boxes to scroll past. They live down here now, with Settings and the health
   dot, and the nav starts immediately under the logo. */
.side-bottom { margin-top: auto; display: flex; flex-direction: column; gap: 6px;
  padding-top: 8px; border-top: 1px solid var(--line); }
.side-bottom .ctx-one { margin: 0; }
.side-settings { display: flex; align-items: center; gap: 10px; padding: 9px 10px;
  border-radius: 10px; color: var(--muted); text-decoration: none; font-size: 14px; }
.side-settings:hover, .side-settings.active { background: rgba(255,255,255,.06); color: var(--text); }
.sidebar-foot { display: flex; align-items: center; gap: 8px; color: var(--muted); font-size: 12px; padding: 4px 10px 6px; }
.dot { width: 8px; height: 8px; border-radius: 50%; background: var(--muted); }
.dot.ok { background: var(--green); }
.dot.bad { background: var(--red); }

/* Content */
.content { flex: 1; min-width: 0; padding: 26px 30px; }
.page-head { display: flex; align-items: center; justify-content: space-between; gap: 16px; margin-bottom: 22px; flex-wrap: wrap; }
.page-title { font-size: 22px; font-weight: 700; margin: 0; }
.page-sub { color: var(--muted); margin: 2px 0 0; }
.loading { color: var(--muted); padding: 40px; text-align: center; }

/* Buttons */
.btn {
  appearance: none;
  border: 1px solid var(--border);
  background: var(--surface-2);
  color: var(--text);
  padding: 9px 14px;
  border-radius: 8px;
  font-size: 13px;
  font-weight: 600;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  /* Center the label horizontally too — a button wider than its text (the
     search popup's min-width OK) left-aligned it (QA round 2, finding 37).
     Buttons sized to their content don't move. */
  justify-content: center;
  gap: 6px;
}
.btn:hover { border-color: #3a4150; }
/* A disabled button has to look disabled. Every other control on the till dims
   to .45 when it is refused (.pos-ract, .pos-paybar, .posm-tender…) and .btn
   had no rule at all — so a full-colour primary button that will not do
   anything sat in the footer of every dialog that gates its action, which the
   charge and discount sheets both do by design (v5.187.0). */
.btn:disabled { opacity: .45; cursor: default; }
.btn.primary { background: var(--primary); border-color: var(--primary); color: var(--primary-ink); }
.btn.primary:hover { filter: brightness(1.05); }
.btn:disabled:hover { filter: none; border-color: var(--line, #2b3040); }
.btn.danger { color: var(--red); border-color: #4a2b2f; }
.btn.danger:hover { background: #2a1a1c; }
.btn.ghost { background: transparent; }
.btn.sm { padding: 6px 10px; font-size: 12px; }

/* Cards / stats */
.grid { display: grid; gap: 16px; }
.stats { grid-template-columns: repeat(auto-fit, minmax(min(180px, 100%), 1fr)); margin-bottom: 22px; }
.card { background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius); padding: 18px; }
.stat-label { color: var(--muted); font-size: 12px; text-transform: uppercase; letter-spacing: .04em; }
.stat-value { font-size: 26px; font-weight: 700; margin-top: 6px; }
.stat-value.warn { color: var(--amber); }
.stat-value.bad { color: var(--red); }
.stat-value.good { color: var(--green); }
.two-col { grid-template-columns: 1fr 1fr; align-items: start; }
@media (max-width: 900px) { .two-col { grid-template-columns: 1fr; } }

/* Toolbar */
.toolbar { display: flex; gap: 10px; align-items: center; flex-wrap: wrap; margin-bottom: 16px; }
.toolbar .grow { flex: 1; min-width: 160px; }
/* Phone (QA round 3, #46): squeezed to the 160px floor beside the filter
   selects, the search field cut its own placeholder in half. On a narrow
   screen it takes the full row and the filters wrap underneath. */
@media (max-width: 640px) {
  .toolbar .grow { flex-basis: 100%; min-width: 100%; }
}
/* QA audit 2026-08-26. Finding 15: `input, select { width: 100% }` below is
   right for a form and wrong for a toolbar — a site picker stretched across
   the row parks its arrow a screen away from its own text. Finding 16: the
   native option list ignores the app's dark theme on most platforms and drew
   near-white text on a white popup, so options carry their own colors.
   Findings 8/19/21/28: the inline field error and the required star. */
.toolbar select { width: auto; min-width: 140px; max-width: 100%; }
select option { color: #16181d; background: #ffffff; }
.field-err { color: #c8382f; font-size: 12.5px; margin: 6px 0 0; }
input.invalid, textarea.invalid, select.invalid { border-color: #c8382f; }
label .req { color: #c8382f; }
/* **Native controls draw themselves dark too** (QA report 2, finding 64).
   The Job dropdown was reported as "white list on a dark app", and the first
   answer given was that a native `<select>` popup cannot be styled by the page.
   That is true of `background-color` — and false of `color-scheme`, which is
   the property that tells the browser which palette to draw its own furniture
   in. One line here and Chrome, Edge, Firefox and Safari all draw the option
   list, the scrollbars and the date and time pickers dark, without giving up
   the keyboard and screen-reader behaviour a hand-built dropdown would cost.
   It sits on the controls rather than on :root so the guest pages, which are
   light and load a different stylesheet, are untouched either way. */
input, select, textarea, option, optgroup {
  color-scheme: dark;
}

input, select, textarea {
  background: var(--surface-2);
  border: 1px solid var(--border);
  color: var(--text);
  border-radius: 8px;
  padding: 9px 11px;
  font-size: 13px;
  font-family: inherit;
  width: 100%;
}
/* …except a tick box, which is not a text field. It has no useful width, and
   the 100% above was stretching every checkbox in the back office to the full
   width of its cell — 324px on a phone, which is how a locked row on Receipt
   printouts came to reach 90px past the edge of the screen. This is why the
   tick boxes were ragged in the first place; v5.115.0 straightened them with a
   grid, which fixed the row it was looking at and left the cause alone. */
input[type=checkbox], input[type=radio] { width: auto; padding: 0; }

input:focus, select:focus, textarea:focus { outline: none; border-color: var(--primary); }
label { display: block; font-size: 12px; color: var(--muted); margin-bottom: 5px; font-weight: 600; }

/* Tables */
.table-wrap { background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius); overflow: auto; }
table { width: 100%; border-collapse: collapse; }
th, td { text-align: left; padding: 11px 14px; border-bottom: 1px solid var(--border); white-space: nowrap; }
th { color: var(--muted); font-size: 12px; text-transform: uppercase; letter-spacing: .03em; font-weight: 600; cursor: default; }
th.sortable { cursor: pointer; user-select: none; }
th.sortable:hover { color: var(--text); }
tbody tr:hover { background: var(--surface-2); }
tbody tr:last-child td { border-bottom: none; }
td.num, th.num { text-align: right; font-variant-numeric: tabular-nums; }
td.muted { color: var(--muted); }
.row-actions { display: flex; gap: 6px; justify-content: flex-end; }
.empty { padding: 40px; text-align: center; color: var(--muted); }

/* Badges */
.badge { display: inline-block; padding: 2px 9px; border-radius: 999px; font-size: 11px; font-weight: 700; }
.badge.green { background: rgba(62,207,142,.15); color: var(--green); }
.badge.amber { background: rgba(245,166,35,.15); color: var(--amber); }
.badge.red { background: rgba(255,92,92,.15); color: var(--red); }
.badge.grey { background: rgba(154,163,178,.15); color: var(--muted); }
.badge.blue { background: rgba(79,140,255,.15); color: var(--primary); }

/* Modal */
.modal-backdrop { position: fixed; inset: 0; background: rgba(0,0,0,.55); display: flex; align-items: flex-start; justify-content: center; padding: 60px 16px; z-index: 70; overflow: auto; overscroll-behavior: contain; }
/* While anything is open, the page behind it is frozen — modal() holds the
   class for as long as at least one dialog is up. The touchmove guard in
   modal() is the other half, for the scrollers overflow cannot reach. */
html.modal-open, html.modal-open body { overflow: hidden; }
/* Translucent since v5.26.0 — the page shows faintly through, which keeps the
   context of where the dialog came from. Falls back to the solid surface where
   backdrop-filter is unsupported and under prefers-reduced-transparency; both
   handled by the tokens in apple.css. */
.modal { background: var(--mat-thick, var(--surface)); border: 1px solid var(--border);
  border-radius: 14px; width: 100%; max-width: 560px; box-shadow: var(--mat-shadow, var(--shadow));
  -webkit-backdrop-filter: blur(var(--mat-blur, 20px)) saturate(var(--mat-sat, 180%));
  backdrop-filter: blur(var(--mat-blur, 20px)) saturate(var(--mat-sat, 180%)); }
@supports not (backdrop-filter: blur(1px)) { .modal { background: var(--surface); } }
.modal.wide { max-width: 760px; }
.modal-head { display: flex; align-items: center; justify-content: space-between; padding: 18px 20px; border-bottom: 1px solid var(--border); }
.modal-head h3 { margin: 0; font-size: 16px; }
.modal-body { padding: 20px; overscroll-behavior: contain; }
.modal-foot { padding: 16px 20px; border-top: 1px solid var(--border); display: flex; justify-content: flex-end; gap: 10px; }
.form-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 14px; }
.form-grid .full { grid-column: 1 / -1; }
/* iPhone (QA round 3, #45): a date input's intrinsic width is wider than half
   a phone modal, and a grid track grows to fit it unless the cell is allowed
   to shrink — which poked the Invoice date field past the modal's edge. */
.form-grid > div, .form-grid > label { min-width: 0; }
.form-grid input { min-width: 0; }
/* A phone gets one column (QA report 2, findings 46/63/73 were all a half-width
   cell too narrow for its own placeholder). The till's modals keep two: they
   draw inside the scaled stage, not the viewport. */
@media (max-width: 560px) {
  .form-grid { grid-template-columns: 1fr; }
  .pos-fit .form-grid { grid-template-columns: 1fr 1fr; }
}
.close-x { background: none; border: none; color: var(--muted); font-size: 20px; cursor: pointer; line-height: 1; }

/* Toast */
#toast-root { position: fixed; bottom: 20px; right: 20px; display: flex; flex-direction: column; gap: 10px; z-index: 100; }
.toast { background: var(--surface-2); border: 1px solid var(--border); border-left: 3px solid var(--primary); padding: 12px 16px; border-radius: 8px; box-shadow: var(--shadow); min-width: 220px; animation: slideIn .18s ease; }
.toast.ok { border-left-color: var(--green); }
.toast.err { border-left-color: var(--red); }
@keyframes slideIn { from { transform: translateX(20px); opacity: 0; } to { transform: none; opacity: 1; } }

.bar { height: 8px; background: var(--surface-2); border-radius: 999px; overflow: hidden; margin-top: 6px; }
.bar > span { display: block; height: 100%; background: var(--primary); }
.list-row { display: flex; align-items: center; justify-content: space-between; padding: 10px 0; border-bottom: 1px solid var(--border); }
.list-row:last-child { border-bottom: none; }
.hint { color: var(--muted); font-size: 12px; margin-top: 4px; }
.mono { font-variant-numeric: tabular-nums; }

/* ---- Auth ---- */
.auth-screen { position: fixed; inset: 0; display: flex; align-items: center; justify-content: center; padding: 20px; background: var(--bg); z-index: 100; }
.auth-card { width: 100%; max-width: 380px; }
.sidebar-user { margin-top: auto; display: flex; align-items: center; justify-content: space-between; gap: 8px; padding: 10px 8px 4px; border-top: 1px solid var(--border); color: var(--muted); font-size: 13px; }
.sidebar-user + .sidebar-foot { margin-top: 0; }
.sidebar-user > span { display: flex; align-items: center; gap: 7px; min-width: 0; }
.sidebar-user > span > svg { flex: 0 0 15px; }
.auth-links a { color: var(--primary); text-decoration: none; }
.auth-links a:hover { text-decoration: underline; }

/* ---- Grouped sidebar (MarketMan-style) ---- */
.nav-group { margin: 14px 8px 4px; font-size: 10px; font-weight: 700; letter-spacing: .08em; text-transform: uppercase; color: var(--muted); }

/* ---- COGS report ---- */
.banner-info { background: rgba(79,140,255,.10); border: 1px solid rgba(79,140,255,.35); color: var(--text); border-radius: var(--radius); padding: 12px 16px; margin-bottom: 16px; font-size: 13px; }
.banner-info a { color: var(--primary); }
.legend-dot { display: inline-block; width: 9px; height: 9px; border-radius: 50%; margin-right: 4px; vertical-align: baseline; }
.cogs-stats { display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 0; margin-top: 16px; border-top: 1px solid var(--border); padding-top: 14px; text-align: center; }
.cogs-stats > div { border-right: 1px solid var(--border); padding: 0 8px; }
.cogs-stats > div:last-child { border-right: none; }
.cogs-stat-value { font-size: 19px; font-weight: 700; margin-top: 4px; }
.cogs-pct { font-size: 15px; font-weight: 700; margin-top: 2px; }
tr.filter-row th { padding: 6px 10px; background: var(--surface); }
tr.filter-row input { padding: 6px 8px; font-size: 12px; }

/* ---- Card-wall dashboard (MarketMan-style) ---- */
.dash-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 16px; align-items: start; }
@media (max-width: 1280px) { .dash-grid { grid-template-columns: 1fr 1fr; } }
@media (max-width: 860px) { .dash-grid { grid-template-columns: 1fr; } }
.dash-col { display: flex; flex-direction: column; gap: 16px; min-width: 0; }
.card-head { display: flex; align-items: baseline; justify-content: space-between; gap: 10px; margin-bottom: 12px; }
.card-head h3 { margin: 0; font-size: 15px; }
.card-link { color: var(--primary); font-size: 12px; text-decoration: none; white-space: nowrap; }
.card-link:hover { text-decoration: underline; }
.mini-row { display: flex; gap: 10px; margin-bottom: 12px; flex-wrap: wrap; }
.mini-stat { flex: 1; min-width: 110px; border: 1px solid var(--border); border-radius: 8px; padding: 10px 12px; }
.mini-label { color: var(--muted); font-size: 11px; }
.mini-value { font-size: 18px; font-weight: 700; margin-top: 3px; }
.mini-value.good { color: var(--green); }
.mini-value.bad { color: var(--red); }
.mini-value.accent { color: var(--primary); }
.mini-chart { width: 100%; display: block; }
.dash-table { width: 100%; border-collapse: collapse; font-size: 12.5px; }
.dash-table th { text-align: left; color: var(--muted); font-size: 10.5px; text-transform: uppercase; letter-spacing: .04em; padding: 6px 6px; border-bottom: 1px solid var(--border); }
.dash-table td { padding: 7px 6px; border-bottom: 1px solid var(--border); }
.dash-table tr:last-child td { border-bottom: none; }
.dash-table th.num, .dash-table td.num { text-align: right; }

/* ---- Invoices tabs + scans list ---- */
.tabs { display: flex; gap: 2px; border-bottom: 1px solid var(--border); margin-bottom: 16px; }
.tabs button { background: none; border: none; color: var(--muted); padding: 10px 16px; font-weight: 700; font-size: 13px; cursor: pointer; border-bottom: 2px solid transparent; letter-spacing: .03em; text-transform: uppercase; }
.tabs button.active { color: var(--primary); border-bottom-color: var(--primary); }
.scan-row { display: flex; align-items: center; gap: 12px; padding: 13px 4px; border-bottom: 1px solid var(--border); cursor: pointer; }
.scan-row:last-child { border-bottom: none; }
.scan-row:hover { background: var(--surface-2); }
.scan-dot { width: 10px; height: 10px; border-radius: 50%; flex: none; }
.mx-pct { font-weight: 700; color: var(--green); font-size: 15px; margin-bottom: 4px; }
.mx-chip { display: inline-block; background: rgba(79,140,255,.10); border-radius: 4px; padding: 2px 6px; margin: 1px 2px 1px 0; font-size: 10.5px; color: var(--text); }

/* The group page each bottom tab opens. Sections, because Config alone is
   twenty-seven screens and twenty-seven links in a column is a wall somebody
   scrolls past rather than reads. Every row says what the screen is for — a
   list of nouns is a puzzle, and this is the only place somebody meets most of
   these names.

   These rows were built for a dialog, which supplied its own panel. On a page
   there is nothing behind them, so the section is the card: without it the
   rows float on the background and the page reads as a list of loose links
   rather than a screen. */
.navhub .navsheet-sec { background: var(--surface); border: 1px solid var(--border);
  border-radius: 14px; padding: 12px 10px 10px; }
.navhub .navsheet-sec + .navsheet-sec { margin-top: 12px; }
.navhub .navsheet-row + .navsheet-row { border-top: 1px solid var(--border); }
.navsheet-sec + .navsheet-sec { margin-top: 16px; }
.navsheet-cap { font-size: 11.5px; letter-spacing: .4px; text-transform: uppercase;
  color: var(--muted); font-weight: 700; margin: 0 2px 6px; }
.navsheet-row { display: flex; align-items: center; gap: 10px; padding: 11px 12px;
  border-radius: 11px; text-decoration: none; color: inherit; }
.navsheet-row:hover { background: var(--surface-2); }

/* The StoreKit hub rebuild (2026-08-26): icons, right-hand statuses, and the
   venue bar. The sections read caps-outside, card-inside, like their Settings
   page. */
.hub-venue { display: flex; align-items: center; gap: 12px; flex-wrap: wrap;
  background: var(--surface); border: 1px solid var(--border); border-radius: 12px;
  padding: 10px 14px; margin-bottom: 16px; }
/* **The venue picker looks like a picker** (v5.504.0). Jamal, 2026-08-31, on a
   phone: "It's too hard to see you allow changing traders here at the top." It
   was `border: none; background: transparent`, which on a phone renders the one
   control that changes which business the whole screen is about as a bold
   heading with a faint native arrow beside it. Now: a bordered pill, a caps
   label, an app-drawn chevron and a 44px tap target. */
.hub-venue-pick { position: relative; display: inline-flex; flex-direction: column;
  justify-content: center; min-height: 44px; box-sizing: border-box;
  background: var(--surface-2); border: 1px solid var(--border); border-radius: 10px;
  padding: 0; cursor: pointer; }
/* **The whole box is the control** (QA report 2, finding 56): the chevron used
   to sit in the wrapper's padding beside a select that only covered the venue
   name, so a tap on the arrow did nothing. The select now fills the box and
   the caption floats over its top-left corner; the arrow is still drawn on the
   wrapper, and a tap on it lands on the select underneath. */
.hub-venue-cap { position: absolute; left: 13px; top: 6px; font-size: 10.5px; font-weight: 700;
  letter-spacing: .07em; text-transform: uppercase; color: var(--muted); line-height: 1;
  pointer-events: none; }
/* Drawn rather than native, so it is the same arrow on every phone. */
.hub-venue-pick::after { content: ''; position: absolute; right: 14px; top: 50%;
  width: 8px; height: 8px; margin-top: -6px; border-right: 2px solid var(--muted);
  border-bottom: 2px solid var(--muted); transform: rotate(45deg);
  pointer-events: none; }
/* One venue: nothing to switch to, so no chevron and no invitation to tap. */
.hub-venue-pick.one { cursor: default; }
.hub-venue-pick.one::after { content: none; }
.hub-venue-pick.one .hub-venue-sel { padding: 12px 13px; }
.hub-venue-sel { font-weight: 700; font-size: 15px; background: transparent;
  border: none; padding: 19px 36px 6px 13px; min-height: 44px; color: var(--text); max-width: 320px;
  cursor: pointer; line-height: 1.25; -webkit-appearance: none; appearance: none; }
.hub-venue-cap + .hub-venue-sel { padding-top: 19px; }
.hub-venue-pick > .hub-venue-sel:first-child { padding-top: 12px; padding-bottom: 12px; }
/* The invite result (finding 58): theme colours, the link readable, a copy button. */
.inv-done .inv-line { padding: 6px 0; }
.inv-done .inv-link { display: block; margin: 4px 0 6px; font-size: 12px; word-break: break-all;
  color: var(--text); background: var(--surface-2); padding: 6px 8px; border-radius: 6px; }
.hub-venue-sel:disabled { opacity: 1; cursor: default; }
.hub-view { margin-left: auto; display: inline-flex; align-items: center; gap: 7px;
  font-size: 13.5px; font-weight: 600; color: var(--accent, #3d6ee0);
  text-decoration: none; }
.hub-view:hover { text-decoration: underline; }
.hub-ic { flex: 0 0 auto; display: inline-flex; opacity: .75; }
.hub-ic .nav-i { width: 18px; height: 18px; }
.hub-status { flex: 0 0 auto; font-size: 12.5px; white-space: nowrap; }
.hub-status.on { padding: 2px 8px; border-radius: 6px; background: var(--surface-2); }
.hub-status.tone-ok { color: #1f9d55; }
.hub-status.tone-warn { color: #d97706; }
.hub-status.tone-bad { color: #dc2626; font-weight: 700; }
.hub-status.tone-muted { color: var(--muted); }
.navhub .navsheet-cap { margin: 18px 2px 6px; }
.navhub .navsheet-cap:first-child { margin-top: 0; }
.navhub .navsheet-row { padding: 12px 14px; }
.navhub .navsheet-txt strong { display: flex; align-items: center; gap: 6px; }

.navsheet-txt { flex: 1; min-width: 0; }
.navsheet-txt .sub { display: block; color: var(--muted); font-size: 12px; margin-top: 1px; }
.navsheet-chev { color: var(--muted); font-size: 20px; line-height: 1; flex: 0 0 auto; }

/* ---- Mobile app view (bottom tabs + drawer sidebar) ---- */
.mobile-nav { display: none; }
@media (max-width: 860px) {
  .sidebar { position: fixed; left: -260px; top: 0; bottom: 0; z-index: 60; width: 240px; transition: left .2s ease; }
  .sidebar.open { left: 0; box-shadow: 0 0 50px rgba(0,0,0,.65); }
  /* A drawer is already hidden; folding it to a rail would only make it a
     narrow drawer nobody asked for (finding 75). */
  .sidebar.rail { width: 240px; flex-basis: 240px; padding: 18px 14px; }
  .sidebar.rail .brand-logo, .sidebar.rail .nav a > span, .sidebar.rail .nav-sub,
  .sidebar.rail .site-switch, .sidebar.rail .ctx-one-txt, .sidebar.rail .ctx-code,
  .sidebar.rail .side-settings > span, .sidebar.rail .sidebar-foot span:not(.dot)
    { display: revert; }
  .content { padding: 16px 14px 84px; }
  .mobile-nav { position: fixed; bottom: 0; left: 0; right: 0; display: flex; background: var(--surface); border-top: 1px solid var(--border); z-index: 55; padding-bottom: env(safe-area-inset-bottom); }
  .mobile-nav a, .mobile-nav button { flex: 1; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 3px; background: none; border: none; color: var(--muted); text-align: center; padding: 8px 0 9px; font-size: 19px; text-decoration: none; cursor: pointer; }
  .mobile-nav span { display: block; font-size: 10px; }
  .mnav-i { flex: 0 0 auto; }
  .mobile-nav .active { color: var(--primary); }
  .page-title { font-size: 19px; }
  .modal { max-width: 96vw; }

  /* ---- Nothing in here makes the *page* move sideways ----------------------
     Jamal, of the QR menu: *"it shouldn't let me slide it left to right."* The
     same was true in the back office — the five-column COGS table on the
     dashboard, the date range on Sales, and the stat rows on Reconciliation
     and COGS were each wider than a phone, so the whole screen could be
     dragged. The rule is that wide content scrolls **inside its own box**, so
     the thing you are reading moves and the page around it stays put. */

  /* A `1fr` grid track means "at least min-content" unless told otherwise, so
     one long unbreakable figure widens its column and shoves the page past the
     edge of the screen. Every column layout in here is laid out for a laptop:
     on a phone it fits as many columns as will actually hold a number and
     wraps the rest, rather than squeezing four of them into 90px each. */
  .grid, .cogs-stats, .mini-stats {
    grid-template-columns: repeat(auto-fit, minmax(min(150px, 100%), 1fr)) !important;
    /* Each card is as tall as what is in it, rather than stretching to match
       whichever one in the row has the most to say. */
    align-items: start; }
  .grid > *, .dash-grid > *, .cogs-stats > *, .mini-stats > * { min-width: 0; }
  /* These two are read down the page rather than across, at any width. Last
     word, so the rule above cannot put them back into columns. */
  .dash-grid, .two-col { grid-template-columns: 1fr !important; }

  /* A table of figures keeps its columns and scrolls inside its card. Any
     depth, not just a direct child — the reconciliation table sits a level
     down and was the one still dragging the page. */
  .card table { display: block; overflow-x: auto; }
  /* A row of controls wraps onto a second line rather than running off. */
  .page-head { flex-wrap: wrap; }
  .row-actions, .date-nav { flex-wrap: wrap; justify-content: flex-start; }
}

/* ---- Nory-style sales insights ---- */
.crumb { color: var(--muted); font-size: 13px; margin-bottom: 6px; }
.date-nav { display: flex; gap: 6px; align-items: center; }
/* A headline figure with its supporting numbers beside it. Wraps, because at
   phone width the third one used to hang off the side of the screen. */
.stat-row { display: flex; gap: 14px; align-items: baseline; margin: 6px 0 8px; flex-wrap: wrap; }
/* A figure with its label under it. Both the holiday balance (v5.10.0) and the
   true-cost row emit `.stat` and neither had ever styled it, so the numbers and
   their captions ran together as one line of body text. */
.stat-row .stat { display: flex; flex-direction: column; gap: 2px; min-width: 120px;
  align-self: stretch; }
.stat-row .stat strong { font-size: 20px; font-weight: 700; line-height: 1.15; }
.stat-row .stat strong.bad { color: var(--red); }
.stat-row .stat span { color: var(--muted); font-size: 12px; }
.stat-row .big { font-size: 22px; font-weight: 700; }
.delta { font-weight: 700; font-size: 12px; padding: 2px 7px; border-radius: 6px; }
.delta.up { color: var(--green); background: rgba(62,207,142,.12); }
.delta.down { color: var(--red); background: rgba(255,92,92,.12); }
.seg-bar { display: flex; height: 14px; border-radius: 7px; overflow: hidden; background: var(--surface-2); }
.seg-bar span { display: block; height: 100%; }
.seg-legend { display: flex; gap: 14px; flex-wrap: wrap; font-size: 12px; color: var(--muted); margin-bottom: 6px; }
@media (max-width: 1100px) { .grid[style*="2fr 2fr 1fr"] { grid-template-columns: 1fr !important; } }

/* ---- Flash P&L ---- */
.chip { display: inline-flex; gap: 6px; align-items: center; border: 1px solid var(--border); border-radius: 999px; padding: 6px 14px; font-size: 12.5px; color: var(--muted); }
.chip strong { color: var(--text); }
.pnl-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(min(230px, 100%), 1fr)); gap: 14px; align-items: start; }
.pnl-card h3 { font-size: 14.5px; }
.pnl-row { border-top: 1px solid var(--border); padding: 10px 0 8px; }
.pnl-row-head { display: flex; justify-content: space-between; align-items: center; }
.pnl-val { font-size: 19px; font-weight: 700; margin-top: 3px; }
.pnl-pct { float: right; font-size: 17px; }
.pnl-sub { display: flex; justify-content: space-between; margin-top: 6px; }
.pnl-box { background: var(--surface-2); border-radius: 6px; padding: 3px 8px; font-size: 11.5px; color: var(--muted); }

/* ---- Order sheet (Nory-style) ---- */
.order-row { border-bottom: 1px solid var(--border); padding: 12px 0; }
.order-row:last-child { border-bottom: none; }
.order-row-top { display: flex; align-items: center; justify-content: space-between; gap: 12px; }
.pack-chip { background: #2a2f3a; border-radius: 6px; padding: 2px 8px; font-size: 11px; margin-left: 8px; color: var(--text); }
.stepper { display: flex; align-items: center; border: 1px solid var(--border); border-radius: 8px; overflow: hidden; }
.stepper button { background: var(--surface-2); border: none; color: var(--text); width: 34px; height: 32px; font-size: 16px; cursor: pointer; }
.stepper span { min-width: 40px; text-align: center; font-weight: 700; }
.usage-strip { display: flex; gap: 14px; align-items: center; margin-top: 8px; overflow-x: auto; }
.usage-cell { text-align: center; font-size: 12px; color: var(--green); min-width: 32px; }
.usage-cell em { display: block; font-style: normal; color: var(--green); font-size: 10.5px; opacity: .85; }
.cover-banner { margin-top: 8px; background: rgba(62,207,142,.10); border: 1px solid rgba(62,207,142,.35); border-radius: 8px; padding: 8px 12px; font-size: 12.5px; }
.order-foot { position: sticky; bottom: 0; display: flex; gap: 10px; padding: 12px 0 4px; background: linear-gradient(transparent, var(--bg) 30%); margin-top: 12px; }
@media (max-width: 860px) { .order-foot { bottom: 64px; } }
.cart-badge { background: var(--amber); color: #1a1408; border-radius: 999px; padding: 1px 8px; font-size: 11.5px; font-weight: 800; margin-left: 6px; }
.cart-totals { background: var(--surface-2); border-radius: 8px; padding: 12px 14px; margin-top: 14px; }
.cart-totals div { display: flex; justify-content: space-between; padding: 3px 0; }

/* ---- HelchPOS brand + collapsible Inventory tab ---- */
.brand-name { font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; font-weight: 700; font-size: 20px; letter-spacing: -0.02em; }
.nav-drop { display: flex; align-items: center; gap: 8px; width: 100%; background: none; border: none; color: var(--muted); padding: 9px 12px; border-radius: 8px; font-size: 14px; cursor: pointer; text-align: left; }
.nav-drop:hover { background: var(--surface-2); color: var(--text); }
.drop-arrow { margin-left: auto; font-size: 11px; }
/* (The old collapsible `.nav-sub { display:none }` lived here. Nothing has
   drawn that dropdown since the StoreKit sidebar of 2026-08-26, and its rule
   silently hid the new under-hub list of v5.567.0 — see `.nav-sub` up top.) */

th.sortable { cursor: pointer; user-select: none; }
th.sortable:hover { color: var(--text); }

/* ---- Order hub (Place order) ---- */
.chip-row { display: flex; flex-wrap: wrap; gap: 8px; flex: 1; }
.chip { background: var(--surface); border: 1px solid var(--border); color: var(--muted);
  border-radius: 999px; padding: 7px 14px; font-size: 13px; cursor: pointer; }
.chip:hover { border-color: var(--primary); color: var(--text); }
.chip.on { background: var(--primary); border-color: var(--primary); color: var(--primary-ink); }
.chip-badge { background: rgba(255,92,92,.25); color: #ffb3b3; border-radius: 999px; padding: 1px 7px; font-size: 11px; margin-left: 4px; }
.chip.on .chip-badge { background: rgba(255,255,255,.25); color: #fff; }
.hub-sup-head { display: flex; justify-content: space-between; gap: 14px; align-items: center; padding: 14px 18px; border-bottom: 1px solid var(--border); flex-wrap: wrap; }
.hub-sup-foot { display: flex; justify-content: flex-end; gap: 16px; align-items: center; padding: 12px 18px; border-top: 1px solid var(--border); flex-wrap: wrap; }
.hub-totline { color: var(--muted); font-size: 13px; }
.hub-qty { background: var(--surface-2); border: 1px solid var(--border); color: var(--text); border-radius: 6px; padding: 6px 8px; font-size: 13px; }
.hub-qty:focus { border-color: var(--primary); outline: none; }

/* ---- Sell (till) ---- */
.sell-layout { display: grid; grid-template-columns: 1fr 330px; gap: 18px; align-items: start; }
.sell-cat { margin: 18px 0 10px; font-size: 13px; font-weight: 700; letter-spacing: .06em; text-transform: uppercase; color: var(--muted); }
.sell-cat:first-child { margin-top: 0; }
.sell-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(min(190px, 100%), 1fr)); gap: 10px; }
.sell-item { display: flex; flex-direction: column; gap: 6px; align-items: flex-start; text-align: left;
  background: var(--surface); border: 1px solid var(--border); border-radius: 10px; padding: 12px 14px;
  color: var(--text); font-size: 13px; cursor: pointer; }
.sell-item:hover { border-color: var(--primary); background: var(--surface-2); }
.sell-item strong { color: var(--green); }
.sell-basket { position: sticky; top: 20px; }
.sell-line { display: flex; align-items: center; justify-content: space-between; gap: 8px; padding: 8px 0; border-bottom: 1px solid var(--border); font-size: 13px; }
.sell-line-name { flex: 1; }
.sell-total { display: flex; justify-content: space-between; font-size: 17px; padding: 14px 0; }
@media (max-width: 1000px) { .sell-layout { grid-template-columns: 1fr; } .sell-basket { position: static; } }

/* ---- Price lists ---- */
.group-row td { background: var(--surface-2); color: var(--muted); font-weight: 600; font-size: 12px; text-transform: uppercase; letter-spacing: .04em; }
.price-input { width: 92px; text-align: right; background: var(--surface-2); border: 1px solid var(--border); color: var(--text); border-radius: 6px; padding: 6px 8px; font-size: 13px; }
.price-input:focus { border-color: var(--primary); outline: none; }

/* ---- Login page (Lightspeed-style, light theme) ---- */
.auth-screen { position: fixed; inset: 0; z-index: 100; overflow: auto; background: #f2f2f3; color: #18181b; display: block; padding: 0; }
.ls-bar { background: #000; display: flex; align-items: center; justify-content: space-between; padding: 15px 26px; }
.ls-brand { font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; font-weight: 700; font-size: 21px; color: #fff; letter-spacing: -0.02em; }
.ls-help { color: #fff; font-size: 15px; text-decoration: none; }
.ls-card { background: #fff; border: 1px solid #e4e4e7; border-radius: 6px; box-shadow: 0 1px 3px rgba(0,0,0,.07); max-width: 860px; margin: 96px auto 0; padding: 52px 48px 56px; }
.ls-title { margin: 0 0 30px; font-size: 27px; font-weight: 700; color: #18181b; }
.ls-sub { margin: -18px 0 26px; color: #52525b; font-size: 15px; }
.ls-label { display: block; font-weight: 600; font-size: 15px; color: #27272a; margin: 20px 0 8px; }
.ls-input { width: 100%; background: #fff; border: 1px solid #d4d4d8; border-radius: 4px; padding: 13px 14px; font-size: 15px; color: #18181b; }
.ls-input:focus { outline: none; border-color: #4f46e5; box-shadow: 0 0 0 1px #4f46e5; }
.ls-row { display: flex; align-items: center; justify-content: space-between; margin-top: 30px; }
.ls-links a { color: #18181b; font-size: 15px; text-decoration: underline; }
.ls-btn { background: #4f46e5; color: #fff; border: none; border-radius: 6px; padding: 14px 32px; font-size: 15px; font-weight: 600; cursor: pointer; }
.ls-btn:hover { filter: brightness(1.08); }
.ls-err { color: #dc2626; font-size: 14px; margin: 14px 0 0; min-height: 18px; }
.ls-foot { text-align: center; margin: 28px 0 48px; }
.ls-foot a { color: #18181b; font-size: 15px; text-decoration: underline; }
@media (max-width: 920px) { .ls-card { margin: 40px 16px 0; padding: 32px 22px 40px; } }

/* ---- Team / timecards ---- */
.tc-day { font-weight: 700; font-size: 13.5px; padding: 14px 0 4px; color: var(--text); }
.tc-row { display: flex; align-items: center; gap: 12px; padding: 11px 0; border-bottom: 1px solid var(--border); }
.tc-row:last-child { border-bottom: none; }
.tc-avatar { display: inline-flex; align-items: center; justify-content: center; width: 34px; height: 34px; border-radius: 50%; background: rgba(139,124,248,.18); color: #b3a9fb; font-weight: 700; font-size: 12.5px; flex: none; }
.tc-times { text-align: right; font-weight: 600; font-size: 13.5px; }
.tc-approve { background: #2a9d74; border-color: #2a9d74; color: #fff; }
.recon-bar { display: flex; align-items: center; gap: 10px; margin: 6px 0; } .recon-bar .hint { width: 78px; }
/* **`.ls-remember` is a login-page class that fifteen back-office controls
   borrowed.** `.auth-screen` is a light page — `#f2f2f3` behind `#18181b` — and
   `#27272a` was right there. In the dark app it is `rgb(39,39,42)` on
   `rgb(23,26,33)`: measured at **1.17:1**, against a 4.5:1 floor for body text.
   Not dim. The same colour. "Removable by customer?", "QR ordering", "POS
   tills" and every per-surcharge On/QR/POS label were invisible on the Business
   screen, and the tick box beside them was the only sign they were there.
   It takes its colour from whatever it is sitting on now, and the login page
   states its own — a class used in two themes cannot name one of them. */
.ls-remember { display: flex; align-items: center; gap: 9px; margin-top: 18px; font-size: 14.5px; color: inherit; }
.ls-remember input { width: 16px; height: 16px; accent-color: var(--primary, #4f46e5); }
.auth-screen .ls-remember { color: #27272a; }
.auth-screen .ls-remember input { accent-color: #4f46e5; }

/* ---- POS Register (fixed-proportion stage) ---- */
.pos-root { position: fixed; inset: 0; z-index: 40; background: var(--k-letterbox); display: flex; align-items: center; justify-content: center; overflow: hidden; }
.pos-stage { flex: none; width: 1366px; height: 1000px; background: var(--k-stage); color: var(--k-stage-ink); display: flex; flex-direction: column; transform-origin: center; font-size: 15px; }
.pos-top { display: flex; align-items: center; justify-content: space-between; padding: 0 22px; height: 58px; background: var(--k-nav); border-bottom: 1px solid var(--k-edge-2); flex: 0 0 58px; }
.pos-logout { background: none; border: none; color: #ff5c5c; font-size: 16px; cursor: pointer; }
.pos-top-name { font-weight: 700; font-size: 16px; }
.pos-top-icons button { background: none; border: none; color: var(--k-ink); font-size: 19px; cursor: pointer; margin-left: 14px; }
.pos-main { flex: 1; display: flex; min-height: 0; }
.pos-nav { display: flex; height: 62px; flex: 0 0 62px; background: var(--k-nav); border-top: 1px solid var(--k-edge-2); }
.pos-nav button { flex: 1; background: none; border: none; color: var(--k-muted); font-size: 19px; cursor: pointer; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 2px; }
.pos-nav button span { font-size: 12px; }
.pos-nav button.active { color: #4f8cff; }

/* left panel */
.pos-left { width: 470px; flex: 0 0 470px; display: flex; flex-direction: column; background: #101218; border-right: 1px solid #23262e; }
.pos-order-head { display: flex; justify-content: space-between; align-items: center; padding: 14px 18px 6px; font-size: 17px; }
.pos-linkbtn { background: none; border: none; color: #4f8cff; font-size: 15px; cursor: pointer; }
.pos-tagrow { display: flex; gap: 8px; padding: 4px 18px 10px; border-bottom: 1px solid #23262e; }
.pos-chip { background: #1a1d24; border: 1px solid #2a2e38; border-radius: var(--pos-r-s); padding: 5px 10px; font-size: 12.5px; color: #aeb4bf; }
.pos-lines { flex: 1; overflow-y: auto; min-height: 0; }
.pos-line { display: flex; align-items: center; gap: 10px; padding: 10px 18px; border-bottom: 1px solid #1c1f27; font-size: 15px; }
.pos-line-qty { width: 20px; font-weight: 700; }
.pos-line-name { flex: 1; }
.pos-line-note { color: #8b93a1; font-size: 12px; margin-top: 2px; }
.pos-line-price { font-variant-numeric: tabular-nums; }
/* Course banding on the current sale (course mode) — a quiet heading over the
   lines of one course, matching the way the kitchen board bands the ticket. */
.pos-course-band { padding: 8px 18px 4px; font-size: 11px; font-weight: 700;
  letter-spacing: .06em; text-transform: uppercase; color: #8b93a1; }
/* The "Which course?" picker — cards that fill one phone width, no scroll. */
.pos-course-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 10px; padding: 4px 0 2px; }
.pos-course-card { padding: 16px 10px; border-radius: var(--pos-r); background: #1f232c;
  color: #e8eaee; border: 1px solid #2a2e38; cursor: pointer; font-size: 16px; font-weight: 600; }
.pos-course-card:active { background: #3b6ff5; border-color: #3b6ff5; color: #fff; }
.pos-line-ctl button { width: 30px; height: 30px; border-radius: var(--pos-r); background: #1c1f27; color: #e8eaee; border: 1px solid #2a2e38; cursor: pointer; margin-left: 5px; font-size: 15px; }
.pos-empty { color: #6c7482; text-align: center; padding: 40px 12px; }
/* The "Add on" row — one line above the totals, hidden overflow so it can
   never grow the panel or scroll it (the whole condition for it being on the
   register at all). */
.pos-upsell { display: flex; align-items: center; gap: 6px; padding: 6px 12px 6px 18px; border-top: 1px solid #23262e; overflow: hidden; flex-wrap: nowrap; white-space: nowrap; }
.pos-upsell-label { color: #8b93a1; font-size: 12px; flex: none; }
.pos-upsell-chip { display: inline-flex; align-items: center; gap: 6px; padding: 5px 10px; border-radius: 999px; background: #1c1f27; border: 1px solid #2a2e38; color: #e8eaee; font-size: 12.5px; cursor: pointer; flex: none; }
.pos-upsell-chip em { font-style: normal; color: #8b93a1; font-variant-numeric: tabular-nums; }
.pos-upsell-chip:active { background: #262b36; }
.pos-totals { border-top: 1px solid #23262e; padding: 10px 18px; }
.pos-totals > div { display: flex; justify-content: space-between; font-size: 17px; }
.pos-vatline { color: #8b93a1; font-size: 13px !important; margin-top: 2px; }
.pos-keypad-hint { display: flex; justify-content: space-between; background: #14161c; color: #8b93a1; font-size: 12.5px; padding: 7px 18px; border-top: 1px solid #23262e; }
.pos-keypad-hint strong { color: #4f8cff; }
.pos-keypad { display: grid; grid-template-columns: 1fr 1fr 1fr 1.35fr; gap: 7px; padding: 10px 14px 14px; }
.pos-keypad button { height: 56px; border-radius: var(--pos-r); border: none; background: #1a1d24; color: #e8eaee; font-size: 20px; cursor: pointer; }
.pos-keypad button:active { background: #262a34; }
.pos-key-c { color: #ff5c5c !important; }
.pos-side { font-size: 15px !important; background: #1f232c !important; color: #cdd3dd !important; }
.pos-side-blue { background: #3b6ff5 !important; color: #fff !important; }
.pos-side-green { background: #1f9d61 !important; color: #fff !important; font-size: 18px !important; }

/* The keypad quick-action picker in POS settings (Register pane) — the vendor
   chooses which buttons sit beside the number pad. Chips, not switches, because
   the choice is "which of these", and order matters (top to bottom). */
.pos-qa-pick { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 6px; }
.pos-qa-chip { background: #1a1d24; border: 1px solid #2a2e38; color: #cdd3dd;
  border-radius: 999px; padding: 8px 15px; font-size: 13.5px; cursor: pointer; }
.pos-qa-chip.on { background: #3b6ff5; border-color: #3b6ff5; color: #fff; }
.pos-qa-chip.gone { opacity: .5; cursor: not-allowed; }

/* category rail */
/* The menu/sections column and the item tiles match the Lightspeed K-Series
   proportions Jamal sent (2026-09-02): the sections column is wider (≈15% of the
   stage, not 11%) and the item tiles are larger, so the grid reads as two-to-
   three big buttons a row rather than five small ones. The exact split is his
   eye's to confirm on a screenshot. */
.pos-rail { width: 200px; flex: 0 0 200px; overflow-y: auto; background: #0e1015; border-right: 1px solid #23262e; display: flex; flex-direction: column; --pos-sc-bg: #0e1015; }
.pos-cat { border: none; border-left: 5px solid transparent; background: transparent; color: #cdd3dd; min-height: 64px; padding: 8px 10px; font-size: 13.5px; cursor: pointer; text-align: center; border-bottom: 1px solid #1c1f27;
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 5px; }
.pos-cat.on { color: #fff; font-weight: 700; }
/* The section's picture on the desktop rail, as a thumbnail rather than a
   background. The rail is a narrow strip of text: a photograph stretched behind
   the word makes the word harder to read, which is the opposite of what the
   picture is for. See posGroupTileStyle in pos.js for the phone, where the tile
   is big enough to wear the photograph properly. */
.pos-cat-img {
  flex: 0 0 auto; width: 30px; height: 30px; border-radius: 7px;
  background-size: cover; background-position: center;
}

/* item grid */
.pos-right { flex: 1; display: flex; flex-direction: column; background: #07080b; min-width: 0; }
.pos-grid-head { text-align: center; font-weight: 700; padding: 12px; font-size: 16px; background: #000; }
.pos-grid { flex: 1; overflow-y: auto; display: grid; grid-template-columns: repeat(auto-fill, minmax(min(230px, 100%), 1fr)); gap: 12px; padding: 14px; align-content: start;
  /* A long group scrolls here, and it says so — a themed scrollbar and a
     scroll-shadow at the fold (QA round 2, finding 32; the register scroll
     build #32/#34/#36 gave the same to the rail, Past Orders and the Live
     board — see the shared `.pos-stage .pos-grid …` rule just below). */
  --pos-sc-bg: #07080b; }

/* --- POS scroll build (#32/#34/#36) — the register's flagged panels scroll
   inside themselves, and now show it. QA saw "content truncated / too small /
   not scrollable": the panels already scrolled (min-height:0 + overflow-y:auto),
   but an overlay scrollbar on a tablet is invisible at rest, so a full grid read
   as a cut-off one and a 14-category rail hid "White Wine" behind its own edge.
   Two affordances, no resizing — the compact K-Series sizing Jamal set is
   untouched (his answer to #34/#36 was "keep compact, fix truncation only"):
     1. A themed 10px scrollbar for desktop and Chromebook tills.
     2. A scroll-shadow for every device, touch included — Lea Verou's background
        trick. Two cover gradients painted in the panel's own colour (--pos-sc-bg)
        ride with the content (background-attachment: local) and hide the shadow
        at the very top and bottom; two shadow radials stay fixed to the panel and
        darken an edge only while content is hidden beyond it. So an edge that can
        scroll looks like it can, and one that cannot shows nothing.
   Scoped to `.pos-stage`, which only the desktop/tablet stage has (the phone till
   is `.pos-root.posm` with no stage), so the phone is untouched — Jamal's
   "desktop/tablet only" and "only the flagged screens". */
.pos-stage .pos-grid, .pos-stage .pos-rail, .pos-stage #tx-rows,
.pos-stage .pos-rcpt-paper, .pos-stage .pos-kds {
  scrollbar-width: thin;
  scrollbar-color: #4a5262 transparent;
  background-image:
    linear-gradient(var(--pos-sc-bg, #0c0e12) 32%, rgba(0, 0, 0, 0)),
    linear-gradient(rgba(0, 0, 0, 0), var(--pos-sc-bg, #0c0e12) 68%),
    radial-gradient(farthest-side at 50% 0, rgba(0, 0, 0, .6), rgba(0, 0, 0, 0)),
    radial-gradient(farthest-side at 50% 100%, rgba(0, 0, 0, .6), rgba(0, 0, 0, 0));
  background-position: top, bottom, top, bottom;
  background-size: 100% 30px, 100% 30px, 100% 15px, 100% 15px;
  background-repeat: no-repeat;
  background-attachment: local, local, scroll, scroll;
}
.pos-stage .pos-grid::-webkit-scrollbar, .pos-stage .pos-rail::-webkit-scrollbar,
.pos-stage #tx-rows::-webkit-scrollbar, .pos-stage .pos-rcpt-paper::-webkit-scrollbar,
.pos-stage .pos-kds::-webkit-scrollbar { width: 10px; height: 10px; }
.pos-stage .pos-grid::-webkit-scrollbar-track, .pos-stage .pos-rail::-webkit-scrollbar-track,
.pos-stage #tx-rows::-webkit-scrollbar-track, .pos-stage .pos-rcpt-paper::-webkit-scrollbar-track,
.pos-stage .pos-kds::-webkit-scrollbar-track { background: transparent; }
.pos-stage .pos-grid::-webkit-scrollbar-thumb, .pos-stage .pos-rail::-webkit-scrollbar-thumb,
.pos-stage #tx-rows::-webkit-scrollbar-thumb, .pos-stage .pos-rcpt-paper::-webkit-scrollbar-thumb,
.pos-stage .pos-kds::-webkit-scrollbar-thumb {
  background: #4a5262; border-radius: 5px; border: 2px solid transparent; background-clip: padding-box;
}
.pos-stage .pos-grid::-webkit-scrollbar-thumb:hover, .pos-stage .pos-rail::-webkit-scrollbar-thumb:hover,
.pos-stage #tx-rows::-webkit-scrollbar-thumb:hover, .pos-stage .pos-rcpt-paper::-webkit-scrollbar-thumb:hover,
.pos-stage .pos-kds::-webkit-scrollbar-thumb:hover { background: #5a6474; }
.pos-item { height: 140px; background: #191c23; border: none; border-bottom: 5px solid #3b6ff5; border-radius: var(--pos-r); color: #e8eaee; font-size: 16.5px; cursor: pointer; padding: 10px; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 6px; }
.pos-item:active { background: #232733; }
.pos-item em { color: #8b93a1; font-size: 12px; font-style: normal; }
.pos-paybar { height: 66px; flex: 0 0 66px; margin: 0 12px 12px; border: none; border-radius: var(--pos-r); background: #1f9d61; color: #fff; font-size: 20px; font-weight: 700; cursor: pointer; }
.pos-paybar:disabled { opacity: .45; cursor: default; }

/* payment screen */
.pos-paywrap { position: absolute; inset: 0; background: #0c0e12; display: flex; flex-direction: column; z-index: 5; }
.pos-root .pos-stage { position: relative; }
.pos-paytop { display: flex; align-items: center; justify-content: space-between; height: 58px; padding: 0 22px; background: #14161c; border-bottom: 1px solid #23262e; }
.pos-paycols { flex: 1; display: flex; gap: 14px; padding: 16px; min-height: 0; }
.pos-paycol { flex: 1; background: #101218; border-radius: var(--pos-r); padding: 16px; overflow-y: auto; display: flex; flex-direction: column; gap: 8px; }
.pos-paycol h3 { margin: 0; font-size: 18px; }
.pos-paysub { color: #8b93a1; margin: 0 0 8px; font-size: 13px; }
.pos-paycat { color: #8b93a1; font-size: 13px; margin-top: 8px; }
.pos-method { display: flex; align-items: center; gap: 10px; background: #1a1d24; border: 1px solid #2a2e38; color: #e8eaee; border-radius: var(--pos-r); padding: 15px 14px; font-size: 16px; cursor: pointer; text-align: left; }
.pos-method.on { background: #3b6ff5; border-color: #3b6ff5; color: #fff; }
.pos-method:disabled { opacity: .45; cursor: default; }
.pos-method em { margin-left: auto; font-size: 12px; font-style: normal; color: inherit; opacity: .8; }
.pos-payitems { flex: 1; overflow-y: auto; }
.pos-payline { display: flex; gap: 10px; padding: 8px 2px; border-bottom: 1px solid #1c1f27; }
.pos-payline span:nth-child(2) { flex: 1; }
.pos-paytotal { display: flex; justify-content: space-between; padding-top: 10px; font-size: 17px; }
/* On a split bill the summary totals the sale, and the portion being taken
   right now sits under it — a list of 9.00 over "Total 3.00" is arithmetic
   nobody can follow. */
.pos-paytotal-part { border-top: 1px solid #2a2e38; margin-top: 8px; padding-top: 8px;
  font-size: 15px; color: #4f8cff; }
.pos-paytotal-part strong { color: #4f8cff; }
.pos-payrow { display: flex; justify-content: space-between; align-items: center; background: #1a1d24; border-radius: var(--pos-r); padding: 13px 14px; font-size: 15px; }
/* The icon column on a register settings row. Same 30px as the kitchen sheet's
   so the two screens do not disagree about where a setting's picture lives. */
/* The venue's standing answer beside a switch it has locked (T3b) — the
   reason the control will not move, said where somebody is about to try. */
/* A settings row whose control is a card group: the cards drop to their own
   full-width line, left to right, so the lit one is obvious at a glance. */
.pos-payrow-cards .kset-cards { justify-content: flex-start; margin-top: 10px; }
.pos-payrow-note { font-style: normal; font-size: 12px; color: var(--muted); margin-left: 6px; }
.pos-payrow .pos-toggle[disabled] { opacity: .55; cursor: not-allowed; }
.pos-payrow .pos-setic { flex: none; width: 26px; display: flex; align-items: center; color: var(--muted); align-self: flex-start; margin-top: 2px; }
.pos-payrow .pos-setmain { flex: 1 1 auto; min-width: 0; padding-right: 12px; }
.pos-payrow-t { display: flex; align-items: center; gap: 8px; font-weight: 700; margin-bottom: 8px; }
.pos-paycard { background: #1a1d24; border-radius: var(--pos-r); padding: 13px 14px; }
.pos-paycard input { width: 100%; background: #10131a; border: 1px solid #2a2e38; color: #e8eaee; border-radius: var(--pos-r); padding: 9px 10px; font-size: 15px; }
.pos-flabel { display: block; color: #8b93a1; font-size: 12.5px; margin: 8px 0 4px; }
.pos-vline { display: flex; justify-content: space-between; padding: 6px 0; font-size: 14px; }
.pos-quick { display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px; margin-top: 8px; }
.pos-quick button { background: #262a34; border: none; color: #e8eaee; border-radius: var(--pos-r); padding: 14px 0; font-size: 16px; cursor: pointer; }
.pos-quick button.on { background: #3b6ff5; color: #fff; }
.pos-toggle { width: 46px; height: 26px; border-radius: var(--pos-r); background: #2a2e38; border: none; position: relative; cursor: pointer; }
.pos-toggle i { position: absolute; top: 3px; left: 3px; width: 20px; height: 20px; border-radius: 50%; background: #fff; transition: left .15s; }
.pos-toggle.on { background: #3b6ff5; }
.pos-toggle.on i { left: 23px; }
.pos-paygo { height: 62px; border: none; border-radius: var(--pos-r); background: #1f9d61; color: #fff; font-size: 19px; font-weight: 700; cursor: pointer; margin-top: auto; }
.pos-paygo:disabled { opacity: .45; }
.pos-changedue { text-align: center; font-size: 22px; background: rgba(31,157,97,.15); border: 1px solid rgba(31,157,97,.4); border-radius: var(--pos-r); padding: 14px; margin-bottom: 12px; }
/* --- The payment confirmation (v5.276.0) ---------------------------------- */
.pos-paid { text-align: center; padding: 6px 0 4px; }
.pos-paid-tick { width: 64px; height: 64px; margin: 0 auto 14px; border-radius: 50%;
  display: grid; place-items: center; background: #16a34a; color: #fff;
  animation: pos-paid-pop .32s cubic-bezier(.2,.9,.3,1.2); }
@keyframes pos-paid-pop { from { transform: scale(.6); opacity: 0 } to { transform: none; opacity: 1 } }
@media (prefers-reduced-motion: reduce) { .pos-paid-tick { animation: none } }
.pos-paid-said { font-size: 22px; font-weight: 800; letter-spacing: -.01em; }
.pos-paid-sub { font-size: 14px; color: var(--muted, #6b7280); margin-top: 5px; }
/* **The change outranks the tick.** Somebody is standing at the counter with
   their hand out — so when there is change the tick shrinks and this is the
   biggest thing on the screen. See paidscreen.js refusal 1. */
.pos-paid-change { margin: 18px auto 0; padding: 14px 22px; border-radius: 14px;
  background: rgba(22,163,74,.12); display: inline-flex; flex-direction: column; gap: 2px; }
.pos-paid-change span { font-size: 13px; font-weight: 600; color: var(--muted, #6b7280);
  text-transform: uppercase; letter-spacing: .06em; }
.pos-paid-change strong { font-size: 40px; line-height: 1.1; font-variant-numeric: tabular-nums; }
.pos-paid.has-change .pos-paid-tick { width: 40px; height: 40px; margin-bottom: 10px; }
.pos-paid.has-change .pos-paid-said { font-size: 17px; }
.pos-paid-voucher { margin-top: 12px; font-size: 13.5px; color: var(--muted, #6b7280); }
/* Offered, not shown — refusal 3. */
.pos-paid-rcpt { margin-top: 16px; }
.pos-paid-rcpt summary { cursor: pointer; font-size: 13.5px; color: var(--muted, #6b7280);
  text-align: center; padding: 6px; }
.pos-paid-rcpt[open] summary { margin-bottom: 8px; }

.pos-receipt { background: #d8d5c8; color: #23241f; border-radius: var(--pos-r); padding: 18px 16px; font: 13px/1.5 ui-monospace, SFMono-Regular, Menlo, monospace; white-space: pre; overflow-x: auto; margin: 0 auto; max-width: 380px; }

/* receipts view */
.pos-rcpts { flex: 1; display: flex; gap: 14px; padding: 16px; min-height: 0; width: 100%; }
.pos-rcpt-list { width: 46%; display: flex; flex-direction: column; gap: 10px; min-height: 0; }
.pos-rcpt-list input { background: #1a1d24; border: 1px solid #2a2e38; color: #e8eaee; border-radius: var(--pos-r); padding: 12px 14px; font-size: 14px; }
#rl-rows { flex: 1; overflow-y: auto; display: flex; flex-direction: column; gap: 8px; }
.pos-rcpt-row { display: grid; grid-template-columns: 1fr auto; gap: 2px 10px; background: #171a21; border: 1px solid #23262e; border-left: 4px solid #3b6ff5; border-radius: var(--pos-r); padding: 10px 14px; color: #e8eaee; cursor: pointer; text-align: left; }
.pos-rcpt-row.void { opacity: .55; border-left-color: #ff5c5c; }
.pos-rcpt-ref { font-weight: 700; }
.pos-rcpt-meta { grid-row: 2; color: #8b93a1; font-size: 12px; }
.pos-rcpt-amt { grid-row: 1 / span 2; align-self: center; font-size: 16px; font-weight: 700; }
.pos-rcpt-amt em { color: #ff8a8a; font-size: 11px; font-style: normal; margin-left: 6px; }
.pos-rcpt-paper { flex: 1; overflow-y: auto; background: #101218; border-radius: var(--pos-r); padding: 22px; --pos-sc-bg: #101218; }
.pos-rcpt-actions { display: flex; justify-content: center; gap: 26px; margin-top: 18px; }
.pos-rcpt-actions button { background: none; border: none; color: #cdd3dd; cursor: pointer; font-size: 22px; display: flex; flex-direction: column; align-items: center; gap: 4px; }
.pos-rcpt-actions button span { font-size: 11.5px; color: #8b93a1; }
.pos-rcpt-actions button:disabled { opacity: .4; }
.pos-top-icons select { width: auto; background: var(--k-control); border: 1px solid var(--k-edge); color: var(--k-ink-2); border-radius: var(--pos-r); padding: 6px 8px; font-size: 13px; }

/* ---- POS v2.5: combo slots, variants, settings, previews ---- */
.pos-line-edit { background: none; border: none; color: #8b93a1; cursor: pointer; padding: 4px; }
.pos-line-edit:hover { color: #e8eaee; }
.pos-item { color: #e8eaee; position: relative; }
.pos-item:active { background: var(--p) !important; background-image: none !important; }
.pos-combo { flex: 1; display: flex; gap: 14px; padding: 16px; min-height: 0; }
.pos-combo-left { width: 400px; flex: 0 0 400px; display: flex; flex-direction: column; gap: 12px; overflow-y: auto; }
.pos-combo-card { background: #101218; border-radius: var(--pos-r); padding: 14px 16px; }
.pos-combo-head { display: flex; justify-content: space-between; align-items: center; margin-bottom: 8px; }
.pos-combo-item { display: flex; justify-content: space-between; font-size: 15px; padding-bottom: 8px; border-bottom: 1px solid #1c1f27; margin-bottom: 8px; }
.pos-slot { display: inline-block; border: none; border-radius: var(--pos-r-s); padding: 6px 12px; margin: 6px 0 2px; font-size: 13px; font-weight: 700; cursor: pointer; background: #b8842a; color: #fff; }
.pos-slot.done { background: #1f9d61; }
.pos-slot.live { outline: 2px solid #4f8cff; outline-offset: 2px; }
.pos-slot-val { color: #8b93a1; font-size: 13.5px; padding: 2px 2px 6px; }
.pos-slot-val em { color: #5f6b7a; font-style: normal; font-size: 12px; margin-left: 6px; }
.pos-combo-total { display: flex; justify-content: space-between; border-top: 1px dashed #2a2e38; padding-top: 8px; font-size: 15px; }
.pos-addmenu { border: 1px dashed #3b6ff5; background: transparent; color: #4f8cff; border-radius: var(--pos-r); padding: 14px; font-size: 15px; cursor: pointer; }
.pos-ticket { margin-top: auto; border: none; border-radius: var(--pos-r); background: #3b6ff5; color: #fff; padding: 18px; font-size: 17px; font-weight: 700; cursor: pointer; }
.pos-ticket:disabled { opacity: .45; }
.pos-combo-right { flex: 1; display: flex; flex-direction: column; background: #07080b; border-radius: var(--pos-r); overflow: hidden; min-width: 0; }
.pos-variant { display: flex; justify-content: space-between; align-items: center; width: 100%; background: var(--surface-2); border: 1px solid var(--border); color: var(--text); border-radius: var(--pos-r); padding: 16px; font-size: 16px; cursor: pointer; margin-bottom: 8px; }
.pos-variant:hover { border-color: var(--primary); }
.pos-settings { flex: 1; max-width: 640px; margin: 0 auto; padding: 22px; display: flex; flex-direction: column; gap: 10px; overflow-y: auto; width: 100%; }
.pos-settings h3 { margin: 0; }
.pos-ic { display: inline-block; vertical-align: -4px; }
.pos-nav .pos-ic { vertical-align: 0; }

/* ---- POS v2.6: receipt actions + settings rail ---- */
.pos-rcpt-tabs { display: flex; gap: 8px; }
.pos-rcpt-tabs button { flex: 1; background: #171a21; border: 1px solid #23262e; color: #8b93a1; border-radius: var(--pos-r); padding: 9px 10px; font-size: 13px; font-weight: 600; cursor: pointer; }
.pos-rcpt-tabs button.on { background: #1e2836; border-color: #3b6ff5; color: #9cbcff; }
.pos-rcpt-row { grid-template-columns: auto 1fr auto; }
.pos-rcpt-pay { grid-row: 1 / span 2; align-self: center; color: #8b93a1; display: flex; gap: 2px; }
.pos-rcpt-meta { grid-column: 2; }
.pos-ract-main { display: block; margin: 18px auto 0; border: none; border-radius: var(--pos-r); background: #3b6ff5; color: #fff; padding: 14px 34px; font-size: 16px; font-weight: 700; cursor: pointer; }
.pos-ract-sheet { display: flex; flex-direction: column; gap: 8px; }
.pos-ract { display: flex; align-items: flex-start; gap: 12px; width: 100%; text-align: left; background: var(--surface-2); border: 1px solid var(--border); color: var(--text); border-radius: var(--pos-r); padding: 12px 14px; font-size: 14.5px; cursor: pointer; }
.pos-ract:hover:not(:disabled) { border-color: var(--primary); }
.pos-ract:disabled { opacity: .45; cursor: default; }
.pos-ract .pos-ic { margin-top: 1px; flex: 0 0 auto; }
.pos-refline { display: flex; align-items: center; gap: 10px; padding: 10px 4px; border-bottom: 1px solid var(--border); cursor: pointer; }
.pos-refline input { width: 18px; height: 18px; }
.pos-refline span { flex: 1; }
.pos-set { flex: 1; display: flex; min-height: 0; width: 100%; }
.pos-set-rail { width: 250px; flex: 0 0 250px; background: #101218; border-right: 1px solid #1c1f27; overflow-y: auto; padding: 14px 0; }
/* An icon beside every pane name, v5.197.0 — the same "look for a picture,
   not a label" the kitchen sheet got. Flex rather than block, so the glyph and
   the word sit on one line at a fixed left edge. */
.pos-set-rail button { display: flex; align-items: center; gap: 11px; width: 100%; text-align: left; background: none; border: none; border-left: 3px solid transparent; color: #aab2bf; padding: 12px 18px; font-size: 14.5px; cursor: pointer; }
.pos-set-rail button .pos-ic { flex: none; opacity: .8; }
.pos-set-rail button.on .pos-ic { opacity: 1; }
.pos-set-rail button.on { background: #171d2a; border-left-color: #3b6ff5; color: #fff; font-weight: 600; }
.pos-set-body { flex: 1; overflow-y: auto; padding: 22px 28px; display: flex; flex-direction: column; gap: 10px; max-width: 720px; }
.pos-set-body h3 { margin: 0; }
.pos-set-quick { display: flex; gap: 10px; }
.pos-set-quick button { flex: 1; display: flex; flex-direction: column; align-items: center; gap: 6px; background: #171a21; border: 1px solid #23262e; color: #e8eaee; border-radius: var(--pos-r); padding: 16px 8px; font-size: 13.5px; font-weight: 600; cursor: pointer; }
.pos-set-quick button:hover { border-color: #3b6ff5; }
.pos-status { border-radius: var(--pos-r-s); padding: 4px 12px; font-size: 12.5px; font-weight: 700; white-space: nowrap; }
.pos-status.ok { background: rgba(31,157,97,.18); color: #4fd08a; }
.pos-status.off { background: rgba(139,147,161,.15); color: #8b93a1; }
.pos-support-phone { background: #101218; border: 1px solid #23262e; border-radius: var(--pos-r); padding: 16px 18px; text-align: center; }
.pos-support-phone a { display: block; color: #4f8cff; font-size: 26px; font-weight: 700; text-decoration: none; margin-top: 4px; }
.pos-qa { background: #101218; border: 1px solid #23262e; border-radius: var(--pos-r); padding: 0 14px; }
.pos-qa summary { padding: 12px 0; font-weight: 600; cursor: pointer; }
.pos-qa p { color: #aab2bf; margin: 0 0 12px; font-size: 13.5px; line-height: 1.55; }

/* Menu creator: sections as the till's tiles, dishes you drag between them.
   Jamal (2026-09-07): *"the sections on here should be just like on the till,
   and they should be tighter together… just have the drag-up and drag-down."* */
.menu-sec { border-radius: var(--radius); padding: 0 2px 2px; margin: 0 -2px 6px; transition: box-shadow .12s, background .12s; }
.menu-sec-head { display: flex; align-items: center; gap: 10px; margin: 4px 0 4px; min-height: 48px; }
.menu-sec.shut .menu-sec-body { display: none; }
.menu-sec.shut .menu-sec-head { margin-bottom: 4px; }
.menu-sec-fold {
  flex: 0 0 auto; width: 26px; height: 26px; border-radius: 7px; cursor: pointer;
  background: var(--surface-2); border: 1px solid var(--border); color: var(--text);
  font-size: 12px; line-height: 1; display: flex; align-items: center; justify-content: center;
  transition: transform .14s ease, border-color .12s;
}
.menu-sec-fold:hover { border-color: var(--primary); }
.menu-sec.shut .menu-sec-fold { transform: rotate(-90deg); }
/* The tile: the register's rule (posmTileStyle) — picture darkened, or the
   secondary colour as ground and the primary as the bar along the bottom. */
.menu-sec-tile {
  flex: 0 0 auto; min-width: 128px; max-width: 240px; height: 46px; padding: 0 14px;
  display: inline-flex; align-items: center; justify-content: center; text-align: center;
  border: none; border-bottom: 4px solid #3b6ff5; border-radius: 9px; cursor: pointer;
  color: #e8eaee; font: inherit; font-size: 13.5px; font-weight: 700; line-height: 1.15;
  background: #1a1f2c; transition: transform .08s ease, filter .12s;
}
.menu-sec-tile b { display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
.menu-sec-tile:hover { filter: brightness(1.12); }
.menu-sec-tile:active { transform: scale(.98); }
.menu-sec-tile.big { min-width: 200px; height: 76px; font-size: 16px; cursor: default; }
.sec-sheet-tile { display: flex; justify-content: center; padding: 6px 0 0; }
.sec-sheet-colours { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }
.sec-sheet-colours .field { display: flex; flex-direction: column; align-items: flex-start; gap: 6px; margin: 0 0 12px; }
.sec-colour { display: inline-flex; align-items: center; gap: 10px; }
.sec-colour input[type=color] { width: 56px; height: 34px; padding: 2px; border-radius: 8px; border: 1px solid var(--border); background: var(--surface-2); }
.sec-colour code { color: var(--muted); font-size: 12.5px; }
.sec-sheet-photo { margin: 6px 0 0; padding: 12px; border: 1px solid var(--border); border-radius: 10px; }
.sec-sheet-photo .field-label { display: block; font-size: 12.5px; font-weight: 600; color: var(--muted); margin-bottom: 8px; }
.menu-sec-count { margin: 0; white-space: nowrap; }
/* The handle. Rows are draggable everywhere, but a row that looks draggable
   nowhere is a feature nobody finds. `touch-action: none` so a finger on the
   grip drags the row instead of scrolling the page. */
.menu-grip {
  display: inline-flex; align-items: center; justify-content: center; width: 22px; height: 30px;
  color: var(--muted); cursor: grab; font-size: 15px; letter-spacing: -1px; user-select: none; touch-action: none;
}
.menu-grip.sec { width: 18px; }
.menu-grip:active { cursor: grabbing; }
[data-drag] { cursor: grab; }
[data-drag].dragging, .menu-sec.dragging { opacity: .4; }
.menu-sec.drop-on { background: rgba(79,140,255,.08); box-shadow: 0 0 0 2px var(--primary) inset; }
.menu-sec.drop-before { box-shadow: 0 -2px 0 0 var(--primary); }
.menu-sec.drop-after { box-shadow: 0 2px 0 0 var(--primary); }
.mi-row.drop-before { box-shadow: inset 0 2px 0 var(--primary); }
.mi-row.drop-after { box-shadow: inset 0 -2px 0 var(--primary); }
/* One grid, header and rows alike — every figure under its heading. */
.menu-sec-body { border: 1px solid var(--border); border-radius: 10px; background: var(--surface); overflow: hidden; }
.mi-head, .mi-row {
  display: grid; align-items: center; gap: 8px;
  grid-template-columns: 96px 22px minmax(0, 1fr) 84px 92px 72px 64px;
  padding: 6px 10px;
}
.mi-head { font-size: 11.5px; font-weight: 700; letter-spacing: .04em; text-transform: uppercase; color: var(--muted); border-bottom: 1px solid var(--border); padding: 8px 10px; }
.mi-row { border-bottom: 1px solid var(--border); }
.mi-row:last-child { border-bottom: none; }
.mi-row:hover { background: var(--surface-2); }
.mi-name { min-width: 0; overflow-wrap: anywhere; }
.mi-name .hint { margin: 2px 0 0; }
.mi-fig { white-space: nowrap; }
.mi-act { text-align: right; }
.mi-head .num, .mi-fig { text-align: right; }
/* Shown / Hidden, in words — the eye alone was "a bit confusing". */
.menu-vis {
  display: inline-flex; align-items: center; gap: 5px; height: 28px; padding: 0 9px 0 7px; border-radius: 999px;
  border: 1px solid var(--border); background: var(--surface-2); color: var(--muted);
  font: inherit; font-size: 12px; font-weight: 700; cursor: pointer; white-space: nowrap;
}
.menu-vis svg { width: 14px; height: 14px; }
.menu-vis.on { color: #34c759; border-color: rgba(52, 199, 89, .45); background: rgba(52, 199, 89, .1); }
.menu-vis.off { color: #c9a24a; border-color: rgba(201, 162, 74, .45); background: rgba(201, 162, 74, .1); }
.menu-vis.off span { text-decoration: line-through; }
.menu-vis:hover { border-color: var(--primary); }
.menu-vis.key { cursor: default; height: 24px; }
.menu-key { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin: 0 0 14px; }
.menu-key-sep { color: var(--muted); }
/* A phone: the row wraps to two lines and every figure carries its own label,
   so nothing scrolls sideways. */
@media (max-width: 860px) {
  .mi-head { display: none; }
  .mi-row {
    grid-template-columns: 96px 22px minmax(0, 1fr) 64px;
    grid-template-areas: "vis grip name act" "f1 f1 f2 f3";
    row-gap: 4px;
  }
  .mi-row .menu-vis { grid-area: vis; }
  .mi-row .menu-grip { grid-area: grip; }
  .mi-row .mi-name { grid-area: name; }
  .mi-row .mi-act { grid-area: act; }
  .mi-row .mi-fig { display: inline-flex; gap: 6px; align-items: baseline; text-align: left; }
  .mi-row .mi-fig::before { content: attr(data-l); font-family: inherit; font-size: 11px; color: var(--muted); font-weight: 600; text-transform: uppercase; letter-spacing: .04em; }
  .mi-row .mi-fig[data-l="Costs"] { grid-area: f1; }
  .mi-row .mi-fig[data-l="Sells for"] { grid-area: f2; }
  .mi-row .mi-fig[data-l="GP"] { grid-area: f3; justify-content: flex-end; }
  .menu-sec-head { flex-wrap: wrap; }
  .sec-sheet-colours { grid-template-columns: 1fr; }
}
/* The wait, answered live under the setting that decides it. Jamal: *"live
   updates… it just makes the process so much smoother because they get direct
   feedback that something's working."* */
.pos-pace-say {
  margin-top: 8px; font-size: 12.5px; line-height: 1.5; color: #8b93a1;
  background: #14171d; border: 1px solid #23262e; border-left: 3px solid var(--green);
  border-radius: 8px; padding: 8px 10px;
}
.pos-pace-say b { color: #e8eaee; font-size: 15px; }

.kds-card { background: var(--k-lane); border: 1px solid var(--k-edge-2); border-left: 4px solid #f5a623; border-radius: 8px; max-width: 300px; margin: 0 auto; padding: 12px 14px; }
.kds-head { display: flex; justify-content: space-between; color: var(--k-muted); font-size: 12.5px; margin-bottom: 6px; }
.kds-line { font-size: 16px; font-weight: 700; }
.kds-chips { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 8px; }
.kds-chip { background: rgba(79,140,255,.18); color: #9cbcff; border-radius: 999px; padding: 3px 10px; font-size: 12px; font-weight: 600; }
/* **Its own name, because `.qr-preview` was two screens** (v5.544.0). The
   menu item editor's channel preview and the table-card QR *artwork* both used
   that class, and the artwork's rule 7,500 lines further down repainted this
   one's background to var(--chip) while this rule's near-black text stayed —
   measured rgba(127,127,127,.1) behind rgb(30,34,41), which on the dark back
   office made the description, the kcal and the price invisible. The card is
   deliberately light: it is standing in for the guest menu, and the guest menu
   is a light page. */
.qrp-card { background: #faf6e4; color: #1e2229; border-radius: 8px; padding: 16px 18px; max-width: 420px; margin: 0 auto; }
/* The guest row, at the guest row's dimensions — 96px square where there is a
   photograph, a 40px + column where there is not. Both numbers are lifted from
   .dr-art / .dr-noart in qr-order.html rather than chosen here, because a
   preview drawn to different proportions than the page answers a different
   question than the one the vendor asked. */
.qrp-row { display: flex; gap: 14px; align-items: flex-start; }
.qrp-info { flex: 1; min-width: 0; }
.qrp-art { flex: none; width: 96px; height: 96px; border-radius: 8px;
  border: 1px solid rgba(30,34,41,.16); background: rgba(30,34,41,.06) center/cover no-repeat; }
.qrp-noart { flex: none; width: 36px; height: 36px; border-radius: 50%; align-self: center;
  border: 1px solid rgba(30,34,41,.22); background: #fff; color: #1e2229;
  display: flex; align-items: center; justify-content: center; font-size: 20px; line-height: 1; }

/* ---- POS v2.7: mobile register (phone) ---- */
.pos-root.posm { display: flex; flex-direction: column; align-items: stretch; justify-content: flex-start; background: #0b0d12; color: #e8eaee; font-size: 15px; }
/* 16px each side, not 14: the header, chips, wait box and item cards below all
   sit on a 16px gutter, and the 2px the top bar was short of it is why the
   "‹ Groups" back link read as pressed against the edge (QA round 3, #33). */
.posm-top { display: flex; align-items: center; justify-content: space-between; gap: 8px; padding: 10px 16px; padding-top: calc(10px + env(safe-area-inset-top)); background: #101218; border-bottom: 1px solid #1c1f27; flex: 0 0 auto; }
.posm-link { background: none; border: none; color: #4f8cff; font-size: 15.5px; cursor: pointer; padding: 4px 0; min-width: 60px; text-align: left; }
.posm-title { font-weight: 700; font-size: 16px; color: #fff; text-align: center; flex: 1; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.posm-topicons { display: flex; gap: 14px; min-width: 60px; justify-content: flex-end; }
.posm-topicons button { background: none; border: none; color: #cdd3dd; cursor: pointer; padding: 2px; }
.posm-body { flex: 1; overflow-y: auto; display: flex; flex-direction: column; min-height: 0; -webkit-overflow-scrolling: touch; }
/* A screen that scrolls its own insides does not also scroll here. Two nested
   scrollers hand a fling from one to the other at the end of the first, which
   reads as the screen freezing and then jumping. */
.posm-body.owns-scroll { overflow: hidden; }
/* The bar's height must sit ON TOP of the home-indicator inset, not contain
   it. This was `height: 58px` with the safe-area as padding inside it — fine
   in a Safari tab where the inset is zero, and broken the moment the app is
   added to the home screen: the ~34px inset came out of the 58, the buttons
   laid out in the 24px left, and the badge landed on top of "Live Orders"
   (Jamal's screenshots, 2026-08-28). */
.posm-nav { display: flex; height: calc(58px + env(safe-area-inset-bottom)); flex: 0 0 auto; background: var(--k-nav); border-top: 1px solid var(--k-edge-2); padding-bottom: env(safe-area-inset-bottom); }
.posm-nav button { flex: 1; background: none; border: none; color: var(--k-muted); cursor: pointer; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 2px; }
.posm-nav button span { font-size: 11.5px; }
.posm-nav button.active { color: #4f8cff; }

.posm-head { display: flex; align-items: center; justify-content: space-between; padding: 12px 16px 4px; }
.posm-head .posm-link { min-width: 0; text-align: right; }
.posm-chips { display: flex; gap: 8px; padding: 6px 16px 10px; flex-wrap: wrap; }
/* 16px, because that is the gutter every other thing on this screen uses —
   the header, the wait box, the totals and the buttons. At 8px the item cards
   were the one column that stuck out past all of them, which is what Jamal was
   looking at: *"the wait time box better lines up with the items"*. The box was
   in the right place; the items were not. */
.posm-lines { flex: 1; overflow-y: auto; padding: 0 16px; }
/* Swipe a line away. The wrapper holds the Delete button still and clips it;
   the row slides over the top of it. `touch-action: pan-y` is what keeps a
   vertical drag a scroll — without it the browser and the swipe handler both
   claim the gesture and the list judders under the finger. */
.posm-swipe { position: relative; margin-bottom: 8px; border-radius: var(--pos-r); overflow: hidden; }
.posm-swipe-del { position: absolute; top: 0; right: 0; bottom: 0; width: 108px;
  border: none; background: #e5484d; color: #fff; font-size: 16px; font-weight: 700;
  cursor: pointer; display: flex; align-items: center; justify-content: center; }
/* `user-select: none` is load-bearing, not tidiness. **A second swipe on the
   same row used to die instantly**: the first drag left a text selection on the
   row, and dragging across already-selected text starts a native
   drag-and-drop — which fires `pointercancel` and takes the gesture away. The
   trace was one `pointermove`, then `pointercancel` at clientX 0, every time,
   and only ever on the second gesture. Nobody selects text on a till row. */
.posm-line { display: flex; align-items: flex-start; gap: 10px; background: #171a21; border: 1px solid #23262e; border-radius: var(--pos-r); padding: 12px 14px; position: relative; touch-action: pan-y; transition: transform 180ms ease;
  user-select: none; -webkit-user-select: none; -webkit-touch-callout: none; }
.posm-swipe.open .posm-line { transform: translateX(-108px); }
/* **The red grows as the commit point passes** (2026-08-23). Jamal: *"you can't
   swipe past that, it should let you and then do a smooth fade across and then
   delete the item."* Past half the row, letting go removes the line — and the
   button fills the width to say so before the finger lifts, because a gesture
   that only reports what it did afterwards is one people stop trusting. */
.posm-swipe.arming .posm-swipe-del { width: 100%; }
.posm-swipe-del { transition: width 140ms cubic-bezier(.32,.72,0,1); }
/* Mid-flight: nothing may take the gesture back while the row is leaving. */
.posm-swipe[data-going="1"] { pointer-events: none; }
.posm-swipe[data-going="1"] .posm-line { will-change: transform, opacity; }
.posm-line-qty { font-weight: 700; min-width: 18px; }
.posm-line-name { flex: 1; }
.posm-pencil { background: none; border: none; color: #8b93a1; cursor: pointer; padding: 0 0 0 8px; vertical-align: middle; }
.posm-line-price { font-weight: 700; }
.posm-totals { padding: 10px 16px 4px; border-top: 1px solid #1c1f27; flex: 0 0 auto; }
.posm-total { display: flex; justify-content: space-between; font-size: 18px; margin-top: 4px; }
.posm-btns { display: grid; grid-template-columns: 1fr 1.4fr; gap: 10px; padding: 12px 14px; flex: 0 0 auto; }
.posm-btn-add { border: none; border-radius: var(--pos-r); background: #2b3a55; color: #9cbcff; font-size: 16px; font-weight: 700; padding: 16px 10px; cursor: pointer; }
.posm-btn-pay { border: none; border-radius: var(--pos-r); background: #1f9d61; color: #fff; font-size: 16px; font-weight: 700; padding: 16px 10px; cursor: pointer; }
.posm-btn-pay:disabled { opacity: .45; }

.posm-cats { display: flex; gap: 8px; overflow-x: auto; padding: 12px 14px 10px; flex: 0 0 auto; -webkit-overflow-scrolling: touch; }
.posm-cat { flex: 0 0 auto; border: 2px solid transparent; border-radius: var(--pos-r); background: var(--p); color: #fff; opacity: .55; font-size: 13.5px; font-weight: 700; padding: 10px 16px; cursor: pointer; white-space: nowrap; }
.posm-cat.on { opacity: 1; border-color: #fff; }
.posm-searchnote { display: flex; align-items: center; justify-content: space-between; padding: 0 16px 6px; color: #8b93a1; font-size: 13px; }
.posm-items { flex: 1; overflow-y: auto; padding: 0 10px; }
.posm-item { display: flex; align-items: center; justify-content: space-between; gap: 10px; width: 100%; background: #14161d; border: none; border-bottom: 1px solid #1f232c; color: #e8eaee; padding: 15px 12px; font-size: 15px; cursor: pointer; text-align: left; }
.posm-item-name em { display: block; color: #8b93a1; font-style: normal; font-size: 12.5px; margin-top: 2px; }
.posm-price { background: #fff; color: #14161d; border-radius: var(--pos-r); padding: 6px 10px; font-weight: 700; font-size: 13.5px; white-space: nowrap; }
.posm-check { border: none; background: #101218; border-top: 1px solid #1c1f27; color: #4f8cff; font-size: 16px; font-weight: 700; padding: 16px; cursor: pointer; flex: 0 0 auto; }

.posm-methods { display: flex; gap: 8px; }
.posm-methods button { flex: 1; display: flex; align-items: center; justify-content: center; gap: 8px; background: #171a21; border: 1px solid #2a2e38; color: #e8eaee; border-radius: var(--pos-r); padding: 13px 8px; font-size: 15px; cursor: pointer; }
.posm-methods button.on { border-color: #3b6ff5; background: #1e2836; }
.posm-methods button:disabled { opacity: .45; }
.posm-quick { grid-template-columns: repeat(3, 1fr); }
.posm-payscreen { z-index: 6; }
.posm-rcpts { display: flex; flex-direction: column; gap: 10px; padding: 12px 12px 8px; flex: 1; min-height: 0; }
.posm-rcpts input { background: #1a1d24; border: 1px solid #2a2e38; color: #e8eaee; border-radius: var(--pos-r); padding: 12px 14px; font-size: 14px; }
.posm-paper { padding: 16px 12px 24px; overflow-y: auto; }
.posm-setlist { display: flex; flex-direction: column; padding: 10px 12px; }
.posm-setlist button { display: flex; align-items: center; justify-content: space-between; background: #14161d; border: none; border-bottom: 1px solid #1f232c; color: #e8eaee; padding: 16px 14px; font-size: 15.5px; cursor: pointer; text-align: left; }
.posm-setlist button:first-child { border-radius: var(--pos-r) 10px 0 0; }
.posm-setlist button em { color: #5f6b7a; font-style: normal; font-size: 18px; }
.posm-setlist .posm-exit { margin-top: 14px; border-radius: var(--pos-r); color: #ff8a8a; }
.posm-pane { display: flex; flex-direction: column; gap: 10px; padding: 14px 16px 24px; }

/* Mobile combo picker: stack the slots above a 2-up option grid. */
.posm .pos-combo { flex-direction: column; padding: 10px; gap: 10px; overflow-y: auto; }
.posm .pos-combo-left { width: auto; flex: 0 0 auto; overflow: visible; }
.posm .pos-combo-right { flex: 0 0 auto; }
.posm .pos-combo-right .pos-grid { grid-template-columns: repeat(2, 1fr) !important; }
.posm .pos-paytop { display: flex; align-items: center; justify-content: space-between; padding: 10px 14px; padding-top: calc(10px + env(safe-area-inset-top)); background: #101218; }
.posm .pos-ticket { position: sticky; bottom: 0; }
body.pos-open .mobile-nav { display: none !important; }
.posm-totals .pos-vatline { display: flex; justify-content: space-between; }

/* ---- POS v2.7.1: mobile tile grid + tender list ---- */
.posm-crumb { padding: 12px 16px 4px; color: #8b93a1; font-size: 12.5px; flex: 0 0 auto; }
.posm-crumb strong { display: block; color: #e8eaee; font-size: 15.5px; margin-top: 1px; }
.posm-gridwrap { flex: 1; overflow-y: auto; padding: 10px 12px 16px; -webkit-overflow-scrolling: touch; }
.posm-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; }
.posm-tile { display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 4px; min-height: 76px; background: #1a1f2c; border: none; border-bottom: 4px solid #3b6ff5; border-radius: var(--pos-r); color: #e8eaee; font-size: 14.5px; font-weight: 600; padding: 12px 10px; cursor: pointer; text-align: center; }
.posm-tile:active { filter: brightness(1.25); }
/* Shrunk-to-fit (posmFitGrid): every group on one page, the fold never cuts
   a row into slivers. The height is the measured share of the screen; the
   padding gives way with it so the name keeps its size. */
.posm-grid.fit .posm-tile { min-height: var(--pm-tile, 76px); height: var(--pm-tile, auto); padding: 6px 8px; }
.posm-tile em { color: #9aa3b2; font-style: normal; font-size: 12px; font-weight: 500; }
/* A group tile wearing the section's photograph. Taller, because a picture the
   height of a line of text is a smudge — and the name goes bold white with a
   shadow under it, because the wash in the inline style handles the average
   case and a bright patch of chips handles the rest. */
.posm-tile.has-img {
  min-height: 104px;
  color: #fff; font-weight: 800; font-size: 15.5px;
  text-shadow: 0 1px 6px rgba(0, 0, 0, .85);
}
.posm-paylist { padding: 10px 14px 14px; display: flex; flex-direction: column; gap: 8px; }
.posm-paylist .posm-paycat, .posm-paycat { color: #8b93a1; font-size: 12.5px; text-transform: none; margin: 8px 2px 0; }
.posm-mrow { display: flex; align-items: center; gap: 12px; width: 100%; background: #171a21; border: 1px solid #23262e; color: #e8eaee; border-radius: var(--pos-r); padding: 15px 14px; font-size: 15.5px; cursor: pointer; text-align: left; }
.posm-mrow.on { background: #2f6bdf; border-color: #2f6bdf; color: #fff; }
.posm-mrow.on .pos-ic { color: #fff; }
.posm-mrow:disabled { opacity: .5; }
.posm-mrow em { margin-left: auto; color: #8b93a1; font-style: normal; font-size: 12px; }
.posm-mrow.on em { color: #cfe0ff; }
.posm-cancel { color: #ff6b6b; }

/* ---- POS v2.8: online orders ---- */
.pos-badge { position: absolute; top: 6px; right: calc(50% - 26px); background: #f5a623; color: #14161c;
  border-radius: var(--pos-r); min-width: 18px; height: 18px; font-size: 11px; font-weight: 800;
  display: inline-flex; align-items: center; justify-content: center; padding: 0 5px; }
.pos-nav button, .posm-nav button { position: relative; }
.pos-pend-head { display: flex; justify-content: space-between; align-items: flex-start; margin-bottom: 10px; }
.pos-pend-table { display: inline-block; background: #f5a623; color: #14161c; border-radius: var(--pos-r);
  padding: 6px 12px; font-size: 16px; font-weight: 800; }
.pos-pend-note { background: rgba(245,166,35,.12); border: 1px solid rgba(245,166,35,.4);
  border-radius: var(--pos-r); padding: 10px 12px; margin-bottom: 10px; font-size: 13.5px; }
.pos-pend-lines { background: #101218; border-radius: var(--pos-r); padding: 8px 14px; margin-bottom: 14px; }
.posm .pos-pend-lines { background: #171a21; }
.pos-pend-btns { display: flex; flex-direction: column; gap: 10px; align-items: stretch; }
.pos-pend-btns .btn.danger { padding: 12px; }

/* ---- v2.9: no double-tap zoom on the till; toasts at the top of the POS ---- */
.pos-root, .pos-root * { touch-action: manipulation; }
body.pos-open #toast-root { top: calc(10px + env(safe-area-inset-top)); bottom: auto;
  left: 50%; right: auto; transform: translateX(-50%); align-items: center; }
body.pos-open .toast { animation: dropIn .18s ease; }
@keyframes dropIn { from { transform: translateY(-16px); opacity: 0; } to { transform: none; opacity: 1; } }
.posm-tender { display: grid; gap: 10px; padding: 0 14px 12px; }
.posm-tender button { border: none; border-radius: var(--pos-r); font-size: 15.5px; font-weight: 700;
  padding: 16px 8px; cursor: pointer; }
.posm-tender .t-cash { background: #1f9d61; color: #fff; }
.posm-tender .t-card { background: #3b6ff5; color: #fff; }
.posm-tender .t-charge { background: #2b3a55; color: #9cbcff; }
.posm-tender button:disabled { opacity: .45; }

/* ---- v2.9.2: one-screen mobile combo flow ---- */
.posm-flow { z-index: 7; display: flex; flex-direction: column; }
.posm-flowsteps { display: flex; gap: 8px; overflow-x: auto; padding: 12px 14px 10px; flex: 0 0 auto; scrollbar-width: none; }
.posm-flowsteps::-webkit-scrollbar { display: none; }

/* ---- Nothing on the till draws a scrollbar ------------------------------
   Jamal, of the sale screen on his phone: *"Make it so there's no scroll
   bar."* A grey track down the right of a till is a desktop artefact — a
   phone scrolls by being touched and says so by moving, and iOS shows a
   scrollbar only while your finger is on the glass. The register is meant to
   look like an app, and a permanent track is the one thing on it that says
   web page.

   Every scroller in the phone register, not just the one in the photograph:
   they are all the same screen to whoever is holding it, and half of them
   showing a track would look like a bug rather than a decision. The content
   still scrolls — this hides the furniture, it does not trap anything. */
.posm-body, .posm-lines, .posm-items, .posm-cats, .posm-gridwrap, .posm-paper,
.posm .pos-combo {
  scrollbar-width: none;
  -ms-overflow-style: none;
}
.posm-body::-webkit-scrollbar, .posm-lines::-webkit-scrollbar,
.posm-items::-webkit-scrollbar, .posm-cats::-webkit-scrollbar,
.posm-gridwrap::-webkit-scrollbar, .posm-paper::-webkit-scrollbar,
.posm .pos-combo::-webkit-scrollbar { width: 0; height: 0; display: none; }
.posm-flowsteps button { flex: 0 0 auto; border: none; border-radius: var(--pos-r); padding: 10px 16px;
  font-size: 13.5px; font-weight: 700; cursor: pointer; background: #23262e; color: #8b93a1; white-space: nowrap; }
.posm-flowsteps button.live { background: #b8842a; color: #fff; outline: 2px solid #4f8cff; outline-offset: 2px; }
.posm-flowsteps button.done { background: #1f9d61; color: #fff; }
.posm-flowgrid { flex: 1; min-height: 0; display: grid; grid-template-columns: 1fr 1fr; gap: 10px;
  padding: 4px 14px 10px; grid-template-rows: repeat(var(--fl-rows, 1), 1fr); }
/* The question, over the grid that answers it. On a modal every group carried
   its own caption; in a flow there is only ever one question on screen, so it
   goes at the top of the panel — Lightspeed's black header strip. */
.posm-flowhead { flex: 0 0 auto; text-align: center; font-weight: 700; font-size: 15px;
  color: #e8eaee; background: #05070b; padding: 12px 14px; }
.posm-flowtile { display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 4px;
  min-height: 0; background: #1a1f2c; border: 2px solid transparent; border-bottom: 4px solid #3f8f5f;
  border-radius: var(--pos-r); color: #e8eaee; font-size: 15.5px; font-weight: 650; padding: 8px; cursor: pointer; text-align: center; }
.posm-flowtile em { color: #9aa3b2; font-style: normal; font-size: 13px; font-weight: 500; }
.posm-flowtile.sel { border-color: #4f8cff; background: #1e2836; }
/* A question that takes something off the dish. Warm rather than red — this is
   somebody on the till repeating a customer back to themselves, not a warning;
   the shouting belongs on the kitchen's copy. Matches the tinted band the guest
   page gives the same question. */
.posm-flowtile.off { border-bottom-color: #E8863B; }
.posm-flowtile.off.sel { border-color: #E8863B; background: #2a2016; }
.posm-flowtile:active { filter: brightness(1.3); }
.posm-flowfoot { display: flex; align-items: center; gap: 14px; padding: 12px 14px calc(14px + env(safe-area-inset-bottom));
  border-top: 1px solid #1c1f27; flex: 0 0 auto; }
.posm-flowfoot .stepper { display: flex; align-items: center; gap: 10px; }
.posm-flowfoot .stepper button { width: 38px; height: 38px; border-radius: var(--pos-r); border: 1px solid #2a2e38;
  background: #171a21; color: #e8eaee; font-size: 18px; line-height: 1; cursor: pointer; }
.posm-flowfoot .stepper button:disabled { opacity: .35; }
.posm-flowfoot .stepper b { min-width: 18px; text-align: center; font-size: 16px; }
.posm-flowfoot .posm-btn-pay { padding: 15px 8px; }
.posm-flowdel { width: 38px; height: 38px; border-radius: var(--pos-r); border: 1px solid #4a2b2f; background: #171a21; color: #ff6b6b; font-size: 20px; line-height: 1; cursor: pointer; flex: 0 0 auto; }
.posm-flownote { padding: 0 14px 4px; flex: 0 0 auto; }
.posm-flownote input { width: 100%; background: #1a1d24; border: 1px solid #2a2e38; color: #e8eaee; border-radius: var(--pos-r); padding: 11px 12px; font-size: 14px; }

/* ---- v3.0: kitchen display + orders-tab polish ---- */
#po-rows { flex: 1; overflow-y: auto; display: flex; flex-direction: column; gap: 8px; min-height: 0; }
.pos-kds-wrap { flex: 1; display: flex; flex-direction: column; padding: 14px; min-height: 0; width: 100%; }
.pos-kds { flex: 1; overflow-y: auto; display: grid; grid-template-columns: repeat(4, 1fr); gap: 12px; align-content: start; --pos-sc-bg: #0c0e12; }
.posm .pos-kds { grid-template-columns: 1fr; }
.pos-ticket-card { background: #14161d; border: 1px solid #23262e; border-top: 4px solid #4f8cff; border-radius: var(--pos-r); padding: 12px 14px; display: flex; flex-direction: column; gap: 8px; }
.pos-ticket-card.warm { border-top-color: #f5a623; }
.pos-ticket-card.late { border-top-color: #ff5c5c; }
.pos-ticket-card.ready { border-top-color: #1f9d61; background: #12201a; }
.pos-ticket-head { display: flex; justify-content: space-between; align-items: center; font-size: 15px; }
.pos-ticket-age { background: #23262e; border-radius: var(--pos-r-s); padding: 3px 10px; font-size: 12.5px; font-weight: 700; color: #cdd3dd; }
.pos-ticket-card.warm .pos-ticket-age { background: rgba(245,166,35,.2); color: #f5c463; }
.pos-ticket-card.late .pos-ticket-age { background: rgba(255,92,92,.2); color: #ff8a8a; }
.pos-ticket-meta { color: #8b93a1; font-size: 12.5px; }
.pos-ticket-note { background: rgba(245,166,35,.12); border: 1px solid rgba(245,166,35,.4); border-radius: var(--pos-r); padding: 7px 10px; font-size: 13px; }
.pos-ticket-lines { display: flex; flex-direction: column; gap: 6px; }
.pos-ticket-line { display: flex; gap: 8px; font-size: 14.5px; line-height: 1.35; }
.pos-ticket-line b { color: #4f8cff; min-width: 22px; }
.pos-ticket-line em { display: block; color: #9aa3b2; font-style: normal; font-size: 12.5px; }
.pos-ticket-ready { text-align: center; color: #4fd08a; font-size: 13px; font-weight: 700; }
.pos-ticket-btn { border: none; border-radius: var(--pos-r); padding: 13px 8px; font-size: 15px; font-weight: 800; cursor: pointer; margin-top: auto; }
.pos-ticket-btn.ready { background: #1f9d61; color: #fff; }
.pos-ticket-btn.done { background: #2b3a55; color: #9cbcff; }
.pos-ticket-btn:disabled { opacity: .5; }


/* ---- v3.3: venue + role switcher ---- */
.ctx-switch { display: block; width: 100%; text-align: left; background: var(--surface-2);
  border: 1px solid var(--border); border-radius: 10px; padding: 12px 14px; margin-bottom: 12px; cursor: pointer; }
.ctx-switch:hover { border-color: var(--primary); }
.ctx-venue { display: block; font-weight: 700; font-size: 14.5px; color: var(--text); }
.ctx-role { display: block; color: var(--muted); font-size: 12px; margin-top: 2px; }
.ctx-row { display: flex; align-items: center; justify-content: space-between; gap: 10px; width: 100%;
  text-align: left; background: var(--surface-2); border: 1px solid var(--border); color: var(--text);
  border-radius: 10px; padding: 14px 16px; font-size: 15px; cursor: pointer; margin-bottom: 8px; }
.ctx-row:hover { border-color: var(--primary); }
.ctx-row.on { border-color: var(--primary); background: rgba(79,140,255,.1); }
.ctx-row .sub { display: block; color: var(--muted); font-size: 12px; margin-top: 1px; }
.ctx-row .tick { color: var(--primary); font-weight: 800; }
.ctx-head { color: var(--muted); font-size: 12px; text-transform: uppercase; letter-spacing: .05em;
  font-weight: 700; margin: 14px 2px 8px; }
.ctx-head:first-child { margin-top: 0; }

/* v4.11: each card drops its own list beneath it. Fixed and appended to the
   body, so the phone drawer (position: fixed, z-index 60) can't clip it. */
.ctx-pop { position: fixed; z-index: 80; background: var(--surface); border: 1px solid var(--border);
  border-radius: 12px; box-shadow: 0 18px 44px rgba(0,0,0,.45); padding: 10px; overflow-y: auto;
  overscroll-behavior: contain; }
.ctx-pop .ctx-row { margin-bottom: 6px; padding: 10px 12px; font-size: 14px; align-items: flex-start; }
.ctx-pop .ctx-row:last-of-type { margin-bottom: 0; }
.ctx-pop .ctx-head { margin: 10px 4px 6px; }
.ctx-pop .ctx-head:first-child { margin-top: 2px; }
.ctx-main { min-width: 0; }
.ctx-main strong { display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ctx-right { display: flex; align-items: center; gap: 8px; flex: none; }
/* The account number: monospaced so digits and letters can't be misread when
   somebody is reading it down a phone. */
.ctx-acct { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 11.5px;
  letter-spacing: .04em; color: var(--muted); background: var(--surface-2);
  border: 1px solid var(--border); border-radius: 6px; padding: 2px 6px; white-space: nowrap; }
.ctx-row.on .ctx-acct { color: var(--text); }
.ctx-note { color: var(--muted); font-size: 11.5px; line-height: 1.45; margin: 8px 4px 2px; }
/* Apps list: a planned surface is still named, so the shape of the product is
   visible from inside it — it just can't be opened yet. */
.ctx-soon { font-size: 10.5px; text-transform: uppercase; letter-spacing: .05em; font-weight: 700;
  color: var(--muted); border: 1px solid var(--border); border-radius: 999px; padding: 2px 7px;
  white-space: nowrap; }
.ctx-row.ctx-off { opacity: .55; cursor: default; }
.ctx-row.ctx-off:hover { border-color: var(--border); }
.ctx-url { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 11px; }
.ctx-right svg { color: var(--muted); }
.ctx-pop .ctx-search { margin-bottom: 8px; }
.ctx-switch[aria-expanded="true"] { border-color: var(--primary); }
/* The card is a flex column (see below), so the caret is positioned rather
   than floated — a float would just become the first row. */
.ctx-switch { position: relative; }
.ctx-caret { position: absolute; top: 11px; right: 12px; color: var(--muted);
  display: flex; transition: transform .15s ease; }
.ctx-switch[aria-expanded="true"] .ctx-caret { transform: rotate(180deg); color: var(--primary); }
.ctx-switch .ctx-val { padding-right: 18px; }
.ctx-switch .ctx-acct { margin-top: 7px; }

/* ---- Staff & access + Logs hub (v3.4) ------------------------------------ */
.f-label { display: block; font-size: 12px; font-weight: 700; color: var(--muted);
  text-transform: uppercase; letter-spacing: .04em; margin: 12px 0 4px; }
.f-input { width: 100%; }
.muted-row td { opacity: .55; }
.pill { display: inline-block; padding: 1px 8px; border-radius: 999px; font-size: 11px;
  font-weight: 700; background: var(--bg-soft, rgba(127,127,127,.12)); color: var(--muted);
  border: 1px solid var(--border); vertical-align: 1px; }
.logs-wrap { display: grid; grid-template-columns: 260px 1fr; gap: 14px; align-items: start; }
.logs-side { padding: 10px; max-height: calc(100vh - 170px); overflow-y: auto; }
.logs-side .ctx-row { width: 100%; margin-bottom: 4px; }
.log-row { padding: 10px 4px; border-bottom: 1px solid var(--border); }
.log-row:last-child { border-bottom: none; }
.log-top { display: flex; justify-content: space-between; gap: 10px; align-items: baseline; }
.log-when { color: var(--muted); font-size: 12px; white-space: nowrap; }
.log-sub { color: var(--muted); font-size: 13px; margin-top: 2px; }
.log-detail { margin-top: 6px; }
.log-detail summary { cursor: pointer; font-size: 12px; color: var(--muted); }
.log-detail pre { white-space: pre-wrap; word-break: break-all; font-size: 12px;
  background: rgba(127,127,127,.08); border-radius: 8px; padding: 8px; margin-top: 6px; }
@media (max-width: 900px) { .logs-wrap { grid-template-columns: 1fr; } .logs-side { max-height: 260px; } }

/* ---- me&u-style sign-in (v3.4.1) ------------------------------------------ */
.auth-screen { position: fixed; inset: 0; z-index: 100; display: flex; padding: 0; overflow: auto;
  background: #FBF4EA; color: #132C22; align-items: stretch; justify-content: flex-start; }
.au-left { flex: 1 1 50%; display: flex; align-items: center; justify-content: center; padding: 32px 24px; }
.au-box { width: 100%; max-width: 350px; text-align: center; }
.au-logo { display: flex; align-items: center; justify-content: center; gap: 9px;
  font-weight: 800; font-size: 30px; letter-spacing: -.03em; color: #0B3B2E; }
.au-logo-dots { display: inline-flex; flex-direction: column; gap: 4px; }
.au-logo-dots i { width: 11px; height: 11px; border-radius: 50%; background: #0B3B2E; }
.au-logo-dots i:first-child { width: 13px; height: 13px; }
.au-title { font-size: 26px; font-weight: 600; color: #132C22; margin: 30px 0 10px; }
.au-sub { color: #44534C; font-size: 14px; line-height: 1.55; margin: 0 0 26px; }
/* The visible label above each sign-in field (QA audit 2026-08-26, finding
   12) — the placeholder names the field only until somebody types. The auth
   pane is cream, so the label color is its own rather than the app's. */
.au-lab { display: block; text-align: left; font-size: 12.5px; font-weight: 700;
  color: #2A3B31; margin: 12px 0 6px; }
.au-lab .req { color: #c8382f; }
.au-input { display: block; width: 100%; background: #fff; border: 1px solid #E5DDD1; border-radius: 10px;
  padding: 15px 16px; font-size: 15px; margin-bottom: 14px; color: #132C22; }
.au-input::placeholder { color: #7C877F; }
.au-input:focus { outline: none; border-color: #0B5C41; box-shadow: 0 0 0 1px #0B5C41; }
.au-pwrap { position: relative; }
.au-pwrap .au-input { padding-right: 48px; }
.au-eye { position: absolute; right: 12px; top: 13px; background: none; border: none; cursor: pointer;
  color: #44534C; padding: 4px; line-height: 0; }
.au-links { text-align: left; margin: 4px 0 20px; }
.au-links a { color: #0B5C41; font-weight: 700; font-size: 14px; text-decoration: none; }
.au-remember { display: flex; gap: 8px; align-items: center; font-size: 13px; color: #44534C;
  margin: 0 0 16px; text-align: left; }
.au-remember input { width: auto; margin: 0; }
/* The choice after signing in with a temporary password. Two blocks rather
   than a radio pair: each one needs a sentence explaining what it means, and
   the sentence is the part that decides it. */
.au-choices { display: grid; gap: 10px; margin: 4px 0 14px; }
.au-choice { display: block; width: 100%; text-align: left; background: #fff;
  border: 1px solid #D8D3C7; border-radius: 10px; padding: 12px 14px; font: inherit;
  color: #1B2A24; cursor: pointer; }
.au-choice:hover { border-color: #0B4A38; }
.au-choice.on { border-color: #0B4A38; box-shadow: 0 0 0 2px rgba(11,74,56,.14); }
.au-choice strong { display: block; font-size: 15px; margin-bottom: 3px; }
.au-choice span { display: block; font-size: 13px; line-height: 1.45; color: #5A6B63; }

.au-btn { width: 100%; background: #0B4A38; color: #fff; border: none; border-radius: 10px;
  padding: 16px; font-size: 16px; font-weight: 600; cursor: pointer; }
.au-btn:hover { background: #0C5C41; }
.au-back { display: inline-block; margin-top: 20px; color: #0B5C41; font-weight: 700;
  font-size: 14px; text-decoration: none; }
.au-err { color: #B3261E; font-size: 13px; min-height: 18px; margin-top: 14px; }
.au-right { flex: 1 1 50%; background: #0B3B2E; display: flex; align-items: center;
  justify-content: center; padding: 40px 32px; }
.au-promo { max-width: 430px; text-align: center; }
.au-mock { background: #FBF4EA; color: #0B3B2E; border-radius: 18px; padding: 42px 32px;
  font-size: 31px; font-weight: 800; letter-spacing: -.02em; line-height: 1.12; text-align: left;
  min-height: 180px; display: flex; align-items: flex-end; margin-bottom: 30px; }
.au-promo h3 { font-size: 19px; font-weight: 700; color: #fff; margin: 0 0 10px; }
.au-promo p { font-size: 14px; line-height: 1.6; color: #CFDDD4; margin: 0; }
.au-dots { display: flex; gap: 7px; justify-content: center; margin-top: 20px; }
.au-dot { width: 8px; height: 8px; border-radius: 50%; background: #2E5A4C; border: none;
  cursor: pointer; padding: 0; }
.au-dot.on { background: #D9F64F; }
@media (max-width: 900px) {
  .au-right { display: none; }
  .au-left { padding-top: 60px; align-items: flex-start; }
}

/* ---- me&u Organization Staff (v3.4.2) ------------------------------------- */
.st-toolbar { display: flex; gap: 10px; margin-bottom: 14px; flex-wrap: wrap; align-items: center; }
.st-search { flex: 1; min-width: 220px; max-width: 360px; display: flex; align-items: center; gap: 8px;
  background: var(--surface); border: 1px solid var(--border); border-radius: 8px; padding: 0 12px; color: var(--muted); }
.st-search input { border: none; background: none; padding: 10px 0; width: 100%; color: var(--text); }
.st-search input:focus { outline: none; box-shadow: none; border: none; }
.st-filter { width: auto; padding: 9px 12px; border-radius: 8px; }
.st-invite svg { vertical-align: -3px; margin-right: 7px; }
.st-name { display: flex; align-items: center; gap: 10px; }
.st-avatar { display: inline-flex; width: 28px; height: 28px; border-radius: 50%; align-items: center;
  justify-content: center; font-size: 11px; font-weight: 700; color: #34302B; flex: none; }
.st-kebab-cell { position: relative; width: 44px; text-align: right; }
.st-kebab { background: none; border: none; cursor: pointer; color: var(--muted); padding: 6px; border-radius: 6px; line-height: 0; }
.st-kebab:hover { background: rgba(127,127,127,.14); color: var(--text); }
.st-menu { position: fixed; z-index: 300; min-width: 150px; background: var(--surface-2); border: 1px solid var(--border);
  border-radius: 10px; box-shadow: var(--shadow); z-index: 30; min-width: 150px; overflow: hidden; }
.st-menu button { display: block; width: 100%; text-align: left; padding: 10px 14px; background: none;
  border: none; cursor: pointer; font-size: 14px; color: var(--text); }
.st-menu button:hover { background: rgba(127,127,127,.12); }

/* Organization Staff on a phone: the table becomes stacked cards, because a
   table five columns wide on a 390px screen is a sideways scroll (#160) and a
   kebab menu clipped inside it. Desktop keeps the table. */
.st-cards { display: none; }
.st-mrow { display: flex; align-items: center; gap: 12px; padding: 11px 4px;
  border-top: 1px solid var(--border); }
.st-mrow:first-child { border-top: 0; }
.st-mmain { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.st-mmain b, .st-mmain .hint { overflow: hidden; text-overflow: ellipsis; }
.st-mrow .st-kebab { flex: 0 0 auto; padding: 10px; }
@media (max-width: 720px) {
  .st-card .table-wrap { display: none; }
  .st-cards { display: block; }
}

.ctx-search { display: flex; align-items: center; gap: 8px; border: 1px solid var(--border); border-radius: 8px;
  padding: 0 10px; margin-bottom: 10px; color: var(--muted); background: var(--surface); }
.ctx-search input { border: none; background: none; padding: 9px 0; width: 100%; color: var(--text); }
.ctx-search input:focus { outline: none; box-shadow: none; }

/* Home Page & Branding uploader thumbs */
.qh-thumb { width: 58px; height: 40px; border: 1px solid var(--border); border-radius: 6px;
  background-size: cover; background-position: center; display: inline-flex; align-items: center;
  justify-content: center; color: var(--muted); font-size: 12px; flex: none; background-color: var(--surface-2); }

/* Service charge & surcharges rows */
.ch-sur { border: 1px solid var(--border); border-radius: 10px; padding: 10px; margin-bottom: 8px; }

/* ---- Till site (pos.helchpos.com): coded entry + no back-office chrome --- */
.till-site .sidebar, .till-site .mobile-nav { display: none !important; }
.till-site .content { margin: 0; }
/* The code-entry keypad. **It used to run off both ends of a phone** — the
   wordmark clipped under the status bar and "Owner sign-in" below the fold —
   for two reasons that had to be fixed together.

   `100vh` on a phone browser is the height of the window *with the address bar
   pretending not to be there*, so it is taller than what you can actually see.
   `100dvh` is the honest one, and it goes second so a browser that has never
   heard of it keeps the old value rather than nothing. That part applies
   everywhere, because it is simply more correct.

   The sizes are a different matter. They added up to about 745px of content,
   which is more than a small phone has and less than a tablet has — so they
   are left exactly as they were and re-stated, clamped, only under
   `max-height`. A tablet keeps the 74px key it always had; nothing that was
   already right is touched to fix something that was not. */
.till-entry { min-height: 100vh; min-height: 100dvh; width: 100%; background: #0c0e12;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 14px; padding: 24px; }
.till-sub { color: #9aa3b2; font-size: 16px; margin: 2px 0 0; }
.till-note { color: #6b7484; font-size: 13px; margin: 4px 0 0; }
.till-dots { min-height: 30px; font-size: 22px; letter-spacing: 10px; color: #e8eaee; }
.till-dots .till-hint { font-size: 14px; letter-spacing: normal; color: #4c5566; }
.till-pad { display: grid; grid-template-columns: repeat(3, 84px); gap: 12px; }
.till-pad button { height: 74px; border-radius: 16px; border: 1px solid #262b36; background: #171a21;
  color: #e8eaee; font-size: 26px; font-weight: 700; cursor: pointer; }
.till-pad button:active { background: #232836; }
.till-go { width: 276px; padding: 16px; border: none; border-radius: 14px; background: #3b6ff5;
  color: #fff; font-size: 17px; font-weight: 700; cursor: pointer; margin-top: 4px; }
.till-owner { color: #6b7280; font-size: 13.5px; text-decoration: underline; }
/* An empty error line still claimed its 14px gap between the button and the
   links, on every screen, for the whole time nothing was wrong. */
.till-entry .au-err:empty { display: none; }
/* The venue's own logo on its till (Toast reference, 2026-08-30): on a white
   card, because most venue marks are drawn for paper and light menus, not for
   the entry screen's near-black. */
.till-vlogo { background: #fff; border-radius: 18px; padding: 12px; width: 128px; height: 128px;
  display: flex; align-items: center; justify-content: center; }
.till-vlogo img { max-width: 100%; max-height: 100%; object-fit: contain; }
.till-cog { position: absolute; top: calc(12px + env(safe-area-inset-top)); right: 14px;
  width: 40px; height: 40px; border-radius: 10px; border: none; background: rgba(255,255,255,.06);
  color: #9aa3b2; font-size: 22px; line-height: 1; cursor: pointer; }
.till-entry { position: relative; }
.till-dev-row { display: flex; justify-content: space-between; gap: 16px; padding: 9px 0;
  border-bottom: 1px solid var(--border); font-size: 14.5px; }
.till-dev-row:last-child { border-bottom: 0; }
.till-dev-row span { color: var(--text-2, #6b7280); }
/* The keyboard/bump-bar selection on the kitchen board: loud enough to read
   across a pass, without repainting the card's own tone. */
.kt.keysel { outline: 3px solid #3b82f6; outline-offset: 2px; }
.till-dev-name { width: 180px; padding: 6px 9px; border: 1px solid var(--border);
  border-radius: 8px; background: var(--surface); color: var(--text);
  font: inherit; font-size: 14px; text-align: right; }

/* Below this there is not room for the full-size keypad, so everything gives a
   little rather than the ends falling off. 760px is the height a 6.1" phone
   leaves once its browser chrome is on screen. */
@media (max-height: 760px) {
  .till-entry { gap: clamp(6px, 1.4vh, 14px); padding: clamp(10px, 2.2vh, 24px) 20px; }
  .till-sub { font-size: clamp(14px, 2vh, 16px); }
  .till-note { font-size: clamp(12px, 1.7vh, 13px); }
  .till-dots { min-height: clamp(20px, 3.2vh, 30px); font-size: clamp(18px, 2.6vh, 22px); }
  .till-dots .till-hint { font-size: clamp(12px, 1.7vh, 14px); }
  .till-pad { grid-template-columns: repeat(3, clamp(62px, 21vw, 84px));
    gap: clamp(6px, 1.2vh, 12px); }
  /* 46px is the floor because a key smaller than a fingertip is a wrong code
     typed in a queue, which costs more than a tidy layout is worth. */
  .till-pad button { height: clamp(46px, 7.4vh, 74px); font-size: clamp(19px, 2.9vh, 26px); }
  .till-go { width: min(276px, 100%); padding: clamp(10px, 1.7vh, 16px);
    font-size: clamp(15px, 2.1vh, 17px); }
  .till-owner { font-size: clamp(12px, 1.8vh, 13.5px); }
  /* The wordmark is drawn at a fixed 86px by `brandLogoHtml(86)`; here the CSS
     wins, so the top of it stops disappearing under the notch. */
  .till-entry .au-logo .hp-logo,
  .till-entry .au-logo .hp-logo-img { height: clamp(40px, 7.2vh, 86px) !important; }
}
.till-shake { animation: till-shake .35s; }
@keyframes till-shake { 0%, 100% { transform: translateX(0); } 25% { transform: translateX(-8px); }
  75% { transform: translateX(8px); } }

/* Till forgot-code phone input */
.till-phone { width: 276px; background: #171a21; border: 1px solid #262b36; border-radius: 14px;
  padding: 16px; color: #e8eaee; font-size: 20px; text-align: center; }


/* Two separate sidebar controls: role on top, venue below (v3.9) */
.ctx-switch { display: flex; flex-direction: column; align-items: flex-start; gap: 1px;
  width: 100%; text-align: left; margin-bottom: 6px; }
.ctx-switch .ctx-cap { font-size: 10.5px; text-transform: uppercase; letter-spacing: .07em;
  color: var(--muted); font-weight: 700; }
.ctx-switch .ctx-val { font-size: 14px; font-weight: 650; color: var(--text);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 100%; }

/* Combo screen: per-item notes + the size picker's quantity row (v4.0) */
.pos-combo-note { display: block; width: 100%; margin-top: 10px; background: none; color: #9aa3b2;
  border: 1px dashed #3a4150; border-radius: var(--pos-r); padding: 9px; font-size: 13px; cursor: pointer; }
.pos-combo-note.has { color: #e8eaee; border-style: solid; border-color: #4a5262; font-style: italic; }
.pos-combo-note:hover { border-color: #5a6376; color: #e8eaee; }
.pos-qtyrow { display: inline-flex; align-items: center; gap: 12px; }
.pos-qbtn { width: 34px; height: 34px; border-radius: var(--pos-r); border: 1px solid #333a47;
  background: #232836; color: #e8eaee; font-size: 18px; line-height: 1; cursor: pointer; }
.pos-item-price { display: block; margin-top: 4px; opacity: .75; font-size: 14px; }

/* ---- Brand logo (v4.1) ---------------------------------------------------
   'mask' mode paints the artwork as a silhouette in the current text colour,
   so one upload works on the dark sidebar, the light sign-in pane and the
   till keypad without needing separate light/dark files. */
.hp-logo { display: inline-block; width: 100%; max-width: 260px; background-color: currentColor;
  -webkit-mask-image: var(--logo); mask-image: var(--logo);
  -webkit-mask-size: contain; mask-size: contain;
  -webkit-mask-repeat: no-repeat; mask-repeat: no-repeat;
  -webkit-mask-position: left center; mask-position: left center; }
.hp-logo-img { display: block; max-width: 100%; object-fit: contain; }
.brand-logo { color: var(--text); display: flex; align-items: center; min-height: 48px;
  flex: 1 1 auto; width: 100%; min-width: 0; }
.brand-logo .hp-logo { max-width: 180px; }
.au-logo { width: 100%; }
.au-logo .hp-logo { width: 100%; max-width: 300px; -webkit-mask-position: center; mask-position: center; }
.au-logo .hp-logo-img { margin: 0 auto; }
.till-entry .au-logo { color: #e8eaee; }
.brand-prev { width: 170px; height: 78px; border-radius: 10px; display: flex; align-items: center;
  justify-content: center; padding: 10px; border: 1px solid var(--border); }
.brand-prev.dark { background: #0f1115; color: #e8eaee; }
.brand-prev.light { background: #fbf4ea; color: #132c22; }

/* ---- Commission ------------------------------------------------------------ */
.f-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(200px, 100%), 1fr)); gap: 12px 14px; }
.f-checks { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(210px, 100%), 1fr));
  gap: 10px 18px; margin-top: 14px; padding-top: 12px; border-top: 1px solid var(--border); }
.f-checks label { display: flex; align-items: center; gap: 8px; font-size: 14px; }
.f-checks input[type="checkbox"] { flex: none; width: 16px; height: 16px; margin: 0;
  accent-color: var(--primary); }
.f-hint { display: block; font-size: 12px; color: var(--muted); margin-top: 4px; }
.cm-sum { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(170px, 100%), 1fr));
  gap: 4px 18px; padding: 16px 18px; }
.cm-fig { display: flex; flex-direction: column; gap: 2px; }
.cm-fig strong { font-size: 26px; font-variant-numeric: tabular-nums; letter-spacing: -.01em; }
.cm-lbl { font-size: 11px; letter-spacing: .08em; text-transform: uppercase; color: var(--muted); font-weight: 700; }
.cm-net strong { color: var(--green); }
.cm-band { display: grid; grid-template-columns: 1fr 1fr auto; gap: 10px; align-items: end;
  padding: 8px 0; border-top: 1px solid var(--border); }
.cm-band:first-child { border-top: 0; }

/* The worked example. Deliberately reads like a receipt rather than a report:
   one column of what happened, one column of money, in the order it happened. */
.cm-receipt { margin-top: 14px; padding: 16px 18px; }
.cm-receipt h3 { margin: 0 0 10px; font-size: 15px; }
.cm-rc { width: 100%; border-collapse: collapse; }
.cm-rc td { padding: 7px 0; border-bottom: 1px solid var(--border); vertical-align: top; }
.cm-rc td.num { text-align: right; font-variant-numeric: tabular-nums; white-space: nowrap;
  padding-left: 14px; }
.cm-rc .sub { font-size: 12px; color: var(--muted); margin-top: 2px; max-width: 60ch; }
/* Money already handed back is shown, because leaving it out is what makes
   somebody think the total is wrong — but it is not part of the running sum. */
.cm-rc-muted td { color: var(--muted); }
.cm-rc-sub td { border-top: 1px solid var(--border); }
.cm-rc-tot td { border-bottom: 0; padding-top: 10px; font-size: 16px; }
@media (max-width: 560px) {
  .cm-sum .cm-fig + .cm-fig { padding-top: 10px; margin-top: 6px; border-top: 1px solid var(--border); }
}

/* ---- Venue settings -------------------------------------------------------- */
.vs-sec { padding: 18px; margin-bottom: 14px; }
.vs-head { display: flex; align-items: center; justify-content: space-between; gap: 12px; flex-wrap: wrap; }
.vs-head h3 { margin: 0; font-size: 18px; }
.vs-note { margin: 6px 0 14px; color: var(--muted); font-size: 13.5px; line-height: 1.5; }

/* The wordiness fold (#220): a small ? that opens the explanation. */
.hf-q { display: inline-flex; align-items: center; justify-content: center;
  width: 18px; height: 18px; margin-left: 6px; border-radius: 50%;
  border: 1px solid var(--border); background: transparent; color: var(--muted);
  font-size: 11px; font-weight: 700; cursor: pointer; vertical-align: middle;
  line-height: 1; padding: 0; }
.hf-q:hover, .hf-q.open { color: var(--text); border-color: var(--muted); }
.hf-body { display: block; margin-top: 6px; }
.hf-body[hidden] { display: none; }

/* Two tiny phone frames: what auto-add actually changes (#220 — a picture
   over a paragraph). */
.aa-pair { display: flex; gap: 18px; margin: 10px 0 4px 30px; flex-wrap: wrap; }
.aa-opt { display: flex; flex-direction: column; align-items: center; gap: 5px;
  font-size: 12px; color: var(--muted); }
.aa-opt svg { display: block; }

.pill { display: inline-flex; align-items: center; gap: 6px; border-radius: 999px; padding: 5px 11px;
  font-size: 12.5px; font-weight: 700; background: var(--surface-2); color: var(--muted); }
.pill.ok { background: rgba(62,207,142,.14); color: var(--green); }
.pill.bad { background: rgba(255,92,92,.14); color: var(--red); }
.pill.lock { background: rgba(245,166,35,.14); color: var(--amber); }
/* A pasted ingredient line still waiting on a decision — not an error, but not
   ready either. Same amber as a lock, because both mean "somebody has to say". */
.pill.warn { background: rgba(245,166,35,.14); color: var(--amber); }
/* The pasted text, kept from setting the table's width. Without this a long
   product name pushes Status off the edge of the dialog, and Status is what the
   review screen is for. */
.rp-raw { max-width: 240px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* The pill must not wrap; the sentence under it must, or "No quantity — say how
   much and this line is ready" gets cut off mid-word at the dialog's edge. */
#rp-rows .pill { white-space: nowrap; }
#rp-rows td:last-child { max-width: 210px; }
#rp-rows td:last-child .hint { white-space: normal; }
/* Wide enough to read which product was chosen — confirming that is the reason
   this dialog exists, and "2 CHI…" confirms nothing. */
#rp-rows .rp-prod { min-width: 190px; max-width: 190px; }
/* Rows a paste just added, so they are findable in a recipe that already had
   ingredients. Fades rather than persisting — it is a "here they are", not a state. */
.rec-ing.ri-new > td { animation: ri-landed 1.6s ease-out; }
@keyframes ri-landed {
  from { background: rgba(62,207,142,.18); }
  to { background: transparent; }
}

.hr-grid { display: flex; flex-direction: column; gap: 8px; }
.hr-row { display: grid; grid-template-columns: 104px 1fr auto 1fr auto; gap: 10px; align-items: center; }
.hr-row .f-input { padding: 8px 10px; }
.hr-day { font-size: 14px; font-weight: 600; }
.hr-to { color: var(--muted); font-size: 13px; }
.hr-shut { display: flex; align-items: center; gap: 7px; font-size: 13px; color: var(--muted); white-space: nowrap; }
.hr-shut input { flex: none; width: 16px; height: 16px; margin: 0; accent-color: var(--primary); }
@media (max-width: 620px) {
  .hr-row { grid-template-columns: 1fr 1fr; grid-template-areas: 'day day' 'from to' 'shut shut'; }
  .hr-day { grid-area: day; } .hr-to { display: none; }
  .hr-shut { grid-area: shut; }
}

.sw-grid { display: flex; flex-wrap: wrap; gap: 8px; }
.sw-grid.is-locked { opacity: .55; pointer-events: none; }
.sw { display: inline-flex; align-items: center; gap: 8px; background: var(--surface-2);
  border: 1.5px solid transparent; border-radius: 999px; padding: 7px 14px 7px 8px;
  color: var(--text); font-size: 13.5px; font-weight: 600; }
.sw.on { border-color: var(--primary); }
.sw-dot { width: 18px; height: 18px; border-radius: 50%; border: 1px solid rgba(255,255,255,.18); }

.vs-prev { margin-top: 14px; padding: 14px; border-radius: 10px; background: #fff;
  display: flex; align-items: center; gap: 12px; flex-wrap: wrap; }
.vs-prev-btn { background: var(--prev, #191919); color: var(--prev-ink, #fff); border-radius: 6px;
  padding: 12px 18px; font-weight: 700; font-size: 15px; }
.vs-prev-chip { background: var(--prev, #191919); color: var(--prev-ink, #fff); border-radius: 999px;
  padding: 7px 14px; font-weight: 700; font-size: 14px; }

/* ---- POS transactions / money / notifications / more (v4.13) --------------
   Square's structure — one list for everything that has happened, a detail
   screen that leads with what you came to do — in the till's own dark skin. */
.pos-tx-list { gap: 0; }
.pos-tx-search { display: flex; align-items: center; gap: 10px; background: #1a1d24;
  border: 1px solid #2a2e38; border-radius: var(--pos-r); padding: 0 12px; margin-bottom: 10px; color: #6c7482; }
.pos-tx-search input { flex: 1; background: none; border: none; padding: 12px 0; font-size: 15px;
  color: #e8eaee; }
.pos-tx-search input:focus { outline: none; box-shadow: none; }
.pos-tx-day { color: #8b93a1; font-size: 13px; font-weight: 700; padding: 14px 2px 6px;
  position: sticky; top: 0; background: #0f1116; z-index: 1; }
.pos-tx-row { display: flex; align-items: center; gap: 12px; width: 100%; text-align: left;
  background: none; border: none; border-bottom: 1px solid #23262e; padding: 12px 6px;
  color: #e8eaee; cursor: pointer; }

/* Filtering and ticking off (v4.52). The chips are toggles because "till and
   Deliveroo but not QR" is a real way to work a lunch rush; All is always
   there, so nobody can untick their way to a blank screen. */
.pos-txchips { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; padding: 8px 6px 4px; }
.pos-txchips-label { color: #6c7482; font-size: 12px; font-weight: 700;
  text-transform: uppercase; letter-spacing: .05em; margin-right: 2px; }
.pos-txchip { background: #1a1d24; border: 1px solid #2a2e38; color: #8b93a1;
  border-radius: 999px; padding: 6px 13px; font-size: 13px; font-weight: 600;
  font-family: inherit; cursor: pointer; white-space: nowrap; }
.pos-txchip:hover { color: #e8eaee; border-color: #3a3f4b; }
.pos-txchip.on { background: rgba(59,111,245,.18); border-color: #3b6ff5; color: #cfdcff; }
.pos-txbar { display: flex; align-items: center; justify-content: space-between; gap: 10px;
  padding: 2px 8px 8px; }
.pos-txwhat { color: #8b93a1; font-size: 12.5px; }
.pos-txtick { background: none; border: 1px solid #2a2e38; color: #8b93a1; border-radius: 999px;
  padding: 5px 12px; font-size: 12.5px; font-weight: 600; font-family: inherit; cursor: pointer; }
.pos-txtick.on { border-color: #3ec98a; color: #3ec98a; background: rgba(62,201,138,.12); }

/* The row and its tick sit side by side, so handing something over never means
   opening it first. */
.pos-tx-line { display: flex; align-items: stretch; }
/* `width: 100%` plus a 54px tick is 54px more than there is. On a wide screen
   nobody noticed; on a phone the list scrolled sideways under a finger that was
   trying to scroll down. A flex item's automatic minimum size is its
   min-content width, so the row could not shrink to make room either — hence
   `min-width: 0` as well as the width. */
.pos-tx-line .pos-tx-row { border-bottom: none; width: auto; flex: 1 1 auto; min-width: 0; }
.pos-tx-line { border-bottom: 1px solid #23262e; }
.pos-tx-tick { flex: none; width: 54px; background: none; border: none; cursor: pointer;
  color: #565d6b; display: grid; place-items: center; }
.pos-tx-tick:hover { color: #8b93a1; background: #171a21; }
.pos-tx-tick.on { color: #3ec98a; }
.pos-tx-row:hover, .pos-tx-row.on { background: #171a21; }
.pos-tx-row.void { opacity: .55; }
.pos-tx-ico { display: flex; gap: 3px; color: #8b93a1; flex: none; }
.pos-tx-main { flex: 1; min-width: 0; }
.pos-tx-main strong { display: block; font-size: 15px; font-weight: 650;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.pos-tx-sub { display: block; color: #8b93a1; font-size: 12.5px; margin-top: 2px; }
.pos-tx-amt { flex: none; text-align: right; font-size: 15px; font-weight: 700;
  font-variant-numeric: tabular-nums; }
.pos-tx-time { display: block; color: #8b93a1; font-size: 12.5px; font-weight: 400; margin-top: 2px; }
.pos-tx-chev { flex: none; color: #4b5260; display: flex; }

.pos-tx-detail { display: flex; flex-direction: column; gap: 0; }
/* The split detail (Jamal, 2026-08-27): on the wide register the receipt and
   its facts sit on the RIGHT and every action on the LEFT, visible the moment
   the page opens — nothing behind a sheet. The actions column is sticky so it
   stays put while a long receipt scrolls. */
.pos-tx-split { display: grid; grid-template-columns: minmax(250px, 320px) 1fr; gap: 18px; align-items: start; }
.pos-tx-split .pos-tx-side { order: -1; position: sticky; top: 0; display: flex; flex-direction: column; gap: 10px; }
.pos-tx-split .pos-tx-main { min-width: 0; display: flex; flex-direction: column; }
.pos-tx-all { display: flex; flex-direction: column; gap: 6px; }
.pos-ract-slim { display: flex; align-items: center; gap: 10px; text-align: left;
  background: #1a1d24; border: 1px solid #2a2e38; color: #e8eaee; border-radius: var(--pos-r);
  padding: 9px 12px; cursor: pointer; font-size: 13.5px; }
.pos-ract-slim:hover { border-color: #3b6ff5; }
.pos-ract-slim svg { color: #8b93a1; flex: none; }
.pos-ract-slim span { min-width: 0; }
.pos-ract-slim .pos-tx-sub { display: block; color: #8b93a1; font-size: 11.5px; margin-top: 1px; font-weight: 400; }
/* The phone: identify the order first — it takes the top half and scrolls
   inside itself — then every action fills the bottom half. One screen, no
   page scroll (the register's standing rule). */
.posm-paper:has(.pos-tx-split) { display: flex; flex-direction: column; overflow: hidden; padding-bottom: 10px; }
.posm-paper .pos-tx-split { display: flex; flex-direction: column; flex: 1; min-height: 0; gap: 10px; }
.posm-paper .pos-tx-split .pos-tx-main { order: -1; flex: 1 1 52%; min-height: 0; overflow-y: auto; scrollbar-width: none; }
.posm-paper .pos-tx-split .pos-tx-main::-webkit-scrollbar { display: none; }
.posm-paper .pos-tx-split .pos-tx-side { order: 0; position: static; flex: 1 1 48%; min-height: 0; overflow-y: auto; }
.posm-paper .pos-tx-split .pos-tx-acts { flex-direction: row; }
.posm-paper .pos-tx-split .pos-tx-act { flex: 1; padding: 12px; font-size: 14px; }
.posm-paper .pos-tx-split .pos-tx-all { display: grid; grid-template-columns: 1fr 1fr; gap: 6px; }
.posm-paper .pos-tx-split .pos-ract-slim { padding: 8px 10px; font-size: 12.5px; }
.posm-paper .pos-tx-split .pos-ract-slim .pos-tx-sub { display: none; }
.pos-tx-acts { display: flex; flex-direction: column; gap: 10px; margin-bottom: 6px; }
.pos-tx-act { background: #1a1d24; border: 1px solid #2a2e38; color: #e8eaee; border-radius: var(--pos-r);
  padding: 16px; font-size: 15px; font-weight: 650; cursor: pointer; }
.pos-tx-act:hover:not(:disabled) { border-color: #3b6ff5; }
.pos-tx-act:disabled { opacity: .4; cursor: default; }
.pos-tx-sect { display: flex; align-items: baseline; justify-content: space-between;
  margin: 18px 0 2px; color: #8b93a1; font-size: 13px; }
.pos-tx-sect h4 { margin: 0; color: #e8eaee; font-size: 16px; font-weight: 700; }
.pos-tx-lines { display: flex; flex-direction: column; }
.pos-tx-line { display: flex; align-items: center; gap: 12px; padding: 12px 2px;
  border-bottom: 1px solid #23262e; font-size: 14.5px; color: #e8eaee; }
.pos-tx-line svg { color: #8b93a1; flex: none; }
.pos-tx-line span { flex: 1; min-width: 0; }
.pos-tx-line b { font-variant-numeric: tabular-nums; }
.pos-tx-none { color: #6c7482; font-size: 13px; padding: 12px 2px; }

.pos-money, .pos-notif, .pos-more { flex: 1; overflow-y: auto; padding: 4px 2px 20px; }
/* On the desktop stage these panes sit inside the stage's own padded frame,
   so 2px sides never touch anything. On a phone the pane IS the screen —
   Jamal, of the Money tab: *"Some of this text is far too close to the left
   side of the screen"* — so the glass edge gets a real margin. */
.posm .pos-money, .posm .pos-notif, .posm .pos-more { padding: 4px 14px 20px; }
.pos-money-hero { background: #171a21; border: 1px solid #23262e; border-radius: var(--pos-r);
  padding: 22px; text-align: center; color: #8b93a1; font-size: 13.5px; }
.pos-money-hero strong { display: block; color: #e8eaee; font-size: 40px; font-weight: 800;
  margin: 6px 0 4px; font-variant-numeric: tabular-nums; }
/* Which days the figure is for, and when it turns up. Jamal, 2026-08-22:
   *"show when the next payment is due and, like, when the amounts are from and,
   like, starting to and from."* The span sits inside the hero, under the label,
   because it is part of what that number means; the date gets a row of its own
   under it, because it is a different fact. */
.pos-money-for { display: block; margin-top: 6px; color: #6c7482; font-size: 12.5px; }
.pos-money-next { display: flex; align-items: center; gap: 12px; margin-top: 10px;
  background: #171a21; border: 1px solid #23262e; border-left: 3px solid var(--green);
  border-radius: var(--pos-r); padding: 13px 15px; color: #8b93a1; }
/* No date is not an error — a hall on a manual schedule has promised none —
   so it loses the green edge rather than gaining a red one. */
.pos-money-next.none { border-left-color: #2a2e38; }
.pos-money-next svg { flex: 0 0 auto; color: #8b93a1; }
.pos-money-next-t { min-width: 0; }
.pos-money-next-t b { display: block; color: #e8eaee; font-size: 15px; font-weight: 700; }
.pos-money-next-t em { display: block; font-style: normal; font-size: 12.5px; margin-top: 2px;
  line-height: 1.45; }
.pos-money-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 10px; margin: 12px 0 16px; }
.pos-money-card { background: #171a21; border: 1px solid #23262e; border-radius: var(--pos-r); padding: 14px;
  color: #8b93a1; font-size: 13px; display: flex; flex-direction: column; gap: 4px; }
.pos-money-card strong { color: #e8eaee; font-size: 20px; font-weight: 700;
  font-variant-numeric: tabular-nums; }
/* #164 — a tender tile with orders behind it is a button, and looks like one.
   A tender nobody took today stays a plain card: an empty list is a worse
   answer than no button. */
.pos-money-card.tap { cursor: pointer; text-align: left; font-family: inherit;
  border-color: #2c3140; transition: background .12s ease, border-color .12s ease; }
.pos-money-card.tap:hover { background: #1c2029; border-color: #3a4152; }
.pos-money-card.tap:active { transform: scale(.985); }
.pos-money-n { color: #6f7787; font-size: 12px; font-style: normal; }
.pos-tender-list { display: flex; flex-direction: column; gap: 6px; max-height: 52vh; overflow-y: auto; }
.pos-tender-row { display: grid; grid-template-columns: 90px 60px 1fr auto; align-items: center;
  gap: 10px; width: 100%; padding: 11px 12px; background: #171a21; border: 1px solid #23262e;
  border-radius: var(--pos-r); color: #c9cfda; font: inherit; font-size: 14px;
  text-align: left; cursor: pointer; }
.pos-tender-row:hover { background: #1c2029; }
.pos-tender-no { font-weight: 700; color: #e8eaee; font-variant-numeric: tabular-nums; }
.pos-tender-when, .pos-tender-who { color: #8b93a1; font-size: 13px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.pos-tender-amt { font-weight: 700; color: #e8eaee; font-variant-numeric: tabular-nums;
  text-align: right; }
/* A split bill says what the rest of the order was, quietly — the big number is
   the part paid in this tender, because that is what has to add up. */
.pos-tender-amt i { display: block; color: #8b93a1; font-size: 12px; font-weight: 500;
  font-style: normal; }
.pos-money-days { display: grid; grid-template-columns: repeat(7, 1fr); gap: 6px; margin: 8px 0 18px; }
.pos-money-day { background: #171a21; border: 1px solid #23262e; border-radius: var(--pos-r); padding: 10px 4px;
  text-align: center; color: #8b93a1; font-size: 12px; }
.pos-money-day strong { display: block; color: #e8eaee; font-size: 14px; font-weight: 700; margin: 4px 0 2px;
  font-variant-numeric: tabular-nums; }
.pos-money-note { background: #171a21; border: 1px solid #23262e; border-radius: var(--pos-r); padding: 16px;
  color: #8b93a1; font-size: 13.5px; line-height: 1.5; }
.pos-money-note strong { display: block; color: #e8eaee; margin-bottom: 4px; }
.pos-money-note p { margin: 0; }
/* The till's Money tab (#207). A failed payout is the one thing on this screen
   allowed to shout, because it is the one thing a vendor has to chase. */
.pos-money-bad { border: 1px solid #ff5c5c; border-radius: var(--pos-r); padding: 0 10px 6px; }
.pos-notif-bad strong { color: #ff5c5c; }
.pos-notif-bad svg { color: #ff5c5c; }
/* A list is a list: it scrolls inside its own box rather than moving the page,
   which is the only exception to the till's no-scrolling rule and the same one
   the kitchen board takes. */
.pos-money-list { max-height: 34vh; overflow-y: auto; -webkit-overflow-scrolling: touch; }
/* A suggestion's second line is a sentence and wraps; a payout's is one line. */
.pos-money-sect .pos-notif-row .pos-tx-sub { white-space: normal; }
.pos-money-cta svg { vertical-align: -3px; margin-right: 6px; }

.pos-notif-row, .pos-more-row { display: flex; align-items: center; gap: 12px; width: 100%;
  text-align: left; background: none; border: none; border-bottom: 1px solid #23262e;
  padding: 14px 6px; color: #e8eaee; cursor: pointer; }
.pos-notif-row:hover:not(:disabled), .pos-more-row:hover { background: #171a21; }
.pos-notif-row:disabled { cursor: default; }
.pos-notif-row svg, .pos-more-row svg { color: #8b93a1; flex: none; }
.pos-notif-row > span, .pos-more-row > span { flex: 1; min-width: 0; }
.pos-notif-row strong, .pos-more-row strong { display: block; font-size: 15px; font-weight: 650; }

/* Split Amount (v4.14) — one bill, several payments. */
.pos-split-note { background: #1a1d24; border: 1px solid #2a2e38; border-radius: var(--pos-r);
  padding: 12px 14px; color: #b9c0cc; font-size: 13.5px; line-height: 1.45; }
.pos-splitlink { background: none; border: none; color: #6f9dff; font-size: 14px; font-weight: 650;
  text-decoration: underline; padding: 2px 0 10px; cursor: pointer; }
.pos-split-amt { display: flex; align-items: center; justify-content: space-between; gap: 12px;
  background: #1a1d24; border: 1px solid #2a2e38; border-radius: var(--pos-r); padding: 6px 14px; }
.pos-split-amt span { font-weight: 650; }
.pos-split-amt input { width: 120px; text-align: right; background: none; border: none;
  color: #e8eaee; font-size: 18px; font-weight: 700; padding: 10px 0; }
.pos-split-amt input:focus { outline: none; box-shadow: none; }

/* The three ways to split, as tabs across the top of the sheet. "By item" is
   only offered when there is more than one item — an empty picker is worse
   than no tab. */
.pos-splitways { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(120px, 100%), 1fr));
  gap: 8px; margin-bottom: 14px; }
.pos-splitway { display: flex; flex-direction: column; gap: 2px; align-items: flex-start;
  padding: 10px 12px; border-radius: var(--pos-r); border: 1px solid #2a2e38;
  background: #171a21; color: #8b93a1; font-family: inherit; text-align: left; cursor: pointer; }
.pos-splitway strong { font-size: 14px; color: #e8eaee; }
.pos-splitway em { font-style: normal; font-size: 11.5px; line-height: 1.3; }
.pos-splitway.on { border-color: #4f8cff; background: #131b2b; }
.pos-splitway.on strong { color: #4f8cff; }
.pos-splitpane { min-height: 150px; }

/* Who is paying. Six colours, and no more than six people — past that the
   picker stops being readable and equal-n is the right tool anyway. */
.pos-split-who { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; margin: 12px 0 10px; }
.pos-split-chip { padding: 3px 10px; border-radius: 999px; font-size: 12px; font-weight: 800;
  color: #0d0f14; background: #8b93a1; border: none; font-family: inherit; cursor: pointer;
  display: inline-flex; align-items: center; gap: 5px; }
/* The × that says a chip can go. Dimmed inside the coloured pill so the
   number stays the loudest thing on it. */
.pos-split-chip b { font-weight: 800; opacity: .55; }
.pos-split-more { padding: 3px 10px; border-radius: 999px; border: 1px dashed #3a4050;
  background: none; color: #8b93a1; font-family: inherit; font-size: 12px; cursor: pointer; }
.pos-split-more[disabled] { opacity: .4; cursor: default; }
.pos-split-chip.p0 { background: #4f8cff; }
.pos-split-chip.p1 { background: #3ec98a; }
.pos-split-chip.p2 { background: #d9a441; }
.pos-split-chip.p3 { background: #c77dff; }
.pos-split-chip.p4 { background: #ff8f6b; }
.pos-split-chip.p5 { background: #5fd0d6; }

/* The bill, one row per line. Tapping a row moves it to the next person and
   off the end to nobody — one control for assign, reassign and undo. */
.pos-split-items { display: flex; flex-direction: column; gap: 6px;
  max-height: 42vh; overflow-y: auto; }
.pos-split-item { display: grid; grid-template-columns: 26px 1fr auto 34px; align-items: center;
  gap: 10px; padding: 9px 11px; border-radius: var(--pos-r);
  border: 1px solid #2a2e38; background: #171a21; color: #c8ceda;
  font-family: inherit; font-size: 14px; text-align: left; cursor: pointer; }
.pos-split-item .q { font-weight: 800; color: #8b93a1; font-variant-numeric: tabular-nums; }
.pos-split-item .n { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.pos-split-item .a { font-variant-numeric: tabular-nums; color: #8b93a1; }
.pos-split-item .w { text-align: right; font-weight: 800; font-size: 12px; color: #565d6b; }
.pos-split-item.taken { border-color: currentColor; }
.pos-split-item.taken .w { color: inherit; }
.pos-split-item.taken.p0 { color: #4f8cff; }
.pos-split-item.taken.p1 { color: #3ec98a; }
.pos-split-item.taken.p2 { color: #d9a441; }
.pos-split-item.taken.p3 { color: #c77dff; }
.pos-split-item.taken.p4 { color: #ff8f6b; }
.pos-split-item.taken.p5 { color: #5fd0d6; }
.pos-split-item.taken .n, .pos-split-item.taken .a { color: #e8eaee; }
.pos-split-eq { display: grid; grid-template-columns: repeat(4, 1fr); gap: 8px; }
.pos-split-eq button { background: #1a1d24; border: 1px solid #2a2e38; color: #e8eaee;
  border-radius: var(--pos-r); padding: 18px 6px; font-size: 16px; font-weight: 700; cursor: pointer; }
.pos-split-eq button.on { border-color: #3b6ff5; background: rgba(59,111,245,.16); }

/* Money tab (v4.15) — Balance / Activity, Square's shape. */
.pos-money-tabs { display: flex; gap: 20px; border-bottom: 1px solid #23262e; margin-bottom: 14px; }
.pos-money-tabs button { background: none; border: none; color: #8b93a1; font-size: 15px;
  font-weight: 650; padding: 10px 0; border-bottom: 2px solid transparent; cursor: pointer; }
.pos-money-tabs button.on { color: #e8eaee; border-bottom-color: #e8eaee; }
/* The full-width pill under the balance, shaped like Square's Instant
   Transfer button — rounded right off, bold, quietly grey while it is only
   a statement rather than an action. */
.pos-money-cta { width: 100%; margin-top: 16px; background: #23262e; border: none;
  color: #8b93a1; border-radius: 999px; padding: 16px; font-size: 16px; font-weight: 750; }
/* The missing bank account, said loudly at the top of Money. Jamal: "it
   should be at the top of the page as a more colourful bold alert." Amber,
   because it is money waiting to go wrong rather than money already lost. */
.pos-money-alert {
  display: flex; align-items: center; gap: 12px; width: 100%; text-align: left;
  margin: 2px 0 12px; padding: 14px 16px; border-radius: var(--pos-r);
  border: 1px solid #b8860b; cursor: pointer;
  background: linear-gradient(135deg, #7a5a08, #4a3706); color: #ffe9b0;
}
.pos-money-alert svg { flex: none; color: #ffd76a; }
.pos-money-alert span { flex: 1; min-width: 0; }
.pos-money-alert strong { display: block; font-size: 16px; font-weight: 800; color: #fff; }
.pos-money-alert em { display: block; font-style: normal; font-size: 13px;
  color: #ffe9b0; margin-top: 3px; line-height: 1.45; }
.pos-money-alert b { flex: none; font-size: 15px; font-weight: 800; color: #ffd76a; }
/* The sheet's three fields. */
.pos-field { display: block; margin-bottom: 12px; }
.pos-field span { display: block; font-size: 13px; font-weight: 650; color: #8b93a1;
  margin-bottom: 5px; }
.pos-field input { width: 100%; box-sizing: border-box; background: #14161c;
  border: 1px solid #2a2e38; border-radius: 9px; color: #e8eaee; font-size: 16px;
  padding: 11px 12px; }
.pos-field input:focus { outline: none; border-color: #4f8cff; }
/* Square's Money screen — the reference this one copies — puts each section
   in its own rounded card with the heading inside, not flat on the page
   (Jamal: "this page is supposed to be the exact copy for the money page and
   its not been followed"). */
.pos-money-sect { margin-top: 14px; background: #171a21; border: 1px solid #23262e;
  border-radius: var(--pos-r); padding: 16px 16px 8px; }
.pos-money-sect h4 { margin: 0 0 4px; font-size: 19px; font-weight: 800; color: #e8eaee; }
.pos-money-sect .pos-notif-row { border-bottom: none; padding: 12px 0; }
/* The icon in its rounded tile, Square-style. */
.pos-money-ic { flex: none; width: 42px; height: 42px; border-radius: 11px;
  display: flex; align-items: center; justify-content: center;
  background: #23262e; color: #e8eaee; }
/* "View all activity" sits centred and underlined at the card's foot. */
.pos-money-sect .pos-splitlink { display: block; width: 100%; text-align: center;
  text-decoration: underline; color: #e8eaee; font-size: 16px; font-weight: 700;
  padding: 12px 0 10px; }
/* The failed-payouts card keeps its red edge — the older .pos-money-bad rule
   sits earlier in the file and would lose the border to the card base. */
.pos-money-sect.pos-money-bad { border-color: #ff5c5c; }
/* Square-style loading skeletons — grey shapes that breathe where the
   content is about to be, instead of the word "Loading" (Jamal, with
   screenshots of Square's: "pages like this are much better than just
   'loading' for every page"). */
.pos-skel { display: flex; flex-direction: column; gap: 12px; padding: 6px 2px; }
.pos-skel-b { display: block; background: #1d2027;
  animation: pos-skel-breathe 1.3s ease-in-out infinite; }
.pos-skel-row { display: flex; gap: 12px; align-items: center; }
.pos-skel-row > span { flex: 1; display: flex; flex-direction: column; gap: 8px; }
.pos-skel-sq { width: 42px; height: 42px; border-radius: 11px; flex: none; }
.pos-skel-tiles { display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 10px; }
/* The kitchen board's first load: ticket-shaped blocks, two-up like the
   phone's chips, until the first poll has actually answered. */
.pos-skel-board { display: grid; grid-template-columns: 1fr 1fr; gap: 8px;
  padding: 8px; align-content: start; width: 100%; }
@keyframes pos-skel-breathe { 0%, 100% { opacity: .5; } 50% { opacity: 1; } }
@media (prefers-reduced-motion: reduce) {
  .pos-skel-b { animation: none; opacity: .65; }
}

/* The slim row above the balance holding only the settings gear — the
   Balance/Activity tabs were scrapped (Jamal: "scrap the top selection until
   I eventually add helch loans"); when Helch Loans exists this row grows the
   Balance | Loans tabs back, which is what Square's actually is. */
.pos-money-top { display: flex; justify-content: flex-end; margin-bottom: 2px; }
.pos-money-cog { margin-left: auto; width: 34px; height: 34px; border-radius: 50%;
  background: #1a1d24; border: 1px solid #2a2e38; color: #cdd3dd;
  display: flex; align-items: center; justify-content: center; cursor: pointer;
  align-self: center; margin-bottom: 6px; }
.pos-money-sect .pos-notif-row b { font-variant-numeric: tabular-nums; }

/* Refund reasons (v4.16) — the confirm button stays off until one is picked. */
.pos-reason-list { display: flex; flex-direction: column; }
.pos-reason { display: flex; align-items: center; justify-content: space-between; gap: 12px;
  width: 100%; text-align: left; background: none; border: none; border-bottom: 1px solid #23262e;
  padding: 15px 2px; color: #e8eaee; font-size: 15px; font-weight: 650; cursor: pointer; }
.pos-reason:hover { background: #171a21; }
.pos-radio { flex: none; width: 22px; height: 22px; border-radius: 50%; border: 2px solid #3a3f4b; }
.pos-reason.on .pos-radio { border-color: #3b6ff5; box-shadow: inset 0 0 0 4px #0f1116, inset 0 0 0 22px #3b6ff5; }

/* Item availability (v5.130.0). Prefixed `stk-` and not `av-`, because `av-`
   is already the staff availability calendar's and its phone rule
   `.av-state { display: none }` was quietly hiding the word beside every
   switch — the same collision as availability.js against soldoutsheet.js.

   Jamal: Jamal: "should be on/off switches with it on
   and green when its in stock and then white switch if out of stock". What was
   here was .pos-reason with a round dot, lit blue when the item was **sold
   out** — so the brightest thing on the sheet was the one thing nobody could
   order. The switch is on when you can sell it, which is what a switch beside a
   dish name means to everybody.

   "White" is drawn as a white outline with a white knob and nothing filled,
   rather than a white slab: a filled white track on a dark sheet shouts louder
   than the green one and would make sold-out the loudest state all over again.
   Filled green = on, outline = off, and the two are told apart across a
   kitchen. */
.stk-row { display: flex; align-items: center; gap: 12px; width: 100%;
  min-height: 52px; padding: 8px 12px; border: 0; border-radius: var(--pos-r);
  background: transparent; color: var(--pos-ink, #e8ebf2); text-align: left;
  font: inherit; cursor: pointer; }
.stk-row:hover { background: #171a21; }
.stk-name { flex: 1 1 auto; min-width: 0; font-size: 15px; font-weight: 600;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.stk-row.off .stk-name { color: var(--muted); font-weight: 500; }
/* **Frozen at the top** (#187, rule 1). The rule, the summary and the tabs stay
   put while the list scrolls under them — a kitchen forty items down a menu
   should not have to scroll back up to reach Ingredients, and on a phone that
   scroll is the whole sheet. Sticky rather than a fixed header so the sheet
   keeps one scroll container and nothing has to be measured. */
/* The availability sheet's body keeps no top padding of its own — rows used to
   slide visibly through that transparent strip above the sticky pin on the
   translucent sheet material (QA round 2, finding 35). The pin supplies the
   breathing room instead, on an opaque background, so scrolling rows pass
   behind something that hides them. */
.stk-sheet .modal-body { padding-top: 0; }
.stk-sheet .stk-pin { padding-top: 14px; }
.stk-pin {
  position: sticky; top: 0; z-index: 4;
  /* Fully opaque, and wide enough. The old version used the surface variable
     (which the material theme can resolve translucent) and reached only 2px
     past its own box — so a row sliding underneath peeked out through the
     modal body's 20px side padding and under the last child's margin. Jamal's
     screenshot: a toggle poking out beside the search box. A solid colour,
     stretched across the body's full width, with its own bottom padding so no
     child margin leaves a see-through band. */
  background: #14161b;
  margin: 0 -20px; padding: 2px 20px 8px;
  box-shadow: 0 6px 10px -8px rgba(0, 0, 0, .6);
}
.stk-pin > :last-child { margin-bottom: 0; }
/* The availability sheet's tabs, v5.202.0. Items and Ingredients pinned first
   — Jamal named those two — with modifiers keeping their place at the end. */
.stk-tabs { display: flex; gap: 6px; margin: 14px 0 6px; }
.stk-tab {
  flex: 1; padding: 9px 10px; border-radius: 10px; border: 1px solid var(--border);
  background: var(--surface-2); color: var(--muted); font: inherit; font-size: 13px;
  font-weight: 700; cursor: pointer;
}
.stk-tab.on { background: var(--primary); border-color: var(--primary); color: #fff; }
/* How many dishes an ingredient would take with it, on the row before it is
   pressed rather than in a toast afterwards. */
.stk-uses { display: block; font-size: 11.5px; font-weight: 600; color: var(--muted); margin-top: 2px; }
/* A dish that is off because a shelf is empty. The switch is disabled and the
   reason is on the row — flipping it here would not find the limes. */
.stk-row.byshortage { opacity: .72; cursor: not-allowed; }
.stk-why { display: block; font-size: 11.5px; font-weight: 700; color: var(--amber); margin-top: 2px; }

.stk-shared { margin-left: 8px; font-size: 11px; font-weight: 600; letter-spacing: .04em;
  text-transform: uppercase; color: var(--muted); }
.stk-state { flex: none; font-size: 12.5px; font-weight: 700; letter-spacing: .02em;
  color: var(--muted); }
.stk-row.on .stk-state { color: var(--green, #3ecf8e); }
.stk-sw { flex: none; position: relative; width: 50px; height: 29px; border-radius: 999px;
  background: transparent; border: 2px solid #fff;
  transition: background .16s ease, border-color .16s ease; }
.stk-sw i { position: absolute; top: 3px; left: 3px; width: 19px; height: 19px;
  border-radius: 50%; background: #fff; transition: left .16s ease; }
.stk-row.on .stk-sw { background: var(--green, #3ecf8e); border-color: var(--green, #3ecf8e); }
.stk-row.on .stk-sw i { left: 24px; }
/* The group's tally is amber, not green: it counts what you cannot sell. */
.stk-gcount { margin-left: 8px; color: var(--amber, #f5a623); }
.stk-sum { color: #fff; }
/* On the narrowest phone the word costs about 60px and starts truncating dish
   names — and the name is the thing somebody is hunting for. The switch is the
   signal; the word is belt-and-braces, and `aria-label` carries it on every row
   at every width regardless. */

/* The reset rule, at the top of the sheet (v5.131.0). Jamal: "they should be
   able to set the stock at the top like when to reset it". It says what the
   rule currently does rather than making somebody open a settings screen to
   find out — a control labelled only "Reset rule" is one nobody presses. */
.stk-rule { display: flex; align-items: center; gap: 12px; width: 100%;
  padding: 12px 14px; border: 1px solid var(--border, #262b35); border-radius: var(--pos-r);
  background: #14171d; color: var(--pos-ink, #e8ebf2); text-align: left; font: inherit;
  cursor: pointer; }
.stk-rule:hover { background: #171a21; }
.stk-rule-what { flex: 1 1 auto; font-size: 13.5px; line-height: 1.4; color: var(--muted); }
.stk-rule-go { flex: none; font-size: 13px; font-weight: 700; color: var(--primary, #4f8cff); }

/* The three modes. A tick rather than a radio: this is a choice with
   consequences, and the row explains itself under its own name. */
.stk-mode { display: flex; align-items: flex-start; gap: 10px; width: 100%;
  padding: 12px; border: 0; border-radius: var(--pos-r); background: transparent;
  color: var(--pos-ink, #e8ebf2); text-align: left; font: inherit; cursor: pointer; }
.stk-mode:hover { background: #171a21; }
.stk-mode.on { background: #171a21; }
.stk-mode-tick { flex: none; width: 20px; padding-top: 2px; color: var(--green, #3ecf8e); }
/* A real tick box on every row, not a mark that appears from nowhere on one —
   an empty box says "this could be chosen" the way a blank gap cannot. */
.stk-mode-box { flex: none; width: 22px; height: 22px; margin-top: 1px;
  border: 1.5px solid var(--border, #3a4150); border-radius: 7px;
  display: inline-flex; align-items: center; justify-content: center;
  color: #fff; background: transparent; }
.stk-mode-box.on { background: var(--green, #3ecf8e); border-color: var(--green, #3ecf8e); }
/* The rule's own picture, one per row, so the list reads at a glance. */
.stk-mode-ico { flex: none; width: 26px; padding-top: 1px; color: var(--muted); }
.stk-mode.on .stk-mode-ico { color: var(--pos-ink, #e8ebf2); }
.stk-mode b { display: block; font-size: 15px; font-weight: 600; }
.stk-mode i { display: block; margin-top: 3px; font-style: normal; font-size: 12.5px;
  line-height: 1.4; color: var(--muted); }

.stk-time { width: 100%; padding: 10px 12px; border-radius: var(--pos-r);
  border: 1px solid var(--border, #262b35); background: #14171d;
  color: var(--pos-ink, #e8ebf2); font: inherit; }

/* The morning-question switch, on the same green-or-outline switch as the
   availability rows so the two read as the same kind of control. */
.stk-ask { display: flex; align-items: center; gap: 12px; width: 100%; min-height: 52px;
  margin-top: 14px; padding: 8px 12px; border: 0; border-radius: var(--pos-r);
  background: transparent; color: var(--pos-ink, #e8ebf2); text-align: left;
  font: inherit; font-size: 14.5px; cursor: pointer; }
.stk-ask span:first-child { flex: 1 1 auto; }
.stk-ask.on .stk-sw { background: var(--green, #3ecf8e); border-color: var(--green, #3ecf8e); }
.stk-ask.on .stk-sw i { left: 24px; }

/* The count on the AVAILABILITY tool. Amber because it counts what you cannot
   sell, and absent rather than zero when there is nothing to say — a badge
   reading 0 is a badge people learn to stop reading. */
.pos-kds-tool { position: relative; }
.stk-badge { position: absolute; top: -6px; right: -6px; min-width: 19px; height: 19px;
  padding: 0 5px; border-radius: 999px; background: var(--amber, #f5a623); color: #14171d;
  font-size: 11px; font-weight: 800; line-height: 19px; text-align: center; }
.pos-kds-tool.has-off { border-color: var(--amber, #f5a623); }


/* Whether the customer was told (v5.136.0). Not the same fact as which lane the
   ticket is in, and it used to be neither shown nor knowable. */
.kt-told { padding: 5px 12px; font-size: 11.5px; font-weight: 800;
  letter-spacing: .05em; text-transform: uppercase; }
.kt-told.idle { color: var(--muted); }
.kt-told.sent { color: var(--green, #3ecf8e); }
.kt-told.failed { color: var(--red, #ff5c5c); }
/* Orange, and flashing, until a transport confirms. Jamal asked for exactly
   this and the reason is worth keeping: a button that went green on the press
   would be reporting its own click, not a delivery. */
.kt-told.sending { color: var(--amber, #f5a623); animation: kt-told-pulse 1s ease-in-out infinite; }
@keyframes kt-told-pulse { 0%, 100% { opacity: 1; } 50% { opacity: .35; } }
/* Somebody who gets motion sick still has to be able to see that this is the
   unresolved state, so it stays amber and stops moving rather than going away. */
@media (prefers-reduced-motion: reduce) { .kt-told.sending { animation: none; } }

/* The alert. It does not dismiss itself — no backdrop click, no Escape, no
   timer — so it also does not wear a close cross, because a cross that does
   nothing is worse than none. */
.kt-alert .modal-head .close-x { display: none; }

/* **And it looks like an alarm.** Jamal, on a photograph of an order told
   eighty-six minutes ago sitting in a plain grey dialog: *"this is good, but it
   needs to be more red."* The tone comes from notifystate.js — `warn` is told
   and still not here, `failed` is nobody could be reached at all — and both are
   somebody standing at a counter not getting their food.

   The whole panel is tinted rather than just the header: a red strip above a
   neutral box reads as decoration, and this has to read as a fault. The glow is
   a static box-shadow rather than an animation, so it is exactly as loud for
   somebody who has asked for reduced motion. */
.kt-alert .modal {
  border: 2px solid var(--red, #ff5a5a);
  box-shadow: 0 0 0 1px rgba(255, 90, 90, .35), 0 18px 60px rgba(255, 60, 60, .28);
  background: linear-gradient(180deg, #2a1416 0%, #1a1013 42%, var(--surface, #171a21) 100%);
}
.kt-alert .modal-head { background: rgba(255, 70, 70, .16); border-bottom: 1px solid rgba(255, 90, 90, .38); }
.kt-alert .modal-head h3, .kt-alert .modal-head strong { color: #ffd7d7; }
.kt-alert .ntf-text { color: #ffeaea; font-weight: 600; }
/* The facts a person needs to go and find somebody stay legible rather than
   being tinted into the background — they are the actionable part. */
.kt-alert .ntf-facts div { background: rgba(255, 255, 255, .05); border: 1px solid rgba(255, 120, 120, .18); }
.kt-alert .ntf-facts b { color: #fff; }
/* Nobody could be reached at all is the worse of the two, and says so. */
.kt-alert.tone-failed .modal { border-color: #ff3b3b; }
.kt-alert.tone-failed .modal-head { background: rgba(255, 45, 45, .24); }

/* A pulse, because a static dialog behind a chef's shoulder is a dialog nobody
   turns round for. Honoured off entirely under reduced motion, where the border
   and the tint above are already doing the work. */
@keyframes kt-alert-pulse {
  0%, 100% { box-shadow: 0 0 0 1px rgba(255, 90, 90, .35), 0 18px 60px rgba(255, 60, 60, .22); }
  50%      { box-shadow: 0 0 0 3px rgba(255, 90, 90, .55), 0 18px 70px rgba(255, 60, 60, .45); }
}
.kt-alert .modal { animation: kt-alert-pulse 1.6s ease-in-out infinite; }
@media (prefers-reduced-motion: reduce) { .kt-alert .modal { animation: none; } }
.ntf-text { margin: 0 0 14px; font-size: 16px; line-height: 1.45; }
.ntf-facts { display: grid; grid-template-columns: repeat(auto-fit, minmax(130px, 1fr));
  gap: 10px; }
.ntf-facts div { padding: 10px 12px; border-radius: var(--pos-r); background: #14171d; }
.ntf-facts em { display: block; font-style: normal; font-size: 10.5px; letter-spacing: .09em;
  text-transform: uppercase; color: var(--muted); }
.ntf-facts b { display: block; margin-top: 3px; font-size: 15.5px; }

@media (max-width: 419px) {
  .stk-state { display: none; }
  /* And the longest name on the menu still does not fit the column that frees
     up — "BONE MARROW TRUFFLE MAYO" runs 9px over at 390px. It wraps rather
     than truncating: a row that grows a line costs nothing, and a chef hunting
     a dish should never be shown half its name. */
  .stk-name { white-space: normal; overflow-wrap: anywhere; line-height: 1.25; }
  .stk-row { padding-top: 10px; padding-bottom: 10px; }
}

/* Refund badge on a transaction row, and the completion screen (v4.17). */
.pos-tx-ico { position: relative; }
.pos-tx-undo { position: absolute; right: -6px; bottom: -5px; width: 15px; height: 15px;
  border-radius: 50%; background: #f5a623; color: #14161b; display: flex;
  align-items: center; justify-content: center; }
.pos-tx-ico.undone > svg:first-of-type { opacity: .55; }
.pos-done { display: flex; flex-direction: column; align-items: center; gap: 8px;
  padding: 26px 10px 10px; text-align: center; }
.pos-done-tick { color: #3ec98a; display: flex; }
.pos-done strong { font-size: 19px; font-weight: 700; margin-top: 6px; }
.pos-done span:last-child { color: var(--muted); font-size: 14px; }

/* Mobile checkout (v4.18): Square's flow — browse on top, the sale at the
   bottom — in the till's own Lightspeed skin. */
.posm-chargebar { display: flex; gap: 8px; padding: 10px 12px calc(10px + env(safe-area-inset-bottom));
  border-top: 1px solid #23262e; background: #14161b; }
.posm-charge { flex: 1; background: #1a1d24; border: 1px solid #2a2e38; color: #6c7482;
  border-radius: var(--pos-r); padding: 14px; font-size: 16px; font-weight: 700; }
.posm-charge.on { background: #e8eaee; border-color: #e8eaee; color: #14161b; }
.posm-charge span { display: block; font-size: 12.5px; font-weight: 500; margin-top: 2px; opacity: .75; }
.posm-chargenow { flex: 0 0 40%; background: #3b6ff5; border: none; color: #fff; border-radius: var(--pos-r);
  padding: 14px 10px; font-size: 16px; font-weight: 700; }
.posm-chargenow span { display: block; font-size: 12.5px; font-weight: 500; margin-top: 2px; opacity: .85; }
/* The just-added flash: the item says so itself, no notification. */
.posm-tile.added { border-color: #2f9e6b; box-shadow: inset 0 0 0 999px rgba(47,158,107,.22); }
.posm-tile.added em { color: #6fe0ac; font-weight: 700; }
.posm-tile em, .posm-tile.added em { transition: color .25s ease; }

/* **A till sheet fits the screen it is on** (v5.59.1).
 *
 * The standing rule is that nothing on the POS should need scrolling, and the
 * thing quietly breaking it was the chrome rather than the content: every
 * dialog gets 60px of breathing room top and bottom, which on a 720px phone is
 * a sixth of the screen spent on nothing. `pos-fit` reclaims it and caps the
 * panel at the window, so a sheet that nearly fits now does.
 *
 * On a tablet — where there is room — the padding stays generous, because the
 * point was never to make the register look cramped on a 10-inch screen. */
.modal-backdrop.pos-fit { padding: 10px 12px; align-items: center;
  /* Both ends of the phone are spoken for: the home indicator below and — the
     one this rule forgot — the clock and battery above. In the add-to-home-
     screen app the viewport starts at the physical top of the screen, so a
     sheet tall enough to fill it put its title bar under the status bar.
     Jamal, on a photograph of the Availability sheet: *"The top bar is
     missing."* env() is 0 in a normal browser tab, where max() keeps the
     10px, so only the standalone app moves. */
  padding-top: max(10px, env(safe-area-inset-top));
  padding-bottom: max(10px, env(safe-area-inset-bottom)); }
/* Against the backdrop, not against the screen. The backdrop is fitted to the
   *visual* viewport in ui.js, so on a phone with the keyboard up it is the
   band you can actually see — and `100dvh` is not, because iOS does not shrink
   the layout viewport for a keyboard. A percentage follows the backdrop
   wherever it ends up; the 20px the calc was reserving is the backdrop's own
   padding and is already accounted for. */
.modal.pos-fit { display: flex; flex-direction: column; max-height: 100%;
  /* dvh where it exists: iOS sizes a fixed backdrop against the layout
     viewport, which runs on behind Safari's bottom bar — Jamal's ticket sheet
     had its blue button half-buried under it. The dynamic viewport unit stops
     at the bar.

     The safe areas come off this height too, and come back as margins. The
     backdrop's padding cannot do this job alone: a sheet at this max-height
     is TALLER than the backdrop's padded content box, so it overflows and
     centres itself, splitting the spare 20px evenly — which in the add-to-
     home-screen app put the title bar under the iPhone's clock (Jamal, on a
     photograph of the Availability sheet: *"The top bar is missing"*). The
     margins are asymmetric on purpose, because the notch (~59px) and the home
     indicator (~34px) are; in a normal browser tab env() is 0 and nothing
     here changes. */
  max-height: calc(100dvh - 20px - env(safe-area-inset-top, 0px) - env(safe-area-inset-bottom, 0px));
  margin-top: env(safe-area-inset-top, 0px);
  margin-bottom: env(safe-area-inset-bottom, 0px); }
/* **Down only.** Jamal, of the kitchen screen settings: *"Scroll broken"* —
   with the dialog dragged sideways, the labels cut off past the left edge and a
   second scrollbar along the bottom. `overflow-y: auto` alone is what does it:
   with `overflow-x` left at `visible` the used value becomes `auto` too, so the
   moment anything inside is a pixel too wide the whole dialog slides. It is the
   same rule the phone screens already hold — nothing makes the *container* move
   sideways; something genuinely wide scrolls inside its own box. */
.modal.pos-fit .modal-body { flex: 1 1 auto; overflow-y: auto; overflow-x: hidden;
  overscroll-behavior: contain; }
.modal.pos-fit .modal-head, .modal.pos-fit .modal-foot { flex: 0 0 auto; }
/* The generous-padding override for tall screens must not undo the safe-area
   floors above: a shorthand `padding` resets all four sides, and an iPhone
   with a Dynamic Island has a ~59px top inset — more than the 40px this hands
   back. So the two vertical sides keep their max(). */
@media (min-height: 820px) { .modal-backdrop.pos-fit { padding: 40px 16px;
  padding-top: max(40px, env(safe-area-inset-top));
  padding-bottom: max(40px, env(safe-area-inset-bottom)); } }
/* Short screens: the prose and the chrome give way before the buttons do —
   a number you have to hit mid-service must stay thumb-sized. */
@media (max-height: 760px) {
  .modal.pos-fit .modal-head { padding: 13px 18px; }
  .modal.pos-fit .modal-body { padding: 14px 18px; }
  .modal.pos-fit .modal-foot { padding: 11px 18px; }
  .modal.pos-fit .pos-split-note { padding: 10px 12px; font-size: 13px; margin-bottom: 10px; }
  .modal.pos-fit .pos-wait-auto { padding: 11px 14px; margin-bottom: 10px; }
  .modal.pos-fit .pos-wait-auto strong { font-size: 15.5px; }
  .modal.pos-fit .pos-disc { padding: 10px 11px; }
  .modal.pos-fit .pos-disc-grid { gap: 7px; }
}
/* A venue with twenty deals can never fit them on a phone, and pretending
   otherwise would mean shrinking a button somebody hits mid-service. The list
   gives way instead — it scrolls inside the panel while Apply and Cancel stay
   exactly where they are. */
.modal.pos-fit .pos-disc-grid { align-content: start; }

/* **The choice picker never scrolls its own Add button away** (v5.58.4).
   The panel caps at the viewport, the questions scroll inside it, and the
   footer stays put — so a burger with eight groups can still be added the
   moment its required ones are answered. Scoped to this one dialog rather
   than to `.modal`, because the other 57 are laid out for their own content. */
.modal.pos-pick { display: flex; flex-direction: column; max-height: calc(100dvh - 120px); }
.modal.pos-pick .modal-body { overflow-y: auto; -webkit-overflow-scrolling: touch; flex: 1 1 auto;
  overscroll-behavior: contain; }
.modal.pos-pick .modal-foot { flex: 0 0 auto; }

/* Modifier groups on the till (v4.19). */
.pos-mg { margin-bottom: 16px; }
.pos-mg-head { display: flex; align-items: baseline; justify-content: space-between; gap: 10px;
  margin-bottom: 8px; }
.pos-mg-head strong { font-size: 15.5px; }
.pos-mg-head span { color: #8b93a1; font-size: 12.5px; font-weight: 700; text-transform: uppercase;
  letter-spacing: .04em;
  /* Always hard right, whether or not the group has a clear button beside it —
     otherwise DONE lands next to the title on some rows and at the edge on
     others, and the eye has to hunt for it. */
  margin-left: auto; }
.pos-mg-head span.req { color: #f5a623; }
/* An answered question reads as finished from across the counter (v5.58.4). */
.pos-mg-head span.done { color: #3ecf8e; }
/* The way back out of a group you can take several from — and, deliberately,
   never rendered on a required pick-one, where the next tap is already the
   undo. `margin-left:auto` keeps it hard right whatever the label says. */
.pos-mg-clear { width: 26px; height: 26px; flex: 0 0 26px;
  display: flex; align-items: center; justify-content: center; align-self: center;
  background: #1a1d24; border: 1px solid #2a2e38; color: #8b93a1;
  border-radius: 50%; font-size: 17px; line-height: 1; cursor: pointer; }
.pos-mg-clear:hover { color: #e8eaee; border-color: #3a3f4b; }
.pos-mg-opts { display: grid; grid-template-columns: repeat(auto-fill, minmax(min(150px, 100%), 1fr)); gap: 8px; }
.pos-mg-opt { display: flex; align-items: center; justify-content: space-between; gap: 8px;
  background: #1a1d24; border: 1px solid #2a2e38; color: #e8eaee; border-radius: var(--pos-r);
  padding: 13px 12px; font-size: 14px; font-weight: 600; text-align: left; cursor: pointer; }
.pos-mg-opt.on { border-color: #3b6ff5; background: rgba(59,111,245,.16); }
.pos-mg-opt:disabled { opacity: .4; cursor: default; }
.pos-mg-opt em { color: #8b93a1; font-style: normal; font-size: 12.5px; }

/* KDS header (v4.20): the wait time, the filter, and — the important one —
   whether the screen is still hearing from the server. */
/* **One row, and it may not run off the screen.** Jamal, 2026-08-23: *"The top
   menu is hidden on mobile a bit as you see."* `kdsphone.bar()` decides what
   fits and in which form; these are the belt — `nowrap` so it never silently
   becomes two rows, and `min-width: 0` on the flexible children so a long label
   shortens rather than pushing its neighbours off the end. */
.pos-kds-bar { display: flex; align-items: center; gap: 12px; padding: 6px 4px 10px;
  flex-wrap: nowrap; max-width: 100%; }
.pos-kds-bar > * { min-width: 0; }
.pos-kds-bar.compact { gap: 8px; }
.pos-kds-bar.tiny { gap: 6px; }
/* The wait number is the loudest thing on the bar and can afford to give a
   little back when the row is tight. */
.pos-kds-bar.tiny .pos-kds-wait strong { font-size: 26px; }
/* The narrowest phones. Tighter than the rest of the bar by 4px a side and 2px
   between, which is 16px — most of the difference between fitting on a 320pt
   screen and not. The ellipsis that used to be here is gone: it was the
   mechanism that hid the overflow rather than a way of coping with it. */
.pos-kds-bar.tiny { gap: 6px; padding: 8px 6px; }
/* The funnel form. Sized to the same tap target as the worded one — a smaller
   button is not the saving, the missing word is. */
.pos-kds-bar.tiny .pos-kds-filter { display: inline-flex; align-items: center; gap: 5px;
  padding: 6px 9px; }
.pos-kds-bar.tiny .pos-kds-filter b { font-size: 12px; font-weight: 800; }
/* **The remaining jobs.** Jamal, 2026-09-10, of what this used to be — the
   count as "· 23 to make" in muted 13px inside the wait chip: *"the remaining
   jobs to do is is up in the top bar, somewhere hidden, so it's a bit stupid."*
   It is the one number on this row the chef acts on, so it is a figure at the
   same size as the wait and is read before it. Sized and spaced as the wait's
   twin deliberately: two figures that mean different things but are read in the
   same glance should not be two different shapes as well. */
.pos-kds-todo { display: flex; align-items: baseline; gap: 8px; white-space: nowrap; }
.pos-kds-todo strong { font-size: 34px; font-weight: 800; line-height: 1;
  font-variant-numeric: tabular-nums; }
.pos-kds-todo span { color: var(--k-muted); font-size: 13px; }
/* Nothing left to make. Greyed rather than removed — see the note in pos.js. */
.pos-kds-todo.clear strong { color: var(--k-muted); }
.pos-kds-bar.tiny .pos-kds-todo strong { font-size: 26px; }

.pos-kds-wait { display: flex; align-items: baseline; gap: 8px; }
.pos-kds-wait strong { font-size: 34px; font-weight: 800; line-height: 1;
  font-variant-numeric: tabular-nums; }
.pos-kds-wait strong em { font-size: 18px; font-style: normal; font-weight: 700; margin-left: 1px; }
.pos-kds-wait span { color: var(--k-muted); font-size: 13px; }
/* A wait somebody typed in, not one the board worked out. Worth being able to
   tell apart at a glance when the number looks wrong for the pass. */
.pos-kds-wait.manual strong { color: var(--amber); }

/* This screen is carrying a printer. Shown only when the bridge is on, because
   a bridge that has quietly stopped is a kitchen that stops getting tickets. */
.pos-kds-bridge {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  margin-left: 14px;
  padding: 5px 10px;
  border: 1px solid var(--k-edge);
  border-radius: 999px;
  background: var(--k-panel);
  color: var(--k-muted);
  font-size: 11px;
  font-weight: 800;
  letter-spacing: .06em;
}
.pos-kds-bridge[hidden] { display: none; }
.pos-kds-bridge i { width: 7px; height: 7px; border-radius: 50%; background: currentColor; }
.pos-kds-bridge.ok { color: var(--green); border-color: rgba(62,207,142,.35); }
.pos-kds-bridge.bad { color: var(--amber); border-color: rgba(245,166,35,.4); }
/* It was a `<select>` and is a button now — same place, same size, but it
   opens a menu of switches rather than ten mutually exclusive answers. When
   anything is ticked it says so without being read: a board showing a subset
   of the orders is a thing somebody has to be able to notice from across a
   kitchen, because the ticket that is missing is invisible by definition. */
/* The display rules below would otherwise beat the hidden attribute — same
   pattern as the bridge chip above. The funnel pill only shows while a filter
   is actually hiding something. */
.pos-kds-filter[hidden] { display: none !important; }
.pos-kds-filter { margin-left: auto; background: var(--k-control); border: 1px solid var(--k-edge);
  color: var(--k-ink-3); border-radius: var(--pos-r); padding: 9px 12px; font-size: 14px;
  font-weight: 650; cursor: pointer; text-align: left;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.pos-kds-filter.on { border-color: #4f8cff; color: #9cbcff; }

/* Whether this screen can actually make a noise (task #200). Four states, and
   the two that mean "you will not hear the next order" are the loud ones —
   amber for locked, because that one is fixed by tapping it. */
/* A speaker, and nothing else. Jamal, 2026-08-23: "just a speaker icon, which
   is green when on and red when off with a big X over the speaker."
   Square rather than the pill the words needed, and a full 44px tap target —
   the saving is the sentence, not the button, and this is the control somebody
   jabs at with a glove on. */
.pos-kds-sound { flex: 0 0 auto; background: var(--k-control); border: 1px solid var(--k-edge);
  color: var(--k-muted-2); border-radius: var(--pos-r); padding: 0; cursor: pointer;
  width: 44px; height: 40px; display: inline-flex; align-items: center;
  justify-content: center; }
/* Coloured from `data-tone`, which alarm.js sets and tests, rather than from
   the four level classes — two places deciding what red means is two places to
   disagree. */
/* On: green, and quiet about it — this is the state nobody needs to notice. */
.pos-kds-sound[data-tone="on"] { border-color: #2f6b3f; color: #3ec98a; background: #12241a; }
/* On, but this browser still obeys the phone's silent switch. Not green, which
   would promise a noise a side switch can kill, and not the red X, which would
   say the sound is off when it may be working. */
.pos-kds-sound[data-tone="warn"] { border-color: #6b5a2f; color: #e8c37d; background: #221f12; }
/* Off. Filled rather than outlined, because the whole point is that this one is
   seen from across a kitchen — and it is the only red thing on the bar. */
.pos-kds-sound[data-tone="off"] { border-color: #ff6b60; color: #fff; background: #b4231a; }

/* Alerts that reach a device nobody is looking at (task #201). Same shape as
   the sound chip beside it, and the same rule: a word rather than a bell, since
   "on" and "you must install this first" are not the same thing and an icon
   cannot say which. */
.pos-kds-push { flex: 0 0 auto; background: var(--k-control); border: 1px solid var(--k-edge);
  color: var(--k-muted-2); border-radius: var(--pos-r); padding: 9px 12px; font-size: 13px;
  font-weight: 650; cursor: pointer; white-space: nowrap; }
.pos-kds-push.on { border-color: #2f6b3f; color: #7ddc9a; }
.pos-kds-push.ask { border-color: #7a4a1e; color: #ffb15c; }
/* Needs installing is not a fault — it is a job, so it reads as one. */
.pos-kds-push.needs-install { border-color: #2f4f7a; color: #8fb8ff; }
.pos-kds-push.blocked { border-color: #6b2f2f; color: #ff9c9c; }
.pos-kds-push.unsupported { border-color: var(--k-edge); color: var(--k-dim-2); }
.push-steps { margin: 12px 0 0; padding-left: 22px; line-height: 1.7; color: var(--text); }
.push-steps li { margin-bottom: 4px; }
/* A row in the board's filter menu. **Named `kfilt-`, not `ksrc-`**: the
   source-colour settings screen further down this file also has a `.ksrc-row`,
   and being later it won — it is a CSS grid with named areas, so it turned this
   flex row into two columns and threw the label into the right-hand track. That
   is the whole of the "bad ui" in the photograph. */
.kfilt-row {
  display: flex; align-items: center; justify-content: flex-start; gap: 11px;
  padding: 11px 14px; text-align: left;
}
.kfilt-txt { flex: 1; min-width: 0; font-weight: 700; font-size: 15px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* The checkbox on the row's left — Jamal: "it should show check boxes to the
   left to each type of order." Always there, empty or ticked, so labels do
   not shuffle sideways as things go on and off. */
.kfilt-ic { flex: 0 0 auto; display: flex; align-items: center; color: var(--muted); }
.kfilt-row.on .kfilt-ic { color: var(--text); }
.kfilt-box { flex: 0 0 20px; height: 20px; display: flex; align-items: center;
  justify-content: center; border: 1.5px solid var(--k-chipedge); border-radius: 5px;
  color: #fff; background: transparent; }
.kfilt-row.on .kfilt-box { border-color: #4f8cff; background: #4f8cff; }
.kfilt-row.on { border-color: #4f8cff; background: rgba(79,140,255,.10); }
/* The individual apps, two-up: ten full-width rows did not fit a phone, and a
   filter sheet you have to scroll is one where the app you want is the one
   below the fold. */
.kfilt-apps { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; margin-top: 8px; }
.kfilt-apps .kfilt-row { padding: 11px 12px; }
.kfilt-apps .kfilt-txt { font-size: 14px; }
.pos-kds-link { flex: none; font-size: 12.5px; font-weight: 700; padding: 6px 10px;
  border-radius: var(--pos-r); border: 1px solid var(--k-edge); color: var(--k-muted); white-space: nowrap; }
.pos-kds-link.ok { color: #3ec98a; border-color: rgba(62,201,138,.4); }
.pos-kds-link.warn { color: #f5a623; border-color: rgba(245,166,35,.45); }
/* A dead link is not a subtle state — the last KDS died quietly. */
.pos-kds-link.bad { color: #fff; background: #c8382f; border-color: #c8382f; }

/* Discounts (v4.21) — reached through Sale actions, Lightspeed's placement. */
.pos-disc-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(min(140px, 100%), 1fr)); gap: 8px; }
.pos-disc { background: #1a1d24; border: 1px solid #2a2e38; color: #e8eaee; border-radius: var(--pos-r);
  padding: 14px 12px; text-align: left; cursor: pointer; }
.pos-disc.on { border-color: #3b6ff5; background: rgba(59,111,245,.16); }
.pos-paydisc { color: #6f9dff; }
.pos-disc strong { display: block; font-size: 14.5px; }
.pos-disc span { color: #8b93a1; font-size: 12.5px; }

/* Wait time (v4.50) — a keypad, not a form. Tapped mid-service with one thumb,
   so every tile is the same size and none of them is small. Twelve choices, so
   the grid is full on both widths: four across on a tablet, three on a phone. */
.pos-wait-auto { display: block; width: 100%; text-align: left; margin-bottom: 12px;
  background: #1a1d24; border: 1.5px solid #2a2e38; color: #e8eaee;
  border-radius: var(--pos-r); padding: 16px 18px; cursor: pointer; font-family: inherit; }
.pos-wait-auto strong { display: block; font-size: 17px; }
.pos-wait-auto span { color: #8b93a1; font-size: 13.5px; }
.pos-wait-auto.on { border-color: #3ec98a; background: rgba(62,201,138,.14); }
.pos-wait-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 10px; }
.pos-wait-key { display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 1px; min-height: 76px; background: #1a1d24; border: 1.5px solid #2a2e38;
  color: #e8eaee; border-radius: var(--pos-r); cursor: pointer; font-family: inherit; }
.pos-wait-key b { font-size: 26px; font-weight: 700; line-height: 1;
  font-variant-numeric: tabular-nums; }
.pos-wait-key span { font-size: 12px; color: #8b93a1; text-transform: uppercase;
  letter-spacing: .06em; }
.pos-wait-key.on { border-color: #3b6ff5; background: rgba(59,111,245,.18); }
.pos-wait-key.on span { color: #b9cbff; }
@media (max-width: 560px) {
  .pos-wait-grid { grid-template-columns: repeat(3, 1fr); gap: 9px; }
  .pos-wait-key { min-height: 68px; }
}

/* Apps on the dashboard (v4.22) — every address this kitchen answers on,
   on the first screen you land on rather than behind a menu. */
.dash-apps { margin-bottom: 16px; }
.app-grid { display: grid; gap: 10px; margin-top: 12px;
  grid-template-columns: repeat(auto-fill, minmax(min(230px, 100%), 1fr)); }
.app-tile { border: 1px solid var(--border); border-radius: 10px; padding: 12px 13px;
  display: flex; flex-direction: column; gap: 4px; background: var(--bg); }
.app-tile.on { border-color: var(--primary); }
.app-tile.off { opacity: .55; }
.app-tile strong { font-size: 14px; }
.app-tile .sub { color: var(--muted); font-size: 12.5px; line-height: 1.35; }
.app-url { display: block; font-size: 12px; color: var(--primary);
  overflow-wrap: anywhere; margin-top: 2px; }
.app-acts { display: flex; gap: 6px; margin-top: 8px; flex-wrap: wrap; align-items: center; }
.app-tag { align-self: flex-start; margin-top: 6px; font-size: 11px; font-weight: 700;
  letter-spacing: .04em; text-transform: uppercase; color: var(--muted);
  border: 1px solid var(--border); border-radius: 999px; padding: 2px 8px; }

/* The card terminal step (v4.23): a card sale happens on the machine first,
   and the till waits for someone to say it went through. */
.pos-term { display: flex; flex-direction: column; align-items: center; gap: 6px;
  padding: 8px 0 4px; text-align: center; }
.pos-term-ico { color: var(--primary); display: flex; }
.pos-term-amt { font-size: 30px; font-weight: 700; letter-spacing: -.01em; }
.pos-term-note { color: var(--muted); font-size: 13.5px; margin: 4px 0 0; max-width: 340px;
  line-height: 1.45; }
.btn.primary.pos-term-go { background: #1f9d61; border-color: #1f9d61; color: #fff; }

/* The staged status card (v5.112.0). Jamal, off another till's card screen:
   one card, ordered stages, a progress bar, the totals pinned underneath.
   The stage strip is also drawn on the receipt screen at its last stage, so
   the two read as one flow rather than two screens that look alike. */
.pos-stages { width: 100%; max-width: 360px; text-align: left; }
.pos-stage-bar { height: 4px; border-radius: 999px; background: var(--line, #23262e);
  overflow: hidden; margin-bottom: 12px; }
.pos-stage-bar i { display: block; height: 100%; border-radius: 999px;
  background: #1f9d61; transition: width .32s cubic-bezier(.32,.72,0,1); }
.pos-stages.failed .pos-stage-bar i { background: #ff6b6b; }
.pos-stages ol { list-style: none; margin: 0; padding: 0; display: grid; gap: 9px; }
.pos-stages li { display: flex; align-items: flex-start; gap: 9px; }
.pos-stages li span:last-child { display: flex; flex-direction: column; line-height: 1.25; }
.pos-stages li strong { font-size: 14px; font-weight: 600; }
.pos-stages li em { font-style: normal; font-size: 12px; color: var(--muted); margin-top: 1px; }
.pos-stage-dot { flex: 0 0 auto; width: 18px; height: 18px; border-radius: 50%;
  display: flex; align-items: center; justify-content: center; margin-top: 1px;
  border: 1.5px solid var(--line, #2a2e37); color: #fff; font-size: 11px; font-weight: 700; }
.pos-stages li.done .pos-stage-dot { background: #1f9d61; border-color: #1f9d61; }
/* The stage you are ON pulses, so a glance says "this is the bit waiting on
   you" without reading a word. */
.pos-stages li.now .pos-stage-dot { border-color: #1f9d61; box-shadow: 0 0 0 3px rgba(31,157,97,.22);
  animation: pos-stage-pulse 1.5s ease-in-out infinite; }
.pos-stages li.now strong { color: var(--text, #fff); }
.pos-stages li.failed .pos-stage-dot { background: #ff6b6b; border-color: #ff6b6b; }
.pos-stages li.failed strong { color: #ff6b6b; }
.pos-stages li.ahead strong, .pos-stages li.ahead em { color: var(--muted); opacity: .65; }
@keyframes pos-stage-pulse { 0%,100% { box-shadow: 0 0 0 3px rgba(31,157,97,.22); }
  50% { box-shadow: 0 0 0 6px rgba(31,157,97,.08); } }
@media (prefers-reduced-motion: reduce) {
  .pos-stages li.now .pos-stage-dot { animation: none; }
  .pos-stage-bar i { transition: none; }
}

.pos-stage-totals { width: 100%; max-width: 360px; margin-top: 14px; padding-top: 12px;
  border-top: 1px solid var(--line, #23262e); display: grid; gap: 5px; }
.pos-stage-totals > div { display: flex; justify-content: space-between; align-items: baseline;
  font-size: 13px; color: var(--muted); }
.pos-stage-totals > div.big { font-size: 22px; color: var(--text, #fff); font-weight: 700;
  letter-spacing: -.01em; }
.pos-stage-totals > div.big span { font-size: 13px; font-weight: 500; color: var(--muted); }

/* Recipe method charts (v4.24) — built to be printed and put on a wall.
   The rowspans are the method, so the borders have to be unambiguous. */
.rc-bar { display: flex; align-items: center; gap: 10px; margin-bottom: 14px; flex-wrap: wrap; }
.rc-bar .grow { flex: 1; }
.rc-badge { font-size: 12px; font-weight: 700; letter-spacing: .03em; text-transform: uppercase;
  border-radius: 999px; padding: 3px 10px; border: 1px solid var(--border); color: var(--muted); }
.rc-badge.rc-hand { color: var(--green); border-color: rgba(62,207,142,.5); }
.rc-badge.rc-ai { color: var(--primary); border-color: rgba(79,140,255,.5); }
.rc-warn { background: rgba(245,166,35,.12); border: 1px solid rgba(245,166,35,.45);
  border-radius: var(--radius); padding: 10px 14px; margin-bottom: 14px; font-size: 13.5px; }

/* The sheet is white and black because it gets printed and photocopied and
   pinned above a hot pass — a dark theme is no use there. */
.rc-sheet { background: #fff; color: #14161b; border-radius: var(--radius);
  padding: 26px 28px; margin: 0 auto; box-shadow: 0 2px 18px rgba(0,0,0,.35); }
.rc-a4 { max-width: 210mm; }
.rc-a4-landscape { max-width: 297mm; }
.rc-a3 { max-width: 420mm; }
.rc-head { margin-bottom: 16px; }
.rc-head h1 { font-size: 27px; margin: 0 0 4px; letter-spacing: -.01em; }
.rc-sub { display: flex; gap: 14px; flex-wrap: wrap; color: #55606f; font-size: 13.5px; }

/* Separate borders, not collapsed. Under border-collapse a blank cell's
   border competes with its neighbour's and can erase a real edge — which on
   this chart deletes part of the method. With `separate`, every cell owns its
   own lines and a transparent one is simply invisible. Each cell draws only
   its right and bottom edge, and the table draws the top and left, so nothing
   doubles up. */
.rc-table { width: 100%; border-collapse: separate; border-spacing: 0;
  table-layout: fixed; border-top: 1.5px solid #1f7a3f; border-left: 1.5px solid #1f7a3f; }
.rc-table td { border-right: 1.5px solid #1f7a3f; border-bottom: 1.5px solid #1f7a3f;
  padding: 7px 9px; font-size: 14.5px; vertical-align: middle; }
.rc-ing { width: 42%; }
.rc-step { text-align: center; font-weight: 600; }
.rc-table td.rc-gap { border-color: transparent; }
/* The app's list tables strip the last row's underline and never wrap cells.
   Both are wrong here: this is a bordered grid, and an ingredient name has to
   wrap rather than run off the page. These selectors have to out-specify
   `tbody tr:last-child td` and `th, td`, hence the extra qualifiers. */
.rc-table tbody tr:last-child td { border-bottom: 1.5px solid #1f7a3f; }
.rc-table tbody tr:last-child td.rc-gap { border-bottom-color: transparent; }
.rc-table td.rc-ing, .rc-table td.rc-step, .rc-table td.rc-prep {
  white-space: normal; }
.rc-prep { text-align: center; font-weight: 600; }
.rc-notes { margin: 16px 0 0; padding-left: 20px; color: #38414f; font-size: 13.5px; }
.rc-notes li { margin-bottom: 3px; }

@media print {
  .no-print, .sidebar, .topbar, .mobile-nav { display: none !important; }
  body, .app, .view { background: #fff !important; margin: 0; padding: 0; }
  .rc-sheet { box-shadow: none; border-radius: 0; padding: 0; max-width: none; }
  .rc-table td { border-color: #1f7a3f; }
  @page { margin: 14mm; }
}


/* The kitchen screen (v4.25): one bar of chrome, everything else tickets. */
.pos-kds-bar { display: flex; align-items: center; gap: 14px; padding: 10px 14px;
  border-bottom: 1px solid var(--k-edge-2); background: var(--k-bar);
  color: var(--k-ink-3); flex: 0 0 auto; }
.pos-kds-bar .grow { flex: 1; }
/* The bar is chrome: it stays compact so the tickets get the screen. */
.pos-kds-bar .pos-kds-filter { flex: 0 0 auto; width: auto; max-width: 220px; }
.pos-kds-wait { white-space: nowrap; }

/* The pass on a phone. Live Orders is a tab of its own now, so a chef with
   nothing but their phone lands on this bar rather than four taps down a list —
   and on 390px the four tools, the filter and the cog ran straight off the
   right-hand edge. The standing rule is that no POS screen scrolls, so the bar
   wraps onto two rows instead: status and wait on the first, the tools and the
   filter on the second. Nothing is hidden and nothing is renamed — the chef
   reads the same words as on the pass screen. */
/* Jamal, of the phone: "the top bar should be fixed, as at the moment there's
   2 horizontal scrolls (for menu and kds orders). So its not functional."
   So: one row, no wrap, no scroll of its own. Whatever does not fit folds
   behind the ⋯ — kdsphone.js decides which, and a wrapped second row is what
   this replaces, because it cost a whole row of chips. */
.posm .pos-kds-wrap { padding: 0; }
.posm .pos-kds-bar { flex-wrap: nowrap; gap: 8px; padding: 8px 10px;
  flex: 0 0 auto; overflow: hidden; }
.posm .pos-kds-bar .grow { display: none; }
/* **Nothing on this bar may be squeezed.** Every child was `flex: 0 1 auto` or
   `1 1 76px`, so when the row was over the width the browser did not overflow
   it — it crushed the children, and the crushing is silent. That is how the
   filter came to read "Everyth…" and the wait "50m wai" while every overflow
   check on the page passed: the bar's scrollWidth equalled its clientWidth
   because the words had been shrunk to fit rather than pushed off the end.
   Held at their natural size, an over-full bar overflows visibly and
   `tests/kdsbar.spec.js` can see it. kdsphone.bar() is then the only thing
   deciding what is on the row, which is where that decision belongs. */
.posm .pos-kds-bar > * { flex: 0 0 auto; min-width: max-content; }
.posm .pos-kds-bar .pos-kds-tool { font-size: 11px; padding: 5px 8px; }
.posm .pos-kds-bar .pos-kds-filter { max-width: none;
  font-size: 12px; padding: 6px 4px 6px 8px; }
/* Ticks this device has taken but the server has not confirmed. Amber rather
   than red: nothing is lost — the outbox holds them and retries — but a chef
   about to walk away from the screen should know there is something in flight.
   Takes no room at all when there is nothing to say. */
.kds-unsaved { display: none; }
.kds-unsaved.on {
  display: inline-block;
  padding: 3px 9px;
  border-radius: 999px;
  background: rgba(230, 160, 40, .16);
  color: #e6a028;
  font-size: 12.5px;
  font-weight: 700;
  white-space: nowrap;
  border: 0;
  cursor: pointer;
  font-family: inherit;
}
/* A real disagreement is not amber. Amber says "in flight, nothing lost"; this
   says the two sides hold different things and a person has to look. */
.kds-unsaved.on.bad {
  background: rgba(220, 60, 60, .16);
  color: #e05252;
}

/* --- The order log (#208) -------------------------------------------------
   Every order on this board, its state, how much of it is ticked, and whether
   the kitchen agrees. The verdict at the top is the whole-board answer; the
   rows are how it was reached, because "something is out of sync" is not
   something anybody can act on. */
.ksync-verdict {
  padding: 10px 12px;
  border-radius: 12px;
  font-weight: 700;
  font-size: 14px;
  margin-bottom: 10px;
  background: rgba(120, 200, 130, .14);
  color: #48b45c;
}
.ksync-verdict.differs { background: rgba(220, 60, 60, .14); color: #e05252; }
.ksync-verdict.sending,
.ksync-verdict.settling { background: rgba(230, 160, 40, .14); color: #e6a028; }
/* Cannot tell. Deliberately not green and deliberately not red: a screen that
   has heard nothing knows nothing, and dressing that as either would be the
   lie the whole check exists to prevent. */
.ksync-verdict.unknown { background: rgba(140, 140, 150, .16); color: var(--muted); }
.ksync-inc { font-size: 12.5px; color: var(--muted); margin: -4px 0 10px; }
.ksync-list { display: flex; flex-direction: column; gap: 6px;
  max-height: 46vh; overflow-y: auto; }
.ksync-row {
  border: 1px solid var(--line);
  border-left: 3px solid var(--line);
  border-radius: 10px;
  padding: 8px 10px;
}
.ksync-row.ok { border-left-color: rgba(120, 200, 130, .55); }
.ksync-row.wait { border-left-color: #e6a028; }
.ksync-row.bad { border-left-color: #e05252; }
.ksync-head { display: flex; align-items: baseline; gap: 8px; }
.ksync-head strong { font-size: 15px; }
.ksync-call { font-size: 12px; color: var(--muted); }
.ksync-state { font-size: 12px; font-weight: 700; color: var(--muted); }
.ksync-prog { margin-left: auto; font-size: 12px; color: var(--muted); }
.ksync-note { font-size: 12.5px; color: var(--muted); margin-top: 2px; }
.ksync-row.bad .ksync-note { color: #e05252; }
.ksync-empty { color: var(--muted); font-size: 13px; padding: 10px 2px; }
.ksync-says { font-size: 12px; color: var(--muted); margin: 10px 0 0; }

.posm .pos-kds-live { font-size: 13px; }
.posm .pos-kds-live strong { display: none; }
.posm .pos-kds-wait strong { font-size: 24px; }
.posm .pos-kds-wait span { font-size: 11px; }
.posm .pos-kds-todo strong { font-size: 24px; }
.posm .pos-kds-todo span { font-size: 11px; }

/* LIVE / OFFLINE. The dot pulses while the board is hearing from the server —
   a still dot means something is wrong, which is the opposite of most status
   lights and is deliberate: silence is what loses tickets. */
.pos-kds-live { display: flex; align-items: center; gap: 8px; font-size: 15px;
  font-weight: 800; letter-spacing: .08em; }
.pos-kds-live i { width: 11px; height: 11px; border-radius: 50%; display: block; }
.pos-kds-live.on { color: #3ec98a; }
.pos-kds-live.on i { background: #3ec98a; animation: kds-pulse 1.4s ease-in-out infinite; }
.pos-kds-live.off { color: #ff6b6b; }
.pos-kds-live.off i { background: #ff6b6b; animation: kds-pulse .6s steps(1, end) infinite; }
@keyframes kds-pulse { 0%, 100% { opacity: 1; } 50% { opacity: .15; } }
@media (prefers-reduced-motion: reduce) {
  .pos-kds-live i { animation: none !important; }
}

.pos-kds-cog { background: none; border: 1px solid var(--k-edge); color: var(--k-ink-2);
  border-radius: var(--pos-r); padding: 7px 9px; display: flex; cursor: pointer; }
.pos-kds-cog:hover { border-color: #3b6ff5; }

/* Lanes. The left lane is the work and always takes two thirds. */
.pos-kds-lanes { display: flex; gap: 10px; padding: 10px; flex: 1; min-height: 0; }
.pos-kds-lane { display: flex; flex-direction: column; min-width: 0;
  background: var(--k-lane); border: 1px solid var(--k-edge-3); border-radius: var(--pos-r); }
.pos-kds-lane-head { display: flex; justify-content: space-between; align-items: center;
  padding: 9px 12px; font-size: 12.5px; font-weight: 700; text-transform: uppercase;
  letter-spacing: .05em; color: var(--k-muted); border-bottom: 1px solid var(--k-edge-3); }
.pos-kds-lane-head span { background: var(--k-edge-3); border-radius: var(--pos-r-s);
  padding: 1px 8px; color: var(--k-ink-2); }
.pos-kds-lane-body { flex: 1; overflow-y: auto; padding: 10px; display: grid; gap: 10px;
  grid-template-columns: repeat(auto-fill, minmax(min(230px, 100%), 1fr)); align-content: start; }
.pos-kds-lane:not(:first-child) .pos-kds-lane-body { grid-template-columns: 1fr; }
.pos-kds-lane-empty { color: var(--k-dim); font-size: 13.5px; padding: 18px 6px; text-align: center; }
.pos-ticket-card { transition: transform .12s ease; touch-action: pan-y; }
.pos-ticket-card.swiping { transition: none; box-shadow: 0 6px 22px rgba(0,0,0,.5); }
.pos-ticket-notify { font-size: 12px; color: #6f9dff; }
/* --- Tickets from a delivery app -------------------------------------------
   The stripe carries the platform's own colour, so a chef packing Deliveroo
   and Uber Eats at once tells the two bags apart across the kitchen without
   reading anything. Urgency still wins the stripe: a ticket ten minutes old
   is a bigger problem than which app it came from. */
.pos-ticket-card.app { border-top-color: var(--tk-app, #4f8cff); }
.pos-ticket-card.app.warm { border-top-color: #f5a623; }
.pos-ticket-card.app.late { border-top-color: #ff5c5c; }
/* A cancellation has to be impossible to walk past — somebody is mid-cook. */
.pos-ticket-card.cancelled { border-color: #ff5c5c; background: #241315; opacity: .85; }
.pos-ticket-cancel { background: #ff5c5c; color: #fff; font-weight: 800; font-size: 12px;
  letter-spacing: .06em; text-align: center; border-radius: var(--pos-r-s); padding: 4px 8px; }
/* An order we could not fully read says so on the card rather than quietly
   looking complete. A wrong ticket a chef knows is wrong beats a wrong ticket
   they don't. */
.pos-ticket-warn { font-size: 12px; color: #f5c463; background: rgba(245,166,35,.12);
  border-radius: var(--pos-r-s); padding: 6px 8px; }

/* Buzzers (v4.25) — an add-on to the sale, so it reads as a different kind of
   thing from the items above it. */
.posm-buzzer { width: 100%; background: rgba(59,111,245,.12); border: 1px solid #2b4a8f;
  color: #8fb4ff; border-radius: var(--pos-r); padding: 12px; font-size: 14.5px;
  font-weight: 650; margin: 4px 0 2px; cursor: pointer; display: flex;
  align-items: center; justify-content: center; gap: 10px; }
.posm-buzzer:hover { border-color: #3b6ff5; }
.posm-buzzer.set { background: rgba(31,157,97,.14); border-color: #226b48; color: #6fe0ac; }
.posm-buzzer strong { font-size: 19px; letter-spacing: .06em; font-variant-numeric: tabular-nums; }

/* Buzzer and covers on one line. Two full-width rows above the totals is two
   lines of a phone screen spent on two numbers, and nothing on the till
   scrolls. */
.pos-bill-chips { display: flex; gap: 8px; }
.pos-bill-chips .posm-buzzer { flex: 1; min-width: 0; }

/* Covers — one tap per number. The table's own seat count is outlined so the
   answer that is nearly always right is the one the eye lands on. */
.pos-cov-grid { display: grid; grid-template-columns: repeat(6, 1fr); gap: 8px; }
.pos-cov { min-height: 52px; border-radius: 12px; border: 1px solid #2a3550;
  background: #161d2e; color: #e7ecf7; font-size: 20px; font-weight: 700;
  font-variant-numeric: tabular-nums; cursor: pointer; }
.pos-cov:hover { border-color: #3b6ff5; }
.pos-cov.seats { border-color: #5a6a8f; }
.pos-cov.on { background: #3b6ff5; border-color: #3b6ff5; color: #fff; }

/* Move mode: the sheet has stopped being a room and become a question. */
.pos-move-on { background: rgba(59,111,245,.14); border: 1px solid #2b4a8f;
  border-radius: 10px; padding: 8px 10px; }

.pos-bz-read { text-align: center; font-size: 46px; font-weight: 700; letter-spacing: .12em;
  font-variant-numeric: tabular-nums; padding: 6px 0 2px; }
.pos-bz-read em { color: #4a5160; font-style: normal; }
.pos-bz-pad { display: grid; grid-template-columns: repeat(5, 1fr); gap: 8px; margin-top: 12px; }
.pos-bz-key { background: #1a1d24; border: 1px solid #2a2e38; color: #e8eaee;
  border-radius: var(--pos-r); padding: 16px 0; font-size: 21px; font-weight: 600;
  cursor: pointer; }
.pos-bz-key:hover { border-color: #3b6ff5; }
.pos-bz-key.wide { grid-column: span 5; font-size: 17px; }

/* Errors (v4.26) — one row per distinct failure, not per occurrence. */
.err-count { font-weight: 700; }
.err-toggle { display: flex; align-items: center; gap: 7px; color: var(--muted); font-size: 13.5px; }
.err-row { display: flex; gap: 14px; align-items: flex-start; padding: 14px 4px;
  border-bottom: 1px solid var(--border); }
.err-row:last-child { border-bottom: none; }
.err-row.done { opacity: .5; }
.err-main { flex: 1; min-width: 0; }
.err-head { display: flex; align-items: center; gap: 9px; flex-wrap: wrap; margin-bottom: 3px; }
.err-head strong { font-size: 14.5px; overflow-wrap: anywhere; }
.err-level { font-size: 11px; font-weight: 800; letter-spacing: .05em; text-transform: uppercase;
  border-radius: var(--radius); padding: 2px 7px; }
.err-critical { background: rgba(255,92,92,.16); color: var(--red); }
.err-error { background: rgba(245,166,35,.16); color: var(--amber); }
.err-warn { background: rgba(154,163,178,.16); color: var(--muted); }
.err-times { font-size: 12px; color: var(--muted); border: 1px solid var(--border);
  border-radius: 999px; padding: 1px 8px; }
.err-msg { font-size: 13.5px; color: var(--text); overflow-wrap: anywhere; }
.err-stack { margin: 6px 0 0; padding: 8px 10px; background: var(--bg); border-radius: var(--radius);
  font-size: 12px; color: var(--muted); overflow-x: auto; white-space: pre; }
.err-meta { display: flex; gap: 12px; flex-wrap: wrap; margin-top: 6px;
  color: var(--muted); font-size: 12px; }

/* The home-screen editor (v4.27) — the screen itself, live, rather than a
   list of checkboxes you have to imagine the result of. */
.pos-lay-list { display: flex; flex-direction: column; gap: 8px; }
.pos-lay-row { display: flex; align-items: center; gap: 10px; background: #171a21;
  border: 1px solid #23262e; border-radius: var(--pos-r); padding: 10px 12px; }
.pos-lay-row.dragging { border-color: #3b6ff5; box-shadow: 0 6px 22px rgba(0,0,0,.5); }
.pos-lay-grip { cursor: grab; color: #6c7482; font-size: 17px; touch-action: none;
  padding: 4px 2px; user-select: none; }
.pos-lay-row.dragging .pos-lay-grip { cursor: grabbing; }
.pos-lay-main { flex: 1; min-width: 0; display: flex; flex-direction: column; }
.pos-lay-main strong { font-size: 14.5px; }
.pos-lay-main em { font-style: normal; color: #8b93a1; font-size: 12.5px; }
.pos-lay-slots { width: auto; background: #1a1d24; border: 1px solid #2a2e38;
  color: #e8eaee; border-radius: var(--pos-r); padding: 7px 9px; font-size: 13px; }
.pos-lay-add { display: flex; gap: 8px; flex-wrap: wrap; margin-top: 8px; }
.pos-lay-foot { display: flex; align-items: center; gap: 8px; margin-top: 16px; }
.pos-lay-warn { background: rgba(245,166,35,.12); border: 1px solid rgba(245,166,35,.4);
  border-radius: var(--pos-r); padding: 10px 13px; font-size: 13px; margin-bottom: 12px; }
.pos-lay-preview { background: #07080b; border: 1px solid #23262e; border-radius: var(--pos-r);
  padding: 12px; max-width: 420px; }
.pos-lay-preview .posm-grid { display: grid; gap: 8px;
  grid-template-columns: repeat(2, 1fr); }
.pos-lay-preview .posm-tile { pointer-events: none; }

/* --- Delivery apps ---------------------------------------------------------
   A feed's webhook URL is a credential a vendor has to paste into somebody
   else's website, so it gets read as a whole rather than truncated with an
   ellipsis — the failure mode of a half-copied URL is orders that never
   arrive and nothing to see. */
.dapp-card { display: flex; flex-direction: column; gap: 10px; margin-bottom: 12px; }
.dapp-head { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.dapp-dot { width: 10px; height: 10px; border-radius: 50%; flex: 0 0 10px; }
.dapp-url { display: flex; align-items: center; gap: 8px; background: var(--surface-2);
  border: 1px solid var(--border); border-radius: 8px; padding: 8px 10px; }
.dapp-url code { font-size: 12.5px; word-break: break-all; flex: 1; }
.chip.warn { background: rgba(245,166,35,.15); color: #d9962a; }

/* --- Setting a kitchen up on a delivery platform ----------------------------
   Toast's shape: numbered steps down the page, each one a question with one
   answer, and the step you are on open while the rest stay as a line you can
   click back to. Four steps is few enough to show all four headings at once,
   which is the whole reason a vendor can tell how much is left. */
.dwiz { display: flex; flex-direction: column; gap: 0; }
.dwiz-step { border-top: 1px solid var(--border); padding: 14px 0 0; }
.dwiz-step:first-of-type { border-top: 0; padding-top: 4px; }
.dwiz-head { display: flex; align-items: center; gap: 10px; cursor: pointer;
  background: none; border: 0; padding: 0; width: 100%; text-align: left; color: inherit;
  font: inherit; }
.dwiz-n { width: 26px; height: 26px; border-radius: 50%; flex: 0 0 26px;
  display: grid; place-items: center; font-size: 13px; font-weight: 700;
  background: var(--surface-2); color: var(--muted); border: 1px solid var(--border); }
.dwiz-step.on .dwiz-n { background: var(--accent, #2f6fed); color: #fff; border-color: transparent; }
.dwiz-step.done .dwiz-n { background: var(--green, #1f9d61); color: #fff; border-color: transparent; }
.dwiz-t { font-weight: 650; }
.dwiz-body { padding: 10px 0 14px 36px; display: flex; flex-direction: column; gap: 10px; }
.dwiz-step:not(.on) .dwiz-body { display: none; }

/* The four answers to "what do we charge?", as cards rather than a dropdown:
   the trade-off is in the sentence under each one, and a <select> hides every
   sentence but the chosen one. */
.dwiz-modes { display: grid; gap: 8px;
  grid-template-columns: repeat(auto-fit, minmax(min(220px, 100%), 1fr)); }
.dwiz-mode { text-align: left; background: var(--surface); border: 1px solid var(--border);
  border-radius: 10px; padding: 12px; cursor: pointer; display: flex; flex-direction: column;
  gap: 4px; color: inherit; font: inherit; }
.dwiz-mode.on { border-color: var(--accent, #2f6fed); box-shadow: 0 0 0 1px var(--accent, #2f6fed) inset; }
.dwiz-mode b { font-size: 14px; }
.dwiz-mode span { font-size: 12.5px; color: var(--muted); line-height: 1.45; }

/* The preview. Every item, what the till charges, what the platform would —
   scrolled inside its own box so a 200-item menu does not push the buttons off
   the screen. */
.dwiz-prev { max-height: 320px; overflow: auto; border: 1px solid var(--border);
  border-radius: 10px; }
.dwiz-prev table { width: 100%; border-collapse: collapse; font-size: 13px; }
.dwiz-prev th { position: sticky; top: 0; background: var(--surface-2); text-align: left;
  padding: 8px 10px; font-weight: 600; font-size: 12px; color: var(--muted); z-index: 1; }
.dwiz-prev td { padding: 7px 10px; border-top: 1px solid var(--border); }
.dwiz-prev td.num { text-align: right; font-variant-numeric: tabular-nums; white-space: nowrap; }
.dwiz-prev .up { color: var(--green, #1f9d61); font-weight: 600; }
.dwiz-prev .flat { color: var(--muted); }
.dwiz-cats { display: flex; flex-direction: column; gap: 6px; }
.dwiz-cat { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.dwiz-note { background: var(--surface-2); border: 1px solid var(--border); border-radius: 10px;
  padding: 10px 12px; font-size: 12.5px; line-height: 1.5; color: var(--muted); }
/* The connect step as a visual checklist: number, icon, one thing to do, and
   an honest state chip. "Icons and images… make it simple" (2026-08-29). */
.dwiz-vstep { display: flex; gap: 10px; align-items: flex-start; background: var(--surface-2);
  border: 1px solid var(--border); border-radius: 12px; padding: 12px; margin-bottom: 8px; }
.dwiz-vnum { flex: 0 0 22px; width: 22px; height: 22px; border-radius: 50%;
  background: var(--accent, #4f7cff); color: #fff; font-size: 12px; font-weight: 700;
  display: inline-flex; align-items: center; justify-content: center; margin-top: 1px; }
.dwiz-vic { flex: 0 0 34px; width: 34px; height: 34px; border-radius: 10px;
  background: rgba(79,124,255,.12); color: #7ea1ff; display: inline-flex;
  align-items: center; justify-content: center; }
.dwiz-vbody { flex: 1; min-width: 0; }
.dwiz-vbody .hint { margin: 4px 0 6px; }
.dwiz-vtop { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; font-size: 13.5px; }
.chip.ok { background: rgba(61,190,122,.15); color: #3dbe7a; }
.dwiz-crumbs { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; margin: 4px 0 6px; }
.dwiz-crumb { background: var(--surface); border: 1px solid var(--border); border-radius: 8px;
  padding: 5px 10px; font-size: 12.5px; font-weight: 600; }
.dwiz-csep { color: var(--muted); font-size: 13px; }
.dwiz-say { display: flex; align-items: center; gap: 8px; font-size: 12.5px; color: var(--muted);
  font-style: italic; background: var(--surface); border: 1px dashed var(--border);
  border-radius: 8px; padding: 7px 10px; margin-top: 2px; }
.dwiz-says { font-size: 13.5px; }

/* The app picker. Each delivery app is set up on its own, because what
   HelchPOS can do differs per app and pretending otherwise is the whole thing
   this screen has to avoid. */
.dwiz-plats { display: flex; gap: 8px; flex-wrap: wrap; margin-bottom: 12px; }
.dwiz-plat { display: flex; align-items: center; gap: 7px; padding: 8px 14px;
  border: 1px solid var(--border); border-radius: 999px; background: var(--surface);
  color: inherit; font: inherit; font-size: 13.5px; cursor: pointer; }
.dwiz-plat.on { border-color: var(--accent, #2f6fed);
  box-shadow: 0 0 0 1px var(--accent, #2f6fed) inset; font-weight: 600; }
.dwiz-pdot { width: 9px; height: 9px; border-radius: 50%; flex: 0 0 9px; }

/* Can and cannot, as a list rather than a paragraph — a vendor reading "what
   happens to my money" needs to see the shape of it at a glance, and a wall of
   prose hides the one line that matters. */
.dwiz-facts { list-style: none; margin: 6px 0 0; padding: 0;
  display: flex; flex-direction: column; gap: 8px; }
.dwiz-facts li { position: relative; padding-left: 24px; font-size: 13px; line-height: 1.5; }
.dwiz-facts li::before { position: absolute; left: 0; top: 0; font-weight: 700; }
.dwiz-facts li.yes::before { content: '✓'; color: var(--green, #1f9d61); }
.dwiz-facts li.no::before { content: '✕'; color: #d9534f; }
.dwiz-note .dwiz-facts { margin-top: 8px; }

/* --- The fortnight ahead -----------------------------------------------------
   Fourteen days as a row of cards rather than a table: the shape of a fortnight
   — which days are big, which are blank — is the first thing a manager reads,
   and a table of fourteen rows hides it. Wraps rather than scrolls sideways, so
   nothing on this page needs a second horizontal gesture. */
.fc-days { display: grid; gap: 8px; margin-top: 12px;
  grid-template-columns: repeat(auto-fill, minmax(118px, 1fr)); }
.fc-day { display: flex; flex-direction: column; gap: 2px; align-items: flex-start;
  padding: 10px 12px; border: 1px solid var(--border); border-radius: 10px;
  background: var(--surface); color: inherit; font: inherit; cursor: pointer; text-align: left; }
.fc-day.on { border-color: var(--accent, #2f6fed); box-shadow: 0 0 0 1px var(--accent, #2f6fed) inset; }
/* A day with too little history is visibly not a number, rather than a zero. */
.fc-day.blank { opacity: .55; }
.fc-day.over { border-left: 3px solid var(--green, #1f9d61); }
.fc-wd { font-size: 11px; letter-spacing: .06em; text-transform: uppercase; color: var(--muted); }
.fc-date { font-size: 11px; color: var(--muted); }
.fc-num { font-size: 17px; font-weight: 650; margin-top: 2px; }
.fc-avg { font-size: 11px; color: var(--muted); }
.fc-tag { font-size: 10px; letter-spacing: .05em; text-transform: uppercase;
  color: var(--green, #1f9d61); font-weight: 600; margin-top: 2px; }
.fc-tag.thin { color: #d9962a; }

/* The three figures side by side — from history, the average, and after any
   adjustment. Three numbers a manager compares at a glance is the whole
   argument this screen is making. */
.fc-figs { display: flex; gap: 24px; flex-wrap: wrap; margin: 4px 0 8px; }
.fc-figs > div { display: flex; flex-direction: column; gap: 2px; }
.fc-figs b { font-size: 22px; font-variant-numeric: tabular-nums; }
.fc-figs .up b { color: var(--green, #1f9d61); }
/* A hint that is a warning still reads as a hint — same size, same place, a
   colour. Shouting at a vendor about a 20% uplift is not the job; telling them
   the platforms say orders fall off above 15% is. */
.hint.warn { color: #d9962a; }
/* The line that replaced the Save button on the Kitchen screens page, v5.198.0.
   Green when a change landed, red when the server refused one — and the red
   does not fade, because an error that cleared itself is an error nobody read. */
.hint.good { color: var(--green); font-weight: 600; }
.hint.bad { color: var(--red); font-weight: 600; }

/* Venue settings, v5.199.0 — the icon column and the line that replaced three
   Save buttons. `.vs-h3` puts the glyph on the section heading; `.vs-ic` puts
   one beside each switch, aligned with the label rather than centred in a
   paragraph that may run to four lines. */
.vs-h3 { display: flex; align-items: center; gap: 9px; }
.vs-h3 svg { flex: none; color: var(--muted); }
.vs-ic { flex: none; display: flex; align-items: center; color: var(--muted); margin-top: 1px; }
.vs-said { margin-top: 12px; font-size: 12.5px; color: var(--muted); }
.vs-said.good { color: var(--green); font-weight: 600; }
.vs-said.bad { color: var(--red); font-weight: 600; }

/* --- Ticket colours by dining behaviour -------------------------------------
   Toast's shape: a row per behaviour with a tick and a swatch, and — the part
   that makes it usable — two example tickets underneath carrying the outlines
   being set. A colour picked without seeing it on a ticket is a colour picked
   twice. */
.kcol-tbl { width: 100%; border-collapse: collapse; }
.kcol-tbl th { text-align: left; font-size: 12px; font-weight: 600; color: var(--muted);
  padding: 0 10px 8px; border-bottom: 1px solid var(--border); }
.kcol-tbl td { padding: 12px 10px; border-bottom: 1px solid var(--border); vertical-align: middle; }
.kcol-tbl tr:last-child td { border-bottom: 0; }
.kcol-tbl td .hint { margin-top: 2px; font-size: 12.5px; }
.kcol-on { width: 90px; text-align: center; }
.kcol-tbl input[type="checkbox"] { width: 20px; height: 20px; accent-color: var(--accent, #2f6fed);
  cursor: pointer; }
/* A colour input draws its own chrome and it is different in every browser.
   Stripped back to the swatch itself, which is the only part that carries any
   information. */
.kcol-swatch { width: 64px; height: 40px; padding: 0; border: 1px solid var(--border);
  border-radius: 8px; background: none; cursor: pointer; }
.kcol-swatch::-webkit-color-swatch-wrapper { padding: 3px; }
.kcol-swatch::-webkit-color-swatch { border: 0; border-radius: 5px; }
.kcol-swatch::-moz-color-swatch { border: 0; border-radius: 5px; }
.kcol-says { margin-top: 12px; font-size: 13.5px; }

.kcol-prevs { display: flex; gap: 16px; flex-wrap: wrap; margin-top: 10px; }
.kcol-prev { display: flex; flex-direction: column; gap: 6px; align-items: center; }
/* The preview ticket is a small copy of the real card, not the real card: the
   real one carries buttons, a state strip and live timings, none of which say
   anything about a colour. Transparent border when the behaviour is off, so
   switching it on does not change the ticket's size — a preview that jumps as
   you tick it is a preview you cannot compare. */
.kcol-tk { width: 190px; background: #fff; color: #111; border-radius: 10px;
  border: 4px solid transparent; overflow: hidden; }
.kcol-tk-head { background: #111; color: #fff; display: flex; align-items: center;
  justify-content: space-between; padding: 7px 10px; font-size: 13px; }
.kcol-tk-sec { background: #eceff3; color: #444; text-align: center; font-size: 10.5px;
  letter-spacing: .06em; padding: 4px; font-weight: 600; }
.kcol-tk-line { display: flex; gap: 8px; padding: 7px 10px; align-items: baseline; }
.kcol-tk-line .q { font-size: 13px; color: #555; min-width: 12px; }
.kcol-tk-line b { font-size: 13.5px; display: block; line-height: 1.25; }
.kcol-tk-line em { font-style: normal; font-size: 12px; color: #b8323c; display: block; }
.kcol-cap { font-size: 12px; color: var(--muted); text-align: center; max-width: 190px; }
.kcol-how { margin-top: 12px; }
.kcol-how ol { margin: 6px 0 0; padding-left: 20px; line-height: 1.6; }

/* --- Modules ---------------------------------------------------------------
   Each card says what the module is, what it needs, and what switching it will
   take with it — the cascade has to be readable *before* the click, not
   discovered by undoing it. */
.mod-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(min(320px, 100%), 1fr)); gap: 12px; }
.mod-card { background: var(--surface); border: 1px solid var(--border); border-left: 3px solid var(--border);
  border-radius: 10px; padding: 14px; display: flex; flex-direction: column; gap: 6px; }
.mod-card.on { border-left-color: var(--green, #1f9d61); }
.mod-head { display: flex; align-items: center; gap: 8px; }
.mod-card p.hint { margin: 0; }

/* --- Setup / readiness ------------------------------------------------------
   A worklist, not a report: every row is a link to the page that fixes it, and
   the severity reads at a glance from the left edge rather than from text. */
.rd-venue { margin-bottom: 12px; }
.rd-head { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin-bottom: 10px; }
.rd-list { display: flex; flex-direction: column; gap: 6px; }
.rd-issue { display: flex; align-items: center; gap: 10px; padding: 9px 11px; border-radius: 8px;
  background: var(--surface-2); text-decoration: none; color: var(--text); }
.rd-issue:hover { background: var(--border); }
.rd-body { display: flex; flex-direction: column; gap: 1px; flex: 1; min-width: 0; }
.rd-body strong { font-size: 13.5px; font-weight: 600; }
.rd-body span { font-size: 12px; color: var(--muted); }
.rd-dot { width: 8px; height: 8px; border-radius: 50%; flex: 0 0 8px; background: var(--muted); }
.rd-blocking .rd-dot { background: #ff5c5c; }
.rd-important .rd-dot { background: #f5a623; }
.rd-minor .rd-dot { background: #7a8699; }

/* --- Per-platform prices ----------------------------------------------------
   The chips are the whole interaction: one is lit in exactly one group, and
   tapping it in another moves it. Nothing here can show a platform twice,
   because the state it renders cannot represent that. */
.pg-list { display: flex; flex-direction: column; gap: 10px; }
.pg-group { border: 1px solid var(--border); border-radius: 10px; padding: 10px 12px;
  background: var(--surface-2); }
.pg-head { display: flex; align-items: center; gap: 8px; margin-bottom: 8px; }
.pg-money input { width: 88px; text-align: right; font-weight: 700; padding: 5px 7px;
  border-radius: 7px; border: 1px solid var(--border); background: var(--surface); color: var(--text); }
.pg-chips { display: flex; flex-wrap: wrap; gap: 6px; }
.pg-chip { font-size: 12px; padding: 4px 10px; border-radius: 999px; cursor: pointer;
  border: 1px solid var(--border); background: transparent; color: var(--muted); }
.pg-chip:hover { border-color: var(--primary); color: var(--text); }
.pg-chip.on { background: var(--primary); border-color: var(--primary); color: var(--primary-ink);
  font-weight: 600; }
.pg-warn { margin-top: 10px; font-size: 12.5px; color: #d9962a;
  background: rgba(245,166,35,.12); border-radius: 8px; padding: 8px 10px;
  display: flex; flex-direction: column; gap: 4px; }

/* --- GP and profit on the home screen ---------------------------------------
   Three windows side by side, and the GP percentage is the biggest thing on
   the card because it is the number a trader checks before anything else.
   Anything the figure does not include is stated on the card rather than
   discovered later. */
/* Namespaced away from the Gross profit report page, which defines its own
   .gp-card (an absolutely-positioned chart tooltip) and .gp-head (a stat block)
   further down this file. Same names, later in the cascade, equal specificity —
   so the dashboard's card was silently becoming a 238px tooltip pinned over the
   page heading, and its header row stopped being a row. Renamed rather than
   fought with !important: two different things should not share a name. */
.gpsum-card { margin-bottom: 16px; }
.gpsum-head { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; margin-bottom: 12px; }
.gpsum-head .grow { flex: 1; }
.gp-cols { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(210px, 100%), 1fr)); gap: 12px; }
.gp-col { background: var(--surface-2); border-radius: 10px; padding: 12px 14px; }
.gp-when { font-size: 11px; letter-spacing: .06em; text-transform: uppercase; color: var(--muted); }
.gp-gp { font-size: 30px; font-weight: 800; line-height: 1.1; margin: 2px 0 8px;
  font-variant-numeric: tabular-nums; }
.gp-gp span { font-size: 12px; font-weight: 600; color: var(--muted); margin-left: 6px; }
.gp-rows { display: flex; flex-direction: column; gap: 3px; font-size: 12.5px; }
.gp-rows > div { display: flex; justify-content: space-between; gap: 10px; }
.gp-rows span { color: var(--muted); }
.gp-rows b { font-variant-numeric: tabular-nums; }
.gp-profit { border-top: 1px solid var(--border); margin-top: 5px; padding-top: 5px; font-weight: 700; }
.gp-profit.up b { color: var(--green, #1f9d61); }
.gp-profit.down b { color: #ff5c5c; }
.gp-caveat { margin-top: 7px; font-size: 11.5px; color: #d9962a; }
.gp-notes { margin-top: 12px; font-size: 12px; color: var(--muted);
  display: flex; flex-direction: column; gap: 3px; }

/* --- Companies -------------------------------------------------------------
   Never a combined number without the per-venue breakdown beneath it: the
   question a multi-venue owner has is which one is losing them money, and the
   worst-performing row is marked so they don't have to scan for it. */
.org-card { margin-bottom: 14px; }
.org-head { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin-bottom: 10px; }
.org-sites { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; margin-bottom: 12px; }
.org-x { background: none; border: none; color: inherit; cursor: pointer; opacity: .55;
  font-size: 14px; line-height: 1; padding: 0 0 0 3px; }
.org-x:hover { opacity: 1; color: #ff5c5c; }
.org-tot { display: flex; gap: 20px; flex-wrap: wrap; margin-bottom: 8px; }
.org-tot > div { display: flex; flex-direction: column; }
.org-tot span { font-size: 11px; letter-spacing: .05em; text-transform: uppercase; color: var(--muted); }
.org-tot b { font-size: 20px; font-variant-numeric: tabular-nums; }
.org-tot b em { font-size: 12px; font-style: normal; color: var(--muted); }
.org-tot .up b { color: var(--green, #1f9d61); }
.org-tot .down b { color: #ff5c5c; }
.table.sm td, .table.sm th { padding: 5px 8px; font-size: 12.5px; }
.org-worst td { background: rgba(255,92,92,.08); }
.input.sm { padding: 4px 8px; font-size: 12.5px; }

/* --- Activity --------------------------------------------------------------
   A day at a time, newest first. The count chip is what stops the collapsing
   from hiding volume: one line can stand for forty events, and says so. */
.act-headline { font-size: 15px; }
.act-headline.act-warn { color: #d9962a; }
.act-day { margin-bottom: 12px; }
.act-daytop { display: flex; align-items: center; gap: 8px; margin-bottom: 8px; }
.act-row { display: flex; align-items: center; gap: 10px; padding: 7px 0;
  border-top: 1px solid var(--border); }
.act-row:first-of-type { border-top: none; }
.act-body { display: flex; flex-direction: column; gap: 1px; flex: 1; min-width: 0; }
.act-body strong { font-size: 13.5px; font-weight: 600; }
.act-body span { font-size: 12px; color: var(--muted); }
.act-dot { width: 7px; height: 7px; border-radius: 50%; flex: 0 0 7px; background: var(--muted); }
.act-warn .act-dot { background: #f5a623; }
.act-good .act-dot { background: var(--green, #1f9d61); }
.act-time { font-size: 12px; color: var(--muted); font-variant-numeric: tabular-nums; }

/* ---- What needs attention -------------------------------------------------
   The ring in the bottom corner and the red bubbles in the sidebar. Both are
   drawn from one payload (see HP_ATTENTION), so what they say cannot differ.

   The ring sits above everything and never moves, which is the whole point of
   putting it in a corner: it is the one thing on the screen that is true no
   matter which page you are on. */
/* Lived fixed at the bottom right until 2026-08-27. Jamal, looking at the
   site's screenshots: "get rid of that bottom warning and move it to the top
   right of the page and start displaying it there." It is a top-bar item now,
   docked beside the bell — where it cannot sit on a page's own buttons, which
   the floating version managed twice. The panel drops below the bar. */
#attn {
  position: relative;
  display: flex;
  align-items: center;
  --attn: var(--primary);
}
#attn.tone-bad { --attn: var(--red); }
#attn.tone-warn { --attn: var(--amber); }
#attn.tone-good { --attn: var(--green); }
#attn:empty { display: none; }

/* Not on the till. The setup ring belongs to the back office — on the register
   it sits directly on top of the Pay button, and a prompt about VAT settings
   has no business between a member of staff and taking money. */
body.pos-open #attn { display: none; }

.attn-bar {
  display: flex;
  align-items: center;
  gap: 9px;
  padding: 3px 10px 3px 3px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 999px;
  color: var(--text);
  cursor: pointer;
  font: inherit;
  text-align: left;
}
.attn-bar:hover { border-color: var(--attn); }
.attn-ringwrap { position: relative; display: grid; place-items: center; flex: 0 0 38px; }
.attn-ring { transform: rotate(-90deg); }
.attn-track { fill: none; stroke: var(--surface-2); stroke-width: 4; }
.attn-arc {
  fill: none;
  stroke: var(--attn);
  stroke-width: 4;
  stroke-linecap: round;
  transition: stroke-dasharray .5s ease;
}
.attn-pct {
  position: absolute;
  font-size: 10px;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  color: var(--text);
}
.attn-text { display: flex; flex-direction: column; line-height: 1.25; }
.attn-text strong { font-size: 13px; }
.attn-text .sub { font-size: 11px; color: var(--muted); }
.attn-caret { color: var(--muted); display: grid; place-items: center; }
#attn.open .attn-caret { transform: rotate(180deg); }

/* Dismiss sits outside the pill so it can never be pressed by somebody
   reaching for the pill itself. */
.attn-hide {
  position: absolute;
  top: -8px;
  right: -6px;
  width: 20px;
  height: 20px;
  border-radius: 50%;
  border: 1px solid var(--border);
  background: var(--surface-2);
  color: var(--muted);
  font-size: 13px;
  line-height: 1;
  cursor: pointer;
  display: grid;
  place-items: center;
}
.attn-hide:hover { color: var(--text); border-color: var(--attn); }
.attn-barwrap { position: relative; }

.attn-mini {
  display: grid;
  place-items: center;
  position: relative;
  width: 46px;
  height: 46px;
  border-radius: 50%;
  background: var(--surface);
  border: 1px solid var(--border);
  box-shadow: var(--shadow);
  cursor: pointer;
  padding: 0;
}
.attn-mini:hover { border-color: var(--attn); }

/* The panel drops from the top bar now. Fixed so the sticky bar cannot clip
   it, and it scrolls inside itself — a venue with thirty jobs must not run
   off the bottom of the screen. */
.attn-panel {
  position: fixed;
  /* Measured, not guessed: the top bar's bottom edge lands at 67px on the
     desktop layout, and 62px put the panel's shadow across it. */
  top: 74px;
  right: 16px;
  z-index: 70;
  width: min(380px, calc(100vw - 32px));
  max-height: min(70vh, 560px);
  overflow-y: auto;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
}
/* A phone's top bar has no room for the sentence — the ring alone carries the
   number, and the panel still says everything on tap. */
@media (max-width: 760px) {
  .attn-text, .attn-caret { display: none; }
  .attn-bar { padding: 2px; }
  .attn-hide { display: none; }
}
.attn-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 10px;
  padding: 13px 14px 9px;
  position: sticky;
  top: 0;
  background: var(--surface);
  border-bottom: 1px solid var(--border);
}
.attn-head .muted { font-size: 12px; font-variant-numeric: tabular-nums; }
.attn-group { padding: 6px 0; }
.attn-glabel {
  font-size: 10px;
  letter-spacing: .07em;
  text-transform: uppercase;
  color: var(--muted);
  padding: 6px 14px 4px;
}
.attn-task {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  width: 100%;
  padding: 9px 14px;
  background: none;
  border: 0;
  color: var(--text);
  font: inherit;
  text-align: left;
  cursor: pointer;
}
.attn-task:hover { background: var(--surface-2); }
.attn-dot { width: 7px; height: 7px; border-radius: 50%; margin-top: 6px; flex: 0 0 7px; background: var(--muted); }
.attn-dot.blocking { background: var(--red); }
.attn-dot.important { background: var(--amber); }
.attn-main { display: flex; flex-direction: column; gap: 1px; min-width: 0; flex: 1; }
.attn-main strong { font-size: 13px; font-weight: 600; }
.attn-main .sub { font-size: 12px; color: var(--muted); }
.attn-venue { font-size: 11px; color: var(--primary); margin-top: 2px; }
.attn-go { color: var(--muted); display: grid; place-items: center; margin-top: 3px; }
.attn-tag {
  font-size: 10px;
  padding: 2px 6px;
  border-radius: 999px;
  background: var(--surface-2);
  border: 1px solid var(--border);
  color: var(--amber);
  white-space: nowrap;
}
.attn-foot { padding: 10px 14px 13px; border-top: 1px solid var(--border); }
.attn-foot a { color: var(--primary); font-size: 12px; text-decoration: none; }
.attn-foot a:hover { text-decoration: underline; }

/* The bubbles. Small, round, and on the right of the label so a long page name
   never pushes the count off the edge of the sidebar. */
.nav-bub {
  margin-left: auto;
  min-width: 18px;
  height: 18px;
  padding: 0 5px;
  border-radius: 999px;
  background: var(--muted);
  color: var(--bg);
  font-size: 11px;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  display: inline-grid;
  place-items: center;
  line-height: 1;
}
.nav-bub.bad { background: var(--red); color: #fff; }
.nav-bub.warn { background: var(--amber); color: #241a00; }
/* A group header already has the arrow on its right, so its bubble sits just
   before it rather than fighting for the same corner. */
.nav-drop .nav-bub { margin-left: auto; }
.nav-drop .drop-arrow { margin-left: 6px; }
.nav a.active .nav-bub { background: var(--primary-ink); color: var(--primary); }
.nav a.active .nav-bub.bad { background: #fff; color: var(--red); }

@media (max-width: 720px) {
  /* Above the mobile tab bar, not behind it. */
  #attn { right: 12px; bottom: 74px; }
}

/* ---- Kitchen screen: settings sheet, the promise, and a pending change ----
   Jamal wanted the toggles on the screen itself rather than in the back
   office, and he is right: the person who wants the sound louder is stood in
   front of it with flour on their hands, not sat at a laptop. */
.kset { display: flex; flex-direction: column; gap: 2px; }
.kset h4 {
  margin: 18px 0 6px;
  font-size: 11px;
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--muted);
}
.kset h4:first-child { margin-top: 0; }
.kset-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  /* Wrap rather than push. A long label beside a wide select is the shape that
     made this row wider than the dialog holding it; given the choice between
     stacking and forcing everything sideways, stacking is the one somebody can
     still read. */
  flex-wrap: wrap;
  gap: 8px 14px;
  padding: 10px 0;
  border-bottom: 1px solid var(--border);
}
/* Share the line, and only drop below when there is genuinely no room. Without
   a basis the text block takes its content width, which on a phone is the whole
   row — so every control wrapped underneath even when it would have fitted
   beside it. */
.kset-row > div { min-width: 0; flex: 1 1 180px; }

/* **An icon on every row**, v5.197.0. Jamal: *"the more, like, icon and image
   based, you can make any single setting, the better… people don't really wanna
   read when they're, like, in a kitchen."* So each setting gets a glyph in a
   fixed column down the left — fixed, because a ragged left edge would make the
   list harder to scan rather than easier, which is the opposite of the point.
   Muted rather than coloured: it is a landmark, not a status. */
.kset-ic {
  flex: none;
  width: 30px;
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--muted);
  /* Aligned with the label rather than centred in a two-line row — a row whose
     hint runs to three lines would otherwise float its icon into the middle of
     the paragraph. */
  align-self: flex-start;
  margin-top: 1px;
}
.kset-row .kset-ic svg { display: block; }

/* **The footer says what happened**, because there is no Save button to press.
   Left of the Done button and quiet until something is happening, so a sheet
   sat still is not shouting "Saved" at somebody who has not touched it. */
.kset-said {
  flex: 1;
  min-width: 0;
  font-size: 12.5px;
  color: var(--muted);
  transition: color .15s ease;
}
.kset-said.saving { color: var(--muted); }
.kset-said.saved { color: var(--green); font-weight: 600; }
.kset-said.error { color: var(--red); font-weight: 600; }

/* Picking a board layout by looking at it, rather than off a dropdown reading
   "Columns / Grid / Lanes". Jamal: *"Show diagrams below this to show what they
   each are with boxes around them which can be selected."*

   Three across at every width this sheet is ever shown at — 1fr each rather
   than a min-width, because the alternative on a 320px phone is one box per row
   and a comparison you have to scroll to make. The diagrams keep their aspect
   ratio and simply get smaller, which is the right thing to give up first. */
/* Printers, on the kitchen screen. Jamal: *"it should show the status on this
   page, but I don't want to have to link between the KDS and the printer."* So
   these rows report and never control: the dot is the printer's own last
   check-in, not this screen's opinion of it. */
/* A photograph and a price on a ticket line. Both were switches that drew
   nothing until v5.169.0 — Jamal: *"it hasn't worked because it doesn't show
   the image on the KDS."* */
.kt-pic {
  width: 40px;
  height: 40px;
  border-radius: 7px;
  object-fit: cover;
  flex: none;
  align-self: flex-start;
  background: var(--surface-2);
}
.kt-price { flex: none; align-self: flex-start; font-size: 13px; font-weight: 700; color: var(--muted); }
/* On a phone the board draws chips rather than full cards, and a chip's line
   is a tighter row — the same picture, smaller, or it pushes the item name
   off its own line. */
.kt-chip .kt-pic { width: 28px; height: 28px; border-radius: 5px; }
.kt-chip .kt-price { font-size: 12px; }

/* The live example above the card toggles. Jamal: *"it should show an exact
   kind of thing at the top… so when I tick and untick stuff, it kind of hides
   or shows it."* It is a picture of a ticket, not a ticket: nothing in it is
   pressable, because a chef's board is the only place a line should tick off. */
.kexample {
  margin: 2px 0 6px;
  padding: 10px;
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: 12px;
  /* The board's cards size themselves to a lane; in a dialog there is no lane,
     so the example is given one. */
  --kt-w: 100%;
}
.kexample .kt { width: 100%; max-width: 100%; margin: 0; cursor: default; }
/* The board sizes its text on the lane wrapper, so the example has to carry the
   same two rules itself — otherwise "Large" is a setting whose effect you can
   only see by closing the dialog it is set in. */
.kexample.text-small { font-size: 13px; }
.kexample.text-large { font-size: 18px; }
.kexample, .kexample * { pointer-events: none; }

/* POS permissions: a column per class, a row per permission. Reading down a
   column says what that class can do; reading across a row says who can do
   that one thing — which is the question an owner actually arrives with. */
.pp-wrap { overflow-x: auto; }
.pp { width: 100%; border-collapse: collapse; min-width: 520px; }
.pp th, .pp td { padding: 9px 8px; border-bottom: 1px solid var(--border); vertical-align: top; }
/* Capped, not just floored. With a min-width alone the explanation column took
   whatever it liked — on a 1280px screen it ran to ~800px and pushed Supervisor
   and Staff off the right edge, which defeats the one thing a grid is for. */
.pp .pp-what { text-align: left; width: 42%; min-width: 210px; max-width: 440px; }
/* The global `th, td` rule is `white-space: nowrap`, which is right for a
   column of amounts and wrong for a sentence: these explanations ran straight
   out of their column and under the tick boxes at every width — visible on a
   phone, and quietly true on a desktop too. They wrap. */
.pp-what strong { display: block; font-size: 13.5px; white-space: normal; }
.pp-what span { display: block; font-size: 12px; color: var(--muted); margin-top: 2px;
  white-space: normal; }
.pp-sec td { white-space: normal; }
.pp-col { text-align: center; min-width: 96px; }
/* The global `th` rule uppercases every table heading, which is right for a
   column called "amount" and wrong for a class a venue named itself: it turned
   "Kitchen porter" into KITCHEN PORTER. These are names, so they read as typed. */
.pp-col strong { display: block; font-size: 13px; text-transform: none;
  letter-spacing: 0; color: var(--text); }
.pp-lock, .pp-made { display: block; font-size: 11px; color: var(--muted);
  font-weight: 500; text-transform: none; letter-spacing: 0; }
.pp-cell { text-align: center; }
/* A section heading inside the table rather than above it, so the grid keeps
   one set of columns and a class never drifts out of alignment with its ticks. */
.pp-sec td { background: var(--surface-2); }
.pp-sec strong { font-size: 12px; text-transform: uppercase; letter-spacing: .04em; }
.pp-sec span { font-size: 12px; color: var(--muted); margin-left: 8px; font-weight: 400; }
.pp-tick {
  width: 26px; height: 26px; border-radius: 7px;
  border: 1.5px solid var(--border); background: var(--surface-2);
  color: var(--primary-ink); font-size: 15px; font-weight: 700; cursor: pointer;
}
.pp-tick.on { background: var(--primary); border-color: var(--primary); }
/* Owner's column is ticked and unpressable: a venue that can untick its way out
   of its own settings page has locked itself out of its own business. */
.pp-tick[disabled] { cursor: default; opacity: .85; }
.pp-actions { display: flex; gap: 8px; justify-content: flex-end; margin-top: 12px; }
/* On a phone the grid is the point, and the explanations were taking the whole
   width — every tick box sat off the right edge, so the screen showed a list of
   permissions and none of the answers. The wording stays (it is what makes the
   page decidable) but it is given the narrowest column that still reads, so at
   least two classes are on screen and the rest are a swipe away. */
@media (max-width: 560px) {
  /* A real min-width, not zero. With `min-width: 0` the table reported itself
     as 327px wide while its cells overflowed to 554 — so the wrapper never
     grew a scrollbar and three of the four classes were unreachable rather
     than merely off screen. This is what the columns actually need. */
  .pp { min-width: 396px; }
  .pp .pp-what { width: 132px; min-width: 132px; max-width: 132px; }
  .pp-what strong { font-size: 13px; }
  .pp-what span { font-size: 11.5px; line-height: 1.3; }
  .pp th, .pp td { padding: 8px 4px; }
  .pp-col { min-width: 66px; }
  .pp-col strong { font-size: 12px; }
}

.pprn { display: flex; flex-direction: column; gap: 6px; margin: 2px 0 8px; }
.pprn-row {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 9px 10px;
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: 10px;
}
.pprn-dot { width: 9px; height: 9px; border-radius: 50%; flex: none; background: var(--muted); }
.pprn-row.ok .pprn-dot { background: #22c55e; }
.pprn-row.warn .pprn-dot { background: #f59e0b; }
.pprn-row.bad .pprn-dot { background: #ef4444; }
.pprn-what { flex: 1 1 auto; min-width: 0; cursor: pointer; }
.pprn-what strong { display: block; font-size: 13.5px; font-weight: 600; }
.pprn-what em {
  display: block;
  font-size: 12px;
  font-style: normal;
  color: var(--muted);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.pprn-wait {
  flex: none;
  font-size: 11.5px;
  font-weight: 700;
  color: var(--primary);
  background: rgba(79, 140, 255, .15);
  padding: 3px 7px;
  border-radius: 999px;
}
/* The address to type into a printer's own setup page, read off a phone while
   typing with the other hand — so it wraps rather than scrolls, and it is
   monospaced because a person is copying it character by character. */
.pprn-url {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 13px;
  line-height: 1.5;
  word-break: break-all;
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 10px 12px;
  user-select: all;
}

.kview { margin: 4px 0 2px; }
.kview-pick { display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px; }
.kview-opt {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 6px;
  padding: 10px 6px 8px;
  background: var(--surface-2);
  border: 1.5px solid var(--border);
  border-radius: 12px;
  color: var(--muted);
  font: inherit;
  font-size: 12.5px;
  font-weight: 600;
  cursor: pointer;
  /* The border thickens on selection, so reserving it here stops the box
     jumping a pixel as you press along the row. */
  transition: border-color .15s ease, background .15s ease, color .15s ease;
}
.kview-opt:hover { border-color: var(--muted); }
.kview-opt.on {
  border-color: var(--primary);
  background: color-mix(in srgb, var(--primary) 12%, var(--surface-2));
  color: var(--text);
}
/* currentColor throughout the artwork, so the chosen diagram tints with its
   box and the other two stay quiet without a second copy of the SVG. */
.kview-art { display: block; width: 100%; height: auto; opacity: .75; }
.kview-opt.on .kview-art { opacity: 1; color: var(--primary); }
.kview-what {
  margin: 8px 2px 0;
  font-size: 12px;
  color: var(--muted);
  min-height: 1.2em;     /* the line never collapses, so nothing below it moves */
}
.kset-row strong { display: block; font-size: 13.5px; font-weight: 600; }
.kset-row span { display: block; font-size: 12px; color: var(--muted); margin-top: 2px; }
.kset-row select {
  background: var(--surface-2);
  color: var(--text);
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 7px 9px;
  font: inherit;
  font-size: 13px;
  /* Wide enough that the chosen value is never clipped by the arrow — "12 min"
     was rendering as "12 mir" on a phone, which is the sort of thing that
     makes somebody doubt the number they just set. */
  min-width: 104px;
  max-width: 190px;
}
/* Never wider than the row it is in, whatever the longest option says. */
.kset-row select, .kset-row .btn.sm { max-width: 100%; }
.kset-row > select + select { margin-left: 6px; }
.kset-row .btn.sm { flex: none; margin-left: 6px; }

/* A switch, not a checkbox: at arm's length on a greasy screen the thumb needs
   somewhere to land and the state needs reading without focusing. */
.kset-toggle {
  flex: none;
  width: 46px;
  height: 27px;
  border-radius: 999px;
  border: 1px solid var(--border);
  background: var(--surface-2);
  padding: 2px;
  cursor: pointer;
  transition: background .15s ease;
}
.kset-toggle i {
  display: block;
  width: 21px;
  height: 21px;
  border-radius: 50%;
  background: var(--muted);
  transition: transform .15s ease, background .15s ease;
}
.kset-toggle.on { background: var(--primary); border-color: var(--primary); }
.kset-toggle.on i { transform: translateX(19px); background: #fff; }

/* What the customer was actually told. Shown against the clock, because
   "twelve minutes on screen" reads completely differently depending on
   whether they were quoted ten or fifty. */
.pos-ticket-promise {
  font-size: 12px;
  font-weight: 700;
  color: var(--muted);
  margin: 2px 0 4px;
  font-variant-numeric: tabular-nums;
}
.pos-ticket-promise.over { color: var(--red); }
.pos-ticket-line em.alg { color: var(--amber); }

/* A change this tablet has made but not managed to send. Marked, because a
   chef who bumped something and sees it still sitting there will bump it
   again — and then wonder why two came off. */
.pos-ticket-card.pending { opacity: .72; }
.pos-ticket-pending {
  font-size: 10.5px;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--amber);
  font-weight: 700;
  margin-bottom: 4px;
}

/* Two queue times in the top bar when the kitchen is running them separately.
   One when it is not — showing "20 / 20" would imply a distinction that
   isn't there. */
.pos-kds-wait .dlv { margin-left: 12px; opacity: .85; }

/* ---- Receipt printers ----------------------------------------------------
   Built around the two questions somebody actually has: is it working, and
   what does it print. Everything else is behind an edit. */
.prn-intro p { margin: 0 0 10px; }
.prn-intro .btn { margin-top: 4px; }
.prn { margin-bottom: 14px; }
.prn-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 14px; flex-wrap: wrap; }
.prn-head strong { font-size: 15px; }
.prn-acts { display: flex; gap: 6px; flex-wrap: wrap; }
.prn-state {
  display: inline-block;
  margin-left: 10px;
  font-size: 11px;
  font-weight: 700;
  padding: 2px 8px;
  border-radius: 999px;
  background: var(--surface-2);
  border: 1px solid var(--border);
  color: var(--muted);
}
.prn-state.ok { color: var(--green); border-color: rgba(62,207,142,.4); }
.prn-state.warn { color: var(--amber); border-color: rgba(245,166,35,.4); }
.prn-state.bad { color: var(--red); border-color: rgba(255,92,92,.4); }
.prn-what { margin: 8px 0 0; color: var(--text); font-size: 13.5px; }

/* How this one is attached, next to whether it is alive — the two facts you
   need before you can guess why nothing came out. */
.prn-conn {
  margin-left: 8px;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: .04em;
  text-transform: uppercase;
  color: var(--muted);
}
/* Said in place of a URL for a printer that cannot call us. Printing an
   address it will never dial would be worse than printing nothing. */
.prn-needs { margin: 10px 0 0; color: var(--muted); font-size: 12.5px; }

/* The buying guide. Numbered because these really are ranked — setup 1 is the
   one to buy unless something rules it out. */
.prn-setup {
  display: flex;
  gap: 12px;
  padding: 12px 0;
  border-top: 1px solid var(--border);
}
.prn-setup p { margin: 4px 0 0; font-size: 13px; color: var(--muted); }
.prn-setup strong { font-size: 14.5px; }
.prn-setup-n {
  flex: 0 0 26px;
  height: 26px;
  border-radius: 50%;
  display: grid;
  place-items: center;
  background: var(--primary);
  color: #fff;
  font-size: 13px;
  font-weight: 800;
}
.prn-setup-kit { color: var(--text) !important; }
.prn-setup-why { color: var(--primary) !important; }
.prn-setup.bad .prn-setup-n { background: var(--red); }
.prn-setup.bad .prn-setup-why { color: var(--amber) !important; }

.prn-conns { display: grid; gap: 8px; }
.prn-conn-opt {
  display: block;
  width: 100%;
  text-align: left;
  padding: 10px 12px;
  border: 1px solid var(--border);
  border-radius: 10px;
  background: var(--surface-2);
  color: var(--text);
  font: inherit;
  cursor: pointer;
}
.prn-conn-opt:hover { border-color: var(--primary); }
.prn-conn-opt.on { border-color: var(--primary); box-shadow: inset 0 0 0 1px var(--primary); }
.prn-conn-opt strong { display: block; font-size: 14px; }
.prn-conn-opt span { display: block; margin-top: 2px; font-size: 12.5px; color: var(--muted); }
/* What it costs, in the row that chooses it. A venue that finds this out
   during service has been badly served. */
.prn-conn-opt em { display: block; margin-top: 4px; font-size: 12px; font-style: normal; color: var(--amber); }
.prn-stats { display: flex; gap: 16px; flex-wrap: wrap; margin-top: 8px; color: var(--muted); font-size: 12px; }
.prn-stats b { color: var(--text); font-variant-numeric: tabular-nums; }
.prn-stats .tone-warn b { color: var(--amber); }
.prn-stats .tone-bad b { color: var(--red); }
/* The address is the credential, so it is shown once, plainly, with a copy
   button rather than left to be transcribed by hand off a screen. */
.prn-url {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
  margin-top: 12px;
  padding-top: 12px;
  border-top: 1px solid var(--border);
}
.prn-url > span { flex: 1 1 100%; font-size: 11.5px; color: var(--muted); }
.prn-url code {
  flex: 1;
  min-width: 0;
  overflow-x: auto;
  white-space: nowrap;
  font-size: 12px;
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 7px 10px;
}
.chipset { display: flex; gap: 6px; flex-wrap: wrap; }
.chip {
  background: var(--surface-2);
  border: 1px solid var(--border);
  color: var(--muted);
  border-radius: 999px;
  padding: 6px 13px;
  font: inherit;
  font-size: 13px;
  cursor: pointer;
}
.chip.on { background: var(--primary); border-color: var(--primary); color: var(--primary-ink); }

/* ---- The expediter board -------------------------------------------------
   Toast's shape, and it is right for a reason: tickets fill a column top to
   bottom then wrap right, and the board scrolls sideways. A kitchen reads a
   board the way it reads a rail of dockets — oldest on the left, work
   rightwards — and because the overflow goes sideways rather than down, a busy
   service never hides a ticket below the fold. */
.pos-kds-board { position: relative; flex: 1; min-height: 0; overflow: hidden; }
.pos-kds-flow {
  height: 100%;
  /* **No horizontal padding.** Measured at five widths, the sliver of ticket
     showing past the right edge was 12px every time — exactly this padding.
     `scroll-snap-type: x` pulls a card's left edge to the scrollport start,
     swallowing the left padding, and the width it swallows reappears at the
     other end as a strip of the next column. The breathing space now comes
     from the gutters the arrows sit in, outside the scroller. */
  padding: 12px 0;
  /* The whole layout, in three declarations. Multi-column fills downwards then
     wraps to the next column, and a fixed height with horizontal overflow
     turns that wrap into a sideways scroll. */
  column-width: 260px;
  column-gap: 12px;
  column-fill: auto;
  overflow-x: auto;
  overflow-y: hidden;
  scrollbar-width: none;
  scroll-snap-type: x proximity;
}
.pos-kds-flow::-webkit-scrollbar { display: none; }
.pos-kds-flow.view-grid { column-width: 200px; }
.pos-kds-flow.text-small { font-size: 13px; }
.pos-kds-flow.text-large { font-size: 18px; }

/* ---- The board on a phone -------------------------------------------------
   The wall board fills a column downwards and wraps rightwards, and scrolls
   sideways. That is right at arm's length and wrong in a hand: a sideways
   board inside a sideways bar gives two horizontal scrolls competing for one
   finger. So the phone turns the axis — a grid of chips, two across, scrolling
   down, and nothing on the screen scrolls across at all.

   Reading order turns with it. Down-then-across is right when the overflow is
   sideways; when it scrolls down it would mean reading to the bottom of the
   phone, scrolling back up and starting again. Across-then-down keeps the
   oldest ticket top left, which is the part of Jamal's rule that matters. */
/* **Two columns that flow independently.** This was a grid, and a grid fills
   left-to-right-then-down: correct sequence, and a 403px ticket made its whole
   *row* 403px, so the 171px card beside it ended early with a column of black
   under it and the next ticket did not start until the long one finished.
   Jamal, asked which he wanted, chose density — so the cards are dealt into
   whichever column is shorter (kdsphone.columnise) and each column is its own
   flex stack. The trade is stated where the rule lives: across a very tall
   neighbour the next card is no longer strictly the next in sequence. */
.pos-kds-flow.phone {
  columns: auto;
  column-width: auto;
  display: flex;
  align-items: flex-start;
  gap: 8px;
  padding: 8px;
  overflow-x: hidden;
  overflow-y: auto;
  /* The wall board snaps on x (see `.pos-kds-flow` above); this was resetting
     that to none because the axis turns on a phone, and then never turning it
     back on for the new one. So a finger came to rest wherever it stopped —
     and since `plan()` sizes a chip so that two or three rows fill the screen
     *exactly*, stopping a quarter of a row out means a sliver of a card at the
     top and the matching sliver at the bottom. Jamal: "half the bottom tickets
     hide." `proximity` rather than `mandatory`: a deliberate nudge to see the
     row below should not be dragged back. The chips already carry
     `scroll-snap-align: start`. */
  scroll-snap-type: y proximity;
  /* Snap to 8px BELOW the top edge — the container's own padding — not to the
     edge itself. Snapping to the edge rested the board at scrollTop 8, which
     scrolled the first row's status rings out of view: the parked green and
     the RUSH red are box-shadows painted OUTSIDE the card, and Jamal's photo
     showed the red line across the top of a rush ticket "hidden on first
     glance" until he pulled the board down. With the padding kept in the
     scrollport, the top row rests at the same height with its rings whole. */
  scroll-padding-top: 8px;
  /* Both of these, and both for the same reason. A grid with a definite height
     and `auto` rows stretches them to fill it — and because a chip clips its
     own overflow, its min-height resolves to zero, so a board of thirty
     tickets shares 690px thirty ways and every chip becomes a 15px sliver.
     `start` stops the distribution; `max-content` sizes each row to the
     tallest chip in it. */
  align-content: start;
  /* **And each chip is its own height inside its row.** `max-content` sizes the
     *row* to its tallest card; without this the shorter card stretches to match
     and carries a block of empty dark under its own button — a three-line
     ticket next to a nine-line one grew six lines of nothing. Seen in the
     screenshot, not in any assertion. */
  /* Stop at the ends rather than handing the gesture to whatever is behind:
     belt to `.posm-body.owns-scroll`'s braces, and it also kills the rubber
     band that made the board feel like it had stuck. */
  overscroll-behavior: contain;
}
/* One column of the phone board. Its own stack, so a long ticket in the other
   column costs it nothing. */
.pos-kds-flow.phone .kt-col {
  flex: 1 1 0;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 8px;
}
/* The row height follows the order. A ticket with two lines on it is
   two lines tall — padding one out to a third of the screen is the dead space
   this layout exists to reclaim — and a ticket with nine gets capped and says
   how many it is hiding. */
.pos-kds-flow.phone .kt-chip {
  /* **A floor, not a ceiling** (Jamal, 2026-08-23). This was `max-height`, and
     everything the card did to cope with that — hiding lines past the fold,
     counting them, drawing "+4 more items", opening again on a tap — is gone
     with it. *"It should never say see more. It should be completely
     expanded."* A nine-line order is nine lines tall and the board scrolls;
     the minimum is only there so a two-line ticket still reads as a card
     rather than a stripe.
     **And not padded out to it, either.** The first cut of this made
     `--kt-chip` a `min-height`, which put a hundred pixels of black under every
     one-line ticket — the same dead space the height cap was originally there
     to reclaim, arrived at from the other direction. A ticket is the height of
     its order and nothing else; `--kt-chip` survives only as the floor the grid
     row uses when the board is nearly empty. */
  min-height: 0;
  min-width: 0;
  display: flex;
  flex-direction: column;
  overflow: visible;
  /* The phone ticket shares the `.kt-chip` class with the little pill
     component, whose base rule sets `white-space: nowrap` — and that
     inherits, so "Waiting for customer to collect" ran straight off the
     card and under its neighbour (Jamal's screenshot, 2026-08-27). The
     ticket wraps; the pills inside it set nowrap for themselves. */
  white-space: normal;
  margin: 0;
  /* The pill's own `padding: 2px 7px` inherits too, and it is why the
     red/green header stopped short of the card's edges — invisible while the
     card behind it was dark, a white sliver down both sides the day the card
     went paper (Jamal's screenshots, 2026-08-28). The ticket is a card, not a
     pill: no padding, and the wall card's radius. */
  padding: 0;
  border-radius: 10px;
}
.pos-kds-flow.phone .kt-chip .kt-head { padding: 5px 8px; gap: 6px;
  /* The band is square and the card is rounded; with the chip's overflow kept
     visible for the rings, the band's top corners have to round themselves. */
  border-radius: 9px 9px 0 0; }
.pos-kds-flow.phone .kt-chip .kt-ref b { font-size: 15px; }
.pos-kds-flow.phone .kt-chip .kt-ref em { font-size: 8.5px; }
.pos-kds-flow.phone .kt-chip .kt-clock { font-size: 12px; }
/* The lines are the only part that may give: a chip that grew to fit six
   items would break the grid it is sitting in. */
/* Not flex:1 — that would stretch a two-line ticket to fill the row and put
   the dead space straight back. The lines take the room they need and no
   more; the row is as tall as the tallest chip in it. */
.pos-kds-flow.phone .kt-chip .kt-lines {
  flex: 0 1 auto; min-height: 0; overflow: hidden; padding: 4px 6px 5px; gap: 0;
}
.pos-kds-flow.phone .kt-chip .kt-line {
  padding: 1px 2px; gap: 6px; align-items: baseline;
  /* The wall board's 44px touch target is most of a phone chip. 34 is still a
     thumb-sized thing to hit and is what `LINE_PX` in kdsphone.js is set to —
     if this changes, that changes with it or the count goes wrong again. */
  min-height: 34px;
}
/* The tick box, at chip scale. The wall board's is 22px, which is right for a
   ticket read at arm's length and half the height of a phone chip's line.
   **The box and the quantity sit against the first line of the name.** They
   used to centre — Jamal, 2026-08-23: *"I would make the number on the box in
   line with the top of each of the items because at the moment it's a bit
   misleading, like, where the item starts and stops."* He is right, and on the
   six-line Mix & Match Tacos it is not a nicety: the 1 floated halfway down the
   block with nothing marking where one item ended and the next began, so a
   glance could not tell whether a name belonged to the row above or below it.
   Centring was itself a fix for baseline alignment putting the box *on* the
   baseline rather than beside the word; the answer to that is a top offset, not
   a centre. */
.pos-kds-flow.phone .kt-chip .kt-line:has(.kt-tick) { align-items: flex-start; }
.pos-kds-flow.phone .kt-chip /* --- KDS: the chip row and the counting clock (v5.275.0) ------------------- */
.kt-chips { display: flex; flex-wrap: wrap; align-items: center; gap: 5px 6px;
  padding: 6px 10px 0; }
.kt-chip { display: inline-flex; align-items: center; gap: 4px; border-radius: 6px;
  padding: 2px 7px; font-size: 11.5px; font-weight: 700; letter-spacing: .01em;
  line-height: 1.5; white-space: nowrap; }
/* Tone words, not colours, come out of kdschips.js — the mapping is here so a
   theme and the black-and-white board can each answer it their own way. */
.kt-chip.kiosk   { background: #16a34a; color: #fff; }
.kt-chip.qr      { background: #2563eb; color: #fff; }
.kt-chip.platform{ background: #7c3aed; color: #fff; }
.kt-chip.counter { background: rgba(127,127,127,.25); color: var(--text, #111); }
.kt-chip.dine    { background: #111827; color: #fff; }
.kt-chip.away    { background: #111827; color: #fff; }
.kt-chip.wait    { background: #b45309; color: #fff; }
.kt-chip.later   { background: #0e7490; color: #fff; }
.kt-chip.warn    { background: #b45309; color: #fff; }
.kt-chip.bad     { background: #b91c1c; color: #fff; }
/* The one that is a warning rather than a label. Loud on purpose. */
.kt-chip.allergen { background: #dc2626; color: #fff; }
.kt-chip.allergen svg { flex: 0 0 auto; }

.kt-ago, .kt-placed { display: inline-flex; align-items: center; gap: 3px;
  font-size: 11.5px; color: var(--muted, #6b7280); font-variant-numeric: tabular-nums; }
.kt-ago b { font-weight: 700; color: var(--text, #111); }
.kt-ago svg, .kt-placed svg { opacity: .65; }
.kt-placed { margin-left: auto; }
/* Black-and-white mode: the board drops every brand colour, and a chip row that
   kept five of them would be the loudest thing left on it. */
.pos-kds.plain .kt-chip { background: rgba(127,127,127,.22); color: var(--text, #111); }
.pos-kds.plain .kt-chip.allergen { background: #000; color: #fff; }

.kt-tick {
  width: 16px; height: 16px; border-width: 1.5px; border-radius: 4px;
  /* Optically on the first line rather than mathematically at its top: a 16px
     box against a ~19px line box wants a couple of pixels to look level with
     the letters instead of level with the line box. */
  margin-top: 2px;
}
.pos-kds-flow.phone .kt-chip .kt-line .kt-qty { margin-top: 0; }
/* The same three-signal done state on the phone chip, at chip scale. */
.pos-kds-flow.phone .kt-chip .kt-line.done { box-shadow: inset 2px 0 0 var(--green); }
/* `.kt-rest` — the "+n more items" button — and the open/closed rules that
   went with it were here. Removed with the fold (Jamal, 2026-08-23): there is
   nothing hidden, so there is nothing to press. */

/* **The whole coloured band is one button** (Jamal, 2026-08-23): *"it should be
   just one click on the red area, but the red area should extend down to the
   being made or the done button."* The target used to be the top strip alone —
   about forty pixels of a card two hundred tall — and a thumb aimed at it
   landed on a dish name instead and struck it through. The button itself is
   bare: the colour still comes from `.kt.<tone> .kt-head` and the strips keep
   their own backgrounds, so this only collects them into one hit area. */
.pos-kds-flow.phone .kt-chip .kt-band {
  display: block; width: 100%; margin: 0; padding: 0;
  border: 0; background: transparent; color: inherit; font: inherit;
  text-align: left; cursor: pointer;
}
.pos-kds-flow.phone .kt-chip .kt-band:hover { filter: brightness(1.08); }
.pos-kds-flow.phone .kt-chip .kt-band:active { transform: scale(.995); }
/* The state strip is a <span> inside that button now rather than a <div>, so it
   is told to lay out as the flex row it always drew as. */
.pos-kds-flow.phone .kt-chip .kt-band .kt-state { display: flex; white-space: normal; }

/* **A dish name wraps rather than trailing off into an ellipsis.**
   "BONE MARROW T…" is a "see more" wearing three dots — the chef still cannot
   read what to cook, and there is nothing to press. Same rule as the fold it
   went out with (Jamal, 2026-08-23): the card says the whole thing. */
.pos-kds-flow.phone .kt-chip .kt-name {
  font-size: 12.5px; line-height: 1.35;
  white-space: normal; overflow-wrap: anywhere; display: block;
}
.pos-kds-flow.phone .kt-chip .kt-qty { font-size: 12.5px; min-width: 13px; }
/* **No item photos on a phone chip.** Jamal, 2026-08-23: *"the tickets are way
   too hard to see… it makes it impossible to read."* He is right about the
   cause even though the shipped default is already off — his kitchen has the
   switch on. A phone board is two columns, a chip is about 185px wide, and a
   28px thumbnail leaves roughly a hundred pixels for the dish name: that is why
   "Mix & Match Tacos —" came out over eleven lines of two words. A photo is
   worth its room on a wall screen and there is no room here, so the chip does
   not draw one whatever the setting says — and the settings row says so, rather
   than offering a switch that makes one of the two boards unreadable.

   The chip does not render the `<img>` at all (see kdsChipHtml) — `display:
   none` still downloads the file, and twenty-seven of them on a till's
   connection is the cost the preference exists to avoid. This stays as a belt
   for any other path that puts a thumbnail on a chip. */
.pos-kds-flow.phone .kt-chip .kt-pic { display: none; }
/* The ticket sheet: the place the chip sends you for the detail it could not
   fit, so it carries the changes, any item note, and the customer's own note. */
/* `.ksheet-what`, `.ksheet-mods` and `.ksheet-lnote` were here. They belonged
   to the slab row the sheet drew before 2026-08-23; `.kshl-*` replaced it. */
.ksheet-note {
  margin: 0 0 8px; padding: 7px 10px; border-radius: 7px;
  background: rgba(240, 173, 78, .16);
  border-left: 3px solid var(--amber, #f0ad4e);
  color: #ffd79a; font-weight: 700; font-size: 13.5px; white-space: normal;
}

/* The customer's note — usually an allergy. Loud, and above the food, because
   it changes how the food is made. */
.pos-kds-flow.phone .kt-chip .kt-cnote {
  flex: none;
  margin: 0 6px 3px; padding: 3px 6px;
  border-radius: 5px;
  background: rgba(240, 173, 78, .16);
  border-left: 3px solid var(--amber, #f0ad4e);
  color: #ffd79a; font-size: 11.5px; font-weight: 700; line-height: 1.25;
  white-space: normal;
}
/* Changes and add-ons, under the dish they belong to. */
.pos-kds-flow.phone .kt-chip .kt-mods.kt-off { color: #E03131; font-weight: 800;
  font-size: .95em; white-space: normal; }
.pos-kds-flow.phone .kt-chip .kt-mods {
  display: block; font-style: normal; font-size: 11px; line-height: 1.25;
  color: #9aa3b2; white-space: normal;
}
.kt-line.done .kt-mods { text-decoration: line-through; }
/* **How big the dish is** (#210). Part of the name, not one of the fillings:
   "Three tacos" answers a question about the plate, and it sat in the grey list
   below with the actual tacos where a chef had to know that the last line was
   not food. Set like a chip on the name's own line — bold enough to be read at
   a glance from the pass, quiet enough not to compete with the dish. */
.kt-size {
  display: inline-block;
  margin-left: 7px;
  padding: 1px 7px;
  border-radius: 999px;
  background: rgba(120, 160, 255, .18);
  color: #9dbcff;
  font-size: .8em;
  font-weight: 800;
  letter-spacing: .01em;
  white-space: nowrap;
  vertical-align: 1px;
}
.kt-line.done .kt-size { opacity: .55; }
/* Said, not spelled out: the changes were traded for item names on a ticket too
   long to show both. Holding it shows the full text; opening the ticket is the
   real answer, and the top bar does that in one tap now. */
.pos-kds-flow.phone .kt-chip .kt-modmark {
  flex: none; margin-left: auto;
  font-size: 9.5px; font-weight: 800; letter-spacing: .03em; text-transform: uppercase;
  color: #06101f; background: var(--amber, #f0ad4e);
  border-radius: 4px; padding: 0 4px; line-height: 15px;
}
/* Outside `.kt-lines` now, so the block that hides its overflow can no longer
   hide the sentence saying something is hidden. `flex: none` keeps it from
   being squeezed to nothing instead, which would be the same fault again.
   Its own class, because `.kt-more` is the wall card's options button. */
.pos-kds-flow.phone .kt-chip .kt-rest {
  flex: none; width: 100%; text-align: left; white-space: nowrap;
  overflow: hidden; text-overflow: ellipsis;
  color: var(--k-muted); font-size: 11px; padding: 1px 2px 0 21px;
}
/* **Room to hit.** Jamal: *"the buttons for Done and Being made and delivery
   should be a bit taller as well, because at the moment it's quite cramped."*
   6px of padding on an 11.5px font is a 24px target, under every guideline
   there is and well under a thumb in a wet glove. */
.pos-kds-flow.phone .kt-chip .kt-btn {
  flex: 0 0 auto; min-height: 44px; padding: 10px 8px; font-size: 13px;
  font-weight: 800;
  /* The wall board's button is inset 8px with 9px corners; this override
     used to zero the margin and the radius while the wall's shortened
     `width: calc(100% - 16px)` still applied — a square button with all of
     its gap on one side (Jamal: "too square at the bottom with weird
     gaps"). Same inset look as the wall, at chip scale. */
  width: calc(100% - 12px); margin: 2px 6px 6px; border-radius: 8px;
}
/* …except the held row's Cancel, which sits BESIDE the "Collected — leaving
   in 12s" text, not under the lines. The full-width rule above grabbed it,
   the label was left about one character of room, and "Collected" came out
   one letter per line down the side of the card (Jamal's screenshots,
   2026-08-28). It keeps its own width and the text keeps the rest. */
.pos-kds-flow.phone .kt-chip .kt-hold .kt-btn.cancel {
  width: auto; margin: 0; flex: 0 0 auto;
}
/* The green strip is the card's last element and the card's corners are
   rounded with overflow kept visible for the rings — so the strip's bottom
   corners round themselves, or the card's corners "go missing" under its
   square green (Jamal's screenshots, 2026-08-28). Same 9px as the header. */
.pos-kds-flow.phone .kt-chip .kt-hold { border-radius: 0 0 9px 9px; }
.pos-kds-flow.phone .kt-chip .kt-rush,
.pos-kds-flow.phone .kt-chip .kt-recalled,
.pos-kds-flow.phone .kt-chip .kt-cancel { font-size: 10px; padding: 1px 6px; }
/* The platform, at chip size: the letter in their colour. The full mark needs
   50px to be shown at all under Deliveroo's policy, and a chip has not got
   50px to give — so this is deliberately not their logo. See
   docs/BRAND-ASSETS.md; tapping through to the ticket shows the real one. */
.kt-app-dot {
  flex: 0 0 auto; width: 18px; height: 18px; border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  font-size: 11px; font-weight: 800;
}
/* The board: an arrow, the scroller, an arrow. Jamal: "without the arrows
   hiding anything" — so they are laid out beside the board rather than floated
   over it, and no ticket is ever underneath one. The gutters are also the
   "space either right or left" that whole-column snapping leaves over. */
.pos-kds-board { display: flex; align-items: stretch; min-height: 0; }
.pos-kds-view { position: relative; flex: 1 1 auto; min-width: 0; overflow: hidden; }
.pos-kds-arrow {
  flex: 0 0 auto; width: 46px; display: flex; align-items: center; justify-content: center;
  border: none; background: none; color: var(--k-muted); cursor: pointer; padding: 0;
}
.pos-kds-arrow { color: var(--k-ink); }
.pos-kds-arrow:hover:not([disabled]) { color: #fff; background: var(--k-bar); }
.pos-kds-arrow:active:not([disabled]) { background: var(--k-control); }
/* Dimmed rather than hidden: a gutter that empties would shift the board
   sideways every time you reached an end. */
.pos-kds-arrow[disabled] { opacity: .18; cursor: default; }
.posm .pos-kds-arrow { display: none; }

/* Ours, not the operating system's — macOS and iOS hide theirs until something
   scrolls, so on a wall-mounted till there is nothing to see and nothing to
   grab. Hidden entirely when the whole board already fits: a control that does
   nothing is a control somebody will still press. */
.pos-kds-sbar { flex: 0 0 auto; padding: 6px 46px 10px; }
.pos-kds-sbar[hidden] { display: none; }
.pos-kds-strack {
  position: relative; height: 10px; border-radius: 999px;
  background: var(--k-track); cursor: pointer;
}
.pos-kds-sthumb {
  position: absolute; top: 0; bottom: 0; border-radius: 999px;
  background: var(--k-thumb); min-width: 48px;
}
.pos-kds-strack:hover .pos-kds-sthumb { background: var(--k-thumb-2); }
.pos-kds-sthumb.dragging { background: #4f8cff; }
.posm .pos-kds-sbar { display: none; }

/* No sideways scroll means nothing off the sides, so nothing to fade to. */
.posm .pos-kds-edge { display: none; }

/* Jamal: "a faded semi circle to the right and left to indicate you trying to
   scroll left or right." Tapping it scrolls that way too — on a wall-mounted
   screen with no mouse, the affordance has to also be the control. */
/* Jamal: "there should be a transparent gradient either side." A straight fade
   rather than the old semicircle — a circle drew attention to a point on the
   edge, and what it needs to say is "the board continues this way".
   **Never a control**: the arrows are the control, and a fade that also
   scrolled would swallow a tap meant for the ticket under it.

   And then, looking at it: *"can you see the blur kind of overlaps with the
   items? I don't want any fade on the items. I want the items to be inside
   that fade rather than the fade overlapping the items."* He is right, and the
   rule he is invoking is one this board already follows a few lines up — the
   arrows sit in gutters *outside* the scroller precisely so that a ticket is
   never underneath one. The fade was breaking that same rule: measured on a
   1180px board, each edge covered **7100px² of a ticket**, painting a card
   somebody is reading 80% dark at its outer edge.

   So the fade moved into the gutter with the arrow, and sits behind it. The
   items are now inside the fade rather than under it, and nothing on this
   board paints over a ticket any more. It is still the same gradient and still
   only drawn when there is something off that side to scroll to. */
.pos-kds-edge {
  /* The gutter, whose width the arrow sets. Absolute against .pos-kds-board,
     not .pos-kds-view — that one-word change is the whole fix. */
  position: absolute;
  top: 0;
  bottom: 0;
  width: 46px;
  pointer-events: none;
  opacity: 0;
  transition: opacity .18s ease;
  border: 0;
  /* Behind the chevron. The arrow is the control and has to stay readable; the
     fade is scenery. */
  z-index: 0;
}
.pos-kds-arrow { position: relative; z-index: 1; }
.pos-kds-edge.on { opacity: 1; }
/* **Lighter, not darker, and that is the point.** The old fade was
   `rgba(10,11,15,.85)` — five units darker than this app's background of
   `rgb(15,17,21)`. Against the background it was invisible; the only reason
   anybody ever saw it was that it was lying across a *ticket*, whose surface is
   far lighter. In other words the fade was only ever visible by doing the exact
   thing Jamal objected to, and moving it into the gutter unchanged would have
   deleted the feature while appearing to keep it.
   So in the gutter it lifts instead: a faint ledge at the outside edge,
   clearing as it reaches the board, so the board reads as continuing out
   underneath it. Same job, nothing dimmed. */
.pos-kds-edge.left { left: 0;
  background: linear-gradient(to right,
    rgba(255,255,255,.10) 0%, rgba(255,255,255,.03) 55%, rgba(255,255,255,0) 100%); }
.pos-kds-edge.right { right: 0;
  background: linear-gradient(to left,
    rgba(255,255,255,.10) 0%, rgba(255,255,255,.03) 55%, rgba(255,255,255,0) 100%); }
/* How far the driver has got, inside the delivery band. Its own row rather than
   another chip in .kt-dfacts: those are standing facts about the order (who,
   what it cost, when it is due) and this one changes while somebody watches. */
.kt-dstage { display: flex; align-items: baseline; flex-wrap: wrap; gap: 8px;
  padding: 6px 10px 2px; font-size: 14px; }
.kt-dstage b { font-weight: 800; }
/* **The age is quiet until it is not.** A fresh update should not compete with
   the stage; a stale one has to be noticed, because the whole point of showing
   it is that nothing on this board advances on a timer. */
.kt-dage { font-size: 12.5px; opacity: .6; }
.kt-dage.old { opacity: 1; font-weight: 700; color: #ffb020; }
/* A courier holding what the pass still holds. Loud on purpose — somebody has
   taken the wrong bag and the next one is about to go the same way. */
.kt-dwarn { flex: 1 1 100%; font-size: 12.5px; line-height: 1.35; color: #ff6b6b;
  font-weight: 600; }

/* What a made ticket is waiting for (#198). Quiet on purpose — this is the
   normal state of a finished order, not a problem, and it sits under the stage
   rather than shouting beside it. The warning above is the loud one. */
.kt-dwait { flex: 1 1 100%; font-size: 12.5px; line-height: 1.35; color: var(--muted); }

.kds-empty { color: var(--muted); padding: 60px 20px; text-align: center; }

/* **The idle backdrop.** Jamal: *"there should be a background for the pos
   screen for when there's no orders due… but there should be a clear boarder
   between it and the kds settings bars top and bottom, which allow easy view of
   stuff."*

   That border is the whole design constraint and it is met structurally rather
   than by drawing a line: this element lives inside `#kds-lanes`, which already
   sits between the top bar and the tab bar, so it fills exactly the space he
   means and physically cannot reach under the controls. Then an inset ring and
   a rounded corner make the edge *visible* — a photograph that runs to the exact
   pixel where the bar starts reads as a rendering fault rather than as a
   deliberate frame. */
.kds-idle {
  position: relative;
  /* **`column-span: all`, because the board is a multi-column layout.** The
     lanes container fills a 260px column downwards and wraps rightwards, so a
     plain block child is laid into one column and the backdrop came out as a
     narrow strip down the left-hand side. Spanning every column is the one
     declaration that makes a multicol child fill the width. Found by looking
     at the screenshot, not by reading the rule. */
  column-span: all;
  width: 100%;
  min-height: 100%;
  display: flex; align-items: center; justify-content: center;
}
.kds-idle.has-media {
  margin: 10px 12px;
  width: calc(100% - 24px);
  height: calc(100% - 20px);
  min-height: 220px;
  border-radius: 14px;
  overflow: hidden;
  box-shadow: inset 0 0 0 2px var(--k-edge);
  background: #0a0c10;
}
.kds-idle-media {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  /* object-fit is set inline from the venue's own choice — cover crops, contain
     shows all of it. */
  display: block;
  border-radius: 14px;
}
/* The words sit on top of it, and have to survive whatever the picture is. A
   plate under white text is unreadable about half the time, so the label gets
   its own ground rather than a text-shadow and a hope. */
.kds-idle.has-media .kds-empty {
  position: relative;
  padding: 12px 20px;
  border-radius: 999px;
  color: var(--k-ink-3);
  background: rgba(8, 10, 14, .72);
  backdrop-filter: blur(3px);
}

/* ---- A ticket ------------------------------------------------------------
   The header band carries the two things read from across a kitchen — which
   order, and how long — and it is coloured, so a board full of red tells you
   the state of service before a single word is read. */
.kt {
  break-inside: avoid;
  -webkit-column-break-inside: avoid;
  page-break-inside: avoid;
  display: block;
  width: 100%;
  margin: 0 0 12px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 10px;
  overflow: hidden;
  scroll-snap-align: start;
}
/* The dining-behaviour outline (#237). Toast draws a thick band right round the
   ticket, and it has to be thick: the whole point is reading it from across a
   pass, not noticing it up close.

   **`outline`, and not the two obvious alternatives.** A `border` would change
   the ticket's box — on a multi-column board that changes how many fit in a
   column, so switching the setting on would reflow the whole board. An inset
   `box-shadow` costs no layout but is painted *below* the element's children,
   and the ticket header is an opaque black strip running the full width, so the
   top of the outline would simply disappear behind it. `outline` costs no
   layout **and** paints above children, which is the only combination that
   gives Toast's picture — a band right round the ticket, header included.

   `outline-offset: -4px` draws it inside the border box rather than outside it,
   so nothing bleeds into the column gutter. */
.kt.dine { outline: 4px solid var(--tk-dine); outline-offset: -4px; }
/* An app ticket already wears its platform's colour as a left border, which
   sits outside the padding box — so the two do not fight, and a Deliveroo
   ticket still reads as Deliveroo underneath its takeaway outline. */
/* The top bar (v5.132.0). Jamal: it opens options when pressed and nothing said
   so. It is a real <button> now, with a surround that shows it is pressable and
   a three-dot mark on the right — the platform's logo moved in here too, which
   bought back the whole row its own band used to cost. */
.kt-head {
  /* Layout is set below with the grid rule — this block owns the look. */
  width: 100%;
  padding: 8px 10px 8px 12px;
  background: var(--surface-2);
  border: 0;
  border-bottom: 1px solid rgba(255, 255, 255, .07);
  color: var(--text);
  font: inherit;
  font-size: 1.05em;
  font-weight: 800;
  font-variant-numeric: tabular-nums;
  text-align: left;
  cursor: pointer;
}
.kt-head:hover { filter: brightness(1.12); }
/* Presses in, so a wet finger gets an answer even when the sheet takes a
   moment to arrive. */
.kt-head:active { transform: scale(.995); }
.kt-clock { justify-self: end; }
.kt-more {
  display: grid;
  place-items: center;
  /* Same stage-scale reasoning as .kt-btn.go: 26px rendered at 21px. The
     header itself is the touch target, so this only has to be *seen* — but it
     has to be seen from across a kitchen. */
  width: 30px; height: 30px;
  margin-left: 2px;
  /* Round, not a rounded square — the chip beside it is a pill and the two were
     speaking different shapes. */
  border-radius: 999px;
  background: rgba(255, 255, 255, .12);
  opacity: .9;
}
.kt-head:hover .kt-more { background: rgba(255, 255, 255, .22); opacity: 1; }
/* In the header the mark sits inline and small — it is a label, not a banner. */
.kt-app.inhead {
  grid-area: 2 / 3;
  justify-self: start;
  margin: 0;
  padding: 2px 5px;
  border-radius: 5px;
  max-height: 20px;
}
.kt-app.inhead img { max-height: 14px; width: auto; }
/* The five digits the guest is holding. Sits between the reference and the
   clock because it is the thing a chef reads out, not the thing they file. */
.kt-pick {
  justify-self: start;
  margin: 0;
  padding: 3px 10px;
  border-radius: 999px;
  background: rgba(0, 0, 0, .18);
  font-size: .92em;
  letter-spacing: .05em;
}
/* A buzzer is the object in somebody's hand and the thing that will go off, so
   it is the loudest chip on the card — filled amber rather than a tint of the
   bar it sits in. Same shape as the collection number beside it, because Jamal
   asked for "a circular kind of thing like you've got for that other thing". */
.kt-pick.buzzer {
  background: var(--amber, #f5a623);
  color: #14171d;
  font-weight: 800;
  letter-spacing: .02em;
}
.kt.ok .kt-head { background: #2b6c46; color: #fff; }
.kt.warn .kt-head { background: #b9781a; color: #fff; }
.kt.late .kt-head { background: var(--red); color: #fff; }
.kt.cancelled .kt-head { background: #6b1418; color: #fff; }
/* **Made, and waiting for somebody to come for it** (Jamal, 2026-08-23).
   *"It should be completely green. There shouldn't be any red. It should be
   like a green outline, but the bits that are red just turn them green, so it's
   not too much but it's still easy to read."*
   A deeper green than `.kt.ok`'s, because this one is not "in time" — it is
   finished — and the outline is what carries it across the board at a glance.
   `board.parked()` decides; the tone is `parked` for both the wall card and the
   phone chip, so a driver and a cook are never looking at two different
   answers. */
.kt.parked .kt-head { background: #12794b; color: #fff; }
.kt.parked {
  /* 3px, same as the rush ring — two ringed cards side by side at the top of
     the board paint their top edges at the same height. At 2px the parked
     card's visible top sat a pixel lower than its rushed neighbour's, which
     is exactly the kind of misalignment a top row makes obvious. */
  box-shadow: 0 0 0 3px #1f9d61;
  border-color: #1f9d61;
}
/* The state strip and the delivery band sit inside the same green, tinted
   rather than repainted: "not too much, but still easy to read". */
.kt.parked .kt-state { background: #10261c; color: #6fd6a4; border-bottom-color: #1b4132; }
.kt.parked .kt-state i { background: #3ecf8e; }
.kt.parked .kt-dstrip { background: #10261c; }
/* The advance button on a parked ticket is the one somebody walking past
   presses — "Driver collected", "Order collected". Green, like the rest of it. */
.kt.parked .kt-btn { background: #1f9d61; color: #04150d; }
.kt.parked .kt-btn:hover { background: #26b672; }

/* **The colours of whoever the order came from** (#203). Jamal: *"for delivery
   orders it shouldn't be red — it should be blue… or like an Uber, it should be
   black with the text, and each should be bright orange, just so that the person
   making it can see quite easily who that's for."*

   `.src` is only ever added when `kdssource.styleFor` returned a colour, and
   that function refuses on a cancelled ticket and on a finished one — so this
   never competes with `.kt.parked` or `.kt.cancelled`. It sits after them in
   the file for the same reason twice over. */
.kt.src .kt-head { background: var(--tk-src); color: var(--tk-srcink); }
/* **The line down the side** (Jamal, 2026-08-25: *"the line next to the ticket
   should be changeable too"*). An inset shadow rather than a border, so adding
   it moves nothing: a border would take four pixels off the card's width and
   reflow every column on a board that was fitted to the screen. Defaults to the
   header colour, so a venue that has never set one sees no change at all. */
.kt.src { box-shadow: inset 5px 0 0 0 var(--tk-srcedge, var(--tk-src)); }
/* **The bottom half** — Jamal's answer to "top and the bottom": both. These two
   classes only appear when a venue set the colour, so an untouched board is
   untouched CSS. The lettering is derived by the module (inkFor), because a
   body you can read is a promise, not an option. */
.kt.srcbody { background: var(--tk-srcbody); color: var(--tk-srcbodyink); }
.kt.srcbody .kt-line, .kt.srcbody .kt-note { color: var(--tk-srcbodyink); }
/* **A control that presses and does nothing is worse than no control**
   (Jamal, 2026-09-09, on a collected ticket: *"there are free buttons on that
   one ticket, where it should be one button … when you press it once, it
   presses like at the top, and if you press on an item, it looks like it's
   pressing on the item"*).

   Two of those three were dead already. In double-tap mode the whole card is
   the gesture and the coloured band is deliberately not bound — but it was
   still a <button>, so it lit up under a thumb and swallowed nothing. And an
   item line is a <button role="checkbox"> even at a venue with line ticking
   switched off, where pressing it has never done anything.

   `ktpass` makes the click go through to the card, which is the one real
   control, and takes the press state with it. The element stays a button so
   the markup and the layout maths are untouched; it simply stops pretending.

   Placed here deliberately, on its own line. The first attempt anchored an
   insert on the string ".kt-note {" and matched the MIDDLE of the selector
   above (the srcbody line/note ink rule), splicing this comment into the
   middle of that selector, leaving a mangled selector behind it and silently
   deleting the ink rule. An anchor that is a substring of a longer selector
   hits the longer one.

   The wording here is deliberately descriptive: a guard test asserts the
   mangled selector does not appear anywhere in this file, and a comment that
   quoted it would fail that test against itself. */
.kt .ktpass { pointer-events: none; }
.kt .ktpass:active { transform: none; filter: none; }

/* A platform-branded card paints its own body and picks its own ink against
   it. Dropping the pale pill there keeps that pairing intact rather than
   putting a fixed background under an ink chosen for a different one. */
.kt.srcbody .kt-note { background: transparent; border-left-color: currentColor; }
.kt.srcbtn .kt-btn { background: var(--tk-srcbtn); color: var(--tk-srcbtnink); border-top-color: transparent; }
.kt.srcbtn .kt-btn:hover { filter: brightness(1.12); background: var(--tk-srcbtn); color: var(--tk-srcbtnink); }
/* Belt, correctly buckled. `styleFor` already refuses on both of these, so
   `.src` should never appear beside them — but if it ever did, the colour that
   means something has to win, and a bare restatement of the `.parked` rule
   would not have (equal specificity, and this block is later in the file). The
   first version of this line also painted cancelled tickets green for one
   declaration before overriding itself, which is the sort of thing that is
   invisible until the one shift somebody cancels an order. */
.kt.parked.src .kt-head { background: #12794b; color: #fff; }
.kt.cancelled.src .kt-head { background: #6b1418; color: #fff; }
/* **Where lateness went.** The band belongs to the brand now, so the clock says
   whether the order is in trouble — a filled pill around a number, which is at
   least as loud at arm's length as a coloured bar and is on a thing that is
   already about time. Off when brand colours are off, because then the band is
   still carrying it and two copies would be noise. */
.kt-clock.pill {
  background: var(--pb, #e6483d); color: var(--pi, #fff);
  border-radius: 999px; padding: 2px 8px; font-weight: 900;
}
.pos-kds-flow.phone .kt-chip .kt-clock.pill { padding: 1px 7px; }
/* The black-and-white board: one delivery mark for every platform, in whatever
   colour the header is already using. */
.kt-app-plain { display: inline-grid; place-items: center; opacity: .9; }
.kt-app-plain svg { display: block; }

.kt.rush {
  box-shadow: 0 0 0 3px var(--red);
  animation: kt-rush-ring 1.1s steps(1, end) infinite;
}


/* What state a ticket is in, said on the ticket. Jamal, of six identical
   Deliveroo chips: "these need to be more obvious which state they are in, so
   its easier to see whats ready... so its not double made." The header colour
   is already spoken for — it is lateness — so the state gets its own strip
   directly under it, with a dot carrying the colour. */
.kt-state {
  display: flex; align-items: center; gap: 7px;
  padding: 4px 12px;
  font-size: .74em; font-weight: 800; letter-spacing: .05em;
  text-transform: uppercase;
  /* The strip carries a sentence now rather than a two-word chip, so it may
     take two lines on a narrow card. Tightened, or the second line floats. */
  line-height: 1.28;
  background: #171a21; color: #8b93a1;
  border-bottom: 1px solid #23262e;
}
.kt-state i { width: 8px; height: 8px; border-radius: 50%; background: currentColor; flex: 0 0 auto; }
/* ---- The runner's board (#199) ----------------------------------------------
   Jamal: "as the food hall they should be able to see a full KDS with every
   single order, but also they should be able to filter it by kitchens, or if
   it's ready for delivery then it should show there."

   A mode the board goes into rather than a screen of its own — the same cards,
   drawn by the same code. What changes is which tickets and how they are
   chosen. */
.pos-hall {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 8px 46px 9px;
  background: #14161b;
  border-bottom: 1px solid var(--border);
  flex-wrap: wrap;
}
.pos-hall-views { display: flex; gap: 6px; flex: 0 0 auto; }
.pos-hall-view {
  min-height: 40px;
  padding: 0 14px;
  background: #1d2129;
  border: 1px solid #3a4150;
  border-radius: 999px;
  color: #cdd6e6;
  font-weight: 800;
  font-size: .9em;
  cursor: pointer;
}
.pos-hall-view.on { background: #16233a; border-color: #4f8cff; color: #cfe0ff; }
.pos-hall-kitchens { flex: 1 1 auto; display: flex; gap: 6px; flex-wrap: wrap; }
/* A kitchen with nothing waiting still gets a chip, reading zero. The strip is
   a map of the hall, not a list of what is busy — a runner who cannot see a
   kitchen does not know whether it is quiet or whether the screen lost it. */
.pos-hall-k {
  min-height: 40px;
  padding: 0 12px;
  background: none;
  border: 1px solid #2a3040;
  border-radius: 999px;
  color: #9aa4b6;
  font-weight: 700;
  font-size: .88em;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  gap: 7px;
}
.pos-hall-k.on { background: #1d2129; border-color: #4f8cff; color: #e7ecf5; }
.pos-hall-k em {
  font-style: normal;
  font-weight: 800;
  font-variant-numeric: tabular-nums;
  color: #6d7789;
}
.pos-hall-k.on em { color: #4f8cff; }
.pos-hall-x {
  min-height: 40px;
  padding: 0 14px;
  background: none;
  border: 1px solid #3a4150;
  border-radius: 9px;
  color: #9aa4b6;
  font-weight: 800;
  cursor: pointer;
}
/* Which kitchen it is standing in. On the hall board this is the only thing
   telling a runner where to walk, so it goes at the top of the card rather
   than among the food. */
.kt-kitchen {
  padding: 5px 10px 5px 12px;
  background: #1b2130;
  border-top: 2px solid #6d7789;
  color: #cdd6e6;
  font-size: .84em;
  font-weight: 900;
  letter-spacing: .04em;
  text-transform: uppercase;
}
.posm .pos-hall { padding: 8px 10px 9px; }

/* ---- Pushing orders back (#193) --------------------------------------------
   Jamal: "maybe you could press on a few of them and then select 'Add half an
   hour to a few orders after they've been submitted'."

   The plural case is what shapes this: one order could have lived in its own
   sheet, six cannot, so the board itself goes into a picking mode. What it will
   refuse is shown *while picking* rather than after — a ticket a rider is
   already coming for is dimmed with the reason on it, so nobody ticks six and
   then finds out two never moved. */
.kt.picking { cursor: pointer; }
.kt.picking:hover { border-color: #4a5364; }
.kt.picked {
  border-color: #4f8cff;
  box-shadow: 0 0 0 2px rgba(79, 140, 255, .45);
}
.kt.picked .kt-head { background: #16233a; }
/* Not hidden. A ticket that simply vanished while picking would look like a
   ticket that had gone off the board. */
.kt.nopick { opacity: .45; cursor: not-allowed; }
.kt-nopick {
  padding: 5px 10px 5px 12px;
  background: #2a1c1c;
  border-top: 2px solid #b4523f;
  color: #f0b3a6;
  font-size: .8em;
  font-weight: 800;
}

.pos-push {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 8px 46px 10px;
  background: #14161b;
  border-top: 1px solid var(--border);
}
.pos-push-n {
  flex: 0 0 auto;
  color: #cdd6e6;
  font-weight: 800;
  font-size: .92em;
}
.pos-push-steps { flex: 1 1 auto; display: flex; gap: 8px; flex-wrap: wrap; }
/* 44px of CSS renders at about 36 on the register's scaled stage — same
   reasoning as .kt-btn.go. */
.pos-push-step {
  min-height: 44px;
  padding: 0 18px;
  background: #1d2129;
  border: 1px solid #3a4150;
  border-radius: 9px;
  color: #e7ecf5;
  font-weight: 800;
  font-variant-numeric: tabular-nums;
  cursor: pointer;
}
.pos-push-step:hover:not([disabled]) { background: #262c36; border-color: #4f8cff; }
.pos-push-step[disabled] { opacity: .35; cursor: default; }
.pos-push-x {
  min-height: 44px;
  padding: 0 16px;
  background: none;
  border: 1px solid #3a4150;
  border-radius: 9px;
  color: #9aa4b6;
  font-weight: 800;
  cursor: pointer;
}
.posm .pos-push { padding: 8px 10px 10px; flex-wrap: wrap; }

/* The confirmation list: every ticket, and what will happen to it. */
.pushlist { max-height: 40vh; overflow: auto; margin: 8px 0; }
.pushrow {
  display: flex;
  align-items: baseline;
  gap: 10px;
  padding: 6px 2px;
  border-bottom: 1px solid rgba(255, 255, 255, .06);
}
.pushrow b { flex: 0 0 auto; min-width: 72px; color: #e7ecf5; }
.pushrow span { color: #9aa4b6; font-size: .92em; }
.pushrow.quiet span { color: #6d7789; font-style: italic; }
.pushrow.blocked { opacity: .6; }
.pushrow.blocked b { text-decoration: line-through; }
.pos-note.quote { color: #9fe7c1; font-style: italic; margin-top: 10px; }

/* ---- Fifteen seconds before it goes (#192) ---------------------------------
   Jamal: "when you press Done, it should show the payment as fully green for
   maybe 10 seconds before it takes off... After 10 seconds of green flashing,
   it could show 5 seconds of solid green."

   Nothing has been sent to the server while this runs — the card is simply not
   bumped yet — which is what makes Cancel free rather than an undo. The whole
   card goes green rather than a corner of it, because the point is to catch an
   eye that is looking somewhere else. */
.kt.holding {
  border-color: #2f8f5b;
  box-shadow: 0 0 0 2px rgba(62, 201, 138, .35);
}
.kt.holding .kt-head,
.kt.holding .kt-state,
.kt.holding .kt-dband,
.kt.holding .kt-dstrip { background: #123021; }
.kt.holding .kt-lines { opacity: .5; }
.kt.holding .kt-line { pointer-events: none; }

/* Only the first ten seconds flash. A thing that flashes for the whole fifteen
   stops being a signal and becomes furniture, which is why `flashing` comes off
   the class list rather than the animation simply being long. */
@keyframes kt-hold-flash {
  0%, 100% { background: #123021; }
  50% { background: #1d5c3a; }
}
.kt.holding.flashing { animation: kt-hold-flash 1s ease-in-out infinite; }

/* Motion sickness is not a reason to lose the warning. Reduced motion gets the
   solid green for the whole window instead of no signal at all. */
@media (prefers-reduced-motion: reduce) {
  .kt.holding.flashing { animation: none; background: #1d5c3a; }
}

.kt-hold {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 8px 10px 9px 12px;
  background: #123021;
  border-top: 1px solid rgba(255, 255, 255, .08);
}
.kt-holdtext {
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  align-items: baseline;
  /* **Both halves whole, on two lines if that is what it takes** (v5.201.0).
     The countdown answers "have I still got time to press Cancel"; the label
     answers "what did I just say happened to this order". Trimming either to
     fit one line answers one of those questions with nothing — a card caught
     mid-window read **"At th… leaving in 13s"**, which is a state nobody can
     act on. The card is fifteen seconds from leaving the board, so a second
     line costs nothing anybody will still be looking at. */
  flex-wrap: wrap;
  gap: 2px 6px;
  color: #9fe7c1;
  font-size: .9em;
  font-weight: 800;
  font-variant-numeric: tabular-nums;
}
/* The label wraps rather than being cut. It was the trimmable part until a
   spec measured what it actually trimmed to. */
.kt-holdtext em {
  font-style: normal;
  min-width: 0;
  white-space: normal;
  overflow-wrap: anywhere;
}
/* The countdown is not. It is the only part that answers "have I still got time
   to press Cancel", and it was the part the browser was cutting when the whole
   thing was one string on a 260px ticket. */
.kt-holdtext b {
  flex: 0 0 auto;
  white-space: nowrap;
  color: #cdf3de;
}
/* A real button, at a real size. Same reasoning as .kt-btn.go: the register
   draws on a stage scaled to about 0.82, so 44px of CSS renders as 36. */
.kt-btn.cancel {
  flex: 0 0 auto;
  width: auto;
  min-height: 44px;
  padding: 0 16px;
  background: #23282f;
  border: 1px solid #3a4150;
  border-radius: 9px;
  color: #e7ecf5;
  font-weight: 800;
  cursor: pointer;
}
.kt-btn.cancel:hover { background: #2c323b; }
.kt-btn.cancel:active { transform: scale(.97); }
.pos-kds-flow.phone .kt-chip .kt-hold { padding: 4px 8px 5px; gap: 6px; }
.pos-kds-flow.phone .kt-chip .kt-holdtext { font-size: 11px; }
.pos-kds-flow.phone .kt-chip .kt-btn.cancel {
  min-height: 26px; padding: 0 10px; font-size: 11px; border-radius: 7px;
}

/* ---- A delivery ticket reads as a delivery ticket (#191) ------------------
   Jamal: "if you could make it more clear that that whole top bar is a
   delivery order, maybe use the delivery blue."

   Under the tone bar rather than instead of it. The header's red/amber/green
   is load-bearing — a board full of red says the state of the service before a
   word is read — so painting every delivery blue would trade one signal for
   another. This band sits directly beneath, full width, and the top of the
   card then reads as a delivery order with the lateness colour still doing its
   own job.

   `--dc` is the one blue that means delivery on this board (#4f8cff, the same
   one "Waiting for a driver" uses). `--dm` is the platform's own colour, and
   it gets the mark and nothing else — see the note at the top of
   worker/src/deliverycard.js for why the brand colour cannot be the band. */
.kt-dband {
  background: #131b2b;
  border-top: 2px solid var(--dc, #4f8cff);
  padding: 7px 10px 8px 12px;
}
.kt-dtop {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
}
/* The platform, as a dot in its own colour. Small on purpose: which app it is
   matters less than that it is going out at all, and the logo already rides in
   the header above. */
.kt-dmark {
  flex: 0 0 auto;
  width: 9px; height: 9px; border-radius: 999px;
  background: var(--dm, #7a8699);
  box-shadow: 0 0 0 2px rgba(255, 255, 255, .06);
}
.kt-dwhat {
  color: var(--dc, #4f8cff);
  font-weight: 900;
  font-size: .82em;
  letter-spacing: .09em;
  text-transform: uppercase;
}
.kt-dwho { color: #9aa4b6; font-size: .82em; font-weight: 700; }
/* The rider, inside the coloured section — a rider is a delivery fact and
   belongs with the other delivery facts rather than filed among the food. */
.kt-drider {
  margin-left: auto;
  color: #cdd6e6;
  font-size: .82em;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
}
.kt-drider.unknown { color: #6d7789; font-weight: 600; font-style: italic; }

/* Name, phone, how many times, what the delivery cost. Two columns, because
   four of these stacked is a third of a ticket. */
.kt-dfacts {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 3px 10px;
  margin-top: 6px;
}
.kt-dfact { display: flex; flex-direction: column; min-width: 0; }
.kt-dfact em {
  font-style: normal;
  color: #6d7789;
  font-size: .68em;
  font-weight: 800;
  letter-spacing: .06em;
  text-transform: uppercase;
}
.kt-dfact b {
  color: #e7ecf5;
  font-size: .88em;
  font-weight: 700;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
/* A fact nobody gave us. Said, not blank — an empty space reads as "this
   customer has no number", which is a different and wrong thing. Quiet enough
   that a chef's eye skips it and specific enough to answer the question when
   somebody does look. */
.kt-dfact.unknown b {
  color: #6d7789;
  font-weight: 600;
  font-style: italic;
  font-size: .8em;
  white-space: normal;
}

/* The phone chip has no room for the block, so it gets the one-line form. */
.kt-dstrip {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 4px 8px;
  background: #131b2b;
  border-top: 2px solid var(--dc, #4f8cff);
  color: var(--dc, #4f8cff);
  font-size: 11px;
  font-weight: 800;
  letter-spacing: .02em;
  /* Wraps rather than trailing off. "Deliveroo · Marek K. · £3.49 c…" hid the
     word *collection* — the rider's name and the fee are two of the three
     things the strip exists to say, and clipping the third is the same
     "see more" the fold went out for (Jamal, 2026-08-23). */
  white-space: normal;
  overflow-wrap: anywhere;
  line-height: 1.3;
}

/* **Made and waiting is not the same kind of thing as still cooking.**
   Jamal, of a board of Deliveroo tickets: *"it should be more clear that some
   of these orders are waiting for the driver to collect."* It was a coloured
   word on the same near-black strip that "being made" uses, so across a grid
   of fourteen chips the two states read as the same chip in two shades.

   The three waiting states get a **filled** band instead — solid colour, dark
   text — and the ones still in the pan stay quiet. That is the distinction
   worth carrying at arm's length: quiet strip means it is still work, a block
   of colour means the work is done and somebody has to come for it. Which one
   of the three it is comes from *which* colour, as before. */
/* **One green for all three, and the word says who.**

   Jamal, on a phone board of Deliveroo tickets: *"'For a driver' should be
   'waiting for driver' and should be in a solid green colour rather than that
   blue. So the restaurant can see it as a finished order. Same with the
   'waiting for customer'."*

   They were three colours — blue, green, amber — for who is coming. That
   answers the wrong question first. At arm's length across fourteen chips the
   thing a kitchen needs is **this one is made and nobody has taken it**, and
   three hues make that one fact look like three different situations. Blue in
   particular reads as information rather than as done. So the band is one
   green, and *which* of the three is a word inside it — which is where a
   distinction that needs reading belongs. */
.kt-state.making { color: #8b93a1; }
.kt-state.driver,
.kt-state.customer,
.kt-state.waiter { color: #04150d; background: #2fbf6b; }
.kt-state.done { color: #565d6b; }
/* The dot carried the colour when the strip was dark. On a filled band it is
   the one thing that cannot be seen, and it is saying nothing the band is not
   already saying twice. */
.kt-state.driver i, .kt-state.customer i, .kt-state.waiter i { display: none; }
/* And at a glance across the whole board, before anybody reads a word: a chip
   that is made and waiting carries the green on its outline too. Same green
   for all three, for the same reason the band is. */
.kt-chip.wait-driver,
.kt-chip.wait-customer,
.kt-chip.wait-waiter { box-shadow: inset 0 0 0 2px #2fbf6b; }
/* Made, and nobody has moved it on. The one at risk of being cooked twice, so
   it is the one that gets the loud treatment rather than a quieter grey. */
.kt-state.plated.making { color: #f5c26b; background: #241c0f; }

/* **Which board you are standing at.** Sits above the state filter and looks
   different from it on purpose: a station is a bigger choice than a filter —
   it changes whose screen this is, not which of its tickets show — so it gets
   segmented-control weight rather than another row of pills that a chef would
   read as "one more thing to untick". Hidden entirely for a kitchen that never
   sends food to a table, which is most of them. */
.pos-kds-stations {
  display: flex; gap: 0; align-items: stretch;
  padding: 8px 14px 0; flex: 0 0 auto;
  background: var(--k-bar);
}
.pos-kds-stations[hidden] { display: none; }
.pos-kds-station {
  flex: 0 0 auto;
  display: inline-flex; align-items: center; gap: 7px;
  padding: 8px 16px;
  border: 1px solid var(--k-edge); background: var(--k-tile); color: var(--k-ink);
  font-family: inherit; font-size: 13px; font-weight: 700;
  cursor: pointer; white-space: nowrap;
}
.pos-kds-station:first-child { border-radius: 9px 0 0 9px; }
.pos-kds-station:last-child { border-radius: 0 9px 9px 0; border-left-width: 0; }
.pos-kds-station em {
  font-style: normal; font-weight: 800; opacity: .55;
  font-variant-numeric: tabular-nums;
}
/* The one that is on is the green the waiting states use, because what a
   waiter's board holds *is* the made-and-waiting pile. */
.pos-kds-station.on {
  background: #2fbf6b; border-color: #2fbf6b; color: #04150d;
}
.pos-kds-station.on em { opacity: .75; }
.posm .pos-kds-stations { padding: 6px 10px 0; }
.posm .pos-kds-station { padding: 7px 13px; font-size: 12px; }

/* The state filter. A row of its own under the bar, because the bar above is
   already the row that folds on a phone. Wraps, never scrolls. */
.pos-kds-states {
  display: flex; flex-wrap: wrap; gap: 6px;
  padding: 8px 14px; flex: 0 0 auto;
  background: var(--k-bar); border-bottom: 1px solid var(--k-edge-2);
}
.pos-kds-states[hidden] { display: none; }
/* The tone rides on a custom property rather than on `color`, because an on
   chip has to paint its background with the tone *and* set a dark text colour
   — and `currentColor` would resolve to whichever `color` won, making the
   background follow the text instead of the state. */
.pos-kds-state {
  --tone: var(--k-muted);
  display: inline-flex; align-items: center; gap: 6px;
  padding: 5px 11px; border-radius: 999px;
  border: 1px solid var(--k-edge); background: var(--k-tile);
  color: var(--tone); font-family: inherit; font-size: 12px; font-weight: 700;
  cursor: pointer; white-space: nowrap;
}
.pos-kds-state em { font-style: normal; font-weight: 800; opacity: .7;
  font-variant-numeric: tabular-nums; }
.pos-kds-state.driver,
.pos-kds-state.customer,
.pos-kds-state.waiter { --tone: #2fbf6b; }
.pos-kds-state.all, .pos-kds-state.making { --tone: var(--k-ink); }
/* A delivery that came back needs somebody to decide something. Amber, not
   red: nothing has gone wrong in the kitchen, and a board that shouts at a
   chef about a rider's failed drop is a board they learn to ignore. */
.pos-kds-state.back { --tone: #f5a623; }
.pos-kds-state.on { background: var(--tone); border-color: var(--tone); color: var(--k-oncontrast); }
.pos-kds-state.on em { color: var(--k-oncontrast); opacity: .8; }
.posm .pos-kds-states { padding: 6px 10px; gap: 5px; }
.posm .pos-kds-state { padding: 4px 9px; font-size: 11px; }

.kt.pending { opacity: .72; }
.kt.app { border-left: 4px solid var(--tk-app, var(--primary)); }

.kt-cancel, .kt-rush, .kt-pending {
  padding: 5px 12px;
  font-size: .72em;
  font-weight: 800;
  letter-spacing: .07em;
}
.kt-cancel { background: var(--red); color: #fff; }
/* **RUSH flashes red to white.** Jamal: *"the rush should show a flashing red
   to white flash, to show that it's in a rush for that order, to emphasise how
   important that is."*

   It was static amber, which put it in the same family as the pending chip and
   the made-but-not-sent strip — three amber things on one board, one of which
   is the most urgent thing on it. Red separates it, and the flash is what makes
   it findable across a pass without reading a word.

   The text colour flips with the background so it is legible in both halves of
   the cycle: white on red, red on white. Animating `color` alongside
   `background-color` rather than picking one ink that survives both, because
   the compromise ink is grey and grey is not what "rush" looks like.

   A cancelled ticket is also red and white, and deliberately does NOT move —
   the flash is the whole difference between "this one first" and "this one is
   off". One is a thing to do now; the other is a thing not to do at all. */
@keyframes kt-rush-flash {
  0%, 44%   { background-color: var(--red); color: #fff; }
  50%, 94%  { background-color: #fff; color: #b00020; }
  100%      { background-color: var(--red); color: #fff; }
}
.kt-rush {
  background: var(--red); color: #fff;
  animation: kt-rush-flash 1.1s steps(1, end) infinite;
}
/* **Somebody who cannot take a flashing screen still has to see the rush.**
   Not a lesser version of the feature: the badge stays red and the outline
   stays red, which is already louder than the amber they replaced. What goes
   is the movement.

   **This has to sit below both rules it overrides**, and it did not: written
   next to `.kt.rush` five hundred lines up, it lost the cascade to `.kt-rush`
   down here on equal specificity, so a reduced-motion screen flashed anyway.
   Caught by sampling the computed colour in a reduced-motion context rather
   than by reading the stylesheet, which is the only way this kind of mistake
   is ever caught. */
@media (prefers-reduced-motion: reduce) {
  .kt-rush, .kt.rush { animation: none; }
  /* The badge, and only the badge. `.kt.rush` is the whole ticket — giving it
     the red background here painted the entire card red, which is not "hold
     the flash still", it is a different and much worse design. Its share of
     this is the outline. */
  .kt-rush { background: var(--red); color: #fff; }
  .kt.rush { box-shadow: 0 0 0 3px var(--red); }
}
/* And the ticket's own outline goes with it, so a rushed order is findable
   from the far side of a kitchen before anybody is close enough to read RUSH.
   Same cycle, same length, so the two pulse together rather than beating
   against each other. */
@keyframes kt-rush-ring {
  0%, 44%  { box-shadow: 0 0 0 3px var(--red); }
  50%, 94% { box-shadow: 0 0 0 3px #fff; }
  100%     { box-shadow: 0 0 0 3px var(--red); }
}
.kt-pending { color: var(--amber); }
/* The dining option, big and centred with a rule through it — Toast's, and it
   is the thing a chef checks before they plate. */
.kt-where {
  text-align: center;
  font-size: 1.15em;
  font-weight: 800;
  padding: 10px 12px 8px;
  border-bottom: 1px solid var(--border);
}
.kt-who { padding: 4px 12px; color: var(--muted); font-size: .86em; }
/* **A kitchen note has to be readable, and it was not** (Jamal, 2026-09-09:
   *"Yellow seems hard to read on this."*). Measured in a real browser on the
   live orders board: `--amber` #f5a623 on the white ticket card is a contrast
   ratio of **2.03**, at 12.9px and weight 400. WCAG AA wants 4.5 for text this
   size. That is not a style quibble — the note is where "no coriander" and
   "nut allergy" land, so a chef who cannot read it at a glance is the whole
   problem the board exists to prevent.
   So the note stops being tinted text and becomes its own block: a pale amber
   pill, an amber bar down the left, and dark amber ink at **6.33** on white.
   The pill carries its own contrast, which means it stays readable whatever
   the card behind it is doing. Bolder and a shade larger too — a note should
   shout a little. */
.kt-note {
  margin: 5px 10px;
  padding: 6px 10px;
  color: var(--tk-note-ink);
  background: var(--tk-note-bg);
  border-left: 3px solid var(--amber);
  border-radius: 6px;
  font-size: .92em;
  font-weight: 700;
}
/* Cutlery, when the guest asked for it. Quieter than a note — a note is
   somebody typing at the kitchen; this is one word the packer needs. */
.kt-cutlery { padding: 6px 12px 0; color: var(--muted); font-size: .82em;
  text-transform: uppercase; letter-spacing: .04em; }
/* The guest's collection code, so the counter can compare rather than ask
   twice. Never a check anything depends on -- the name and the order number
   are still on the card. */
.kt-code { padding: 6px 12px 0; font-size: .86em; color: var(--muted); }
.kt-code b { color: var(--text); font-variant-numeric: tabular-nums; letter-spacing: .06em; }
.kt-course {
  background: var(--surface-2);
  color: var(--muted);
  text-align: center;
  font-size: .68em;
  letter-spacing: .08em;
  text-transform: uppercase;
  font-weight: 700;
  padding: 5px 12px;
}
.kt-lines { padding: 4px 0; }
.kt-line {
  display: flex;
  align-items: flex-start;
  gap: 8px;
  width: 100%;
  /* 11px, not 7px. The line — not the box inside it — is what a finger hits,
     and on the register's scaled stage (~0.82) 7px of padding rendered a 30px
     row. Sized for the glass. */
  padding: 11px 12px;
  min-height: 44px;
  background: none;
  border: 0;
  color: var(--text);
  font: inherit;
  text-align: left;
  cursor: pointer;
}
.kt-line:hover { background: var(--surface-2); }
/* A fulfilled line is struck through and dimmed rather than removed: the chef
   needs to see that it went, not that it vanished. */
.kt-line.done .kt-name { color: var(--muted); text-decoration: line-through; }
.kt-line.done .kt-qty { color: var(--muted); }
/* A real tick box (v5.132.0). Jamal: "it should have like a tick box." It was
   an empty 16px span that grew a tick when done — so before you ticked it there
   was nothing on screen saying the line could be ticked at all, and the whole
   affordance was folklore. Empty box, green box with a tick in it. */
.kt-tick {
  flex: none;
  display: grid;
  place-items: center;
  width: 22px; height: 22px;
  margin-top: 1px;
  border: 2px solid #4a5060;
  border-radius: 5px;
  color: #fff;
}
.kt-line.done .kt-tick {
  background: var(--green);
  border-color: var(--green);
}
/* **A ticked line has to read as done from three feet, not as a dimmer one.**
   Jamal: *"a tick just makes the item a dimmer colour for a few seconds."* The
   strikethrough and the muted grey were the whole signal, and on a phone at
   arm's length in a kitchen that is not a signal. The row now carries a green
   left edge and a faint green wash as well — the box, the rule and the ground
   all saying the same thing, so no single one of them has to carry it. */
.kt-line.done {
  background: rgba(46, 160, 87, .10);
  box-shadow: inset 3px 0 0 var(--green);
}
/* And it lands **immediately**: the class is toggled on the row itself now
   rather than falling out of a full board rebuild, so there is nothing to
   animate away from. A short transition keeps it from looking like a glitch
   without putting anything between the finger and the state. */
.kt-line { transition: background .12s ease-out; }
.kt-line:hover .kt-tick { border-color: var(--green); }
.kt-qty { flex: none; min-width: 16px; font-weight: 800; font-variant-numeric: tabular-nums; }
.kt-name { flex: 1; min-width: 0; line-height: 1.3; }
.kt-name em { display: block; font-style: normal; font-size: .82em; color: var(--muted); }
.kt-name em.note { color: var(--amber); }
/* **A removal, on a kitchen screen.** Not the same grey as "extra cheese": the
   two are different kinds of thing and the difference is what this release
   exists for. Bold, red, and at the size of the dish name rather than shrunk to
   .82em — a chef reads this at three feet in a hurry, and nothing here may be
   truncated (see the refusals in worker/src/removals.js). */
.kt-name em.kt-off { color: #E03131; font-weight: 800; font-size: .95em;
  letter-spacing: .01em; white-space: normal; }
.kt-line.done .kt-name em.kt-off { color: var(--muted); }
.kt-name em.alg { color: var(--amber); }
.kt-warn { padding: 6px 12px; color: var(--amber); font-size: .8em; }
.kt-part { padding: 6px 12px; color: var(--muted); font-size: .78em; font-weight: 700; }
.kt-btn {
  display: block;
  width: 100%;
  padding: 11px;
  background: var(--surface-2);
  border: 0;
  border-top: 1px solid var(--border);
  color: var(--text);
  font: inherit;
  font-weight: 700;
  cursor: pointer;
}
.kt-btn:hover { background: var(--primary); color: var(--primary-ink); }

/* The advance button (v5.132.0). Jamal: it should read obviously as a button.
   It was a full-bleed strip the same colour as the header, which on a card made
   of strips is indistinguishable from a caption. Now it is filled, inset, and
   carries the chevron that says it moves the ticket on. */
.kt-btn.go {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  width: calc(100% - 16px);
  margin: 8px;
  /* 16px, not 12px. The kitchen view sits on the register's scaled design
     stage — about 0.82 — so 12px of padding rendered a 38px button, under the
     44px a wet finger needs. Sized for the glass, not for the stylesheet. */
  padding: 16px 10px;
  border-radius: 9px;
  border-top: 0;
  background: var(--primary);
  color: var(--primary-ink);
  font-weight: 800;
  box-shadow: 0 1px 0 rgba(0, 0, 0, .35);
}
.kt-btn.go:hover { filter: brightness(1.08); }
.kt-btn.go:active { transform: translateY(1px); box-shadow: none; }
.kt-btn.go svg { flex: none; opacity: .9; }

/* The toolbar buttons — Toast's three, each answering a question a pass asks. */
.pos-kds-tool {
  display: flex;
  align-items: center;
  gap: 6px;
  background: none;
  border: 1px solid var(--border);
  border-radius: 8px;
  color: var(--muted);
  padding: 7px 11px;
  font: inherit;
  font-size: 11px;
  font-weight: 800;
  letter-spacing: .06em;
  cursor: pointer;
}
.pos-kds-tool:hover { color: var(--text); border-color: var(--primary); }

/* One ticket, opened. */
/* ---- The ticket sheet, rebuilt 2026-08-23 (#200) ---------------------------
   Jamal, with five screenshots: *"All looks a bit rubbish and out of design
   style for the deeper info menu."*

   It was a stack of filled, bordered, 13px-padded slabs — eight of them filled
   a phone, and a fourteen-line ticket was a scroll of identical grey blocks
   with no hierarchy inside any of them. The rebuild is the shape the rest of
   the app uses: **hairline rows**, one card around the lot, weight carried by
   type rather than by borders. */
.ksheet-top {
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  margin: 0 0 10px;
}
.ksheet-where {
  padding: 4px 10px; border-radius: 999px;
  background: var(--surface-2); border: 1px solid var(--border);
  font-size: 12px; font-weight: 800; letter-spacing: .03em; text-transform: uppercase;
}
.ksheet-due { font-size: 13px; font-weight: 700; color: var(--text); }
.ksheet-count { font-size: 12.5px; color: var(--muted); }
/* Rush and Print were two ghost buttons the same size as everything else, next
   to a naked muted word. An icon and a label, sized as tools rather than as
   decisions.

   **`.kshtool`, not `.ksheet-tool`.** That name was already taken — by the
   two-column grid of big stacked tiles further down this file — so the first
   version of this inherited `flex-direction: column` and `padding: 16px`, and
   Rush and Print came out as two 77px blocks stacked down the sheet. Caught by
   reading the computed style rather than the screenshot, which only said they
   looked wrong. */
.kshtool {
  display: inline-flex; align-items: center; gap: 6px;
  min-height: 34px; padding: 0 11px; border-radius: 9px;
  border: 1px solid var(--border); background: var(--surface-2); color: var(--muted);
  font: inherit; font-size: 12.5px; font-weight: 700; cursor: pointer;
}
.kshtool:hover { color: var(--text); }
.kshtool.on { border-color: var(--red); color: var(--red); }
.ksheet-hint { margin: 0 0 10px; font-size: 12.5px; color: var(--muted); line-height: 1.4; }

/* One card, rows inside it. */
.ksheet-lines {
  display: flex; flex-direction: column;
  border: 1px solid var(--border); border-radius: 14px;
  background: var(--surface); overflow: hidden;
}
.kshl {
  display: grid;
  grid-template-columns: 22px 22px minmax(0, 1fr);
  align-items: start;
  gap: 12px;
  width: 100%;
  padding: 12px 14px;
  background: none; border: 0; border-top: 1px solid var(--border);
  color: var(--text); font: inherit; text-align: left; cursor: pointer;
}
.kshl:first-child { border-top: 0; }
.kshl:hover { background: var(--surface-2); }
/* The tick is on the left, where the board puts it — the sheet had it on the
   right, so the same gesture had two different targets depending which screen
   you were on. */
.kshl-tick {
  display: grid; place-items: center;
  width: 22px; height: 22px; margin-top: 1px;
  border: 2px solid var(--border); border-radius: 6px; color: transparent;
}
.kshl.on .kshl-tick, .kshl.done .kshl-tick {
  background: var(--green, #3ecf8e); border-color: var(--green, #3ecf8e); color: #04150d;
}
.kshl.on { background: color-mix(in srgb, var(--primary) 14%, transparent); }
.kshl-qty {
  font-size: 15px; font-weight: 800; font-variant-numeric: tabular-nums;
  text-align: right; margin-top: 1px;
}
.kshl-main { min-width: 0; }
.kshl-dish { display: block; font-size: 15px; font-weight: 700; line-height: 1.3; }
/* **Only the dish is struck through.** The whole block used to be, so a ticked
   mix-and-match taco was five lines of struck-out grey and unreadable. */
.kshl.done .kshl-dish { color: var(--muted); text-decoration: line-through; }
.kshl.done .kshl-qty { color: var(--muted); }
/* The choices: one per line, indented under the dish, quieter than it. They
   were a comma-joined paragraph inside the dish name until this release. */
.kshl-choices { display: block; margin-top: 3px; }
.kshl-choice {
  display: block; padding-left: 11px; position: relative;
  font-size: 13px; line-height: 1.45; color: #9aa3b2;
}
.kshl-choice::before {
  content: ''; position: absolute; left: 0; top: .62em;
  width: 4px; height: 4px; border-radius: 50%; background: currentColor; opacity: .7;
}
/* A removal on the sheet, the same red the card gives it. A missed addition is
   a slightly wrong dish; a missed removal is a dish sent back. */
.kshl-choice.off { color: #ff8686; font-weight: 700; }
.kshl-note {
  display: block; margin-top: 4px; font-size: 13px; line-height: 1.4;
  color: #ffd79a; font-style: italic;
}
.kshl-alg {
  display: inline-block; margin-top: 5px; padding: 2px 8px; border-radius: 999px;
  background: rgba(230, 72, 61, .18); color: #ff9c93;
  font-size: 11.5px; font-weight: 800; letter-spacing: .04em; text-transform: uppercase;
}
.recent-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 10px 0;
  border-bottom: 1px solid var(--border);
}
.recent-row span { display: block; color: var(--muted); font-size: 12px; }

/* All day: how many of each thing, right now. */
.allday { display: flex; flex-direction: column; }
.allday-row {
  display: flex;
  align-items: flex-start;
  gap: 14px;
  padding: 10px 0;
  border-bottom: 1px solid var(--border);
}
.allday-row b {
  min-width: 34px;
  font-size: 20px;
  font-variant-numeric: tabular-nums;
  text-align: right;
}
.allday-row span { flex: 1; font-size: 15px; }
.allday-row em { display: block; font-style: normal; font-size: 12.5px; color: var(--muted); margin-top: 2px; }

/* A ticket that has been brought back. Toast prints RECALLED across the card
   and it is worth copying: a ticket reappearing with no explanation looks like
   a bug, and the chef needs to know this one is back rather than new. */
.kt-recalled {
  padding: 6px 12px;
  text-align: center;
  font-size: .82em;
  font-weight: 800;
  letter-spacing: .1em;
  color: var(--red);
  background: rgba(255,92,92,.1);
}
/* And it is scrolled to and marked, because a ticket that comes back off the
   right-hand edge of a busy board has not really come back. */
.kt.flash { animation: ktflash 1.1s ease 2; }
@keyframes ktflash {
  0%, 100% { box-shadow: 0 0 0 0 rgba(79,140,255,0); }
  50% { box-shadow: 0 0 0 3px var(--primary); }
}
/* The admin code in the sidebar: masked until asked for. */
.ctx-code {
  display: flex;
  flex-direction: column;
  gap: 3px;
  padding: 8px 12px 10px;
  border: 1px solid var(--border);
  border-radius: 10px;
  background: var(--surface);
  margin-bottom: 8px;
}
.ctx-code-val {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  background: none;
  border: 0;
  padding: 0;
  color: var(--text);
  font: inherit;
  font-size: 15px;
  font-weight: 700;
  letter-spacing: .14em;
  font-variant-numeric: tabular-nums;
  cursor: pointer;
}
.ctx-code-val .unset { color: var(--muted); font-size: 13px; letter-spacing: 0; font-weight: 500; }
.ctx-eye { color: var(--muted); display: grid; place-items: center; letter-spacing: 0; }
.ctx-code-val:hover .ctx-eye { color: var(--text); }
.allday-row .from { color: var(--primary); font-size: 11.5px; }
.allday-send { display: flex; gap: 6px; flex-wrap: wrap; align-items: center; }

/* The wait time at the counter — the same number the kitchen screen shows and
   the QR menu quotes, because a counter saying twenty minutes while the menu
   says forty is worse than neither saying anything. */
.pos-wait {
  display: flex;
  align-items: baseline;
  gap: 8px;
  width: 100%;
  margin: 0 0 8px;
  padding: 9px 12px;
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: 10px;
  color: var(--text);
  font: inherit;
  text-align: left;
  cursor: pointer;
}
.pos-wait:hover { border-color: var(--primary); }
.pos-wait.manual { border-color: var(--amber); }
.pos-wait-n { font-size: 21px; font-weight: 800; font-variant-numeric: tabular-nums; }
.pos-wait-n em { font-size: 13px; font-style: normal; font-weight: 700; margin-left: 1px; }
.pos-wait-l { font-size: 12px; color: var(--muted); }
.pos-wait.manual .pos-wait-l { color: var(--amber); }
.posm-waitwrap { padding: 4px 16px 0; }   /* matches .posm-head's gutter */

/* A mini kitchen board inside the till, for a venue running the whole thing
   off one handset. The full board is a tab away; this is the one-glance
   version, so they never have to leave the screen that takes money. */
/* The prep strip along the bottom of the kitchen screen — what is still to
   cook, at the ingredient level. It scrolls sideways on its own rather than
   wrapping, because a strip that grows to two rows steals them from tickets. */
.pos-kds-prep {
  display: flex;
  gap: 18px;
  align-items: baseline;
  padding: 10px 14px;
  border-top: 1px solid var(--k-edge);
  background: var(--k-panel);
  overflow-x: auto;
  white-space: nowrap;
  scrollbar-width: none;
}
.pos-kds-prep::-webkit-scrollbar { display: none; }
.pos-kds-prep[hidden] { display: none; }
.pos-prep-item { display: inline-flex; align-items: baseline; gap: 6px; }
.pos-prep-item strong { font-size: 14px; font-weight: 650; color: #c9ced8; }
.pos-prep-item em {
  font-style: normal;
  font-size: 19px;
  font-weight: 800;
  color: #e8eaee;
  font-variant-numeric: tabular-nums;
}
.pos-prep-item.miss strong { color: var(--amber); font-size: 12.5px; }

.pos-pend-kitchen {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
  padding: 10px 0;
  margin-top: 8px;
  border-top: 1px solid var(--border);
}
.pos-pend-state {
  font-size: 11px;
  font-weight: 800;
  letter-spacing: .05em;
  text-transform: uppercase;
  padding: 3px 9px;
  border-radius: 999px;
  background: var(--surface-2);
  border: 1px solid var(--border);
  color: var(--muted);
  margin-right: auto;
}
.pos-pend-state.ready { color: var(--amber); border-color: rgba(245,166,35,.4); }
.pos-pend-state.done { color: var(--green); border-color: rgba(62,207,142,.4); }


/* ---- Kitchen screens, from the back office ---------------------------------
   The same settings as the sheet on the iPad, plus the one thing the iPad
   cannot do: a padlock per row. A row a wider level has claimed is shown
   greyed *with the reason next to it* — a disabled control with no explanation
   is the most annoying thing a settings page can do. */
.kset-h { margin: 0 0 4px; font-size: 16px; }
.kset-ctl { display: flex; align-items: center; gap: 8px; flex: none; }
.kset-row.blocked { opacity: .65; }
.kset-by {
  display: inline-block !important;
  margin-top: 4px;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: .04em;
  text-transform: uppercase;
  color: var(--amber);
}
.kset-by.mine { color: var(--green); }
.kset-lock {
  display: grid;
  place-items: center;
  width: 34px;
  height: 34px;
  border-radius: 9px;
  border: 1px solid var(--border);
  background: var(--surface-2);
  color: var(--muted);
  cursor: pointer;
}
.kset-lock:hover:not(:disabled) { border-color: var(--primary); color: var(--text); }
.kset-lock.on { background: var(--amber); border-color: var(--amber); color: #14181f; }
.kset-lock:disabled { cursor: not-allowed; opacity: .5; }
.kset-save { display: flex; align-items: center; gap: 14px; margin: 18px 0 40px; }

/* ---- Welcome: the induction, one ask at a time -----------------------------
   Eight tasks at once reads as a chore and gets closed. One task with a bar
   behind it gets done — so the next thing is the biggest thing on the page and
   the rest is a list you can ignore. */
.wel { max-width: 720px; margin: 0 auto; padding: 18px 0 60px; }
.wel-head { text-align: center; margin-bottom: 26px; }
.wel-eyebrow { font-size: 11.5px; font-weight: 800; letter-spacing: .09em;
  text-transform: uppercase; color: var(--primary); }
.wel-head h1 { margin: 8px 0 6px; font-size: 34px; letter-spacing: -.03em; }
.wel-head p { margin: 0; color: var(--muted); font-size: 15.5px; }
.wel-bar { height: 8px; border-radius: 999px; background: var(--surface-2);
  overflow: hidden; border: 1px solid var(--border); }
.wel-bar i { display: block; height: 100%; background: var(--green); transition: width .4s ease; }
.wel-pct { display: flex; justify-content: space-between; margin: 8px 2px 26px;
  font-size: 13px; color: var(--muted); }
.wel-pct .ok { color: var(--green); font-weight: 700; }
.wel-pct .no { color: var(--amber); font-weight: 700; }

.wel-next { background: var(--surface); border: 1px solid var(--border); border-radius: 16px;
  padding: 26px; margin-bottom: 26px; }
.wel-next.done { border-color: rgba(62,207,142,.4); }
.wel-tag { display: inline-block; font-size: 11px; font-weight: 800; letter-spacing: .07em;
  text-transform: uppercase; color: var(--amber); margin-bottom: 8px; }
.wel-next h2 { margin: 0 0 6px; font-size: 23px; letter-spacing: -.02em; }
.wel-next p { margin: 0 0 18px; color: var(--muted); font-size: 15px; }
.wel-mins { margin-left: 12px; font-size: 13px; color: var(--muted); }

.wel-list { display: grid; gap: 2px; }
.wel-step { display: flex; align-items: center; gap: 12px; padding: 13px 14px;
  background: var(--surface); border: 1px solid var(--border); border-radius: 11px; }
.wel-step > div { flex: 1; min-width: 0; }
.wel-step strong { display: block; font-size: 14.5px; }
.wel-step span { display: block; font-size: 12.5px; color: var(--muted); }
.wel-step.done strong { color: var(--muted); text-decoration: line-through; }
.wel-tick { width: 22px; height: 22px; border-radius: 50%; display: grid; place-items: center;
  border: 1.5px solid var(--border); color: #0d1117; flex: none; }
.wel-step.done .wel-tick { background: var(--green); border-color: var(--green); }
.wel-state { font-size: 12px; font-weight: 700; color: var(--green); }
.wel-foot { margin-top: 24px; text-align: center; color: var(--muted); font-size: 14px; }
.wel-foot code { background: var(--surface-2); padding: 2px 7px; border-radius: 5px; }


/* The public website's two switches (v4.53). Options rather than a dropdown,
   because each one needs a sentence saying what it actually does. */
.wb-opts { display: grid; gap: 10px; grid-template-columns: repeat(auto-fit, minmax(min(240px, 100%), 1fr)); }
.wb-opt { text-align: left; padding: 14px 16px; border-radius: 12px; cursor: pointer;
  border: 1.5px solid var(--border); background: var(--surface-2); color: var(--text);
  font-family: inherit; }
.wb-opt:hover { border-color: var(--muted); }
.wb-opt.on { border-color: var(--primary); background: rgba(79,140,255,.14);
  box-shadow: inset 0 0 0 1px var(--primary); }
.wb-opt strong { display: block; font-size: 15px; }
.wb-opt span { display: block; font-size: 13px; color: var(--muted); margin-top: 3px; line-height: 1.45; }
/* Still usable — the choice is remembered — but visibly not in force. */
.wb-moot { opacity: .55; }
/* The way back in while the shopfront is down (v5.59.0). Only drawn with the
   site switched off, because with it on there is nothing to let anybody past. */
.wb-prev { margin-top: 16px; padding-top: 16px; border-top: 1px solid var(--border); }
.wb-prev h4 { margin: 0 0 4px; font-size: 14.5px; }
.wb-prow { display: flex; gap: 8px; flex-wrap: wrap; margin-top: 12px; }
.wb-prow input { flex: 1 1 220px; min-width: 0; padding: 10px 12px; font-size: 15px;
  border: 1px solid var(--border); border-radius: 10px;
  background: var(--surface-2); color: var(--text); font-family: inherit; }
.wb-prow input:focus { outline: none; border-color: var(--primary); }


/* "We see you're here" (v4.54). Offered once, already in their language, and
   never again whichever way they answer — so it is a bar and not a modal. */
/* Top, not bottom (QA usability review §5): the bottom of every screen is
   where the primary action lives, and the pill was sitting on it. i18n.js
   places the exact offset below any fixed header at runtime. */
#hp-lang-offer { position: fixed; left: 50%; top: 14px; transform: translateX(-50%);
  z-index: 9000; display: flex; align-items: center; gap: 10px;
  background: var(--surface); border: 1px solid var(--border); border-radius: 999px;
  padding: 8px 10px 8px 18px; box-shadow: 0 18px 50px rgba(0,0,0,.45); max-width: calc(100vw - 24px); }
.hp-lang-txt { font-size: 14.5px; font-weight: 600; color: var(--text); white-space: nowrap; }
#hp-lang-offer button { border-radius: 999px; padding: 7px 15px; font-size: 13.5px;
  font-weight: 700; font-family: inherit; cursor: pointer; border: 1px solid var(--border);
  background: var(--surface-2); color: var(--text); white-space: nowrap; }
#hp-lang-offer .hp-lang-yes { background: var(--primary); border-color: var(--primary);
  color: var(--primary-ink); }

/* The language list (v4.54) — each named in its own script, because somebody
   looking for their own language is not reading the English name of it. */
.lang-list { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(170px, 100%), 1fr)); gap: 8px;
  max-height: 52vh; overflow-y: auto; margin-top: 12px; }
.lang-row { display: flex; align-items: baseline; gap: 8px; text-align: left; padding: 11px 14px;
  border-radius: 10px; border: 1px solid var(--border); background: var(--surface-2);
  color: var(--text); font-family: inherit; cursor: pointer; }
.lang-row:hover { border-color: var(--muted); }
.lang-row.on { border-color: var(--primary); background: rgba(79,140,255,.14); }
.lang-row strong { font-size: 15px; }
.lang-row span { font-size: 12.5px; color: var(--muted); }
.lang-row .tick { margin-left: auto; color: var(--primary); }

/* The VAT table (v4.55). An item nobody has looked at is not the same as one
   somebody set to zero, so it is marked rather than left to be inferred. */
.vat-todo { background: rgba(245,166,35,.07); }
.vat-sel { background: var(--surface-2); color: var(--text); border: 1px solid var(--border);
  border-radius: 8px; padding: 6px 8px; font-family: inherit; font-size: 13.5px; }
.vat-sel.undecided { border-color: var(--amber); color: var(--amber); }

/* The registration switch reads as one sentence with its box, not a box
   floating away from a paragraph. */
.vat-reg-row { display: flex; align-items: flex-start; gap: 12px; cursor: pointer; }
.vat-reg-row input { margin-top: 3px; flex: none; }
.vat-reg-row .hint { margin-top: 3px; max-width: 62em; }

/* The P&L board (v4.56). Cards side by side, because the comparison between
   venues is the reason the screen exists — a list of one venue at a time is a
   report, not a board. */
.pnl-board { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(280px, 100%), 1fr));
  gap: 16px; align-items: start; }
.pnl-card { background: var(--surface); border: 1px solid var(--border); border-radius: 14px;
  padding: 18px 18px 16px; }
.pnl-card.total { border-color: var(--primary); box-shadow: inset 0 0 0 1px var(--primary); }
.pnl-name { display: flex; align-items: baseline; justify-content: space-between; gap: 8px;
  padding-bottom: 12px; border-bottom: 1px solid var(--border); margin-bottom: 4px; }
.pnl-name strong { font-size: 18px; letter-spacing: -.01em; }
.pnl-nobudget { font-size: 11.5px; color: var(--amber); white-space: nowrap; }

.pnl-line { padding: 12px 0; border-bottom: 1px dashed var(--border); }
.pnl-line:last-of-type { border-bottom: 0; }
.pnl-line-head { display: flex; align-items: center; justify-content: space-between; gap: 8px;
  margin-bottom: 6px; }
.pnl-line-name { font-size: 14px; color: var(--muted); font-weight: 600; }

/* Green is not "down", it is "better" — see pnl.js. */
.pnl-chip { font-size: 12px; font-weight: 700; border-radius: 999px; padding: 2px 9px;
  font-variant-numeric: tabular-nums; white-space: nowrap; }
.pnl-chip.good { background: rgba(62,207,142,.16); color: var(--green); }
.pnl-chip.bad { background: rgba(255,92,92,.14); color: var(--red); }
.pnl-chip.level { background: var(--surface-2); color: var(--muted); }
/* Not green. A cost line at zero against a budget is missing data, and dressing
   it as good news is how a manager finds out in month three. */
.pnl-chip.missing { background: rgba(245,166,35,.14); color: var(--amber); }

.pnl-row { display: grid; grid-template-columns: 1fr auto 52px; align-items: baseline; gap: 8px; }
/* The flash P&L card uses the same class name for a completely different row —
   a heading, a big number and a strip of comparison boxes stacked, not three
   columns. It was being squeezed into the 52px third column of a layout meant
   for another screen, and its boxes spilled past the right of the phone. */
.pnl-card .pnl-row { display: block; }
.pnl-row.muted .pnl-v, .pnl-row.muted .pnl-k, .pnl-row.muted .pnl-pct { color: var(--muted); }
.pnl-k { font-size: 13px; color: var(--muted); }
.pnl-v { font-size: 19px; font-weight: 700; font-variant-numeric: tabular-nums;
  letter-spacing: -.02em; }
.pnl-v.bad { color: var(--red); }
.pnl-pct { font-size: 13.5px; font-weight: 600; text-align: right;
  font-variant-numeric: tabular-nums; color: var(--muted); }

.pnl-foot { margin-top: 12px; padding-top: 12px; border-top: 1px solid var(--border);
  display: grid; gap: 6px; }
.pnl-foot > div { display: grid; grid-template-columns: 1fr auto 52px; align-items: baseline; gap: 8px; }
.pnl-setb { margin-top: 12px; width: 100%; }

/* Waste (v4.57). The report that replaced a units-and-total table: why it went,
   what share of sales that was, and which venue is not filling the book in. */
.ws-warn { background: rgba(245,166,35,.1); border: 1px solid rgba(245,166,35,.35);
  border-radius: 12px; padding: 13px 15px; margin-bottom: 16px; font-size: 14px; line-height: 1.5; }
.ws-warn strong { display: block; color: var(--amber); margin-bottom: 2px; }

.ws-heads { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(220px, 100%), 1fr));
  gap: 14px; margin-bottom: 14px; }
.ws-head { background: var(--surface); border: 1px solid var(--border); border-radius: 14px;
  padding: 16px 18px; display: grid; gap: 4px; }
.ws-h-label { font-size: 12.5px; color: var(--muted); font-weight: 600;
  text-transform: uppercase; letter-spacing: .04em; }
.ws-h-value { font-size: 28px; font-weight: 700; letter-spacing: -.02em;
  font-variant-numeric: tabular-nums; }
.ws-h-value.bad { color: var(--red); }
.ws-h-sub { font-size: 12.5px; color: var(--muted); }

.ws-card-head { display: flex; align-items: center; justify-content: space-between;
  gap: 12px; flex-wrap: wrap; margin-bottom: 8px; }
.ws-chart { width: 100%; overflow-x: auto; }
.ws-chart svg { width: 100%; height: 220px; display: block; }
.ws-chart-empty { padding: 40px 0; text-align: center; }
.ws-grid { stroke: var(--border); stroke-width: 1; stroke-dasharray: 3 4; }
.ws-axis { fill: var(--muted); font-size: 11px; font-variant-numeric: tabular-nums; }
.ws-legend { display: flex; flex-wrap: wrap; gap: 6px 16px; margin-top: 10px; font-size: 12.5px; }
.ws-leg { display: inline-flex; align-items: center; gap: 6px; color: var(--muted); }

.ws-reasons { width: 100%; }
.ws-reasons td { padding: 9px 8px; border-bottom: 1px solid var(--border); font-size: 14px; }
.ws-reasons tr:last-child td { border-bottom: 0; }
.ws-barcell { width: 34%; min-width: 90px; }
.ws-bar { display: block; height: 8px; border-radius: 999px; background: var(--surface-2); overflow: hidden; }
.ws-bar > span { display: block; height: 100%; border-radius: 999px; }
/* Staff meals sit in the table with everybody else but are marked, so nobody
   reads the total as the number a kitchen is judged on. */
.ws-tag { font-size: 11px; font-weight: 700; color: var(--muted); background: var(--surface-2);
  border-radius: 999px; padding: 1px 7px; margin-left: 7px; text-transform: uppercase;
  letter-spacing: .03em; }

.ws-stack { display: flex; height: 10px; border-radius: 999px; overflow: hidden;
  background: var(--surface-2); min-width: 80px; }
.ws-stack > span { height: 100%; }

.ws-chip { font-size: 12px; font-weight: 700; border-radius: 999px; padding: 2px 9px;
  font-variant-numeric: tabular-nums; white-space: nowrap; display: inline-block; }
.ws-chip.good { background: rgba(62,207,142,.16); color: var(--green); }
.ws-chip.watch { background: rgba(245,166,35,.14); color: var(--amber); }
.ws-chip.bad { background: rgba(255,92,92,.14); color: var(--red); }
/* Not green, and not blank. A venue with an empty logbook is the one thing on
   the page somebody has to fix before the rest of it means anything. */
.ws-chip.unlogged { background: rgba(245,166,35,.16); color: var(--amber); margin-left: 6px; }
.ws-chip.closed { background: var(--surface-2); color: var(--muted); margin-left: 6px; }

/* Sales insights (v4.58). Takings, orders and average spend together, because
   takings alone cannot say whether more people came or the same people spent
   more. Reuses the waste page's chart chrome (.ws-chart, .ws-grid, .ws-axis,
   .ws-legend) rather than growing a second set of the same six rules. */
.sb-heads { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(220px, 100%), 1fr));
  gap: 14px; margin-bottom: 12px; }
.sb-head { background: var(--surface); border: 1px solid var(--border); border-radius: 14px;
  padding: 16px 18px; display: grid; gap: 4px; }
.sb-h-label { font-size: 12.5px; color: var(--muted); font-weight: 600;
  text-transform: uppercase; letter-spacing: .04em; }
.sb-h-value { font-size: 28px; font-weight: 700; letter-spacing: -.02em;
  font-variant-numeric: tabular-nums; }
.sb-h-sub { font-size: 12.5px; color: var(--muted); display: flex; align-items: center;
  gap: 7px; flex-wrap: wrap; }

/* The one sentence that says which lever moved. */
.sb-moved { font-size: 15px; font-weight: 600; margin: 2px 0 6px; }

.sb-card-head { display: flex; align-items: baseline; justify-content: space-between;
  gap: 12px; flex-wrap: wrap; margin-bottom: 8px; }
.sb-card-head .muted { font-size: 13px; }

.sb-chip { font-size: 12px; font-weight: 700; border-radius: 999px; padding: 2px 9px;
  font-variant-numeric: tabular-nums; white-space: nowrap; display: inline-block; }
.sb-chip.good { background: rgba(62,207,142,.16); color: var(--green); }
.sb-chip.bad { background: rgba(255,92,92,.14); color: var(--red); }
.sb-chip.level { background: var(--surface-2); color: var(--muted); }
/* Not a colour. A movement below the noise floor gets words instead, or the
   screen shouts at every small channel and teaches people to ignore all of it. */
.sb-chip.quiet { background: transparent; border: 1px dashed var(--border); color: var(--muted);
  font-weight: 600; }

/* A forecast built on three same-weekdays is answered, and labelled. */
.sb-thin { font-size: 11px; font-weight: 700; color: var(--amber); background: rgba(245,166,35,.13);
  border-radius: 999px; padding: 1px 7px; margin-left: 6px; text-transform: uppercase;
  letter-spacing: .03em; }

.sb-stack { display: flex; height: 12px; border-radius: 999px; overflow: hidden;
  background: var(--surface-2); margin: 4px 0 14px; }
.sb-stack > span { height: 100%; }

.sb-bar-actual { fill: var(--primary); }
.sb-bar-forecast { fill: #8b93a7; opacity: .5; }

/* The rota (v4.59). A week grid: people down the side, days across, and the
   cost of the week underneath — because a rota built without the cost in view
   is a rota somebody signs off and then queries on payday. */
.rt-grid { width: 100%; border-collapse: collapse; min-width: 820px; }
.rt-grid th, .rt-grid td { border: 1px solid var(--border); padding: 6px 8px;
  vertical-align: top; text-align: left; }
.rt-grid thead th { font-size: 12px; color: var(--muted); text-transform: uppercase;
  letter-spacing: .04em; font-weight: 700; background: var(--surface-2); }
.rt-grid tbody td { width: 12.4%; }

.rt-dept th { background: var(--surface-2); font-size: 12.5px; font-weight: 700;
  text-transform: uppercase; letter-spacing: .05em; color: var(--muted); }
.rt-person { width: 13%; font-size: 13.5px; font-weight: 600; white-space: nowrap; }
/* Over the weekly limit. Amber and always on screen, because the number is a
   property of the week and appears nowhere else. */
.rt-over { display: inline-block; margin-left: 6px; font-size: 11px; font-weight: 700;
  color: var(--amber); background: rgba(245,166,35,.14); border-radius: 999px; padding: 1px 7px; }
.rt-young { display: inline-block; margin-left: 6px; font-size: 10.5px; font-weight: 700;
  color: var(--muted); background: var(--surface-2); border-radius: 999px; padding: 1px 6px;
  text-transform: uppercase; letter-spacing: .03em; }

.rt-shift { display: block; width: 100%; text-align: left; margin-bottom: 4px; padding: 5px 7px;
  border-radius: 8px; border: 1px solid var(--border); background: var(--surface-2);
  cursor: pointer; font: inherit; color: inherit; }
.rt-shift:hover { border-color: var(--primary); }
.rt-time { display: block; font-size: 12.5px; font-weight: 700; font-variant-numeric: tabular-nums; }
.rt-meta { display: block; font-size: 11px; color: var(--muted); font-variant-numeric: tabular-nums; }
/* A draft is dashed: nobody can work from a rota they cannot see. */
.rt-shift.draft { border-style: dashed; }
.rt-shift.live { border-left: 3px solid var(--green); }
/* Published and then changed. Somebody has already planned their week around
   the old times, so the change has to announce itself. */
.rt-shift.amended { border-left: 3px solid var(--amber); }
.rt-shift.off { opacity: .5; }
.rt-shift.off .rt-time { text-decoration: line-through; }
.rt-flag { display: inline-block; margin-top: 3px; font-size: 10px; font-weight: 700;
  text-transform: uppercase; letter-spacing: .04em; color: var(--amber); }
.rt-shift.off .rt-flag { color: var(--muted); }

.rt-add { width: 100%; border: 1px dashed var(--border); background: transparent;
  color: var(--muted); border-radius: 8px; padding: 3px; cursor: pointer; font-size: 15px;
  line-height: 1.1; opacity: 0; transition: opacity .12s; }
.rt-grid td:hover .rt-add, .rt-add:focus { opacity: 1; }
@media (hover: none) { .rt-add { opacity: 1; } }

.rt-forecast th, .rt-costs th { font-size: 12.5px; color: var(--muted); font-weight: 600; }
.rt-forecast td { font-size: 13px; font-variant-numeric: tabular-nums; }
.rt-fc { color: var(--muted); }
.rt-costs td { font-variant-numeric: tabular-nums; }
.rt-cost { display: block; font-size: 14.5px; font-weight: 700; }
.rt-hours { display: block; font-size: 11.5px; color: var(--muted); }
.rt-pct { display: inline-block; margin-top: 3px; font-size: 11.5px; font-weight: 700;
  border-radius: 999px; padding: 1px 7px; }
.rt-pct.good { background: rgba(62,207,142,.16); color: var(--green); }
.rt-pct.watch { background: rgba(245,166,35,.14); color: var(--amber); }
.rt-pct.bad { background: rgba(255,92,92,.14); color: var(--red); }
/* A share of a forecast is a forecast of a share. No forecast, no percentage —
   an invented denominator is worse than a dash. */
.rt-nopct { color: var(--muted); }

/* The pay run (v4.60). The notice goes first, always: this is not payroll
   software, and a screen that lets somebody think it is would be the most
   damaging thing here. */
.pr-notice { border: 1px solid var(--border); border-left: 4px solid var(--amber);
  background: var(--surface); border-radius: 12px; padding: 14px 16px; margin-bottom: 16px; }
.pr-notice strong { display: block; font-size: 15px; margin-bottom: 4px; }
.pr-notice p { margin: 0; font-size: 13.5px; color: var(--muted); line-height: 1.55;
  max-width: 68em; }

.pr-issue { border-top: 1px solid var(--border); padding: 12px 0; }
.pr-issue:first-of-type { border-top: 0; }
.pr-issue p { margin: 4px 0 0; font-size: 14px; }
.pr-issue-head { display: flex; align-items: center; gap: 9px; }
.pr-issue .btn { margin-top: 8px; }
.pr-sev { font-size: 11px; font-weight: 700; border-radius: 999px; padding: 2px 8px;
  text-transform: uppercase; letter-spacing: .04em; white-space: nowrap; }
.pr-sev.blocking { background: rgba(255,92,92,.14); color: var(--red); }
.pr-sev.serious { background: rgba(245,166,35,.14); color: var(--amber); }
.pr-sev.note { background: var(--surface-2); color: var(--muted); }

.pr-ok { font-size: 12.5px; color: var(--muted); }
.pr-under { font-size: 12px; font-weight: 700; border-radius: 999px; padding: 2px 8px;
  background: rgba(255,92,92,.14); color: var(--red); white-space: nowrap; }
/* Not blank, and not a tick. Somebody whose age nobody recorded has not passed
   the check — they have not been checked. */
.pr-unknown { font-size: 12px; font-weight: 700; border-radius: 999px; padding: 2px 8px;
  background: rgba(245,166,35,.13); color: var(--amber); white-space: nowrap; }
.pr-relief { font-size: 12px; color: var(--green); font-weight: 600; }

/* What you use (v4.61). The promise about data sits above the switches, not
   in a tooltip — a vendor who suspects that unticking a box might lose two
   years of stocktakes never unticks it, and then the modules are decoration. */
.mod-keeps { background: rgba(62,207,142,.09); border: 1px solid rgba(62,207,142,.3);
  border-radius: 12px; padding: 12px 15px; margin-bottom: 16px; }
.mod-keeps strong { font-weight: 600; font-size: 14px; line-height: 1.55; }

/* The workforce module (v4.62). Staff as cards rather than table rows, because
   the colour is the thing being scanned for and a colour in a table cell is a
   dot nobody sees. */
.tm-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(min(260px, 100%), 1fr)); gap: 12px; }
.tm-person { display: flex; align-items: center; gap: 12px; text-align: left; width: 100%;
  background: var(--surface); border: 1px solid var(--border); border-radius: 14px;
  padding: 14px 16px; cursor: pointer; font: inherit; color: inherit; }
.tm-person:hover { border-color: var(--primary); }
.tm-dot { width: 12px; height: 38px; border-radius: 6px; flex: none; }
.tm-who { display: grid; gap: 2px; flex: 1; min-width: 0; }
.tm-who strong { font-size: 15px; }
.tm-role { font-size: 12.5px; color: var(--muted); }
.tm-facts { display: grid; gap: 3px; text-align: right; font-size: 12px; }
/* Amber, not grey: something missing about somebody is a job, not a footnote. */
.tm-gap { color: var(--amber); font-weight: 600; white-space: nowrap; }

.tm-colours { display: flex; align-items: center; gap: 7px; flex-wrap: wrap; margin-bottom: 14px; }
.tm-swatch { width: 26px; height: 26px; border-radius: 8px; border: 2px solid transparent;
  cursor: pointer; padding: 0; }
.tm-swatch.on { border-color: var(--text); box-shadow: 0 0 0 2px var(--surface) inset; }

.tm-owed { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(180px, 100%), 1fr)); gap: 12px;
  margin-top: 16px; padding-top: 14px; border-top: 1px solid var(--border); }
.tm-owed > div { display: grid; gap: 2px; }
.tm-owed strong { font-size: 20px; font-variant-numeric: tabular-nums; }
.tm-near { color: var(--green); font-weight: 600; }
/* A clock-in from a long way off is not an accusation, but it is a fact worth
   seeing without having to open a map. */
.tm-far { color: var(--amber); font-weight: 600; }

/* Availability. Four states and a fifth that is the absence of one. */
.av-head { display: grid; grid-template-columns: repeat(7, minmax(0, 1fr)); gap: 6px; margin-bottom: 6px; }
.av-head span { font-size: 11.5px; color: var(--muted); text-transform: uppercase;
  letter-spacing: .04em; text-align: center; font-weight: 700; }
/* `1fr` has an automatic minimum of min-content, so seven cells each insisting
   on room for the words "not said" made the month 456px wide on a 390pt phone
   and the whole page scrolled sideways. `minmax(0, 1fr)` lets a track be
   narrower than its content; the caption is hidden below, so nothing is
   squeezed — the day and its colour are what a month view is read for, and the
   legend under it already says what each colour means. */
.av-cal { display: grid; grid-template-columns: repeat(7, minmax(0, 1fr)); gap: 6px; }
.av-cell { aspect-ratio: 1 / 0.82; border-radius: 10px; border: 1px solid var(--border);
  background: var(--surface-2); display: grid; align-content: center; justify-items: center;
  gap: 2px; cursor: pointer; font: inherit; color: inherit; padding: 2px; }
.av-cell.blank { border: 0; background: none; cursor: default; }
.av-cell.past { opacity: .35; cursor: default; }
.av-num { font-size: 15px; font-weight: 700; font-variant-numeric: tabular-nums; }
.av-state { font-size: 10px; text-transform: uppercase; letter-spacing: .03em; opacity: .85;
  text-align: center; line-height: 1.15; }
.av-cell.st-all, .av-leg.st-all { background: rgba(62,207,142,.2); border-color: rgba(62,207,142,.5); }
.av-cell.st-am, .av-leg.st-am { background: rgba(79,140,255,.18); border-color: rgba(79,140,255,.5); }
.av-cell.st-pm, .av-leg.st-pm { background: rgba(167,139,250,.18); border-color: rgba(167,139,250,.5); }
.av-cell.st-none, .av-leg.st-none { background: rgba(255,92,92,.14); border-color: rgba(255,92,92,.45); }
/* Dashed, because a day nobody has answered for is not an answer. */
.av-cell.unset, .av-leg.unset { border-style: dashed; background: transparent; color: var(--muted); }
.av-key { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 12px; }
.av-leg { font-size: 12px; border-radius: 999px; padding: 2px 10px; border: 1px solid var(--border); }
.ws-warn.urgent { border-color: rgba(255,92,92,.5); background: rgba(255,92,92,.1); }
.ws-warn.urgent strong { color: var(--red); }

/* Time off and cover. */
.to-offer { display: flex; align-items: center; justify-content: space-between; gap: 12px;
  flex-wrap: wrap; padding: 12px 0; border-top: 1px solid var(--border); }
.to-offer:first-of-type { border-top: 0; }
.to-badge { font-size: 11.5px; font-weight: 700; border-radius: 999px; padding: 2px 9px;
  text-transform: uppercase; letter-spacing: .04em; }
.to-badge.pending { background: rgba(245,166,35,.14); color: var(--amber); }
.to-badge.approved { background: rgba(62,207,142,.16); color: var(--green); }
.to-badge.declined { background: rgba(255,92,92,.14); color: var(--red); }
.to-badge.cancelled { background: var(--surface-2); color: var(--muted); }
/* A pending ask about a day that has gone. Not stored — computed — and it has
   to look settled rather than waiting, or the queue never empties. */
.to-badge.missed { background: var(--surface-2); color: var(--muted); }

/* Asking for work, rather than being given it. */
.ask-head { display: flex; align-items: center; justify-content: space-between; gap: 12px;
  flex-wrap: wrap; }
.ask-head h3 { margin: 0; }
.ask-sub { font-weight: 700; font-size: 13px; margin: 14px 0 2px; }
.ask-pick { border-top: 1px solid var(--border); margin-top: 12px; padding-top: 4px; }
.mh-days { display: flex; flex-wrap: wrap; gap: 6px; }
.mh-day { border: 1px solid var(--border); background: var(--surface-2); color: var(--text);
  border-radius: 999px; padding: 6px 13px; font: inherit; font-size: 13px; cursor: pointer; }
.mh-day.on { background: var(--primary); border-color: var(--primary); color: #fff; }
/* An ask is a whole sentence, not a date — left to `nowrap` it pushed the
   Approve button off the right of the card. */
.ask-what { white-space: normal; min-width: 260px; }

/* A week the screen offers. Every row has a tick because nothing here is
   written until somebody presses Add. */
.sg-day { border-top: 1px solid var(--border); padding: 12px 0 6px; }
.sg-day:first-of-type { border-top: 0; }
.sg-head { display: flex; align-items: baseline; justify-content: space-between; gap: 12px;
  flex-wrap: wrap; margin-bottom: 6px; }
.sg-row { display: grid; grid-template-columns: auto minmax(0, 1fr); gap: 10px;
  align-items: start; padding: 7px 0; cursor: pointer; }
.sg-row .hint { display: block; }

/* Dragging a shift onto another day. The cell says yes or no before anybody
   lets go, because finding out after the drop is finding out too late. */
.rt-shift[draggable="true"] { cursor: grab; }
.rt-shift.dragging { opacity: .45; cursor: grabbing; }
.rt-cell.over { outline: 2px dashed var(--primary); outline-offset: -3px;
  background: rgba(79,140,255,.08); }
.rt-cell.no { outline: 2px dashed var(--red); outline-offset: -3px;
  background: rgba(255,92,92,.07); }
.rt-dragbar { position: sticky; top: 0; z-index: 5; margin: 0 0 10px;
  background: var(--surface-2); border: 1px solid var(--border); border-radius: 8px;
  padding: 8px 12px; font-size: 13px; color: var(--text); }
.rt-dragbar.bad { border-color: var(--red); color: var(--red); }

/* Typing across the timesheet without reaching for the mouse. */
.ts-cell:focus { outline: 2px solid var(--primary); outline-offset: -1px; }

/* A tick with an explanation under it.
   This was a flex row, and inside a grid cell the text span collapsed to zero
   width and spilled out of the dialog entirely. Grid with an explicit
   `auto 1fr` is the fix: the second column is told to take what is left rather
   than left to work it out from its own content. */
.vat-reg-row { display: grid; grid-template-columns: auto minmax(0, 1fr); gap: 12px;
  align-items: start; cursor: pointer; }
.vat-reg-row > span { min-width: 0; }
.vat-reg-row > span > .hint,
.vat-reg-row > span > small { display: block; margin-top: 3px; }

/* Your pay (v4.63). */
.mp-thin { color: var(--amber); }
.mp-assume { margin: 6px 0 0; padding-left: 20px; }
.mp-assume li { font-size: 13.5px; color: var(--muted); line-height: 1.6; }
/* Each person's own colour down the left of their rota chips, and beside their
   name — the thing an eye actually scans a week grid for. */
.rt-shift { border-left: 3px solid var(--who, transparent); }
.rt-shift.live { border-left-color: var(--who, var(--green)); box-shadow: inset 2px 0 0 var(--green); }
.rt-swatch { display: inline-block; width: 9px; height: 9px; border-radius: 3px;
  margin-right: 6px; vertical-align: middle; }

/* --- A month per person (A1) -------------------------------------------------
   Every state gets its own fill. Deliberately not a heat map: "rostered" and
   "worked" are different kinds of thing, not more and less of one, and shading
   them by intensity is how a screen stops answering the question it was opened
   for. The two that mean somebody has to do something — nothing clocked, and
   still clocked in — are the only two that carry an outline. */
.sm-person { margin-bottom: 16px; }
.sm-top { display: flex; align-items: center; justify-content: space-between;
  gap: 12px; margin-bottom: 12px; }

.sm-var { font-size: 12px; font-weight: 700; padding: 3px 10px; border-radius: 999px; }
.sm-var.level { background: rgba(62,207,142,.15); color: var(--green); }
.sm-var.over  { background: rgba(245,166,35,.15); color: var(--amber); }
.sm-var.under { background: rgba(255,92,92,.12); color: var(--red); }
.sm-var.none  { background: rgba(154,163,178,.12); color: var(--muted); }

.sm-figs { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(130px, 100%), 1fr));
  gap: 12px; margin-bottom: 14px; }
.sm-figs > div { display: flex; flex-direction: column; gap: 1px; }
.sm-f-l { font-size: 11px; text-transform: uppercase; letter-spacing: .04em; color: var(--muted); }
.sm-figs strong { font-size: 19px; }
.sm-f-s { font-size: 11px; color: var(--muted); }

.sm-cal { width: 100%; border-collapse: separate; border-spacing: 3px; table-layout: fixed; }
.sm-cal thead th { font-size: 11px; color: var(--muted); text-transform: uppercase;
  letter-spacing: .03em; font-weight: 600; padding-bottom: 2px; }
.sm-cell { position: relative; height: 46px; border-radius: 7px; vertical-align: top;
  padding: 4px 5px; background: var(--surface-2); border: 1px solid transparent; }
.sm-pad { background: none; }
.sm-d { font-size: 10px; color: var(--muted); display: block; line-height: 1.1; }
.sm-h { font-size: 13px; font-weight: 700; display: block; line-height: 1.2; }

.sm-cell.worked      { background: rgba(62,207,142,.20); }
.sm-cell.worked .sm-h{ color: var(--green); }
.sm-cell.rostered    { background: rgba(79,140,255,.16); }
.sm-cell.rostered .sm-h { color: var(--primary); }
/* The two that need somebody to do something. */
.sm-cell.no_clock    { background: rgba(255,92,92,.12); border-color: rgba(255,92,92,.45); }
.sm-cell.no_clock .sm-h { color: var(--red); }
.sm-cell.open        { background: rgba(245,166,35,.14); border-color: rgba(245,166,35,.5); }
.sm-cell.open .sm-h  { color: var(--amber); }
.sm-cell.off         { background: rgba(167,139,250,.20); }
.sm-cell.off .sm-h   { color: #a78bfa; }
.sm-cell.requested   { background: rgba(167,139,250,.09);
  border-style: dashed; border-color: rgba(167,139,250,.5); }
.sm-cell.unavailable { background: repeating-linear-gradient(135deg,
  rgba(154,163,178,.10) 0 5px, transparent 5px 10px); }
.sm-cell.clear       { background: var(--surface-2); }
/* Approved leave over a shift somebody is still down for: the normal case, and
   the one thing on this grid that is nobody's answer yet. */
.sm-cell.clash { box-shadow: inset 0 0 0 2px var(--amber); }
.sm-x { position: absolute; right: 4px; bottom: 4px; width: 8px; height: 8px; opacity: .6;
  background: linear-gradient(45deg, transparent 44%, var(--muted) 44% 56%, transparent 56%),
              linear-gradient(-45deg, transparent 44%, var(--muted) 44% 56%, transparent 56%); }

.sm-legend { display: flex; flex-wrap: wrap; gap: 14px; margin: 4px 0 16px; }
.sm-key { display: inline-flex; align-items: center; gap: 6px; font-size: 12px; color: var(--muted); }
.sm-sw { width: 12px; height: 12px; border-radius: 4px; display: inline-block;
  background: var(--surface-2); border: 1px solid transparent; }
.sm-sw.worked { background: rgba(62,207,142,.5); }
.sm-sw.rostered { background: rgba(79,140,255,.5); }
.sm-sw.no_clock { background: rgba(255,92,92,.3); border-color: rgba(255,92,92,.7); }
.sm-sw.open { background: rgba(245,166,35,.35); border-color: rgba(245,166,35,.8); }
.sm-sw.off { background: rgba(167,139,250,.55); }
.sm-sw.requested { background: rgba(167,139,250,.2); border-style: dashed;
  border-color: rgba(167,139,250,.7); }
.sm-sw.unavailable { background: repeating-linear-gradient(135deg,
  rgba(154,163,178,.35) 0 3px, transparent 3px 6px); }

.sm-note { font-size: 12px; color: var(--muted); margin: 8px 0 0; }
.sm-note.warn { color: var(--amber); }

@media (max-width: 620px) {
  .sm-cell { height: 40px; }
  .sm-h { font-size: 12px; }
}

/* --- Locks (A2) ----------------------------------------------------------- */
.lk-req { display: flex; align-items: center; justify-content: space-between; gap: 14px;
  padding: 10px 0; border-bottom: 1px solid var(--border); flex-wrap: wrap; }
.lk-req:last-child { border-bottom: none; }

/* The explanation is a sentence, and a sentence with nowhere to wrap pushes the
   numbers that matter off the right of the table. It gets a column, not a line. */
.ug-why { max-width: 52ch; white-space: normal; }

/* Gross profit — theoretical against actual. Two lines, and a card that
   follows the pointer the way the reference does. */
.seg-row { display: flex; gap: 6px; margin: 0 0 14px; }
.seg { background: var(--card); border: 1px solid var(--line); color: var(--muted);
  padding: 7px 14px; border-radius: 999px; cursor: pointer; font-size: 13px; font-weight: 600; }
.seg.on { background: var(--primary); border-color: var(--primary); color: #fff; }
/* Menu engineering (#202). The counts across the top, then the table. */
.me-heads { display: flex; flex-wrap: wrap; gap: 22px 36px; margin: 16px 0 14px; }
.me-head-n { font-size: 30px; font-weight: 700; line-height: 1.1; }
.me-head-l { font-size: 12.5px; color: var(--muted); margin-top: 2px; }
.me-chip { display: inline-block; padding: 3px 9px; border-radius: 999px; color: #fff;
  font-size: 11.5px; font-weight: 700; white-space: nowrap; }
/* The sentence about what cannot be seen. Deliberately not styled as a hint
   somebody scrolls past — a report blind to most of the trade has to say so. */
.me-blind { border-left: 3px solid #b26a00; padding-left: 12px; max-width: 72ch; }
.me-what { margin-top: 3px; max-width: 46ch; line-height: 1.35; }
.me-tbl td { vertical-align: top; }

/* Loss prevention (#203). One card per person, one block per flag, and the
   evidence one click away — never further, because a flag without its rows is
   an accusation. */
.lp-person { margin-top: 16px; }
.lp-who { display: flex; align-items: baseline; gap: 12px; flex-wrap: wrap;
  padding-bottom: 10px; border-bottom: 1px solid var(--border); }
.lp-who h3 { margin: 0; }
.lp-note { border-left: 3px solid var(--border); padding-left: 12px; max-width: 72ch;
  margin: 12px 0 0; }
.lp-flag { margin-top: 16px; padding-top: 14px; border-top: 1px solid var(--border); }
.lp-flag:first-of-type { border-top: 0; }
.lp-flag-top { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.lp-nums { margin-top: 6px; font-size: 14px; }
.lp-flag .hint { max-width: 72ch; margin: 6px 0 0; }
.lp-ev { margin-top: 10px; }
.lp-ev summary { cursor: pointer; font-size: 13px; font-weight: 600; color: var(--primary); }
.lp-ev table { margin-top: 10px; }

/* The kitchen's Money screen (#201), on Square's Money tab. The balance card is
   the one piece of deliberate visual copying: venue name small, the figure very
   large, the label under it, and a full-width pill beneath. Square's pill offers
   an instant transfer; ours says how the money actually arrives, because there
   is no rail here to offer. */
.mn-balance { padding: 22px 24px 20px; }
.mn-venue { font-weight: 700; font-size: 15px; }
.mn-big { font-size: 44px; font-weight: 700; letter-spacing: -0.02em; line-height: 1.1;
  margin: 10px 0 2px; }
.mn-sub { color: var(--muted); font-size: 14px; }
.mn-for { color: var(--muted); font-size: 13px; margin-top: 4px; }
/* The next payout row, at the top of Scheduled transfers. Green edge when there
   is a date, plain when there is not — the same two states the till draws. */
.mn-next { border-top: 0; border-left: 3px solid var(--green); padding-left: 13px;
  border-radius: 8px; background: rgba(62,207,142,.05); margin-bottom: 4px; }
.mn-next-none { border-left-color: var(--border); background: none; }
.mn-pill { display: flex; align-items: center; justify-content: center; gap: 8px;
  margin-top: 20px; padding: 14px; border-radius: 999px; background: var(--surface-2);
  border: 1px solid var(--border);
  color: var(--muted); font-weight: 600; font-size: 14px; }
.mn-pill svg { opacity: .75; }
/* Why the figure can still move. Not styled as a hint somebody scrolls past —
   it is the difference between an estimate and a promise. */
.mn-estimate { border-left: 3px solid #b26a00; padding-left: 12px; max-width: 74ch; }
.mn-row { display: flex; align-items: center; gap: 14px; padding: 13px 0;
  border-top: 1px solid var(--border); }
.mn-row:first-of-type { border-top: 0; }
.mn-ico { display: inline-flex; align-items: center; justify-content: center;
  width: 38px; height: 38px; border-radius: 9px; background: var(--surface-2);
  border: 1px solid var(--border); color: var(--text); flex: 0 0 auto; }
.mn-ico-bad { color: #b3261e; }
.mn-row-t { flex: 1 1 auto; min-width: 0; }
.mn-row-t b { display: block; font-size: 14.5px; }
.mn-row-t em { display: block; font-style: normal; color: var(--muted); font-size: 12.5px;
  margin-top: 2px; }
.mn-row-a { font-weight: 600; font-size: 15px; white-space: nowrap; }
.mn-all { display: block; text-align: center; margin-top: 16px; font-weight: 600;
  font-size: 14px; text-decoration: underline; cursor: pointer; }
.mn-attn { border: 1px solid #b3261e; }
.mn-tbl { margin-top: 14px; }

.gp-heads { display: flex; flex-wrap: wrap; gap: 28px 40px; margin-bottom: 20px; }
.gp-head { display: block; min-width: 140px; }
.gp-head-label { display: block; color: var(--muted); font-size: 12px;
  text-transform: uppercase; letter-spacing: .04em; }
.gp-head-val { display: block; font-size: 30px; font-weight: 700; line-height: 1.2; }
.gp-head-sub { display: block; color: var(--muted); font-size: 12px; }
.gp-chart-wrap { position: relative; }
.gp-chart .gp-hit { cursor: crosshair; }
.gp-card { position: absolute; top: 10px; width: 238px; background: var(--card);
  border: 1px solid var(--line); border-radius: 12px; padding: 10px 12px;
  box-shadow: 0 10px 30px rgba(0,0,0,.35); pointer-events: none; z-index: 3; }
.gp-card-head { font-size: 12px; color: var(--muted); margin-bottom: 8px; }
.gp-row { display: flex; align-items: center; justify-content: space-between; gap: 10px;
  padding: 6px 0; border-top: 1px solid var(--line); }
.gp-row:first-of-type { border-top: 0; }
.gp-row-name { display: flex; align-items: center; gap: 7px; flex-wrap: wrap; font-size: 13px; }
.gp-row-name i { width: 12px; height: 4px; border-radius: 2px; display: inline-block; }
.gp-row-name small { color: var(--muted); font-size: 11px; width: 100%; padding-left: 19px; }
.gp-row-val { font-weight: 700; font-size: 14px; white-space: nowrap; }
.gp-diff .gp-row-val { color: #ef4444; }
.gp-muted .gp-row-val { color: var(--muted); }
.gp-na { color: var(--muted); }
.gp-why, .gp-flag { margin: 8px 0 0; font-size: 11px; color: var(--muted); line-height: 1.4; }
.gp-flag { color: #f59e0b; }
.gp-bad { color: #ef4444; }
.gp-warn { border-left: 3px solid #f59e0b; }

/* Supplier price lists — the review before a supplier's file is accepted. */
.cost-h { margin: 20px 0 6px; font-size: 14px; }
.cost-warn { margin: 10px 0; padding: 9px 12px; border-radius: 8px;
  background: rgba(245,158,11,.12); border-left: 3px solid #f59e0b; font-size: 13px; }
.cost-drop { color: #22c55e; }

/* Different times on different days for one discount. */
.de-days { display: flex; flex-direction: column; gap: 6px; }
.de-day-row { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.de-day-tick { display: inline-flex; align-items: center; gap: 6px; min-width: 108px;
  font-size: 13px; color: var(--muted); }
.de-day-row input[type=time] { flex: 0 0 118px; width: 118px; }

/* Timesheets — the corrected hours, with what the clock actually recorded. */
.ts-open { color: #22c55e; font-weight: 600; font-size: 12px; }
.ts-pic { background: rgba(99,102,241,.16); border: 1px solid rgba(99,102,241,.4); color: #a5b4fc;
  border-radius: 6px; padding: 2px 8px; font-size: 11px; cursor: pointer; margin-right: 4px; }
.ts-nopic { font-size: 11px; margin-right: 4px; opacity: .45; text-decoration: line-through; }
.ts-log td { background: rgba(255,255,255,.02); padding-top: 4px !important; padding-bottom: 8px !important; }
.ts-was { font-size: 12px; color: var(--muted); margin-bottom: 3px; }
.ts-edit { font-size: 12px; color: #f59e0b; }

/* Timesheets, edited like a spreadsheet. */
.ts-cell { width: 104px; padding: 5px 7px; font-size: 13px; background: transparent;
  border: 1px solid transparent; border-radius: 6px; }
.ts-cell:hover { border-color: var(--line); }
.ts-cell:focus { border-color: var(--primary); background: var(--surface-2); }
.ts-cell.ts-mins { width: 74px; text-align: right; }
.ts-cell.ts-dirty { border-color: #f59e0b; background: rgba(245,158,11,.10); }
/* Worked and Pay come from the server. Once the row has been edited they are
   about the old times, so they are struck through rather than left standing as
   if they still described what is on screen. */
.ts-derived.ts-stale { opacity: .35; text-decoration: line-through; }
#ts-heads.ts-stale .gp-head-val { opacity: .35; text-decoration: line-through; }
#ts-heads.ts-stale .gp-head-sub { color: #f59e0b; }
.ts-bar { position: sticky; bottom: 14px; z-index: 5; display: flex; gap: 10px; align-items: center;
  background: var(--card); border: 1px solid var(--line); border-radius: 12px;
  padding: 10px 14px; margin: 14px 0; box-shadow: 0 10px 30px rgba(0,0,0,.4); }
.ts-bar #ts-reason { flex: 1; min-width: 160px; }
.ts-bar > span { font-weight: 600; font-size: 13px; white-space: nowrap; }

/* A rota shift that clashes with what somebody said, or with approved leave. */
.rt-clash { display: block; font-size: 10px; font-weight: 800; letter-spacing: .04em;
  text-transform: uppercase; color: #fff; background: #dc2626; border-radius: 4px;
  padding: 1px 5px; margin-top: 3px; }
.rt-clashbar { display: flex; align-items: center; gap: 10px; background: rgba(220,38,38,.12);
  border-left: 3px solid #dc2626; border-radius: 8px; padding: 9px 13px; margin-bottom: 12px;
  font-size: 13.5px; }
.ts-unrostered { font-size: 11px; opacity: .6; font-style: italic; }

/* Product groups — the two things people actually press on a product row.
   The old boxes were thin enough to miss on a laptop trackpad. */
.cell-select { width: 100%; min-height: 38px; padding: 8px 30px 8px 11px; font-size: 13.5px;
  background: var(--surface-2); border: 1px solid var(--border); border-radius: 8px;
  color: var(--text); cursor: pointer; }
.cell-select:hover { border-color: var(--primary); }
.grp-bar { display: flex; align-items: center; gap: 12px; flex-wrap: wrap;
  padding: 10px 12px; margin: 0 0 8px; border-radius: 10px;
  background: rgba(120,150,255,.07); border: 1px solid var(--line); font-size: 13.5px; }
.grp-bar .cell-select { width: auto; min-width: 230px; }

/* --- API keys ---------------------------------------------------------------
   One row per credential, each saving on its own. A form that posts every key
   together posts the ones it did not change too, which means a masked field
   either round-trips a real secret through the browser or overwrites a good key
   with "••••1234". */
.kv-row { padding: 10px 0; border-top: 1px solid var(--border); }
.kv-row:first-of-type { border-top: 0; }
.kv-row label { display: flex; align-items: baseline; gap: 8px; font-size: 13px;
  color: var(--muted); margin-bottom: 5px; }
.kv-in { display: flex; gap: 8px; align-items: center; }
.kv-in .input { flex: 1; min-width: 0;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace; }

/* --- Labour by hour ---------------------------------------------------------
   Bars scaled against the worst hour rather than against 100%: a chart where
   every bar is a fifth of its box tells nobody which hour to look at. */
.lab-hours { display: flex; align-items: flex-end; gap: 3px; height: 130px; margin-top: 6px; }
.lab-h { flex: 1; min-width: 0; display: flex; flex-direction: column; justify-content: flex-end;
  align-items: center; height: 100%; gap: 4px; }
.lab-h > span { font-size: 10px; color: var(--muted); font-variant-numeric: tabular-nums; }
.lab-bar { width: 100%; min-height: 2px; border-radius: 3px 3px 0 0;
  background: var(--green, #1f9d61); }
.lab-h.over .lab-bar { background: #ff5c5c; }
.lab-h.unknown .lab-bar { background: var(--border); }

/* --- Adding somebody: three screens (v5.11.0) --------------------------------
   Its own page rather than a dialog, because uploading a contract is not a
   five-second job and a modal says it is. The middle screen holds nothing but
   the upload and the way past it — see renderOnboard in app.js. */
.ob { max-width: 780px; margin: 0 auto; }
.ob-body.wide { max-width: 780px; }
.ob-rail { display: flex; gap: 6px; margin: 0 0 18px; }
.ob-step { flex: 1; display: flex; align-items: center; gap: 8px; padding: 10px 12px;
  border-radius: 10px; background: var(--card); color: var(--muted); font-size: 13.5px;
  border: 1px solid var(--border); }
.ob-step.on { color: var(--text); border-color: var(--green, #1f9d61); }
.ob-step.done { color: var(--text); }
.ob-dot { width: 22px; height: 22px; border-radius: 50%; display: grid; place-items: center;
  background: var(--border); color: var(--text); font-size: 12px; font-weight: 700; flex: none; }
.ob-step.on .ob-dot { background: var(--green, #1f9d61); color: #fff; }
.ob-step.done .ob-dot { background: var(--green, #1f9d61); color: #fff; }
.ob-card { margin-bottom: 14px; }
.ob-actions { display: flex; align-items: center; gap: 10px; margin-top: 16px; }
.btn.lg { padding: 12px 22px; font-size: 15px; }

/* The upload, which is the whole of screen two. Big on purpose. */
.ob-drop { border: 2px dashed var(--border); border-radius: 16px; padding: 44px 28px;
  text-align: center; background: var(--card); transition: border-color .12s, background .12s; }
.ob-drop.over { border-color: var(--green, #1f9d61); background: rgba(31, 157, 97, .06); }
.ob-drop-icon { color: var(--muted); margin-bottom: 10px; }
.ob-drop h3 { margin: 0 0 8px; font-size: 19px; }
.ob-drop p { color: var(--muted); max-width: 46ch; margin: 0 auto 18px; font-size: 14px; }
.ob-drop-sub { font-size: 12.5px; margin-top: 14px !important; }

/* **`display` on the element beats the browser's own `[hidden] { display:none }`.**
   This has now caught us twice — `nav[hidden]` in the staff app in v5.8.0, and
   this spinner, which sat on screen throughout the upload step announcing that
   it was reading a contract nobody had chosen yet. Said once, for anything with
   a display rule of its own. */
[hidden] { display: none !important; }

.ob-reading { display: flex; align-items: center; gap: 14px; padding: 26px;
  background: var(--card); border-radius: 16px; border: 1px solid var(--border); }
.ob-reading p { margin: 2px 0 0; }
.ob-spin { width: 26px; height: 26px; border-radius: 50%; flex: none;
  border: 3px solid var(--border); border-top-color: var(--green, #1f9d61);
  animation: ob-spin 700ms linear infinite; }
@keyframes ob-spin { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) { .ob-spin { animation-duration: 2.5s; } }

/* What the model read, and how sure it was. The eye should go to the two
   fields that need a decision, not evenly over all twelve. */
.ob-note { background: var(--card); border: 1px solid var(--border); border-left: 3px solid
  var(--green, #1f9d61); border-radius: 12px; padding: 14px 16px; margin-bottom: 14px; }
.ob-note p, .ob-note ul { margin: 8px 0 0; font-size: 13.5px; color: var(--muted); }
.ob-note ul { padding-left: 18px; }
.ob-field label { display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap; }
.ob-tag { font-size: 10.5px; font-weight: 700; letter-spacing: .03em; text-transform: uppercase;
  padding: 2px 7px; border-radius: 999px; background: var(--border); color: var(--muted); }
.ob-tag.ok { background: rgba(31, 157, 97, .16); color: var(--green, #1f9d61); }
.ob-tag.warn { background: rgba(245, 166, 35, .18); color: #b97c00; }
.ob-guessed input, .ob-guessed select { border-color: rgba(245, 166, 35, .7) !important; }
.ob-file { display: flex; align-items: center; gap: 12px; }
.ob-file > span { color: var(--muted); flex: none; }

/* The contract history on a profile — what happened, in order. */
.ct-list { margin-top: 10px; }
.ct-row { display: flex; gap: 12px; padding: 12px 0; border-top: 1px solid var(--border); }
.ct-row:first-child { border-top: 0; }
.ct-when { flex: none; width: 150px; color: var(--muted); font-size: 13px; }
.ct-what { flex: 1; }
.ct-kind { display: inline-block; font-size: 10.5px; font-weight: 700; text-transform: uppercase;
  letter-spacing: .03em; padding: 2px 7px; border-radius: 999px; background: var(--border);
  color: var(--muted); margin-bottom: 4px; }
.ct-kind.promotion, .ct-kind.rise { background: rgba(31, 157, 97, .16); color: var(--green, #1f9d61); }
.ct-kind.demotion, .ct-kind.cut { background: rgba(255, 92, 92, .16); color: #ff5c5c; }
.ct-now { border: 1px solid var(--green, #1f9d61); border-radius: 10px; padding: 3px 9px;
  font-size: 11px; color: var(--green, #1f9d61); font-weight: 700; }
.ct-gap { background: rgba(245, 166, 35, .1); border-radius: 10px; padding: 10px 12px;
  font-size: 13px; color: var(--muted); margin-top: 10px; }

/* --- Everything, in one list (v5.12.0) --------------------------------------- */
.tr-tier { font-size: 11px; font-weight: 700; letter-spacing: .03em; text-transform: uppercase;
  padding: 2px 8px; border-radius: 999px; background: var(--border); color: var(--muted); }
.tr-tier.t-raw { background: rgba(79, 140, 255, .16); color: #4f8cff; }
.tr-tier.t-prep { background: rgba(15, 155, 142, .16); color: #0f9b8e; }
.tr-tier.t-menu { background: rgba(31, 157, 97, .16); color: var(--green, #1f9d61); }
.tr-tier.t-modifier { background: rgba(167, 139, 250, .16); color: #a78bfa; }
tr.tr-flag td:first-child { box-shadow: inset 3px 0 0 rgba(245, 166, 35, .8); }

/* An unknown cost, drawn as a question rather than as a figure.
   Jamal: "they should have, like, an orange question mark where it should have
   the price, because no items are linked." Orange rather than red because this
   is work outstanding, not a fault — and never a £0.00, which is what made a
   menu of 250 uncosted items read as pure profit. */
.cost-unknown {
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 22px; height: 22px; padding: 0 6px;
  border-radius: 999px; font-weight: 700; font-size: 13px;
  background: #fdf3dc; color: #8a6100; border: 1px solid #e8c98a;
  cursor: help;
}
.warn-card { border-left: 3px solid #e8a33d; }
.gp-low { color: #b4552d; font-weight: 600; }

/* The Photos screen — a vendor walking their counter with a phone. */
/* Empty, it has to look like a space waiting for a picture rather than a gap in
   the table — against the dark surface a plain fill of var(--line) is invisible,
   so the placeholder is outlined instead. */
.ph-thumb { display: flex; align-items: center; justify-content: center;
  width: 52px; height: 52px; border-radius: 8px; background: var(--line);
  overflow: hidden; }
.ph-thumb:empty { background: transparent; border: 1px dashed var(--border); }
.ph-thumb img { width: 100%; height: 100%; object-fit: cover; }
/* Five columns do not fit a phone, and a phone is the entire point of this
   screen — somebody standing at their own counter. Left as a table it scrolls
   sideways and the Take button, the only control here, sits off the edge. So
   below 620px the row becomes a card: picture, name, state, then the buttons on
   their own line. The Sold column goes, because the sentence under the state
   already says "47 sold in the last month" — the bare number would be the same
   fact twice. Row order still carries it: best sellers first. */
@media (max-width: 620px) {
  .ph-table thead { display: none; }
  .ph-table, .ph-table tbody, .ph-table tr { display: block; }
  .ph-table tr { display: grid; gap: 2px 12px; padding: 12px 14px;
    grid-template-columns: 52px minmax(0, 1fr) auto;
    grid-template-areas: "thumb name badge" "thumb says says" "thumb acts acts"; }
  .ph-table tr + tr { border-top: 1px solid var(--border); }
  .ph-table td { display: block; padding: 0; border: none; }
  .ph-table td:nth-child(1) { grid-area: thumb; }
  .ph-table td:nth-child(2) { grid-area: name; align-self: center; }
  .ph-table td:nth-child(3) { display: contents; }
  .ph-table td:nth-child(3) .ph-state { grid-area: badge; align-self: center; }
  .ph-table td:nth-child(3) .hint { grid-area: says; }
  .ph-table td:nth-child(4) { display: none; }
  .ph-table td:nth-child(5) { grid-area: acts; margin-top: 10px; text-align: left; }
  .ph-table td:nth-child(5) .btn { min-height: 38px; }
  /* The empty-menu row is one cell, not five — it must not be laid out as one. */
  .ph-table tr:has(td[colspan]) { display: block; }
  .ph-table td[colspan] { display: block; }
}

/* Find a photo (#213) — three candidates, tap one. Its own overlay rather
   than a shared sheet: it is opened from two different screens (the Photos
   list and the item editor's dialog) and must sit above either. */
.dpx-ovl { position: fixed; inset: 0; z-index: 95; background: rgba(8, 10, 14, .6);
  display: flex; align-items: center; justify-content: center; padding: 16px; }
.dpx-card { background: var(--surface); border: 1px solid var(--border);
  border-radius: 14px; box-shadow: var(--shadow); padding: 16px;
  width: min(600px, 100%); max-height: min(80vh, 640px); overflow-y: auto; }
.dpx-head { display: flex; align-items: center; justify-content: space-between;
  gap: 10px; margin: 0 0 10px; }
.dpx-row { display: flex; gap: 8px; margin: 0 0 12px; }
.dpx-row input { flex: 1; min-width: 0; }
.dpx-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 10px; }
/* A candidate is one big tap target — the photograph itself is the button. */
.dpx-pic { position: relative; aspect-ratio: 4 / 3; border: 1px solid var(--border);
  border-radius: 10px; background-color: var(--surface-2);
  background-size: cover; background-position: center; cursor: pointer;
  padding: 0; overflow: hidden; }
.dpx-pic:hover { border-color: var(--primary); }
.dpx-credit { position: absolute; left: 0; right: 0; bottom: 0; padding: 8px 6px 4px;
  font-size: 10px; line-height: 1.3; color: #fff; text-align: left;
  background: linear-gradient(transparent, rgba(0, 0, 0, .72));
  overflow: hidden; white-space: nowrap; text-overflow: ellipsis; }
/* One column on a phone — three side by side would be thumbnails too small
   to judge food by, and judging is the whole job here. */
@media (max-width: 560px) {
  .dpx-grid { grid-template-columns: 1fr; }
  .dpx-pic { aspect-ratio: 16 / 9; }
}

/* The byte preview. Monospace and scrollable in its own box: a hex dump that
   wraps is unreadable, and one that widens the dialog is worse. */
/* Setting a cloud printer up — the steps in the printer's own words, on the
   card, rather than in a video somebody has to go and find. Jamal sent me&u's
   help article, which is exactly this list behind a link. */
.prn-setup { border: 1px solid var(--border); border-radius: 10px; padding: 12px 14px; margin-top: 12px;
  background: var(--surface-2); }
.prn-setup > summary { cursor: pointer; font-weight: 600; font-size: 14px; }
.prn-steps { margin: 12px 0 0; padding-left: 20px; display: grid; gap: 10px; }
.prn-steps li { font-size: 13.5px; }
.prn-steps b { display: inline-block; min-width: 128px; }
.prn-steps code { background: var(--bg); border: 1px solid var(--border); border-radius: 6px;
  padding: 2px 7px; font-size: 12.5px; word-break: break-all; }
.prn-steps code.secret { letter-spacing: .06em; }
.prn-steps em { display: block; font-style: normal; color: var(--muted); font-size: 12.5px;
  margin-top: 3px; }
/* Whether the address alone is the whole key. Green when it is not, amber when
   it is — not red: an open URL is a decision a venue is allowed to make, and it
   is the state every printer set up before v5.196 is already in. */
.prn-secure { margin-top: 14px; padding: 10px 12px; border-radius: 8px; font-size: 13px;
  line-height: 1.5; display: flex; flex-wrap: wrap; align-items: center; gap: 10px; }
.prn-secure.on { background: rgba(62,207,142,.08); border: 1px solid rgba(62,207,142,.35); }
.prn-secure.off { background: rgba(245,166,35,.09); border: 1px solid rgba(245,166,35,.4); }
.prn-secure .btn { margin-left: auto; }

.prn-bytes { margin: 0; padding: 12px 14px; background: var(--surface-2); border: 1px solid var(--border);
  border-radius: 10px; font-family: ui-monospace, Menlo, Consolas, monospace; font-size: 12.5px;
  line-height: 1.55; white-space: pre; overflow-x: auto; max-height: 300px; overflow-y: auto; }

/* ---- The shell: one switcher, one top bar (v5.61.0) -----------------------
   Jamal, sending screenshots of ElevenLabs: *"I like this profile icon
   location and design and notification button and the menu bar design and the
   drop down which should let the user chose between sites (if it can) and also
   to choose between modes."*

   Three stacked cards in the sidebar became one pill; the name, the language
   and the way out moved from the bottom of the sidebar to behind the avatar,
   which is where a person looks for them. */

.brand { position: relative; }
/* Folding the sidebar to a rail of icons (QA round 3, finding 75). The control
   lives in the brand row and is the same button the mobile drawer closes with —
   on a wide screen it folds rather than closes, and the choice is remembered.
   Nothing becomes unreachable: every icon keeps its tooltip and its link. */
.side-hide { display: flex; align-items: center; justify-content: center; width: 30px;
  height: 30px; margin-left: auto; border: none; background: none; color: var(--muted);
  cursor: pointer; border-radius: 8px; flex: 0 0 auto; }
.side-hide:hover { background: var(--surface-2); color: var(--text); }
.sidebar.rail { width: 64px; flex: 0 0 64px; padding: 18px 8px; }
.sidebar.rail .brand { padding: 6px 0 14px; justify-content: center; }
.sidebar.rail .brand-logo, .sidebar.rail .brand-name, .sidebar.rail .brand-sub,
.sidebar.rail .nav a > span, .sidebar.rail .nav-sub, .sidebar.rail .site-switch,
.sidebar.rail .ctx-one-txt, .sidebar.rail .ctx-one-caret, .sidebar.rail .ctx-code,
.sidebar.rail .sidebar-foot span:not(.dot), .sidebar.rail .side-settings > span,
.sidebar.rail .sidebar-user > span { display: none; }
.sidebar.rail .side-hide { margin: 0 auto; }
.sidebar.rail .nav a, .sidebar.rail .side-settings {
  justify-content: center; padding: 10px 0; }
.sidebar.rail .ctx-one { justify-content: center; padding: 8px 0; }
.sidebar.rail .sidebar-foot { justify-content: center; padding: 6px 0; }
.sidebar.rail .nav a .badge, .sidebar.rail .nav a .pill { display: none; }

/* The one control. A square standing for the business, its name, and what is
   worth quoting on a call underneath. */
.ctx-one { display: flex; align-items: center; gap: 10px; width: 100%; text-align: left;
  margin-bottom: 10px; padding: 8px 10px; border-radius: 12px;
  border: 1px solid var(--border); background: var(--surface-2); cursor: pointer;
  color: var(--text); }
.ctx-one:hover { border-color: var(--primary); }
.ctx-one-mark { flex: 0 0 28px; height: 28px; border-radius: 8px; display: flex;
  align-items: center; justify-content: center; font-weight: 700; font-size: 13px;
  background: linear-gradient(150deg, #ff9a56, #ff5f6d); color: #fff; }
.ctx-one-txt { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; }
.ctx-one-name { font-size: 14px; font-weight: 650; white-space: nowrap; overflow: hidden;
  text-overflow: ellipsis; }
.ctx-one-sub { font-size: 11px; color: var(--muted); white-space: nowrap; overflow: hidden;
  text-overflow: ellipsis; }
.ctx-one-sub:empty { display: none; }
.ctx-one-caret { flex: 0 0 auto; color: var(--muted); display: flex; }

/* The bar. Sticky, so the way back to the menu and the two controls that are
   about you are reachable from anywhere down a long page. */
.topbar { position: sticky; top: 0; z-index: 40; display: flex; align-items: center; gap: 10px;
  margin: -26px -30px 18px; padding: 10px 30px; min-height: 56px;
  border-bottom: 1px solid var(--border); }
/* The frosted glass lives on a layer BEHIND the bar's content, not on the bar
   itself. iOS WebKit re-rasterizes the children of a fixed element that
   carries backdrop-filter — in Safari's full screen the burger, the bell and
   the avatar all went soft (Jamal's screenshot, 2026-08-30). On a ::before
   the filter only ever touches what is behind the bar. */
.topbar::before { content: ''; position: absolute; inset: 0; z-index: -1;
  background: color-mix(in srgb, var(--bg) 82%, transparent);
  -webkit-backdrop-filter: saturate(180%) blur(14px);
  backdrop-filter: saturate(180%) blur(14px); }
@supports not (backdrop-filter: blur(1px)) { .topbar::before { background: var(--bg); } }
.tb-burger { display: none; }
.tb-title { font-size: 15px; font-weight: 650; min-width: 0; overflow: hidden;
  white-space: nowrap; text-overflow: ellipsis; }
.tb-right { margin-left: auto; display: flex; align-items: center; gap: 10px; }
.tb-icon { position: relative; width: 36px; height: 36px; border-radius: 50%; border: none;
  background: none; color: var(--muted); cursor: pointer; display: flex; align-items: center;
  justify-content: center; }
.tb-icon:hover { background: var(--surface-2); color: var(--text); }
.tb-dot { position: absolute; top: 7px; right: 8px; width: 8px; height: 8px; border-radius: 50%;
  background: var(--muted); border: 2px solid var(--bg); }
.tb-dot.tone-bad { background: var(--red); }
.tb-dot.tone-warn { background: var(--amber, #f0a020); }
.tb-dot.tone-ok { background: var(--green); }
/* The ring around the avatar is the ElevenLabs detail Jamal singled out. */
.tb-avatar { width: 34px; height: 34px; border-radius: 50%; cursor: pointer; font-size: 12px;
  font-weight: 700; letter-spacing: .02em; color: #fff; display: flex; align-items: center;
  justify-content: center; background: linear-gradient(150deg, #7f8cff, #4f8cff);
  border: 2px solid var(--surface); box-shadow: 0 0 0 2px var(--primary); }
.tb-avatar:hover { box-shadow: 0 0 0 3px var(--primary); }

/* Behind the avatar: who you are, then the rows. */
.ctx-who { display: flex; align-items: center; gap: 10px; padding: 4px 4px 10px;
  margin-bottom: 8px; border-bottom: 1px solid var(--border); }
.ctx-who-av { flex: 0 0 34px; height: 34px; border-radius: 50%; display: flex;
  align-items: center; justify-content: center; font-weight: 700; font-size: 12px; color: #fff;
  background: linear-gradient(150deg, #7f8cff, #4f8cff); }
.ctx-who .sub { display: block; color: var(--muted); font-size: 12px; }
.ctx-acctpop .ctx-row { border-color: transparent; }
.ctx-acctpop .ctx-row:hover { border-color: var(--border); background: var(--surface-2); }
.ctx-row.ctx-danger strong { color: var(--red); }

/* A mode, opened or shared. Never configured — see shell.js. */
.mode-share { display: flex; gap: 14px; align-items: flex-start; margin: 12px 0 4px; }
.mode-qr { flex: 0 0 120px; border-radius: 10px; background: #fff; padding: 6px; }
.mode-share-txt { min-width: 0; }
.mode-url { display: block; font-size: 12.5px; word-break: break-all; color: var(--text);
  background: var(--surface-2); border-radius: 8px; padding: 7px 9px; }
.mode-settings { border-top: 1px solid var(--border); padding-top: 10px; margin-bottom: 0; }

@media (max-width: 860px) {
  /* Fixed, not sticky, on a phone. iOS WebKit drops a sticky bar whose box
     is pulled up by negative margins — Jamal's screenshots show the bar
     scrolled clean off the top, hamburger and all: "Make these top things
     visible, as I can't use the menu at the moment." Fixed depends on none
     of the sticky constraint maths; the content puts the bar's height back
     as padding, and the phone's notch rides on top via the safe area. */
  .topbar { position: fixed; top: 0; left: 0; right: 0; margin: 0;
    padding: 8px 14px; padding-top: max(8px, env(safe-area-inset-top)); }
  .content { padding-top: calc(68px + env(safe-area-inset-top)); }
  /* The register hides the bar entirely — its screens keep their own edge. */
  body.pos-open .content, .till-site .content { padding-top: 16px; }
  .tb-burger { display: flex; align-items: center; justify-content: center; width: 36px;
    height: 36px; border: none; background: none; color: var(--text); cursor: pointer;
    border-radius: 10px; }
  /* The drawer closes from inside itself, the way the panel icon in Jamal's
     screenshot does — with the sidebar over the page there is nothing else on
     screen to press. */
  .side-hide { display: flex; align-items: center; justify-content: center; width: 32px;
    height: 32px; border: none; background: none; color: var(--muted); cursor: pointer; }
  .mode-share { flex-direction: column; align-items: center; text-align: center; }
  .mode-qr { flex: 0 0 auto; }
}

/* The modes, as a strip above the kitchens rather than a list below them.
   Four of these and possibly fifty kitchens: as rows they sat ten scroll-lines
   down, which for a dropdown asked to offer both is not offering them. */
.ctx-chips { display: grid; grid-template-columns: 1fr 1fr; gap: 6px; margin: 0 0 4px; }
.ctx-chip { display: flex; align-items: center; gap: 8px; padding: 9px 10px; border-radius: 10px;
  border: 1px solid var(--border); background: var(--surface-2); color: var(--text);
  font-size: 13px; font-weight: 600; cursor: pointer; text-align: left; }
.ctx-chip:hover { border-color: var(--primary); }
.ctx-chip.ctx-off { opacity: .5; cursor: default; }
.ctx-chip.ctx-off:hover { border-color: var(--border); }
.ctx-chip-i { display: flex; color: var(--muted); }
.mode-settings a { color: var(--primary); font: inherit; text-decoration: underline;
  text-underline-offset: 2px; }

/* The register covers the viewport (.pos-root is fixed, inset 0) and Jamal's
   standing rule is that nothing on it may need scrolling. It happens to paint
   over the bar already, by DOM order — which is a coincidence of source order
   rather than a decision, and the sort of thing that quietly stops being true.
   Say it. */
body.pos-open .topbar, .till-site .topbar { display: none !important; }

/* ---- Stocktake: a table you can count from on a phone (v5.62.0) -----------
   Jamal: *"It should freeze the item name to the left, so on mobile and iPad,
   you can see it and enter the stock at the same time while having other info
   to the side."* Which is the whole problem with counting on a phone — the
   name scrolls away exactly when you need it. */

/* As tall as the screen leaves it. Jamal: *"The full stock list should be
   bigger too, so you can see a more full screen stock list on one screen, just
   stretch it to be the height of the screen."* The number subtracted is the
   chrome above and below it — the top bar, the page head, the toolbar and the
   footer — and it is a floor rather than a guess, so a short window still gets
   a usable list instead of two rows. */
.st-wrap { overflow: auto; max-height: calc(100dvh - 268px); min-height: 320px; }

/* `max-content` is the whole trick: a table left to fit the viewport squeezes
   its columns, and a squeezed frozen column with wrapping turned on collapses
   to one letter per line — which is what the first build of this did on a
   390px phone. Let it be as wide as it needs and scroll instead. */
.st-table { border-collapse: separate; border-spacing: 0; width: max-content; min-width: 100%; }
.st-table th, .st-table td { white-space: nowrap; }

/* Jamal, looking at it on a phone: *"There should be more visible borders
   between the column titles, and they should be more compact width for the
   columns with numbers, as at the moment you can't see much."*
   Both of those are the same complaint — three columns were taking the width of
   six, and with nothing dividing them you could not tell where one ended. So:
   a rule down every column, and numbers packed to what a number actually
   needs. */
.st-table th + th, .st-table td + td { border-left: 1px solid var(--border); }
.st-table thead th { letter-spacing: .3px; }
/* A count rarely runs past four digits, and a header like "IN SYSTEM" is
   allowed to wrap rather than to set the width of the whole column. */
.st-table th.num, .st-table td.num {
  padding-left: 8px; padding-right: 10px;
  width: 1%; max-width: 96px;
}
.st-table thead th.num { white-space: normal; line-height: 1.15; }
.st-table td.num input { width: 100%; min-width: 56px; max-width: 84px; padding: 7px 8px; }
.st-table th, .st-table td { padding-top: 9px; padding-bottom: 9px; }
@media (max-width: 640px) {
  .st-table th, .st-table td { padding-top: 8px; padding-bottom: 8px; font-size: 13.5px; }
  .st-table th.num, .st-table td.num { padding-left: 6px; padding-right: 7px; max-width: 76px; }
  .st-table td.num input { min-width: 48px; max-width: 66px; }
  /* The text columns beside the name were taking a third of a phone each, which
     is why only two numbers were on screen at a time. A supplier's name is
     worth knowing and not worth 140px — it truncates and the sort still works
     on the whole of it. */
  .st-table th:not(.num):not(.st-stick), .st-table td:not(.num):not(.st-stick) {
    max-width: 92px; overflow: hidden; text-overflow: ellipsis;
    padding-left: 8px; padding-right: 8px;
  }
}

/* The name column stays put while everything to its right scrolls under it.
   It needs its own background — a transparent sticky cell shows the row
   sliding beneath it, which reads as a rendering fault rather than a column. */
.st-table .st-stick {
  position: sticky; left: 0; z-index: 2;
  background: var(--surface);
  /* Wide enough to read a dish name, narrow enough to leave room for the box
     you are typing into — which is the point of freezing it at all. */
  min-width: 190px; max-width: 300px;
  white-space: normal; overflow-wrap: break-word;
  border-right: 1px solid var(--border);
}
@media (max-width: 520px) { .st-table .st-stick { min-width: 132px; max-width: 160px; } }

/* And the box you are typing into, frozen to the other edge. Name on the left,
   count on the right, everything else scrolling in between — which is the only
   arrangement where a phone can show you what you are counting and where to put
   the number at the same time. The left border is the whole of what separates it
   from a column sliding underneath. */
.st-table .st-stick-r {
  position: sticky; right: 0; z-index: 2;
  background: var(--surface);
  border-left: 1px solid var(--border);
  box-shadow: -6px 0 10px -8px rgba(0,0,0,.6);
}
.st-table thead .st-stick-r { z-index: 3; }
.st-table thead .st-stick { z-index: 3; }
.st-table thead th {
  position: sticky; top: 0; z-index: 1; background: var(--surface);
  border-bottom: 1px solid var(--border);
}
.st-table .st-stick .sub { display: block; font-size: 11px; color: var(--muted); }

/* Sortable headers. The arrow is the only thing that says which way it went,
   so it is part of the header rather than a colour change. */
.st-sort { cursor: pointer; user-select: none; }
.st-sort:hover { color: var(--primary); }
.st-arrow { margin-left: 5px; font-size: 10px; opacity: .8; }

/* The order pack — the basket a stocktake leaves you holding. One card per
   supplier, and the money in the head of each so a manager can decide whether
   to send it without reading the table underneath. */
.pack-saved { margin: 14px 0 0; padding: 12px 16px; border-radius: 12px;
  border: 1px solid color-mix(in srgb, var(--green, #34c759) 40%, transparent);
  background: color-mix(in srgb, var(--green, #34c759) 10%, var(--surface));
  font-weight: 600; }
.pack-sup { margin-bottom: 18px; padding: 0; overflow: hidden; }
.pack-head { display: flex; gap: 16px; align-items: flex-start;
  padding: 16px 18px; border-bottom: 1px solid var(--border); flex-wrap: wrap; }
.pack-head > div:first-child { flex: 1; min-width: 200px; }
.pack-name { font-size: 17px; margin-right: 8px; }
.pack-money { text-align: right; }
.pack-total { font-size: 20px; font-weight: 700; }
.pack-wrap { border: none; border-radius: 0; max-height: 380px; overflow: auto; }
.pack-table { width: 100%; }
/* What was counted, when it is at or under the level somebody set. The one
   number on the row that is a problem rather than a fact. */
.pack-low { color: var(--red); font-weight: 600; }
.pack-x { background: none; border: none; color: var(--muted); font-size: 18px;
  line-height: 1; cursor: pointer; padding: 2px 6px; border-radius: 6px; }
.pack-x:hover { color: var(--red); background: var(--surface-2); }
.pack-foot { display: flex; align-items: center; gap: 12px; padding: 14px 18px;
  border-top: 1px solid var(--border); flex-wrap: wrap; }
/* The supplier's copy, shown in the app. Tall enough that a whole A4 page is
   legible without the reader's own zoom, and the browser's PDF viewer supplies
   the scrolling — this is a preview of a document, not a document viewer we
   have any business writing. */
.pdf-view { width: 100%; height: min(66vh, 720px); border: 1px solid var(--border);
  border-radius: 10px; background: #fff; }
@media (max-width: 640px) { .pdf-view { height: 58vh; } }

.pack-spacer { height: 64px; }
.pack-orphan .pack-head { border-bottom-color: color-mix(in srgb, var(--amber, #ff9f0a) 40%, transparent); }
@media (max-width: 640px) {
  .pack-head, .pack-foot { padding: 13px 14px; }
  .pack-money { text-align: left; }
  .pack-foot .btn.primary { flex: 1 1 100%; }
}

/* The resume banner. Jamal asked for "a bigger lot at the top which says resume
   draft" — so it is a band across the whole width in the accent colour rather
   than a line of muted text, because the thing it has to beat is somebody
   starting the count again from zero without noticing it is there. */
/* What the shelf is worth — two figures and, when it matters, the honest note
   about what the figures do not cover. */
.st-worth { padding: 12px 16px; }
.st-worth[hidden] { display: none; }
.st-worth-row { display: flex; align-items: baseline; gap: 22px; flex-wrap: wrap; }
.st-worth-fig { display: flex; align-items: baseline; gap: 8px; }
.st-worth-fig strong { font-size: 20px; font-variant-numeric: tabular-nums; }
.st-worth-fig span { color: var(--grey, #6b7280); font-size: 13px; }
.st-worth-un { color: var(--amber, #b45309); font-size: 12.5px; }

/* The three steps: ingredients → recipes → menu. A strip, not a wizard — it
   explains the order and links out; it never traps anybody in a flow. */
.setup-steps { padding: 14px 16px; }
.setup-steps-row { display: flex; align-items: stretch; gap: 10px; }
.setup-step { display: flex; gap: 10px; flex: 1 1 0; padding: 10px 12px; border-radius: 10px;
  text-decoration: none; color: inherit; border: 1px solid var(--border, #e5e7eb); }
.setup-step.next { border-color: var(--primary, #2563eb); box-shadow: 0 0 0 1px var(--primary, #2563eb); }
.setup-step-n { flex: 0 0 auto; width: 26px; height: 26px; border-radius: 50%;
  display: flex; align-items: center; justify-content: center; font-weight: 700;
  background: var(--surface-2, #f3f4f6); font-size: 13px; }
.setup-step.done .setup-step-n { background: #2b6c46; color: #fff; }
.setup-step-body { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
.setup-step-body strong { font-size: 14px; }
.setup-step-what { font-size: 12.5px; color: var(--grey, #6b7280); line-height: 1.45; }
.setup-step-skip { font-size: 12px; color: var(--grey, #6b7280); font-style: italic; }
.setup-step-says { font-size: 12.5px; font-weight: 600; }
.setup-step-says.done { color: #2b6c46; }
.setup-step-says.started { color: var(--amber, #b45309); }
.setup-step-arrow { align-self: center; color: var(--grey, #9ca3af); flex: 0 0 auto; }
@media (max-width: 760px) {
  .setup-steps-row { flex-direction: column; }
  .setup-step-arrow { transform: rotate(90deg); align-self: flex-start; margin-left: 22px; }
}

.st-draft { display: flex; align-items: center; gap: 14px; flex-wrap: wrap;
  margin: 14px 0 0; padding: 16px 18px; border-radius: 14px;
  border: 1px solid color-mix(in srgb, var(--primary) 35%, transparent);
  background: color-mix(in srgb, var(--primary) 8%, var(--surface)); }
.st-draft[hidden] { display: none; }
.st-draft-txt { display: flex; flex-direction: column; gap: 2px; flex: 1; min-width: 180px; }
.st-draft-txt strong { font-size: 16px; }
.st-draft .btn { flex: 0 0 auto; }
@media (max-width: 520px) {
  .st-draft { padding: 14px; gap: 10px; }
  .st-draft .btn.primary { flex: 1 1 100%; }
}

/* The footer. Jamal: *"the bottom note bit should be better formatted."* It was
   a flex row of four unlike things — a label, a date box, a sentence, a text
   box and two buttons — which on a phone wrapped into a column with no
   structure at all. Three bands now: the two fields, the running count, and the
   buttons. Every field carries its own caption above it rather than beside it,
   so nothing depends on there being room to the left. */
.st-foot { margin-top: 14px; display: flex; flex-direction: column; gap: 12px; }
.st-foot-fields { display: flex; gap: 12px; flex-wrap: wrap; }
.st-field { display: flex; flex-direction: column; gap: 4px; min-width: 0; }
.st-field.grow { flex: 1 1 220px; }
.st-field-cap { font-size: 11.5px; letter-spacing: .3px; text-transform: uppercase;
  color: var(--muted); font-weight: 600; }
.st-field input { width: 100%; }
.st-foot-note { font-size: 13px; }
.st-foot-acts { display: flex; gap: 10px; flex-wrap: wrap; }
/* The one that saves is the one that carries the weight, and on a phone it gets
   its own line rather than being squeezed to three words. */
.st-foot-acts .btn.primary { margin-left: auto; }
@media (max-width: 640px) {
  .st-foot-acts { gap: 8px; }
  .st-foot-acts .btn { flex: 1 1 auto; }
  .st-foot-acts .btn.primary { flex: 1 1 100%; margin-left: 0; }
}

/* The column chooser: every row says what the column is for, because a list of
   eight nouns is a puzzle rather than a choice. */
.col-list { display: flex; flex-direction: column; gap: 2px; }
.col-row { display: flex; align-items: flex-start; gap: 10px; padding: 9px 10px;
  border-radius: 10px; cursor: pointer; }
.col-row:hover { background: var(--surface-2); }
.col-row input { margin-top: 2px; flex: 0 0 auto; }
.col-row .sub { display: block; color: var(--muted); font-size: 12px; margin-top: 1px; }

@media (max-width: 860px) {
  /* The bottom tab bar takes 64px on a phone, so the list gets what is left
     rather than the desktop allowance. */
  .st-wrap { max-height: calc(100dvh - 330px); }
  .st-foot { gap: 10px; }
}

/* Inside a dialog the same table must fit rather than scroll: the whole point
   of the Change column is that you see it while you are typing the New par
   next to it, and a column you have to scroll to is a column you do not read. */
.modal .st-table { width: 100%; min-width: 0; }
.modal .st-table .st-stick { min-width: 120px; max-width: 240px; }
.modal .st-table th, .modal .st-table td { white-space: normal; }

/* --- Recipe & cost, inside the menu item editor ---------------------------------- */
.rc-figs { display: flex; align-items: center; gap: 20px; flex-wrap: wrap;
  padding: 12px 14px; border: 1px solid var(--border); border-radius: 10px; margin-bottom: 14px; }
.rc-fig { display: flex; align-items: baseline; gap: 7px; }
.rc-fig strong { font-size: 19px; font-variant-numeric: tabular-nums; }
.rc-fig span { color: var(--grey, #6b7280); font-size: 13px; }
.rc-drop { position: absolute; left: 0; right: 0; top: 100%; z-index: 10; margin-top: 4px;
  padding: 4px; max-height: 280px; overflow-y: auto; }
.rc-opt { display: flex; justify-content: space-between; gap: 12px; width: 100%;
  padding: 9px 10px; border: none; background: none; color: inherit; text-align: left;
  border-radius: 8px; font-size: 13.5px; cursor: pointer; }
.rc-opt:hover { background: var(--surface-2, #f3f4f6); }

/* --- The menu item editor's own tab bar ------------------------------------------ */
/* Six tabs, an icon each, sentence case, and one line always — the row scrolls
   sideways before a label is allowed to wrap into a wall of words. */
/* Seven tabs wrap onto a second line rather than scrolling off the edge with the
   scrollbar hidden — the Preview tab sat past the right edge of a 1100px dialog
   and nobody knew it was there (Jamal, 2026-09-07). */
.mi-tabs { display: flex; flex-wrap: wrap; gap: 4px; border-bottom: 1px solid var(--border);
  margin-bottom: 14px; }
.mi-tabs::-webkit-scrollbar { display: none; }
.mi-tab { display: flex; align-items: center; gap: 6px; white-space: nowrap;
  background: none; border: none; color: var(--muted); padding: 10px 9px;
  font-size: 13px; font-weight: 600; cursor: pointer;
  border-bottom: 2px solid transparent; flex: 0 0 auto; }
.mi-tab svg { flex: 0 0 auto; opacity: .75; }
.mi-tab.active { color: var(--primary); border-bottom-color: var(--primary); }
.mi-tab.active svg { opacity: 1; }
.mi-tab:hover { color: var(--text); }

/* Which surface the Preview shows: pills, because they are a second, smaller
   question than the tabs above them and should not look like more tabs. */
.mi-ptabs { display: flex; gap: 6px; flex-wrap: wrap; margin-bottom: 14px; }
.mi-ptab { border: 1px solid var(--border); background: none; color: var(--muted);
  padding: 6px 13px; border-radius: 999px; font-size: 13px; font-weight: 600; cursor: pointer; }
.mi-ptab.active { background: var(--primary); border-color: var(--primary); color: #fff; }

/* --- Cup and bag labels ----------------------------------------------------------- */
.lbl-card { margin-bottom: 14px; }
.lbl-modes { display: flex; gap: 8px; margin: 12px 0; flex-wrap: wrap; }
.lbl-mode { flex: 1 1 160px; border: 1px solid var(--border); border-radius: 10px;
  padding: 10px 12px; cursor: pointer; display: block; }
.lbl-mode input { display: none; }
.lbl-mode strong { display: block; font-size: 14px; }
.lbl-mode span { font-size: 12.5px; color: var(--muted); }
.lbl-mode.on { border-color: var(--primary); box-shadow: 0 0 0 1px var(--primary); }
.lbl-fields { display: flex; flex-wrap: wrap; gap: 8px 14px; margin: 6px 0 12px; }
.lbl-field { display: flex; align-items: center; gap: 6px; font-size: 13.5px; cursor: pointer; }
.lbl-brand { margin-bottom: 4px; }
.lbl-acts { display: flex; justify-content: flex-end; }

/* The live sticker. White like the paper, black like the print, exactly as
   wide as the roll — a 29mm roll visibly cannot hold what a 62mm one can. */
.lbl-layout { display: flex; gap: 22px; align-items: flex-start; margin-top: 12px; }
.lbl-form { flex: 1 1 auto; min-width: 0; }
.lbl-preview { flex: 0 0 auto; display: flex; flex-direction: column; align-items: center;
  padding: 14px; background: var(--surface-2, #f3f4f6); border-radius: 12px; }
.lbl-sticker { background: #fff; color: #111; border-radius: 6px; padding: 12px 10px;
  box-shadow: 0 2px 10px rgba(0,0,0,.25); font-family: Arial, Helvetica, sans-serif;
  display: flex; flex-direction: column; gap: 3px; }
.lbl-s-logo { text-align: center; font-weight: 800; letter-spacing: .12em; font-size: 15px; }
.lbl-s-big { font-weight: 800; font-size: 17px; line-height: 1.2; }
.lbl-s-soft { color: #555; font-size: 11.5px; font-style: italic; }
.lbl-s-row { font-size: 11.5px; }
.lbl-s-qr { align-self: flex-end; margin: 4px 0; }
.lbl-s-qr svg { width: 62px; height: 62px; display: block; }
.lbl-prev-note { margin-top: 10px; font-size: 12px; color: var(--muted); text-align: center;
  max-width: 240px; }
.lbl-prev-off { color: var(--muted); font-size: 13px; padding: 20px 10px; }
@media (max-width: 900px) { .lbl-layout { flex-direction: column; } .lbl-preview { align-self: stretch; } }

/* The live receipt beside the Business form — same idea as the sticker:
   white like the page a guest opens, built by the same model. */
.bz-card { max-width: 1020px !important; }
.bz-layout { display: flex; gap: 24px; align-items: flex-start; }
.bz-form { flex: 1 1 auto; min-width: 0; }
.bz-preview { flex: 0 0 300px; }
.bzr-paper { background: #fff; color: #191919; border-radius: 10px; padding: 18px 16px;
  box-shadow: 0 2px 10px rgba(0,0,0,.25);
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif;
  font-size: 12.5px; line-height: 1.5; }
.bzr-title { font-weight: 800; font-size: 15px; color: #0b4a38; }
.bzr-ref { color: #6b6f76; font-size: 11px; margin-bottom: 8px; }
.bzr-pickup { background: #f4efe2; border-radius: 8px; text-align: center; padding: 7px; margin-bottom: 8px; }
.bzr-pickup span { display: block; font-size: 9.5px; letter-spacing: .08em; text-transform: uppercase; color: #6b6f76; }
.bzr-pickup b { font-size: 21px; letter-spacing: .06em; color: #0b4a38;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace; }
.bzr-row { display: flex; justify-content: space-between; gap: 12px; padding: 2px 0; }
.bzr-row.strong { font-weight: 700; }
.bzr-row.mod { padding-left: 14px; color: #6b6f76; font-size: 11.5px; }
.bzr-paper hr { border: none; border-top: 1px solid #ececec; margin: 8px 0; }
.bzr-note { margin-top: 8px; font-size: 11.5px; color: #191919; }
.bzr-foot { margin-top: 10px; padding-top: 8px; border-top: 1px solid #ececec;
  font-size: 10.5px; color: #6b6f76; }
.bzr-foot.faint { font-style: italic; }
@media (max-width: 920px) { .bz-layout { flex-direction: column; } .bz-preview { flex: 1 1 auto; width: 100%; max-width: 320px; } }

/* The delivery app cards in the item editor's Preview tab. One shell, each
   app's bar and accents doing the anchoring. */
.dlv-app { max-width: 340px; border: 1px solid; border-radius: 12px; overflow: hidden;
  background: #fff; }
.dlv-bar { color: #fff; font-weight: 800; font-size: 12px; letter-spacing: .04em;
  padding: 6px 13px; }

/* --- Staff documents (task #67) ------------------------------------------- */
/* The pill is the date turned into words. Colour says urgency at a glance:
   red for lapsed or a lapsing kind with no date, amber inside the warning
   window, quiet for fine. */
.doc-pill {
  display: inline-block; font-size: 12px; font-weight: 700;
  padding: 2px 9px; border-radius: 999px; margin-top: 3px;
  background: var(--paper-2, #f0ede6); color: var(--ink-soft, #666);
}
.doc-pill.expired, .doc-pill.dateless { background: #fde8e6; color: #b3261e; }
.doc-pill.expiring { background: #fdf3d7; color: #8a6100; }
.doc-pill.ok { background: #e6f4ea; color: #1e7b34; }
/* The quiet answer, said as clearly as the loud one — "nothing lapses" is what
   tells a manager the check ran at all. */
.doc-clear { color: var(--green); }

/* --- The owner's website figures (SEO screen) ---------------------------- */
.seo-stats { display: grid; grid-template-columns: repeat(4, 1fr); gap: 12px; margin: 14px 0 8px; }
.seo-stats .stat-card { background: var(--surface); border: 1px solid var(--border);
  border-radius: 12px; padding: 16px 18px; }
.seo-stats .stat-card b { display: block; font-size: 26px; line-height: 1.15; }
.seo-stats .stat-card span { display: block; font-size: 12.5px; color: var(--muted); margin-top: 4px; }
.seo-note { margin: 2px 2px 16px; }
.seo-h { margin: 0 0 12px; font-size: 15px; }
/* One column per day. `align-items: flex-end` so a quiet day is a short bar
   sitting on the axis rather than a bar hanging from the ceiling. */
.seo-bars { display: flex; align-items: flex-end; gap: 3px; height: 150px;
  padding: 0 2px; border-bottom: 1px solid var(--border); }
.seo-bar { flex: 1 1 0; height: 100%; display: flex; align-items: flex-end; min-width: 3px; }
.seo-fill { width: 100%; background: var(--primary); border-radius: 3px 3px 0 0;
  transition: height .18s ease; }
.seo-axis { display: flex; justify-content: space-between; font-size: 11.5px;
  color: var(--muted); margin-top: 6px; }
.seo-tbl { width: 100%; }
.seo-tbl td, .seo-tbl th { padding: 8px 6px; }
.seo-tbl .num { text-align: right; font-variant-numeric: tabular-nums; }
@media (max-width: 900px) { .seo-stats { grid-template-columns: repeat(2, 1fr); } }

/* ── Payouts ──────────────────────────────────────────────────────────────
   The list, the invoice that opens under a row, and the one-page form. Debits
   read as negative everywhere rather than as a separate column, because that
   is how the money actually behaves on the document. */
.po-bar { padding: 12px; margin-bottom: 14px; display: flex; gap: 10px; flex-wrap: wrap; align-items: flex-end; }
.po-tbl .po-row { cursor: pointer; }
.po-tbl .po-row:hover { background: var(--hover, rgba(0,0,0,.03)); }
.po-tbl td.r, .po-lines td.r, .po-lines th.r { text-align: right; }
.po-detail > td { background: var(--panel-2, rgba(0,0,0,.02)); padding: 0; }
.po-invoice { padding: 16px 18px; }
.po-inv-head { display: flex; justify-content: space-between; align-items: flex-start; gap: 12px; margin-bottom: 10px; }
.po-lines { width: 100%; margin: 6px 0 10px; }
.po-lines .po-debit td { color: var(--muted, #6b6b6b); }
.po-lines tfoot .po-vat td { border-top: 1px solid var(--line, #e6e2da); font-size: 13px; color: var(--muted, #6b6b6b); }
.po-totals { display: flex; gap: 22px; justify-content: flex-end; align-items: baseline; flex-wrap: wrap; }
.po-totals div { display: flex; gap: 8px; align-items: baseline; }
.po-totals span { color: var(--muted, #6b6b6b); font-size: 13px; }
.po-totals .po-pay b { font-size: 20px; }
.po-acts { margin-top: 12px; }
.pill.po-paid { background: #1f7a4d; color: #fff; }
.pill.po-scheduled { background: #2f5ea8; color: #fff; }
.pill.po-draft { background: var(--line, #e6e2da); }
.pill.po-failed, .pill.po-cancelled { background: #9a3324; color: #fff; }
.po-form { padding: 16px 18px; margin-bottom: 16px; }
.po-h { margin: 0 0 4px; font-size: 16px; }
.po-h4 { margin: 16px 0 8px; font-size: 14px; display: flex; gap: 10px; align-items: center; }
.po-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(160px, 100%), 1fr)); gap: 10px; }
.po-charge { display: grid; grid-template-columns: 1.6fr 1fr 0.9fr 1fr 0.7fr auto; gap: 8px; margin-bottom: 8px; align-items: center; }
.po-suggest { border-left: 3px solid #2f5ea8; padding: 8px 12px; margin: 12px 0; background: var(--panel-2, rgba(0,0,0,.02)); }
.po-suggest p { margin: 4px 0 0; }
.po-suggest.po-warn { border-left-color: #9a3324; }
.po-part { border: 1px solid var(--line, #e6e2da); border-radius: 10px; padding: 10px 12px; margin-bottom: 10px; }
.po-part-head { display: flex; justify-content: space-between; font-size: 13px; color: var(--muted, #6b6b6b); }
.po-part-head b { color: var(--ink, #1c1c1c); font-size: 15px; }
.po-note { margin-top: 14px; }
@media (max-width: 720px) {
  .po-charge { grid-template-columns: 1fr 1fr; }
  .po-totals { justify-content: flex-start; }
}

/* ---- Branding: name, logo and palette, edited by the platform owner ---- */
.br-card { padding: 16px 18px; margin-bottom: 14px; }
.br-h { margin: 0 0 6px; font-size: 16px; display: flex; gap: 10px; align-items: center; }
.br-name-row { display: flex; gap: 14px; align-items: center; flex-wrap: wrap; }
.br-name-row .f-input { flex: 0 1 260px; font-size: 17px; }
.br-name-preview { color: var(--muted); font-size: 14px; }
.br-name-preview b { color: var(--text); }
.br-logo-row { display: flex; gap: 18px; align-items: flex-start; flex-wrap: wrap; }
.br-logo-box { display: flex; align-items: center; justify-content: center;
  min-width: 110px; min-height: 76px; padding: 12px 16px;
  background: var(--bg); border: 1px solid var(--border); border-radius: var(--radius); }
.br-logo-actions { flex: 1 1 260px; display: flex; flex-direction: column; gap: 8px; align-items: flex-start; }
.br-mode { display: flex; gap: 8px; align-items: center; font-size: 14px; white-space: nowrap; }
.br-mode input { flex: 0 0 auto; }
.br-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(190px, 100%), 1fr)); gap: 12px; margin-top: 4px; }
.br-swatch-row { display: flex; gap: 8px; align-items: center; }
.br-swatch-row input[type=color] { width: 42px; height: 34px; padding: 2px; border: 1px solid var(--border);
  border-radius: 8px; background: var(--surface); cursor: pointer; }
.br-hex { flex: 1 1 auto; font-family: ui-monospace, SFMono-Regular, Menlo, monospace; }
.br-warn { border-left: 3px solid var(--amber); padding: 8px 12px; margin-top: 12px; background: var(--bg); }
.br-warn p { margin: 4px 0 0; }
.br-actions { margin-top: 4px; align-items: center; gap: 10px; }

/* ---- Page insights: which screens get opened ---- */
.pu-tbl td { vertical-align: middle; }
.pu-tbl .pu-zero td { color: var(--muted); }
.pu-bar-cell { display: flex; align-items: center; gap: 10px; min-width: 190px; }
.pu-bar { flex: 1 1 auto; height: 8px; border-radius: 4px; background: var(--border); overflow: hidden; }
.pu-fill { display: block; height: 100%; background: var(--primary); }
.pu-n { min-width: 46px; text-align: right; font-variant-numeric: tabular-nums; }
.pu-chips { display: flex; flex-wrap: wrap; gap: 8px; }
.pu-proposed { margin: 6px 0 0; padding-left: 22px; columns: 2; }
.pu-proposed li { margin-bottom: 4px; }
.pu-note { margin-top: 14px; }
@media (max-width: 720px) { .pu-proposed { columns: 1; } }

/* ---- Till tiles: the count badge and the stock chip ---- */
.pos-item { position: relative; }
.pos-tile-n { position: absolute; top: 4px; right: 6px; min-width: 20px; height: 20px;
  border-radius: 10px; background: rgba(255,255,255,.92); color: #14171d;
  font-size: 12px; font-weight: 700; line-height: 20px; text-align: center; padding: 0 5px; }
.pos-tile-stock { position: absolute; bottom: 4px; right: 6px; font-style: normal;
  font-size: 11px; font-weight: 700; padding: 1px 6px; border-radius: 6px; }
.pos-stock-low { background: #f5a623; color: #21160a; }
/* A £0 tile (Jamal, 2026-09-07): sellable, and honest about it. */
.pos-tile-free { background: #2b3140; color: #cfd6e4; }
/* Live Orders and Past Orders at natural size: the stage is the window, no
   letterbox, and the screen's own list scrolls (Jamal, 2026-09-07). */
.pos-root.pos-natural { align-items: stretch; justify-content: stretch; }
.pos-root.pos-natural .pos-stage { transform: none; font-size: 15px; }
.pos-stock-out { background: #ff5c5c; color: #2a0c0c; }
/* Marked, never disabled: the person at the counter can see the shelf.
   Washed out as well as outlined — Jamal's photo showed a full-colour tile
   over an item the availability sheet said was off, and an outline alone
   does not read as "gone" at till speed. The wash is a veil rather than a
   filter: a filter would grey the red ring and the "Out of stock" label
   along with the photo it is dimming. */
.pos-item-out { outline: 2px solid #ff5c5c; outline-offset: -2px; position: relative; }
.pos-item-out::after { content: ''; position: absolute; inset: 0; border-radius: inherit;
  background: rgba(13,15,19,.55); pointer-events: none; }
.pos-item-out > * { position: relative; z-index: 1; }


/* Receipt printouts (v5.114) — the two documents and the printers under them. */
.rp-grid { display: grid; gap: 16px; grid-template-columns: repeat(auto-fit, minmax(min(340px, 100%), 1fr));
  margin-bottom: 22px; }
.rp-doc-head { margin-bottom: 10px; }
.rp-doc-head .hint { margin: 3px 0 0; }
/* A grid, not space-between. With flex the control sat wherever the label
   happened to end, so on a narrow card the tick boxes marched across the page
   at a different x on every row — which is exactly what Jamal photographed.
   One fixed control column means they line up whatever the label does. */
.rp-row { display: grid; grid-template-columns: 1fr 190px; align-items: center; gap: 14px;
  padding: 9px 0; border-bottom: 1px solid var(--line, #23262e); }
.rp-row:last-child { border-bottom: 0; }
.rp-row > span:first-child { display: flex; flex-direction: column; min-width: 0; }
.rp-ctl { display: flex; align-items: center; justify-content: flex-end; gap: 8px;
  min-width: 0; }
/* Not `width: 100%` — that is 100% of the control cell, and anything sitting
   beside it in the same flex row then pushes the input past the edge. */
.rp-ctl input[type=text], .rp-ctl input[type=url] { flex: 1 1 auto; min-width: 0; width: auto; }
/* The header and footer are textareas (v5.540.0) — both print line by line and
   a one-line input could never produce the second line. They take the same
   width as the text inputs beside them, and the row aligns to the top so a
   three-line footer does not push its own label off centre. */
.rp-ctl textarea { flex: 1 1 auto; min-width: 0; width: auto; resize: vertical;
  font: inherit; line-height: 1.35; }
.rp-row.rp-text { align-items: flex-start; }
.rp-ctl input[type=checkbox] { flex: 0 0 auto; margin: 0; }
.rp-row b { font-weight: 600; font-size: 14px; }
.rp-row em { font-style: normal; font-size: 12px; color: var(--muted); margin-top: 2px; }
.rp-row.locked { opacity: .75; }
.rp-lock { font-size: 11px; text-transform: uppercase; letter-spacing: .06em; color: var(--muted); }
.rp-count { border-bottom-width: 2px; }

/* Nought first, because nought is the answer people come here to set. */
.rp-copies { display: flex; gap: 4px; flex: 0 0 auto; }
.rp-copies button { min-width: 32px; padding: 5px 0; border-radius: 8px;
  border: 1px solid var(--line, #2a2e37); background: transparent; color: var(--muted);
  font-weight: 600; font-size: 13px; cursor: pointer; }
.rp-copies button.on { background: var(--primary, #4f8cff); border-color: var(--primary, #4f8cff);
  color: #fff; }
/* The arrangement picker reuses the copies group, but its buttons hold words
   rather than one digit — so they take their own width and wrap on a phone. */
.rp-style .rp-copies { flex-wrap: wrap; justify-content: flex-end; }
.rp-style .rp-copies button { min-width: 0; padding: 5px 12px; }

.rp-h { margin: 26px 0 2px; }
.rp-sub { margin: 0 0 12px; }
.rp-prn-head { display: flex; align-items: center; gap: 12px; margin-bottom: 8px; }
.rp-prn-head .hint { margin: 2px 0 0; }
.rp-off { margin-left: auto; font-size: 12px; color: var(--muted); }
.prn-badge { display: flex; align-items: center; justify-content: center; flex: 0 0 auto;
  width: 46px; height: 46px; border-radius: 12px; border: 1px solid var(--line, #2a2e37);
  color: var(--text, #fff); }
/* A model we do not recognise is drawn faintly, so it reads as "not set yet"
   rather than as a printer we are confident about. */
.prn-badge.unknown { color: var(--muted); border-style: dashed; }
.prn-badge-img { flex: 0 0 auto; width: 46px; height: 46px; border-radius: 12px;
  object-fit: cover; border: 1px solid var(--line, #2a2e37); }
.rp-ident { display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  padding: 8px 0 12px; border-bottom: 1px solid var(--line, #23262e); margin-bottom: 4px; }
.rp-model { display: flex; align-items: center; gap: 8px; font-size: 13px; color: var(--muted); }
.rp-model select { min-width: 190px; }
.rp-warn ul { margin: 8px 0 0; padding-left: 20px; }
.rp-warn li { margin-bottom: 4px; }
.rp-actions { margin-top: 20px; }
/* The paper. Monospaced, because that is what a 42-column roll is, and the
   big lines doubled so the preview shows the shape rather than only the words. */
.rp-preview { margin-top: 16px; border-top: 1px solid var(--line, #23262e); padding-top: 12px; }
.rp-preview h4 { margin: 0 0 8px; font-size: 13px; color: var(--muted); font-weight: 600;
  text-transform: uppercase; letter-spacing: .06em; }
.rp-paper { background: #fff; color: #111; border-radius: 8px; padding: 14px 12px;
  font: 12px/1.45 ui-monospace, SFMono-Regular, Menlo, monospace; white-space: pre;
  overflow-x: auto; margin: 0; max-height: 380px; overflow-y: auto; }
.rp-paper span { display: block; }
.rp-paper span.big { font-size: 15px; font-weight: 700; letter-spacing: -.01em; }
.rp-paper span.qr { text-align: center; color: #555; padding: 6px 0; }
/* The logo band on the paper preview: its own row, aligned where the document
   puts it. A thermal head cannot flow text beside an image, so this is a row
   and not a float — the preview has to be honest about that. */
.rp-paper span.lg { padding: 2px 0 6px; }
.rp-paper span.lg.right { text-align: right; }
.rp-paper span.lg.center { text-align: center; }
.rp-paper span.lg img { display: inline-block; max-height: 34px; max-width: 45%;
  object-fit: contain; }
.rp-paper span.lg i { font-style: normal; font-size: 11px; color: #888;
  border: 1px dashed #bbb; border-radius: 4px; padding: 3px 8px; }
.rp-paper span.rl { border-top: 1px dashed #bbb; margin: 5px 0; height: 0; }
.rp-prn-name { flex: 1; min-width: 0; }
.rp-prn-name input { width: 100%; max-width: 320px; font-weight: 600; }
@media (max-width: 560px) {
  /* One row, not three. A tick under its own label wastes a third of a phone
     screen per switch, and there are twelve of them — the tick belongs on the
     right of the thing it switches. The copies picker is the exception: six
     buttons need a line of their own, so it wraps onto one. */
  .rp-row { display: flex; flex-wrap: wrap; align-items: center;
    justify-content: space-between; gap: 8px 12px; }
  .rp-row > span:first-child { flex: 1 1 180px; min-width: 0; }
  .rp-ctl { flex: 0 0 auto; justify-content: flex-end; }
  .rp-ctl.wide { flex: 1 1 100%; justify-content: flex-start; flex-wrap: wrap; }
}

/* Prep areas (v5.116) — the list on the left, the dish picker on the right. */
.pa-wrap { display: grid; grid-template-columns: 260px 1fr; gap: 18px; align-items: start; }
.pa-list { display: grid; gap: 10px; }
/* A button does not inherit the page's text colour, so the area's own name was
   rendering in the browser's default button grey on a near-black card — Jamal,
   of this screen on a phone: "Cant read this text". */
.pa-row { display: flex; flex-direction: column; gap: 3px; text-align: left; cursor: pointer;
  color: var(--text, #e8eaee);
  border: 1px solid var(--line, #23262e); background: transparent; }
.pa-row.on { border-color: var(--primary, #4f8cff); }
.pa-name { font-weight: 600; }
.pa-pick { border: 1px solid var(--line, #23262e); border-radius: 12px; padding: 14px 16px;
  max-height: 62vh; overflow-y: auto; }
.pa-head { display: flex; align-items: center; gap: 12px; flex-wrap: wrap;
  padding-bottom: 12px; border-bottom: 1px solid var(--line, #23262e); margin-bottom: 12px; }
.pa-head input[type=text] { flex: 1; min-width: 160px; font-weight: 600; }
.pa-catch { display: flex; align-items: center; gap: 7px; font-size: 13px; color: var(--muted); }
.pa-sec { margin-bottom: 16px; }
.pa-sec-head { display: flex; align-items: center; gap: 9px; padding: 6px 0; cursor: pointer; }
.pa-sec-head .hint { margin-left: auto; }
/* Two columns of dishes: a menu is long, and a picker that needs scrolling for
   forty items is a picker nobody finishes. */
.pa-items { display: grid; grid-template-columns: repeat(auto-fill, minmax(min(230px, 100%), 1fr));
  gap: 2px 14px; padding-left: 22px; }
.pa-item { display: flex; align-items: center; gap: 8px; padding: 4px 0; font-size: 13px;
  color: var(--muted); cursor: pointer; }
.pa-item.on { color: var(--text, #fff); }
.pa-item em { font-style: normal; font-size: 11px; opacity: .6; margin-left: auto; }
.pa-warn { margin-bottom: 16px; }
.pa-warn p { margin: 6px 0 0; }
@media (max-width: 780px) {
  .pa-wrap { grid-template-columns: 1fr; }
  /* The picker's header is a name, a switch and a Remove button. Side by side
     on a phone they wrapped into a name box, a floating tick and three words
     stacked one per line. Two rows instead. */
  .pa-head { display: grid; grid-template-columns: 1fr auto; gap: 10px 12px; }
  .pa-head input[type=text] { grid-column: 1 / -1; min-width: 0; }
  .pa-catch { white-space: nowrap; }
  .pa-items { grid-template-columns: 1fr; }
  .pa-pick { padding: 12px; }
}

/* The platform badge and the order number on a kitchen ticket (v5.117).
   Jamal: "it should show their exact logo, so it's easy to see which is which"
   and "should clearly identify which is the order number". */
/* (v5.135.0) Jamal: *"make it look a bit nicer, because it's not aligned."*
   He was right, and the cause was structural rather than a nudge.

   This was `align-items: baseline` over a row whose first child is a **two-line
   stack** — a small "ORDER" label above the big reference. Baseline alignment
   takes the *first* line's baseline, so the amber chip, the clock and the ⋯ all
   lined up with the word ORDER rather than with the number underneath it, and
   floated visibly high against it.

   A grid instead, so the alignment is by construction and cannot drift: ORDER
   gets a row of its own, and the reference, the chip and the clock share the row
   below — which makes them level with each other because they are literally in
   the same row. The ⋯ spans both and centres over the whole bar, which is what
   a control at the edge should do. */
.kt-head {
  display: grid;
  grid-template-columns: auto auto 1fr auto auto;
  grid-template-rows: auto auto;
  align-items: center;
  column-gap: 10px;
}
/* `display: contents` rather than restructuring the markup: the two children
   join the parent grid directly, and every existing selector — including the
   `.kt-ref b` two audits look for — still matches. */
.kt-ref { display: contents; }
/* The word above it, small — a bare number on a busy pass is read as a table, a
   cover count or a pickup number depending on who is looking. It used to say
   "Order" over every card whatever the number was; it now says which of those
   four it actually is, because callnum.callFor() knows. That comment was
   describing the ambiguity and the label was not resolving it. */
.kt-ref em { grid-area: 1 / 1; align-self: end;
  font-style: normal; font-size: 10px; letter-spacing: .1em; line-height: 1.15;
  text-transform: uppercase; opacity: .55; }
.kt-ref b { grid-area: 2 / 1; align-self: center;
  font-size: 19px; line-height: 1.05; letter-spacing: -.01em; }
/* A table can be called "Window 3" rather than "12", and a Deliveroo code can
   be longer than the R-16 this replaced. Shrink rather than clip: a truncated
   table number sends food to the wrong table, which is the one outcome worse
   than a slightly smaller one. */
.kt-call b { min-width: 0; overflow-wrap: anywhere; }
.kt-call.table b, .kt-call.code b { font-size: 17px; }
.kt-pick { grid-area: 2 / 2; }
.kt-clock { grid-area: 2 / 4; }
.kt-more { grid-area: 1 / 5 / 3 / 6; }
.kt-app { display: flex; align-items: center; justify-content: center; border-radius: 6px;
  padding: 5px 10px; margin: 6px 0 2px; font-weight: 800; font-size: 13px;
  letter-spacing: .06em; text-transform: uppercase; }
/* Their file, never scaled below the minimum their own guidelines set, and
   never recoloured or stretched. A logo band is white — the one background all
   three policies permit, and the only one that suits a teal mark, a
   near-black-and-green one and an orange one without altering any of them. */
.kt-app.logo { padding: 6px 12px; }
.kt-app img { display: block; max-width: 100%; height: 20px; width: auto; object-fit: contain; }
/* On a phone chip the mark sits in the header beside the order number, because
   a chip has no room for a band of its own. Height-constrained rather than
   width-constrained so the aspect ratio is never touched. */
/* On white here too, and for the same reason as the band: the header is tone-
   coloured (green, amber, red as a ticket ages) and a near-black Uber Eats
   wordmark on green is unreadable — as well as putting their mark on a
   background none of the three policies name. */
.kt-app-mark { height: 13px; width: auto; max-width: 74px; object-fit: contain;
  display: block; flex: 0 0 auto;
  background: #fff; padding: 3px 6px; border-radius: 4px; box-sizing: content-box; }

/* The tools that did not fit on the phone's kitchen bar. */
/* **Two columns was a phone answer applied to every screen.** Jamal, on an iPad
   in Safari with six tools folded in: *"this menu needs scroll"* — and it did
   scroll, which is the interesting part. Measured at his page height, the body
   was `overflow-y: auto` with `scrollHeight` 276 against `clientHeight` 169, so
   it could be scrolled; but the second row was cut **7px** short, and a row
   sliced by seven pixels reads as a rendering fault rather than as an
   invitation. He could not tell there was anything below it.
   The fix is upstream of the scrolling. A tablet in landscape is *wide and
   short* — 1000×300 of page once Safari has taken its chrome — and six tools in
   two columns is three rows in a window with room for one. `auto-fit` spends
   the axis that is actually free: six across on his iPad, three on a small
   tablet, two on a phone, and no scrolling at all until the screen is genuinely
   tiny. The standing rule is that nothing on the POS should need scrolling, so
   the first move is always to stop needing it. */
.ksheet-tools {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(148px, 1fr)); gap: 10px;
}
.ksheet-tool {
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 7px; padding: 16px 10px; background: #1a1d24; border: 1px solid #2a2e38;
  border-radius: 12px; color: #e8eaee; font-size: 13px; font-weight: 600; cursor: pointer;
}
/* And where it still has to scroll, it has to *look* scrollable — the whole
   complaint was not being able to tell. A visible track costs nothing on a
   sheet and answers the question without a tap. */
.modal.pos-fit .modal-body { scrollbar-width: thin; scrollbar-color: #3a4150 transparent; }
.modal.pos-fit .modal-body::-webkit-scrollbar { width: 10px; }
.modal.pos-fit .modal-body::-webkit-scrollbar-thumb {
  background: #3a4150; border-radius: 999px; border: 3px solid transparent;
  background-clip: content-box;
}
/* Squeeze the tiles before anything gets cut: a shorter tool still reads. */
@media (max-height: 460px) {
  .ksheet-tool { padding: 10px 8px; gap: 4px; font-size: 12.5px; }
}
.ksheet-tool:hover { border-color: var(--primary); }

/* ---- Nothing reaches past the edge of a phone -----------------------------
   Jamal, on the third screen in a row that scrolled sideways under his thumb:
   "Same here, theres an unnecessary horizontal scrolls". These are the two
   elements that do it without being asked: a <select>, which sizes itself to
   its longest option no matter how narrow its container, and a <input>, which
   carries a default size in characters. Both belong to the browser rather than
   to any screen, so both are fixed once, here. */
select, input:not([type=checkbox]):not([type=radio]) { max-width: 100%; }

@media (max-width: 560px) {
  /* A month of availability on a phone: the day and its colour. The word under
     each number is what made the grid wider than the screen, and the legend
     beneath the calendar already says what every colour means. */
  .av-state { display: none; }
  .av-cell { aspect-ratio: 1 / 1; }
}

/* What the till has to talk to. Grey rather than red: no reader paired is the
   normal, designed state of this product — card is taken on a terminal beside
   the till — not a fault to alarm somebody about mid-sale. */
/* The recording state the sheet holds through — a spinner, the words, and
   the live counter with its "usually about Xs" estimate (2026-08-29). */
.pos-term-wait { display: flex; align-items: center; gap: 12px; padding: 14px 4px 4px; }
.pos-term-wait strong { font-size: 15px; }
.pos-term-spin { width: 22px; height: 22px; flex: 0 0 22px; border-radius: 50%;
  border: 3px solid rgba(61,190,122,.25); border-top-color: #3dbe7a;
  animation: pos-term-spin .8s linear infinite; }
@keyframes pos-term-spin { to { transform: rotate(360deg); } }
.pos-term-count { margin: 3px 0 0; font-size: 12.5px; color: var(--muted);
  font-variant-numeric: tabular-nums; }
button .pos-term-count { display: inline; margin: 0; color: inherit; opacity: .85; }
.pos-term-reader { border: 1px solid #2a2e38; border-radius: 12px; padding: 12px 14px;
  margin-top: 14px; background: #14161c; }
.pos-term-reader strong { display: block; font-size: 14px; color: #e8eaee; margin-bottom: 5px; }
.pos-term-reader p { margin: 0; font-size: 13px; line-height: 1.5; color: #8b93a1; }
.pos-term-reader em { display: block; font-style: normal; font-size: 12px;
  color: #6c7482; margin-top: 8px; }
.pos-term-reader.on { border-color: rgba(62,201,138,.4); }
.pos-term-reader.on strong { color: #3ec98a; }

/* The transaction list scrolls inside its own column. Without this the rows
   div takes its natural height — fifteen thousand pixels of it on a busy
   venue — and runs off the bottom of the screen under the tab bar, with
   nothing to scroll. Jamal: "overlap at bottom and the scroll doesnt work on
   this list." The column is already a flex column with min-height: 0; the
   rows just never claimed the space or the overflow. */
.pos-rcpt-list #tx-rows { flex: 1 1 0; min-height: 0; overflow-y: auto; --pos-sc-bg: #0c0e12; }

/* ---- The item flow on a tablet or a desktop -------------------------------
   Jamal's reference is his own screen at desk width: the item and its quantity
   down the left, and one question filling the right. On a phone it stays a
   single column — there is no room for two — but above 740 the questions get a
   panel of their own and the tiles stop being stretched to the full height of
   it, which made two options look like two doors. */
@media (min-width: 740px) {
  .posm-flow {
    display: grid;
    grid-template-columns: 340px 1fr;
    grid-template-rows: auto auto 1fr auto;
    column-gap: 16px;
    padding-right: 16px;
  }
  .posm-flow .posm-top { grid-column: 1 / -1; grid-row: 1; }
  .posm-flow .posm-flowsteps { grid-column: 1; grid-row: 2; flex-direction: column;
    overflow-x: visible; overflow-y: auto; }
  .posm-flow .posm-flowsteps button { text-align: left; }
  .posm-flow .posm-flownote { grid-column: 1; grid-row: 3; align-self: end; }
  .posm-flow .posm-flowfoot { grid-column: 1; grid-row: 4; }
  /* `align-self: start` or the strip stretches to whatever the question list
     on the left happens to be tall — a 44px caption in a 100px box. */
  .posm-flow .posm-flowhead { grid-column: 2; grid-row: 2; border-radius: 12px 12px 0 0;
    align-self: start; }
  .posm-flow .posm-flowgrid {
    grid-column: 2; grid-row: 3 / span 2;
    /* Content-sized rows, aligned to the top. `1fr` rows in a panel this tall
       gave two options a tile each the height of the screen. */
    grid-template-rows: none;
    grid-auto-rows: minmax(92px, 132px);
    align-content: start;
    overflow-y: auto;
    padding: 12px 0 14px;
  }
}

/* ---- Splitting a bill ------------------------------------------------------
   Jamal: *"just need a better split payments buttone?"* It was a bare blue
   text link sitting above the tenders, which reads as a footnote rather than
   something you press — and it is the entry to the one flow a group of six
   needs. A real button, the width of the column, saying what it does. */
.pos-splitbtn {
  display: flex; align-items: center; gap: 12px; width: 100%;
  background: rgba(79,140,255,.10); border: 1px solid rgba(79,140,255,.45);
  border-radius: var(--pos-r); padding: 13px 15px; margin-bottom: 14px;
  color: #cfdcff; font-family: inherit; text-align: left; cursor: pointer;
}
.pos-splitbtn:hover { background: rgba(79,140,255,.18); border-color: #4f8cff; }
.pos-splitbtn svg { flex: 0 0 auto; }
.pos-splitbtn span { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.pos-splitbtn strong { font-size: 15px; font-weight: 700; color: #e8eaee; }
.pos-splitbtn em { font-style: normal; font-size: 12.5px; color: #8b93a1; }

/* While a split is running the control stops being an entry point and becomes
   the progress: which payment this is, and what is still to collect after it —
   the figure a counter is actually holding in their head. */
.pos-splitrun {
  background: #14161c; border: 1px solid #2a2e38; border-radius: var(--pos-r);
  padding: 12px 14px; margin-bottom: 14px;
}
.pos-splitrun-top { display: flex; align-items: center; justify-content: space-between; gap: 10px; }
.pos-splitrun-top strong { font-size: 15px; color: #e8eaee; }
.pos-splitrun-x {
  background: none; border: none; color: #8b93a1; font-family: inherit;
  font-size: 12.5px; font-weight: 600; cursor: pointer; padding: 2px 0;
}
.pos-splitrun-x:hover { color: #ff6b6b; }
.pos-splitrun-x.muted { color: #565d6b; cursor: default; }
.pos-splitrun-x.muted:hover { color: #565d6b; }
.pos-splitrun-dots { display: flex; gap: 5px; margin: 9px 0 8px; }
.pos-splitrun-dots i { flex: 1; height: 4px; border-radius: 2px; background: #2a2e38; }
.pos-splitrun-dots i.done { background: #3ec98a; }
.pos-splitrun-dots i.now { background: #4f8cff; }
.pos-splitrun-sum { display: flex; justify-content: space-between; gap: 10px;
  font-size: 13px; color: #8b93a1; }
.pos-splitrun-sum b { color: #e8eaee; font-weight: 700; }
.posm-paylist .pos-splitbtn, .posm-paylist .pos-splitrun { margin: 0 0 12px; }

/* The kitchen chooser, for an account that can see more than one. A Money
   screen that silently picks one of seven is a Money screen showing somebody
   else's figure. */
.mn-pick { display: flex; align-items: center; gap: 14px; width: 100%; text-align: left;
  padding: 14px 4px; background: none; border: 0; border-top: 1px solid var(--border);
  color: var(--text); font: inherit; cursor: pointer; }
.mn-pick:first-of-type { border-top: 0; }
.mn-pick:hover { background: var(--surface-2); }
.mn-pick-n { flex: 1 1 auto; font-weight: 600; }
.mn-pick-c { color: var(--muted); }

/* A phone is 414px wide and this table has five columns. It scrolls inside its
   own box rather than pushing the page sideways — the fault #160 was raised
   for, and one worth not shipping twice. */
.mn-scroll { overflow-x: auto; -webkit-overflow-scrolling: touch; margin-top: 14px; }
.mn-scroll .mn-tbl { margin-top: 0; min-width: 520px; }

/* One payout opened up (#202). A tappable row keeps the same geometry as a
   static one — only a chevron and a hover appear — so the two read as one list
   rather than two designs. */
.mn-row-go { width: 100%; text-align: left; background: none; border-radius: 8px;
  color: var(--text); font: inherit; cursor: pointer; }
.mn-row-go:hover { background: var(--surface-2); }
.mn-go { color: var(--muted); display: inline-flex; flex: 0 0 auto; }
.mn-row-off { opacity: .55; }
.mn-row-off .mn-row-a { text-decoration: line-through; }
.mn-line { display: flex; align-items: baseline; justify-content: space-between; gap: 18px;
  padding: 13px 0; border-top: 1px solid var(--border); }
.mn-line:first-of-type { border-top: 0; }
.mn-line > span:first-child { color: var(--muted); font-size: 14px; }
.mn-line > span:last-child { text-align: right; font-size: 14.5px; }
.mn-line-b { font-weight: 700; font-size: 16px; }
.mn-acct { display: block; font-style: normal; color: var(--muted); font-size: 12.5px;
  margin-top: 2px; }
/* Square puts the transfer ID above the toggle, centred and quiet. It is the
   thing somebody reads down the phone to a bank, so it gets to be selectable
   and monospaced rather than pretty. */
.mn-ref { text-align: center; color: var(--muted); font-size: 13px; }
.mn-ref-v { text-align: center; font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 14px; letter-spacing: .02em; margin: 4px 0 16px; user-select: all;
  overflow-wrap: anywhere; }
.mn-toggle { justify-content: center; margin-bottom: 12px; }

/* Transfer settings (#203). The gear sits in the balance card's corner, where
   Square puts it on the Money header. */
.mn-balance { position: relative; }
.mn-gear { position: absolute; top: 16px; right: 16px; width: 38px; height: 38px;
  display: inline-flex; align-items: center; justify-content: center; border-radius: 999px;
  background: var(--surface-2); border: 1px solid var(--border); color: var(--muted);
  cursor: pointer; }
.mn-gear:hover { color: var(--text); }
/* Square's schedule choice is two rows with a radio on the right, not a select.
   A select hides the option you did not pick, and the whole point of these two
   is that a vendor reads both before choosing. */
.mn-radio { display: flex; align-items: center; gap: 14px; width: 100%; text-align: left;
  padding: 15px 4px; background: none; border: 0; border-top: 1px solid var(--border);
  color: var(--text); font: inherit; cursor: pointer; }
.mn-radio:first-of-type { border-top: 0; }
.mn-radio:disabled { cursor: default; opacity: .7; }
.mn-dot { width: 22px; height: 22px; border-radius: 999px; border: 2px solid var(--border);
  flex: 0 0 auto; position: relative; }
.mn-dot.on { border-color: var(--primary); background: var(--primary); }
.mn-dot.on::after { content: ''; position: absolute; inset: 5px; border-radius: 999px;
  background: #fff; }
.mn-right { flex: 0 0 auto; text-align: right; }

/* Square's Select Time sheet: one row an hour, scrolling inside the dialog so
   the dialog itself stays where it is on a phone. */
.mn-slots { max-height: 52vh; overflow-y: auto; -webkit-overflow-scrolling: touch;
  margin: 0 -4px; }
.mn-slot { display: flex; align-items: center; justify-content: space-between; gap: 14px;
  width: 100%; padding: 13px 8px; background: none; border: 0;
  border-top: 1px solid var(--border); color: var(--text); font: inherit; font-size: 15px;
  text-align: left; cursor: pointer; }
.mn-slot:first-child { border-top: 0; }
.mn-slot:hover { background: var(--surface-2); }
.mn-slot.on { font-weight: 700; }

/* A suggestion's second line is a sentence, not a subtitle: it wraps rather
   than being clipped to one line like the payout rows above it. */
.mn-row-t em { white-space: normal; }
.mn-row-go .mn-row-t em { max-width: 60ch; }

/* --- The forecast screen, on Supy's shape (v5.179.0) ------------------------
   Four tiles, a chart of what was said against what happened, the daily table,
   and one day opened up: the adjust card, the ring, and the dishes. */

.fc-tiles { display: grid; gap: 10px; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); }
.fc-tile { display: flex; flex-direction: column; gap: 2px; padding: 12px 14px;
  border: 1px solid var(--line); border-radius: 12px; background: var(--surface-2, transparent); }
.fc-tile b { font-size: 24px; font-weight: 650; font-variant-numeric: tabular-nums; line-height: 1.15; }
.fc-tile.good b { color: var(--green, #1f9d61); }
.fc-tile.warn b { color: var(--amber, #d9962a); }
.fc-tl { font-size: 11px; letter-spacing: .05em; text-transform: uppercase; color: var(--muted); }
.fc-tn { font-size: 12px; color: var(--muted); }

/* The chart. Sized in a viewBox so it scales with the card rather than
   overflowing it on a phone, and scrolls rather than squashing below 520px. */
.fc-chartwrap { margin-top: 12px; overflow-x: auto; }
.fc-chart { display: block; width: 100%; min-width: 520px; height: auto; }
.fc-grid { stroke: var(--line); stroke-width: 1; }
.fc-ax { fill: var(--muted); font-size: 10px; }
.fc-ax.now { fill: var(--muted); font-size: 9px; letter-spacing: .08em; text-transform: uppercase; }
.fc-today { stroke: var(--muted); stroke-width: 1; stroke-dasharray: 3 3; }
.fc-l-act { fill: none; stroke: #2f6fed; stroke-width: 2.5; stroke-linejoin: round; stroke-linecap: round; }
.fc-l-fore { fill: none; stroke: var(--amber, #d9962a); stroke-width: 2.5; stroke-linejoin: round; stroke-linecap: round; }
.fc-l-adj { fill: none; stroke: var(--green, #1f9d61); stroke-width: 2.5; stroke-linejoin: round; stroke-linecap: round; }
.fc-key { display: flex; flex-wrap: wrap; gap: 14px; margin-top: 8px; font-size: 12px; color: var(--muted); }
.fc-key span { display: inline-flex; align-items: center; gap: 6px; }
.fc-sw { width: 14px; height: 3px; border-radius: 2px; display: inline-block; }
.fc-sw.act { background: #2f6fed; }
.fc-sw.fore { background: var(--amber, #d9962a); }
.fc-sw.adj { background: var(--green, #1f9d61); }

/* The daily table. **Its own wrapper, not .dwiz-prev** — that one caps at
   320px because it is a preview of something, and under that cap a fourteen-day
   forecast shows eight days and hides the rest behind an inner scrollbar. */
.fc-wrap { border: 1px solid var(--border); border-radius: 10px; overflow-x: auto; margin-top: 10px; }
.fc-wrap table { width: 100%; border-collapse: collapse; font-size: 13px; }
.fc-wrap th { background: var(--surface-2); text-align: left; font-weight: 600;
  color: var(--muted); padding: 8px 10px; font-size: 11px; letter-spacing: .04em;
  text-transform: uppercase; white-space: nowrap; }
/* A right-aligned column under a left-aligned heading reads as a fault. */
.fc-wrap th.num { text-align: right; }
.fc-wrap td { padding: 9px 10px; border-top: 1px solid var(--border); }
.fc-wrap td.num { text-align: right; font-variant-numeric: tabular-nums; white-space: nowrap; }
.fc-table td, .fc-table th { vertical-align: middle; }
.fc-table tr.on { background: color-mix(in srgb, var(--accent, #2f6fed) 8%, transparent); }
.fc-table tr.blank { opacity: .6; }
.fc-pick { background: none; border: 0; padding: 0; font: inherit; color: inherit;
  cursor: pointer; text-align: left; display: inline-flex; align-items: center; gap: 6px; }
.fc-pick:hover b { text-decoration: underline; }
.fc-delta { font-size: 12px; white-space: nowrap; font-variant-numeric: tabular-nums; }
.fc-delta.up { color: var(--green, #1f9d61); }
.fc-delta.down { color: var(--red, #e05252); }
.fc-delta.quiet { color: var(--muted); }

/* The adjust card — Supy's shape: the figure, the reason, the stepper, the
   result, and both buttons. Both numbers stay on screen the whole time. */
.fc-adj { border: 1px solid var(--line); border-radius: 12px; padding: 14px; margin-top: 10px;
  display: flex; flex-direction: column; gap: 10px; max-width: 560px; }
.fc-adj-row { display: flex; align-items: center; justify-content: space-between; gap: 12px; }
.fc-adj-row.wide { flex-wrap: wrap; }
.fc-adj-row > span { color: var(--muted); font-size: 13px; }
.fc-adj-row b { font-size: 20px; font-variant-numeric: tabular-nums; }
.fc-adj-row.total { border-top: 1px solid var(--line); padding-top: 10px; }
.fc-adj-row.total b { font-size: 26px; color: var(--accent, #2f6fed); }
.fc-adj-note { display: flex; align-items: center; gap: 8px; font-size: 13px;
  padding: 8px 10px; border-radius: 8px; color: var(--amber, #b57614);
  background: color-mix(in srgb, var(--amber, #d9962a) 12%, transparent); }
.fc-step { display: inline-flex; align-items: center; border: 1px solid var(--line);
  border-radius: 10px; overflow: hidden; }
.fc-stepb { width: 40px; height: 38px; border: 0; background: var(--surface-2, transparent);
  font-size: 18px; cursor: pointer; color: inherit; }
.fc-stepb:hover { background: var(--line); }
.fc-stepv { width: 84px; height: 38px; border: 0; border-left: 1px solid var(--line);
  border-right: 1px solid var(--line); background: transparent; color: inherit;
  text-align: center; font: inherit; font-variant-numeric: tabular-nums; }
.fc-unit { display: inline-flex; border: 1px solid var(--line); border-radius: 10px; overflow: hidden; }
.fc-unitb { width: 42px; height: 38px; border: 0; background: transparent; color: var(--muted);
  font-size: 15px; cursor: pointer; }
.fc-unitb.on { background: var(--accent, #2f6fed); color: #fff; }
.fc-adj-btns { display: flex; justify-content: space-between; gap: 10px; align-items: center; }

/* The ring, and the bar under it. */
.fc-ring { display: flex; gap: 20px; align-items: center; flex-wrap: wrap; margin: 12px 0; }
.fc-legend { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 6px; }
.fc-legend li { display: flex; align-items: center; gap: 8px; font-size: 13px; }
.fc-legend i { width: 12px; height: 12px; border-radius: 3px; flex: 0 0 auto; }
.fc-legend b { margin-left: auto; font-variant-numeric: tabular-nums; }
.fc-bar { display: flex; align-items: center; gap: 18px; flex-wrap: wrap;
  padding: 10px 14px; border-radius: 10px; background: var(--surface-2, transparent);
  border: 1px solid var(--line); font-size: 13px; }
.fc-bar > span b { font-size: 16px; font-variant-numeric: tabular-nums; }
.fc-bar > button { margin-left: auto; }

/* A dish somebody set by hand. */
.fc-qty { width: 78px; height: 32px; text-align: right; border: 1px solid var(--line);
  border-radius: 8px; background: transparent; color: inherit; font: inherit;
  font-variant-numeric: tabular-nums; padding: 0 8px; }
.fc-over .fc-qty { border-color: var(--green, #1f9d61);
  background: color-mix(in srgb, var(--green, #1f9d61) 10%, transparent); }
.fc-undo { background: none; border: 0; padding: 0; font: inherit; font-size: 12px;
  color: var(--muted); text-decoration: underline; cursor: pointer; }

@media (max-width: 560px) {
  .fc-adj-row.wide { flex-direction: column; align-items: stretch; }
  .fc-ring { justify-content: center; }
  .fc-bar > button { margin-left: 0; width: 100%; }
}

/* --- Purchase control (v5.180.0) -------------------------------------------
   A requisition, the chain of signatures on it, and the goods received note.
   Supy's "Orders & requisitions", "Permissions & limits" and "Exception
   management" screens. */

.rq-card { border-left: 3px solid var(--line); }
.rq-card.ok { border-left-color: var(--green, #1f9d61); }
.rq-card.wait { border-left-color: var(--amber, #d9962a); }
.rq-card.no { border-left-color: var(--red, #e05252); }
.rq-top { display: flex; align-items: flex-start; justify-content: space-between; gap: 16px; flex-wrap: wrap; }
.rq-right { display: flex; align-items: center; gap: 10px; }
.rq-total { font-size: 22px; font-variant-numeric: tabular-nums; }
.rq-more { margin-top: 10px; }
.rq-warn { margin-top: 10px; padding: 9px 12px; border-radius: 8px; font-size: 13px;
  color: var(--amber, #b57614); background: color-mix(in srgb, var(--amber, #d9962a) 12%, transparent); }

/* The flow, drawn as a ladder with a dot per rung. */
.rq-flow { margin-top: 12px; border: 1px solid var(--line); border-radius: 10px; overflow: hidden; }
.rq-flow-h { padding: 8px 12px; background: var(--surface-2, transparent); font-size: 12px;
  letter-spacing: .04em; text-transform: uppercase; color: var(--muted); }
.rq-step { display: flex; align-items: center; gap: 10px; padding: 9px 12px; font-size: 13px;
  border-top: 1px solid var(--line); }
.rq-flow > .rq-step:first-child { border-top: 0; }
.rq-step .rq-when { margin-left: auto; white-space: nowrap; }
.rq-dot { width: 14px; height: 14px; border-radius: 50%; flex: 0 0 auto;
  border: 2px solid var(--line); }
.rq-dot.ok { border-color: var(--green, #1f9d61);
  background: color-mix(in srgb, var(--green, #1f9d61) 35%, transparent); }
.rq-dot.wait { border-color: var(--amber, #d9962a); }
.rq-dot.no { border-color: var(--red, #e05252);
  background: color-mix(in srgb, var(--red, #e05252) 35%, transparent); }
.rq-step.wait { color: var(--muted); }

/* The goods received note. A flagged number is coloured; everything that
   matched is left alone, because a screen where everything is highlighted is a
   screen where nothing is. */
.grn-bad td:first-child { font-weight: 600; }
.grn-off { color: var(--amber, #d9962a); font-variant-numeric: tabular-nums; }
.grn-bad { background: color-mix(in srgb, var(--amber, #d9962a) 7%, transparent); }

@media (max-width: 560px) {
  .rq-top { flex-direction: column; }
  .rq-right { width: 100%; justify-content: space-between; }
}

/* The goods received note's item column: name on one line, faults under it.
   Three chips inline with a real dish name pushed every number off screen. */
.grn-name { max-width: 320px; }
.grn-name > span { display: block; }
.grn-chips { display: flex; flex-wrap: wrap; gap: 4px; margin-top: 5px; }
.grn-chips .chip { font-size: 11px; }

/* The base unit sits at the top of the pack list and is not editable — it is
   what everything else converts through. */
.pk-base { background: var(--surface-2, transparent); }
.pk-base td { color: var(--muted); }
.pk-base td b { color: var(--text); }


/* ---- Ticket colours by where the order came from (#203) ---------------------
   Jamal asked for *"the full configuration, and interactive colour-changing
   buttons… so you can see what colour is the delivery button going to be."* So
   each row is a control and its answer side by side: two colour inputs, a strip
   of swatches, and the real ticket underneath, redrawn on every change. */
/* **Settings on the left, the ticket on the right, and it stays there.**
   Jamal, 2026-08-25: *"that main ticket should show alongside, maybe to the
   right, and then all of these changes should show to the left."* Sticky
   rather than fixed, so it scrolls with its own column and stops at the foot
   of it rather than floating over the save line. */
.kset-split { display: grid; gap: 18px; align-items: start; }
@media (min-width: 1060px) {
  .kset-split { grid-template-columns: minmax(0, 1fr) 360px; }
  .kset-side { position: sticky; top: 74px; }
}
/* Under that width there is no room for a column, so it rides the top instead
   — same rule, same reason: it must be on screen while you change things. */
@media (max-width: 1059px) {
  .kset-side { position: sticky; top: 56px; z-index: 3; }
  .kset-livecard { box-shadow: 0 10px 24px rgba(0, 0, 0, .35); }
}
.kset-main { display: grid; gap: 18px; min-width: 0; }
/* Real space between the cards. Jamal: the page "is way too dense. There's no
   spacing between the boxes and stuff." */
.kset-main > .card { margin: 0; }
.kset-livenote { margin: 0 0 10px; font-size: 12px; }
/* **A ticket the size a ticket is.** Jamal: *"it's all stretched out, so it's
   not very realistic. On an iPad, it's kind of stretched rather than fitted."*
   A board column on a 10.9" iPad is about 300px, so the preview is capped
   there and centred rather than filling whatever the card happens to be. */
.kexample { display: flex; justify-content: center; }
.kexample .kt { width: 100%; max-width: 310px; margin: 0; }

/* The mark that stands in for a brand logo, shown rather than described. */
.ksrc-icons { display: flex; gap: 8px; flex-wrap: wrap; margin: 10px 0 4px; }
.ksrc-icon {
  display: grid; gap: 4px; justify-items: center; width: 84px; padding: 10px 6px;
  border-radius: 12px; border: 1px solid var(--border); background: var(--surface-2);
  color: var(--muted); font-family: inherit; font-size: 11px; cursor: pointer;
}
.ksrc-icon.on {
  border-color: var(--primary); color: var(--text);
  background: color-mix(in srgb, var(--primary) 18%, transparent);
}
.ksrc-icon[disabled] { opacity: .5; cursor: not-allowed; }
.ksrc-iconart { display: block; line-height: 0; }

.ksrc-modes { display: flex; gap: 8px; margin: 10px 0; flex-wrap: wrap; }
.ksrc-mode {
  flex: 1 1 220px; text-align: left; padding: 10px 12px; border-radius: 12px;
  border: 1px solid var(--border); background: var(--surface-2); color: var(--muted);
  font: inherit; cursor: pointer;
}
.ksrc-mode b { display: block; color: var(--text); font-size: 14px; }
.ksrc-mode span { font-size: 12px; }
.ksrc-mode.on { border-color: var(--primary); background: color-mix(in srgb, var(--primary) 18%, transparent); }
.ksrc-mode.on b { color: var(--text); }
.ksrc-mode[disabled] { opacity: .5; cursor: not-allowed; }

.ksrc-rows { display: grid; gap: 14px; margin-top: 12px; }
.ksrc-row {
  display: grid; gap: 10px; padding: 12px; border-radius: 14px;
  border: 1px solid var(--border); background: var(--surface);
  grid-template-columns: minmax(0, 1fr) auto;
  grid-template-areas: 'name pick' 'swatches swatches' 'prev prev';
  align-items: start;
}
/* Wide enough and the preview sits beside its controls rather than under them,
   which is the arrangement that lets somebody compare two rows at once. */
@media (min-width: 900px) {
  .ksrc-row {
    grid-template-columns: minmax(0, 1fr) auto minmax(240px, 300px);
    grid-template-areas: 'name pick prev' 'swatches swatches prev';
  }
}
.ksrc-row.edited { border-color: color-mix(in srgb, var(--primary) 55%, var(--border)); }
.ksrc-name { grid-area: name; }
.ksrc-pick { grid-area: pick; display: flex; gap: 10px; align-items: flex-end; }
/* **Which swatch is which.** Three colours now — the bar, the words on it and
   the line down the side — and Jamal's note was that it was not clear which was
   which even with two. The word sits under its own swatch rather than above a
   row of them. */
.ksrc-lab {
  display: grid; gap: 5px; justify-items: center; font-size: 10px; font-weight: 700;
  color: var(--muted); letter-spacing: .04em; text-transform: uppercase; cursor: pointer;
}
.ksrc-swatch {
  width: 46px; height: 32px; padding: 0; border-radius: 8px;
  border: 1px solid var(--border); background: none; cursor: pointer;
}
.ksrc-swatch[disabled] { cursor: not-allowed; opacity: .5; }
/* A picker still following the board's theme is shown quieter than one a venue
   has set — the difference between "no opinion" and "chose this" is the same
   distinction the lock model draws, and it should be visible at a glance. */
.ksrc-lab.auto .ksrc-swatch { opacity: .55; border-style: dashed; }
.ksrc-undo {
  align-self: flex-end; padding: 7px 10px; border-radius: 8px; font-size: 12px;
  border: 1px solid var(--border); background: var(--surface-2); color: var(--muted);
  font-family: inherit; cursor: pointer;
}
.ksrc-swatches { grid-area: swatches; display: flex; gap: 6px; flex-wrap: wrap; }
.ksrc-chip {
  width: 26px; height: 26px; border-radius: 7px; border: 2px solid transparent;
  cursor: pointer; padding: 0;
  /* An outline of its own so a near-black swatch is still a visible object on a
     dark screen — the palette has one, and it looked like a hole. */
  box-shadow: inset 0 0 0 1px rgba(255, 255, 255, .18);
}
.ksrc-chip.on { border-color: var(--text); }
.ksrc-chip[disabled] { cursor: not-allowed; opacity: .5; }
/* The live ticket. Scaled down rather than restyled: a preview that is a
   different size from the board is still the board, and one that is a
   different *design* is a drawing. */
.ksrc-prev { grid-area: prev; min-width: 0; }
.ksrc-prev .kt {
  width: 100%; max-width: 300px; margin: 0; pointer-events: none;
  transform: none;
}
.ksrc-prev .kt-btn, .ksrc-prev .kt-line { pointer-events: none; }

/* --- The order ID, at the top of the ticket sheet -------------------------
   Jamal, 2026-08-23: "if you press on any of them, it should show the ID… that
   needs to be bold and clear as the order ID." The card carries the short
   number a customer was told; this is the other one, and it is the first thing
   in the sheet rather than a footnote in it. */
.ksheet-id {
  display: flex; align-items: flex-start; justify-content: space-between;
  gap: 18px; flex-wrap: wrap;
  padding: 14px 16px; margin: 0 0 14px;
  background: var(--surface-2); border-radius: 12px;
}
.ksheet-id-main { min-width: 0; }
.ksheet-id-lab {
  display: block; font-size: 11px; font-weight: 800; letter-spacing: .09em;
  text-transform: uppercase; color: var(--muted);
}
/* Mono and tabular, because this is a string somebody is about to copy into a
   search box character by character — and an l that looks like a 1 is how that
   goes wrong. */
.ksheet-id-val {
  display: block; margin-top: 2px;
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 28px; font-weight: 800; letter-spacing: -.01em;
  font-variant-numeric: tabular-nums; color: var(--text);
  user-select: all;   /* one tap selects the whole thing, not a word of it */
}
.ksheet-id-how { display: block; margin-top: 4px; font-size: 12.5px; color: var(--muted); }
/* The other references — the platform's own, the customer's name. A definition
   list because that is what they are, and because it keeps label and value
   tied together when the row wraps on a phone. */
.ksheet-id-rows { display: grid; gap: 8px; margin: 0; text-align: right; }
.ksheet-id-rows div { margin: 0; }
.ksheet-id-rows dt {
  font-size: 11px; font-weight: 700; letter-spacing: .06em;
  text-transform: uppercase; color: var(--muted);
}
.ksheet-id-rows dd {
  margin: 1px 0 0; font-size: 15px; font-weight: 700; color: var(--text);
  font-variant-numeric: tabular-nums;
}
@media (max-width: 560px) {
  .ksheet-id { gap: 12px; }
  .ksheet-id-rows { text-align: left; grid-auto-flow: column; grid-auto-columns: max-content;
    gap: 20px; }
}

/* --- A control that reads as a link ---------------------------------------
   The "view" beside a staff document was `<a href="#">`, and this stylesheet
   has no rule for a bare anchor — so it fell through to the browser's own
   colours. `href="#"` marks a link **visited** the instant it is clicked, and
   Chrome's visited purple against this app's near-black panel is all but
   invisible: after one use, the only way into somebody's right-to-work
   document was a word you could no longer read.
   It was never navigation anyway — it opens a file the API hands back. A
   button says that, cannot be visited, and cannot be middle-clicked into a
   new tab that would go nowhere. */
.linkish {
  background: none; border: 0; padding: 0; font: inherit; cursor: pointer;
  color: var(--blue, #4f8cff); text-decoration: underline; text-underline-offset: 2px;
}
.linkish:hover { color: #7ea9ff; }
.linkish:disabled { color: var(--muted); cursor: default; text-decoration: none; }

/* ---- The runner's board (#199) ------------------------------------------
   A hall's own KDS: every kitchen on one screen, filtered by kitchen or by
   what is ready for a driver. Read at a glance while walking, so the card is
   short and the two things that decide where somebody goes next — the kitchen
   and how long it has been standing — are the two largest things on it. */
/* **The tokens this app actually has, not invented fallbacks.** The first cut
   used `var(--card, #fff)` and `var(--line, #e3e3e6)`. Neither exists here —
   the app's are `--surface`, `--border`, `--muted`, `--text` — so every
   fallback fired at once and the board came out as white cards on a dark app
   with grey-on-white text. That is the same fault the Apple rollout hit and
   wrote up ("the materials were white on a dark app"), repeated. */
.rn-bar { padding: 12px; margin-bottom: 12px; display: flex; gap: 12px; flex-wrap: wrap;
  align-items: flex-end; }
.rn-views { display: flex; gap: 6px; flex-wrap: wrap; margin-left: auto; }
.rn-view { border: 1px solid var(--border); background: transparent; color: inherit;
  padding: 8px 14px; border-radius: 999px; font-size: 13.5px; font-weight: 600; cursor: pointer; }
.rn-view.on { background: var(--primary); border-color: transparent; color: var(--primary-ink); }

.rn-chips { display: flex; gap: 8px; flex-wrap: wrap; margin: 0 0 10px; }
.rn-chip { border: 1px solid var(--border); background: transparent; color: inherit;
  padding: 7px 12px; border-radius: 999px; font-size: 13px; font-weight: 600; cursor: pointer;
  display: inline-flex; gap: 7px; align-items: center; }
.rn-chip.on { background: var(--primary); border-color: transparent; color: var(--primary-ink); }
/* A kitchen with nothing waiting still gets a chip — reading zero is an
   answer, and dropping it would leave a runner unable to tell "quiet" from
   "the screen has lost it". Dimmed, not hidden. */
.rn-chip.zero { opacity: .55; }
.rn-chip .n { font-variant-numeric: tabular-nums; opacity: .75; }

.rn-line { margin: 2px 0 14px; font-size: 14px; color: var(--muted); }

.rn-grid { display: grid; gap: 12px;
  grid-template-columns: repeat(auto-fill, minmax(230px, 1fr)); }
.rn-card { border: 1px solid var(--border); border-radius: 14px; padding: 12px 13px;
  background: var(--surface); border-left: 4px solid var(--border); }
.rn-card.driver { border-left-color: var(--primary); }
.rn-card.customer { border-left-color: var(--green); }
.rn-card.waiter { border-left-color: var(--amber); }
.rn-card.cold { border-left-color: var(--red); }
.rn-top { display: flex; justify-content: space-between; gap: 10px; align-items: baseline; }
.rn-kitchen { font-size: 15px; color: var(--text); }
.rn-who { font-size: 15px; font-weight: 700; font-variant-numeric: tabular-nums;
  color: var(--text); }
.rn-state { display: flex; gap: 8px; align-items: center; margin: 8px 0 0; flex-wrap: wrap; }
.rn-stand { font-size: 12.5px; color: var(--muted); }
.rn-stand.cold { color: var(--red); font-weight: 700; }
.rn-cour { margin-top: 6px; font-size: 12.5px; color: #8fb6ff; }
.rn-lines { margin: 8px 0 0; padding: 0 0 0 16px; font-size: 13.5px; line-height: 1.5;
  color: var(--text); }
.rn-more { color: var(--muted); list-style: none; margin-left: -16px; }
.rn-cust { margin-top: 6px; font-size: 12.5px; color: var(--muted); }
.rn-note { margin-top: 16px; }
.pill.rn-driver { background: rgba(79,140,255,.16); color: #8fb6ff; }
.pill.rn-customer { background: rgba(62,207,142,.16); color: #6fe0b0; }
.pill.rn-waiter { background: rgba(245,166,35,.16); color: #f5c26b; }
.pill.rn-making { background: var(--surface-2); color: var(--muted); }

/* --- Charity round-up (v5.247.0) -------------------------------------------
   Deliberately plain. A takings report gets a chart and a comparison with last
   month; this one gets a number and a sentence, because the number is a debt
   and nothing about it is an achievement. */
.cha-bar { padding: 12px; margin-bottom: 14px; display: flex; gap: 10px; flex-wrap: wrap; align-items: flex-end; }
.cha-head { padding: 20px; margin-bottom: 14px; }
.cha-big { font-size: 34px; font-weight: 700; letter-spacing: -0.02em; }
.cha-says { margin-top: 4px; opacity: 0.75; }
.cha-split { margin-top: 12px; display: flex; gap: 18px; flex-wrap: wrap; font-size: 13px; opacity: 0.85; }

/* --- What guests said (v5.248.0) --------------------------------------------
   The spread as five bars rather than one average, because two answers of 1 and
   5 average to the same 3 as two answers of 3, and those are different nights. */
.fb-spread { display: flex; align-items: flex-end; gap: 4px; height: 34px; }
.fb-spread span { display: flex; flex-direction: column; justify-content: flex-end;
  align-items: center; width: 16px; height: 100%; }
.fb-spread i { display: block; width: 100%; min-height: 2px; border-radius: 2px 2px 0 0; }
.fb-spread em { font-size: 10px; font-style: normal; opacity: .6; line-height: 1.1; }
.fb-b1 { background: #e5484d; } .fb-b2 { background: #e08a12; }
.fb-b3 { background: #8b8d98; } .fb-b4 { background: #46a758; }
.fb-b5 { background: #30a46c; }
.fb-h { margin: 22px 0 10px; font-size: 15px; }
.fb-notes { display: grid; gap: 10px; }
.fb-note { padding: 12px 14px; }
.fb-note-top { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin-bottom: 6px; }
.fb-note p { margin: 0; }
.fbp { font-size: 11px; font-weight: 700; padding: 2px 8px; border-radius: 999px; color: #fff; }
.fbp-1 { background: #e5484d; } .fbp-2 { background: #e08a12; }
.fbp-3 { background: #8b8d98; } .fbp-4 { background: #46a758; } .fbp-5 { background: #30a46c; }

/* --- Stock health (v5.250.0) ------------------------------------------------
   Four counts, each a button that opens the lines behind it. A number on its
   own makes somebody anxious and gives them nothing to do. */
.sh-tiles { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  gap: 12px; margin-bottom: 16px; }
.sh-tile { padding: 16px; text-align: left; cursor: pointer; border: 1px solid transparent;
  font: inherit; color: inherit; }
.sh-tile.on { border-color: currentColor; }
.sh-n { font-size: 30px; font-weight: 700; letter-spacing: -0.02em; line-height: 1.1; }
.sh-l { font-size: 13px; opacity: .75; margin-top: 2px; }
.sh-bad .sh-n { color: #e5484d; }
.sh-warn .sh-n { color: #e08a12; }
.sh-note .sh-n { color: #3b82f6; }
.sh-quiet .sh-n { opacity: .55; }
.sh-what { margin: 0 0 12px; max-width: 70ch; }
.sh-neg { color: #e5484d; font-weight: 700; }

/* --- What guests see (v5.251.0) ---------------------------------------------
   Jamal: a switch should SHOW the difference, not describe it. So every row
   carries the real component twice — off and on — drawn from the same words the
   guest is served. */
.gf-list { display: grid; gap: 14px; }
.gf-row { padding: 16px 18px; }
.gf-head { display: flex; gap: 14px; align-items: flex-start; }
.gf-t { flex: 1; min-width: 0; }
.gf-t strong { margin-right: 6px; }
.gf-t .hint { margin-top: 3px; }
.gf-t .hint em { opacity: .7; }
.gf-i { width: 20px; height: 20px; border-radius: 50%; border: 1px solid currentColor;
  background: none; color: inherit; opacity: .55; font: italic 600 12px/1 serif;
  cursor: pointer; vertical-align: middle; }
.gf-i:hover { opacity: 1; }
/* The switch itself. */
.gf-sw { flex: none; position: relative; width: 46px; height: 27px; cursor: pointer; }
.gf-sw input { position: absolute; opacity: 0; width: 100%; height: 100%; margin: 0; cursor: pointer; }
.gf-sw span { position: absolute; inset: 0; border-radius: 999px; background: rgba(127,127,127,.35);
  transition: background .15s; pointer-events: none; }
.gf-sw span::after { content: ''; position: absolute; top: 3px; left: 3px; width: 21px; height: 21px;
  border-radius: 50%; background: #fff; transition: transform .15s; }
.gf-sw input:checked + span { background: #30a46c; }
.gf-sw input:checked + span::after { transform: translateX(19px); }
/* Off | On, side by side — refusal 3: the absence is drawn, not imagined. */
.gf-pair { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; margin-top: 14px; }
.gf-lab { font-size: 11px; text-transform: uppercase; letter-spacing: .04em;
  opacity: .55; margin-bottom: 5px; }
.gfp { border: 1px solid rgba(127,127,127,.28); border-radius: 10px; padding: 11px 12px;
  background: #fff; color: #17181c; font-size: 12.5px; min-height: 64px; }
.gfp-off, .gfp-none { display: flex; align-items: center; justify-content: center;
  background: rgba(127,127,127,.07); color: inherit; opacity: .6; font-style: italic;
  border-style: dashed; }
.gfp-t { font-weight: 700; margin-bottom: 6px; }
.gfp-n { opacity: .7; margin-top: 6px; font-size: 11.5px; line-height: 1.35; }
.gfp-chips { display: flex; gap: 5px; flex-wrap: wrap; }
.gfp-chip { border: 1px solid rgba(0,0,0,.16); border-radius: 999px; padding: 4px 10px;
  font-size: 11.5px; display: inline-flex; gap: 4px; align-items: baseline; }
.gfp-chip b { font-size: 11px; }
.gfp-code { display: flex; gap: 5px; }
.gfp-code i { width: 26px; height: 32px; border: 1px solid rgba(0,0,0,.2); border-radius: 6px;
  display: flex; align-items: center; justify-content: center; font-style: normal;
  font-weight: 700; font-size: 15px; }
.gfp-faces { display: flex; gap: 9px; }
.gfp-face { width: 22px; height: 22px; border-radius: 50%; display: inline-block;
  background: #f5c04e; border: 1px solid rgba(0,0,0,.18); }
.gff-angry { background: #e5716f; } .gff-sad { background: #efa06a; }
.gff-meh { background: #e8d07a; } .gff-good { background: #bcd97a; }
.gff-great { background: #86cf7f; }
.gfp-badge { display: flex; align-items: center; gap: 8px; }
.gfp-rating { background: #007b5f; color: #fff; font-weight: 800; border-radius: 4px;
  padding: 3px 9px; font-size: 14px; }
.gfp-row { display: flex; align-items: center; justify-content: space-between; gap: 10px; }
.gfp-sw { width: 32px; height: 19px; border-radius: 999px; background: rgba(0,0,0,.18);
  display: inline-block; position: relative; }
.gfp-sw::after { content: ''; position: absolute; top: 2px; left: 2px; width: 15px; height: 15px;
  border-radius: 50%; background: #fff; }
.gfp-field { border: 1px solid rgba(0,0,0,.16); border-radius: 8px; padding: 8px 10px;
  opacity: .55; font-size: 11.5px; }
/* A labelled box in a multi-field preview (v5.542.0). Table delivery adds
   three boxes at once, and an unlabelled stack says nothing about which is
   which — the point of the preview is that a vendor can read it. */
.gfp-lab { font-size: 12.5px; font-weight: 600; margin: 10px 0 4px; }
.gfp-lab em { font-weight: 400; font-style: normal; opacity: .6; }
.gfp-banner { border-radius: 8px; padding: 9px 11px; background: rgba(52,168,83,.12);
  border: 1px solid rgba(52,168,83,.3); }
.gfp-banner b { display: block; }
.gfp-banner span { opacity: .8; font-size: 11.5px; }

/* How a printer model reaches the network — three answers, always all three,
   because a row that only shows what is true cannot answer "does any of these
   do 4G?". `part` is the middle answer: yes, but you have to buy something. */
.prn-nets { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 6px; }
.prn-net { font-size: 11.5px; font-weight: 600; padding: 3px 8px; border-radius: 999px;
  border: 1px solid rgba(127,127,127,.35); color: var(--muted, #8b8b8b); }
.prn-net.on { background: rgba(52,168,83,.14); border-color: rgba(52,168,83,.4); color: #2e8b57; }
.prn-net.part { background: rgba(224,138,18,.13); border-color: rgba(224,138,18,.38); color: #b4740f; }
.prn-net.off { opacity: .55; text-decoration: line-through; }
.prn-net.num { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-weight: 500; }
.prn-net-note { margin-top: 5px; }

/* Store notices — StoreKit's layout: the explanation on the left, the cards on
   the right, one Save under all of them. */
.sn-wrap { display: grid; grid-template-columns: minmax(220px, 300px) 1fr; gap: 32px; align-items: start; }
.sn-side h3 { margin: 0 0 10px; font-size: 17px; }
.sn-side p { margin: 0 0 12px; font-size: 14px; line-height: 1.55; opacity: .8; }
.sn-top { display: flex; justify-content: flex-end; margin-bottom: 12px; }
.sn-card { padding: 0; overflow: hidden; }
.sn-body { padding: 18px 20px; display: flex; flex-direction: column; gap: 14px; }
.sn-row { display: grid; grid-template-columns: 150px 1fr; gap: 16px; align-items: start; }
.sn-lab { font-size: 14.5px; padding-top: 8px; }
.sn-lab em { display: block; font-style: normal; font-size: 13px; opacity: .55; }
.sn-surfaces { display: flex; flex-wrap: wrap; gap: 10px 20px; padding-top: 8px; }
.sn-tick { display: inline-flex; align-items: center; gap: 7px; font-size: 14.5px; }
.sn-help { width: 17px; height: 17px; border-radius: 50%; border: none; cursor: pointer;
  background: rgba(127,127,127,.35); color: inherit; font-size: 11px; font-weight: 700;
  line-height: 17px; padding: 0; }
.sn-foot { padding: 10px 20px; background: rgba(127,127,127,.08);
  border-top: 1px solid rgba(127,127,127,.18); display: flex; justify-content: flex-end; }
.sn-save { display: flex; justify-content: flex-end; margin-top: 18px;
  padding-top: 18px; border-top: 1px solid rgba(127,127,127,.18); }
.sn-empty { text-align: center; padding: 46px 20px; }
.sn-empty p { margin: 0 0 14px; font-size: 17px; font-weight: 600; }
.sn-card + .sn-card { margin-top: 16px; }
@media (max-width: 860px) { .sn-wrap { grid-template-columns: 1fr; gap: 18px; }
  .sn-row { grid-template-columns: 1fr; gap: 6px; } .sn-lab { padding-top: 0; } }

/* And what a guest actually sees — the four stylings, on any surface. */
.gn { border-radius: 12px; padding: 12px 14px; margin: 0 0 10px;
  border: 1px solid rgba(127,127,127,.28); background: rgba(127,127,127,.08); font-size: 14px; }
.gn b { display: block; font-size: 15px; margin-bottom: 3px; }
.gn a { color: inherit; text-decoration: underline; }
.gn.info { background: rgba(37,99,235,.10); border-color: rgba(37,99,235,.34); }
.gn.success { background: rgba(52,168,83,.11); border-color: rgba(52,168,83,.34); }
.gn.warning { background: rgba(224,138,18,.12); border-color: rgba(224,138,18,.36); }

/* The checkout gate. A sheet rather than a banner, because it is the one
   notice that stops somebody — worker/src/notices.js refusal 4. */
.ng-sheet { padding: 18px; }
.ng-body { max-height: 60vh; overflow-y: auto; }
.ng-foot { display: flex; gap: 10px; margin-top: 14px; }
.ng-foot .btn { flex: 1; }

/* The Stores → Settings hub: four groups, a card of rows each, a value or an
   honest note on the right. */
.ss-group { max-width: 900px; margin: 0 0 22px; }
.ss-group h4 { margin: 0 0 8px; font-size: 12px; font-weight: 700; letter-spacing: .06em;
  text-transform: uppercase; opacity: .55; }
.ss-card { padding: 0; overflow: hidden; }
.ss-row { display: flex; align-items: center; gap: 14px; padding: 15px 18px;
  color: inherit; text-decoration: none; }
.ss-row + .ss-row { border-top: 1px solid rgba(127,127,127,.16); }
a.ss-row:hover { background: rgba(127,127,127,.07); }
.ss-row.off { opacity: .62; }
.ss-icon { width: 21px; height: 21px; flex: none; opacity: .8; }
.ss-text { flex: 1; min-width: 0; }
.ss-text b { display: block; font-size: 15.5px; }
.ss-text em { display: block; font-style: normal; font-size: 13.5px; opacity: .65; margin-top: 2px; }
.ss-text i { display: block; font-style: normal; font-size: 12.5px; opacity: .8; margin-top: 5px; }
.ss-soon { font-size: 12.5px; opacity: .7; text-align: right; max-width: 260px; }
.ss-go { font-size: 20px; opacity: .45; }
@media (max-width: 700px) { .ss-soon { display: none; } }

/* --- Opening Hours ------------------------------------------------------- */
.oh-wrap { display: grid; grid-template-columns: 300px minmax(0, 1fr); gap: 22px; align-items: start; }
.oh-side h3 { margin: 0 0 8px; font-size: 17px; }
.oh-side p { margin: 0 0 10px; color: var(--muted); font-size: 13.5px; line-height: 1.5; }
.oh-now { border-radius: 10px; padding: 10px 12px; font-size: 13px; display: grid; gap: 2px; }
.oh-now.on { background: rgba(52, 199, 89, .12); color: #34c759; }
.oh-now.off { background: rgba(255, 159, 10, .12); color: #ff9f0a; }
.oh-now span { color: var(--muted); }
.oh-card { padding: 14px 16px; margin-bottom: 14px; }
.oh-24 { display: flex; gap: 12px; align-items: flex-start; cursor: pointer; }
.oh-24 em { display: block; font-style: normal; color: var(--muted); font-size: 13px; margin-top: 2px; }
.oh-week { padding: 4px 16px; }
.oh-day { display: grid; grid-template-columns: 130px minmax(0, 1fr) auto; gap: 14px;
  align-items: start; padding: 14px 0; border-bottom: 1px solid var(--line); }
.oh-day:last-child { border-bottom: 0; }
.oh-dayname { font-weight: 600; padding-top: 8px; }
.oh-times { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; }
.oh-period { display: flex; align-items: center; gap: 6px; }
.oh-time { width: 118px; }
.oh-dash { color: var(--muted); }
.oh-x { background: none; border: 0; color: var(--muted); font-size: 18px; line-height: 1;
  cursor: pointer; padding: 4px 6px; border-radius: 6px; }
.oh-x:hover { color: #ff453a; background: rgba(255, 69, 58, .1); }
.oh-add { background: none; border: 0; color: var(--accent, #0a84ff); font-size: 13px;
  cursor: pointer; padding: 6px 4px; }
.oh-closed { color: var(--muted); font-size: 13.5px; padding-top: 8px; display: inline-block; }
.oh-dayend { display: grid; gap: 6px; justify-items: end; }
.oh-shut { display: flex; gap: 6px; align-items: center; font-size: 13px; white-space: nowrap; }
.oh-apply { display: flex; gap: 4px; }
.oh-copy { background: none; border: 0; color: var(--muted); font-size: 12px; cursor: pointer;
  padding: 2px 4px; border-radius: 6px; }
.oh-copy:hover { color: var(--accent, #0a84ff); background: rgba(10, 132, 255, .08); }
.oh-day.shut .oh-dayname { color: var(--muted); }
.oh-special h4 { margin: 0 0 6px; font-size: 15px; }
.oh-special > p { margin: 0 0 12px; color: var(--muted); font-size: 13.5px; line-height: 1.5; }
.oh-tablewrap { overflow-x: auto; }
.oh-table { width: 100%; border-collapse: collapse; font-size: 13.5px; }
.oh-table th { text-align: left; color: var(--muted); font-weight: 500; font-size: 12px;
  padding: 0 8px 6px 0; }
.oh-table td { padding: 4px 8px 4px 0; vertical-align: middle; }
.oh-table tr.oh-gone td { opacity: .55; }
.oh-none { color: var(--muted); font-size: 13.5px; padding: 10px 0 4px; }

/* --- Tables (v5.273.0) — areas, numbers and a link per seat ---------------- */
.ta-two { display: grid; grid-template-columns: 220px minmax(0, 1fr); gap: 16px; }
.ta-areas { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 12px; }
.ta-tab { display: inline-flex; align-items: center; gap: 7px; border: 1px solid var(--line);
  background: var(--card); color: var(--text); border-radius: 999px; padding: 6px 13px;
  font-size: 13px; cursor: pointer; }
.ta-tab span { background: var(--chip, rgba(127,127,127,.16)); border-radius: 999px;
  padding: 0 7px; font-size: 11.5px; color: var(--muted); }
.ta-tab.on { border-color: var(--accent); color: var(--accent); }
.ta-tab.on span { color: var(--accent); }
.ta-tab.add { border-style: dashed; color: var(--muted); }
.ta-areabar { display: flex; align-items: center; gap: 12px; flex-wrap: wrap;
  margin-bottom: 12px; }
.ta-areabar .f-input { max-width: 220px; }
.ta-areabar .hint { flex: 1 1 220px; margin: 0; }
.ta-table .ta-num { max-width: 110px; }
.ta-table .ta-cov { max-width: 84px; }
/* A snoozed row stays legible — refusal 3 is that it is still there and still
   says something, not that it fades out of the venue's own list. */
.ta-table tr.ta-off td { opacity: .72; }
.ta-snooze { display: inline-flex; align-items: center; gap: 7px; font-size: 12.5px;
  color: var(--muted); white-space: nowrap; }
.ta-copy { border: 1px solid var(--line); background: none; color: var(--accent);
  border-radius: 8px; padding: 4px 10px; font-size: 12.5px; cursor: pointer; }
.ta-link { max-width: 140px; }
.ta-range .ta-two { grid-template-columns: 1fr 1fr; }
.ta-says { margin: 12px 0 0; font-size: 13px; color: var(--accent); }
@media (max-width: 760px) {
  .ta-two { grid-template-columns: 1fr; }
}

/* --- QR codes on the Tables screen (v5.274.0) ------------------------------ */
.ta-tabs { display: flex; gap: 4px; margin: 0 0 16px; }
.ta-tabbtn { border: 0; background: none; color: var(--muted); font-size: 14px;
  font-weight: 600; padding: 8px 2px; margin-right: 18px; cursor: pointer;
  border-bottom: 2px solid transparent; }
.ta-tabbtn.on { color: var(--text); border-bottom-color: var(--accent); }
.ta-scans { display: block; font-size: 11.5px; color: var(--muted); margin-top: 2px; }

/* --- Delivery statements on the Commission screen (v5.279.0) --------------- */
.ps-form { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  gap: 10px; align-items: end; margin-top: 14px; padding-top: 14px;
  border-top: 1px solid var(--border); }
.ps-form .f-lab { display: block; font-size: 13px; color: var(--muted); }
.ps-form .f-input { margin-top: 4px; }
/* The VAT field carries a line of explanation, which made it taller than its
   neighbours and bumped it onto a row of its own in the auto-fit grid — it read
   as a broken layout. The hint is positioned out of the flow instead, so every
   field in the row is the same height. */
.ps-form .f-lab { position: relative; padding-bottom: 16px; }
.ps-form .hint { position: absolute; left: 0; top: 100%; margin-top: -14px;
  display: block; font-size: 11.5px; line-height: 1.25; }
.ps-go { display: flex; align-items: end; }
.ps-go .btn { width: 100%; }

/* --- The floorplan (v5.277.0) ----------------------------------------------
   Drawn in an SVG whose viewBox IS the grid, so one cell is one user unit and
   nothing here is sized in pixels.

   **Every colour below is one of this stylesheet's own variables.** The first
   version reached for --line, --accent, --card and --panel-2, which belong to
   the public site's palette and do not exist here: they resolved to nothing,
   the chairs fell back to black, and the selected highlight never appeared at
   all. It looked like a rendering bug and it was a spelling one. */
.tm-bar { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin-bottom: 10px; }
.tm-says { font-size: 13px; color: var(--muted); font-variant-numeric: tabular-nums; }
.tm-grow { flex: 1 1 auto; }

.tm-stage { border: 1px solid var(--border); border-radius: 14px; overflow: hidden;
  background: var(--bg); }
.tm-svg { display: block; width: 100%; aspect-ratio: 33.2 / 21.2; /* the grid plus its padding — see TM_PAD */ touch-action: none; }
.tm-floor { fill: var(--bg); }
.tm-dot { fill: var(--border); }

.tm-table { cursor: grab; }
.tm-table.drag { cursor: grabbing; }
/* The top is the lit surface and the chairs are the dark ones around it, which
   is the way round a person reads a floorplan from above. */
.tm-top { fill: var(--surface-2); stroke: var(--muted); stroke-width: .1; }
.tm-chair { fill: var(--border); }
.tm-num { font-size: .95px; font-weight: 700; fill: var(--text); text-anchor: middle;
  pointer-events: none; font-variant-numeric: tabular-nums; }
/* Selected reads as selected without moving anything — a highlight that changed
   the stroke width would shift the table by a fraction of a cell. */
.tm-table.on .tm-top { stroke: var(--primary); fill: #24344f; }
.tm-table.on .tm-chair { fill: var(--primary); }
.tm-table:focus-visible { outline: none; }
.tm-table:focus-visible .tm-top { stroke: var(--primary); stroke-dasharray: .35 .25; }
/* A snoozed table is still on the map and still says so — tables.js refusal 3. */
.tm-table.off .tm-top { fill: var(--bg); stroke-dasharray: .35 .25; }
.tm-table.off .tm-num { fill: var(--muted); }

.tm-off { margin-top: 14px; padding-top: 12px; border-top: 1px solid var(--border); }
.tm-off b { display: block; font-size: 13px; }
.tm-chips { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 8px; }
.tm-chip { border: 1px dashed var(--border); background: none; color: var(--text);
  border-radius: 999px; padding: 5px 12px; font-size: 13px; font-weight: 600;
  cursor: pointer; font-family: inherit; }
.tm-chip:hover { border-style: solid; border-color: var(--primary); color: var(--primary); }

.tm-insp { margin-top: 14px; padding: 12px 14px; border: 1px solid var(--border);
  border-radius: 14px; background: var(--surface); }
.tm-insphead { display: flex; align-items: center; justify-content: space-between;
  margin-bottom: 8px; }
.tm-row { display: flex; align-items: center; gap: 12px; margin-bottom: 8px; }
.tm-row > span:first-child { width: 54px; flex: none; font-size: 13px; color: var(--muted); }
.tm-picks { display: flex; flex-wrap: wrap; gap: 6px; }
.tm-pick { border: 1px solid var(--border); background: none; color: var(--text);
  border-radius: 999px; padding: 5px 12px; font-size: 13px; font-weight: 600;
  cursor: pointer; font-family: inherit; }
.tm-pick:hover { border-color: var(--muted); }
.tm-pick.on { border-color: var(--primary); color: var(--primary); }
.tm-help { margin-top: 12px; }

@media (max-width: 640px) {
  .tm-row { align-items: flex-start; flex-direction: column; gap: 6px; }
  .tm-row > span:first-child { width: auto; }
}

.qr-modes { display: grid; grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));
  gap: 10px; margin: 14px 0; }
.qr-mode { display: block; border: 1px solid var(--line); border-radius: 12px;
  padding: 12px 14px; cursor: pointer; }
.qr-mode.on { border-color: var(--accent); box-shadow: inset 0 0 0 1px var(--accent); }
.qr-mode input { margin-right: 8px; }
.qr-mode b { font-size: 13.5px; }
.qr-mode em { display: block; font-style: normal; font-size: 12px; color: var(--muted);
  margin-top: 4px; line-height: 1.45; }
.qr-check { display: inline-flex; align-items: center; gap: 8px; font-size: 13px;
  color: var(--muted); margin-top: 8px; }
.qr-checks { display: flex; flex-wrap: wrap; gap: 6px 18px; margin-top: 6px; }
.qr-warn { margin: 12px 0 0; padding-left: 18px; font-size: 12.5px; color: var(--muted);
  line-height: 1.5; }
.qr-warn li + li { margin-top: 5px; }
.qr-warn-one { font-size: 12.5px; color: var(--warn, #b45309); margin: 6px 0 0; }
/* A refusal, not a warning — it stops the save. */
.qr-stop { margin: 14px 0 0; padding: 11px 14px; border-radius: 10px; font-size: 13px;
  background: rgba(220, 38, 38, .1); color: var(--bad, #dc2626); }

.qr-nums { display: flex; flex-wrap: wrap; gap: 26px; margin: 14px 0 10px; }
.qr-nums div { min-width: 74px; }
.qr-nums b { display: block; font-size: 26px; line-height: 1.1; }
.qr-nums span { font-size: 12px; color: var(--muted); }
.qr-nums .off b { color: var(--muted); }

.qr-split { display: grid; grid-template-columns: minmax(0, 260px) minmax(0, 1fr);
  gap: 26px; align-items: start; margin-top: 14px; }
/* The preview is the real card at a screen size — same composer, so it cannot
   flatter what comes out of the printer. */
.qr-preview { background: var(--chip, rgba(127,127,127,.1)); border-radius: 12px;
  padding: 18px; display: grid; place-items: center; position: sticky; top: 16px; }
.qr-preview svg { width: 100%; height: auto; max-height: 60vh;
  filter: drop-shadow(0 4px 14px rgba(0,0,0,.18)); }
.qr-controls { display: grid; gap: 12px; }
.qr-swatches { display: flex; flex-wrap: wrap; gap: 10px 14px; }
.qr-swatch { display: inline-flex; align-items: center; gap: 7px; font-size: 12.5px;
  color: var(--muted); }
.qr-swatch input[type=color] { width: 34px; height: 26px; padding: 0; border-radius: 7px;
  border: 1px solid var(--line); background: none; cursor: pointer; }
.qr-heading { resize: vertical; font-family: inherit; }
.qr-controls input[type=range] { width: 100%; }
@media (max-width: 900px) {
  .qr-split { grid-template-columns: 1fr; }
  .qr-preview { position: static; }
}


.oh-specialfoot { display: flex; gap: 8px; margin-top: 12px; }
/* **Clear of the attention pill.** `#attn` is fixed at the bottom right with a
   z-index of 60, and this row is right-aligned at the end of the page — so on
   any venue with something needing attention, the pill sat directly on top of
   Save and swallowed the click. The same fault was found once before on the
   till, where the fix was to hide the pill entirely (`body.pos-open #attn`);
   here the row just keeps its distance, because a settings screen should still
   show the prompt. Found by a browser refusing to click Save, 2026-08-25. */
.oh-save { display: flex; gap: 10px; align-items: center; justify-content: flex-end;
  margin-bottom: 96px; }
.oh-dirty { color: #ff9f0a; font-size: 13px; }
@media (max-width: 900px) {
  .oh-wrap { grid-template-columns: 1fr; }
  .oh-day { grid-template-columns: 1fr; gap: 8px; }
  .oh-dayend { justify-items: start; }
}
.oh-past { display: inline-block; margin-left: 6px; font-size: 11px; padding: 1px 6px;
  border-radius: 999px; background: rgba(255, 159, 10, .15); color: #ff9f0a; vertical-align: middle; }

/* --- All Orders ---------------------------------------------------------- */
.ao-bar { display: flex; gap: 10px; align-items: center; flex-wrap: wrap; padding: 12px 14px;
  margin-bottom: 14px; }
.ao-chips { display: flex; gap: 6px; flex-wrap: wrap; }
/* The upsell strip exactly as the QR menu draws it, framed on the guest
   page's own light ground so the operator sees what the guest sees —
   deliberately light even in the dark back office, because the guest page is. */
.ups-prev { position: relative; background: #fff; color: #16181c; border-radius: 12px;
  padding: 14px 14px 12px; margin-top: 2px; border: 1px solid var(--border); }
.ups-prev-tag { position: absolute; top: -9px; left: 12px; background: var(--primary);
  color: #fff; font-size: 10.5px; font-weight: 800; letter-spacing: .06em;
  text-transform: uppercase; padding: 2px 8px; border-radius: 999px; }
.ups-prev h4 { margin: 2px 0 10px; font-size: 17px; font-weight: 800; color: #16181c; }
.ups-prev-row { display: flex; gap: 10px; overflow-x: auto; scrollbar-width: none; padding-bottom: 2px; }
.ups-prev-row::-webkit-scrollbar { display: none; }
.ups-prev-card { flex: 0 0 92px; display: block; }
.ups-prev-art { display: block; position: relative; width: 92px; height: 92px; border-radius: 6px;
  background: #eef0f3 center/cover no-repeat; }
.ups-prev-art i { position: absolute; right: 6px; bottom: 6px; width: 26px; height: 26px;
  background: #fff; border-radius: 50%; display: flex; align-items: center; justify-content: center;
  font-style: normal; font-size: 17px; font-weight: 600; color: #0b5c41;
  box-shadow: 0 2px 8px rgba(0,0,0,.18); }
.ups-prev-card b { display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
  overflow: hidden; margin-top: 6px; font-size: 12.5px; font-weight: 700; line-height: 1.25;
  min-height: 31px; color: #16181c; }
.ups-prev-card > span:last-child { color: #5a6272; font-size: 12px; }
/* The search window — "start-finish times to look for when it was". */
.ao-when { display: flex; gap: 10px; flex-wrap: wrap; }
.ao-when label { display: inline-flex; align-items: center; gap: 6px;
  color: var(--muted); font-size: 13px; }
.ao-when input { padding: 7px 9px; font-size: 13px; }
/* The phone's list — two lines per order, everything key, no sideways scroll
   (Jamal: "just summarise and make smaller and condensed"). */
.ao-cards { padding: 4px 0; }
.ao-rowm { display: block; width: 100%; text-align: left; background: none;
  border: none; border-bottom: 1px solid var(--border); padding: 10px 14px;
  color: var(--text); cursor: pointer; }
.ao-rowm:last-child { border-bottom: none; }
.ao-rm-top { display: flex; align-items: center; gap: 8px; font-size: 14.5px; }
.ao-rm-top > b:first-child { font-size: 15px; }
.ao-rm-amt { margin-left: auto; }
.ao-rm-sub { display: block; margin-top: 3px; color: var(--muted); font-size: 12.5px;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.ao-sel { width: 170px; }
/* The search keeps its whole placeholder (QA report 2, finding 63): a floor on
   the box, and a row of its own on a phone — the same shape as .toolbar .grow. */
/* A search box with a way to empty it (QA round 3, finding 74). The X sits over
   the input's right end and only exists while there is something to clear. */
.q-wrap { position: relative; display: flex; align-items: center; }
.q-wrap input { width: 100%; padding-right: 34px; }
.q-clear { position: absolute; right: 6px; top: 50%; transform: translateY(-50%);
  width: 24px; height: 24px; border: 0; border-radius: 50%; background: none; cursor: pointer;
  color: var(--muted); font-size: 20px; line-height: 1; display: flex;
  align-items: center; justify-content: center; padding: 0; }
.q-clear:hover { background: var(--bg-2, rgba(128,128,128,.16)); color: var(--text); }

/* The sentinel under a list that loads ahead (QA round 3, finding 77). It is a
   real box with height, because an empty element with none never intersects. */
.list-more { min-height: 24px; }
.list-more-wait { color: var(--muted); font-size: 13px; padding: 10px 2px; }

/* The count line above a list (QA round 3, finding 76). */
.list-count { color: var(--muted); font-size: 13px; margin: 2px 0 8px; }

.ao-qwrap { position: relative; display: inline-flex; align-items: center; flex: 1 1 260px; min-width: 240px; }
.ao-q { width: 100%; }
@media (max-width: 640px) { .ao-qwrap { flex-basis: 100%; min-width: 100%; } }
.ao-tablewrap { overflow-x: auto; padding: 4px 6px; }
.ao-table { width: 100%; border-collapse: collapse; font-size: 13.5px; }
.ao-table th { text-align: left; color: var(--muted); font-weight: 500; font-size: 12px;
  padding: 8px 10px; border-bottom: 1px solid var(--line); }
.ao-table th.r, .ao-table td.r { text-align: right; }
.ao-table td { padding: 10px; border-bottom: 1px solid var(--line); }
.ao-table tbody tr { cursor: pointer; }
.ao-table tbody tr:hover { background: rgba(255, 255, 255, .03); }
.ao-table tbody tr:last-child td { border-bottom: 0; }
.ao-tbl { display: block; color: var(--muted); font-size: 12px; }
.ao-ch { display: inline-block; padding: 2px 8px; border-radius: 999px; font-size: 12px;
  background: rgba(139, 124, 248, .15); color: #b3a9fb; }
.ao-more { color: var(--muted); font-size: 12px; margin-left: 6px; }
.ao-go { color: var(--muted); }
.ao-rcpt { display: grid; gap: 4px; }
.ao-meta { color: var(--muted); font-size: 12.5px; margin-bottom: 8px; }
.ao-line { display: grid; grid-template-columns: 34px minmax(0, 1fr) auto; gap: 8px;
  padding: 5px 0; font-size: 13.5px; }
.ao-qty { color: var(--muted); }
.ao-nm em { display: block; font-style: normal; color: var(--muted); font-size: 12px; }
.ao-row { display: flex; justify-content: space-between; padding: 5px 0; font-size: 13.5px; }
.ao-row.total { border-top: 1px solid var(--line); margin-top: 8px; padding-top: 8px;
  font-weight: 600; }
.ao-row.refund { color: #ff9f0a; }
.lo-card { padding: 22px; }
.lo-card p { margin: 0 0 8px; }

/* --- Ideas board --------------------------------------------------------- */
.id-bar { display: flex; gap: 10px; align-items: center; flex-wrap: wrap; padding: 12px 14px;
  margin-bottom: 14px; }
.id-tabs { display: flex; gap: 6px; }
.id-sel { width: 150px; }
.id-q { flex: 1 1 200px; }
.id-list { display: grid; gap: 10px; }
.id-row { display: flex; gap: 14px; padding: 14px 16px; cursor: pointer; align-items: flex-start; }
.id-row:hover { background: rgba(255, 255, 255, .03); }
.id-vote { display: grid; justify-items: center; gap: 1px; min-width: 54px; padding: 6px 8px;
  border: 1px solid var(--line); border-radius: 10px; background: none; color: var(--muted);
  cursor: pointer; }
.id-vote:hover:not(:disabled) { border-color: var(--accent, #0a84ff); color: var(--accent, #0a84ff); }
.id-vote.on { border-color: var(--accent, #0a84ff); color: var(--accent, #0a84ff);
  background: rgba(10, 132, 255, .1); }
.id-vote:disabled { opacity: .45; cursor: default; }
.id-caret { font-size: 10px; line-height: 1; }
.id-n { font-weight: 700; font-size: 15px; }
.id-main { min-width: 0; flex: 1; }
.id-main h4 { margin: 0 0 4px; font-size: 15px; }
.id-main p { margin: 0 0 8px; color: var(--muted); font-size: 13.5px; line-height: 1.5; }
.id-meta { display: flex; gap: 10px; flex-wrap: wrap; align-items: center;
  color: var(--muted); font-size: 12px; }
.id-pill { display: inline-block; padding: 2px 9px; border-radius: 999px; font-size: 12px; }
.id-open { background: rgba(142, 142, 147, .18); color: #aeaeb2; }
.id-in-review { background: rgba(10, 132, 255, .15); color: #64a8ff; }
.id-planned { background: rgba(191, 90, 242, .15); color: #d08bfa; }
.id-in-progress { background: rgba(255, 159, 10, .15); color: #ffb340; }
.id-shipped { background: rgba(52, 199, 89, .15); color: #4cd964; }
.id-declined { background: rgba(142, 142, 147, .12); color: #8e8e93;
  text-decoration: line-through; }
.id-board { padding: 2px 8px; border-radius: 6px; background: rgba(255, 255, 255, .05); }
.id-age { color: var(--muted); }
/* A status that has not moved in three months says so — the thing their board
   will not tell you about itself. */
.id-stale { color: #ff9f0a; }
.id-form { display: grid; gap: 12px; }
.id-form em { font-style: normal; color: var(--muted); }
.id-sheet { display: grid; grid-template-columns: minmax(0, 1fr) 260px; gap: 22px;
  align-items: start; }
.id-body { margin: 0 0 16px; line-height: 1.6; white-space: pre-wrap; }
.id-similar { border: 1px solid var(--line); border-radius: 10px; padding: 10px 12px;
  margin-bottom: 16px; }
.id-similar > b { font-size: 12px; color: var(--muted); display: block; margin-bottom: 6px; }
.id-simrow { display: flex; justify-content: space-between; gap: 10px; width: 100%;
  background: none; border: 0; color: inherit; text-align: left; padding: 6px 0;
  cursor: pointer; font-size: 13.5px; }
.id-simrow:hover { color: var(--accent, #0a84ff); }
.id-simrow em { font-style: normal; color: var(--muted); font-size: 12px; white-space: nowrap; }
.id-thread > b { font-size: 12px; color: var(--muted); display: block; margin-bottom: 8px; }
.id-c { padding: 10px 0; border-top: 1px solid var(--line); }
.id-c p { margin: 4px 0 0; line-height: 1.55; white-space: pre-wrap; }
.id-who { display: flex; gap: 8px; align-items: center; font-size: 13px; font-weight: 600; }
.id-who span { font-weight: 400; color: var(--muted); font-size: 12px; }
.id-tick { color: #0a84ff; font-weight: 700; }
.id-act { padding: 10px 0; border-top: 1px solid var(--line); font-size: 13px;
  color: var(--muted); display: flex; gap: 8px; align-items: baseline; flex-wrap: wrap; }
.id-act b { color: var(--text, #fff); font-weight: 600; }
.id-act p { width: 100%; margin: 4px 0 0; }
.id-reply { margin-top: 14px; display: grid; gap: 8px; justify-items: end; }
.id-reply textarea { width: 100%; }
.id-rail { display: grid; gap: 10px; border-left: 1px solid var(--line); padding-left: 18px; }
.id-railrow { display: flex; justify-content: space-between; align-items: center; gap: 10px;
  font-size: 13px; }
.id-railrow > span:first-child { color: var(--muted); }
.id-reason { border-top: 1px solid var(--line); padding-top: 10px; }
.id-reason b { font-size: 12px; color: var(--muted); }
.id-reason p { margin: 4px 0 0; font-size: 13px; line-height: 1.5; }
.id-mod { border-top: 1px solid var(--line); padding-top: 12px; display: grid; gap: 8px; }
.id-mod b { font-size: 12px; color: var(--muted); }
@media (max-width: 820px) {
  .id-sheet { grid-template-columns: 1fr; }
  .id-rail { border-left: 0; padding-left: 0; border-top: 1px solid var(--line); padding-top: 14px; }
}

/* --- Tags & Allergens ---------------------------------------------------- */
.ta-wrap { display: grid; gap: 6px; }
.ta-said { background: rgba(52, 199, 89, .12); color: #4cd964; border-radius: 10px;
  padding: 10px 12px; font-size: 13.5px; margin-bottom: 6px; }
/* Nobody has answered. Deliberately not green — an all-clear colour on a row
   that says nothing is the whole bug this tab exists to fix. */
.ta-said.unknown { background: rgba(255, 159, 10, .12); color: #ff9f0a; }
.ta-h { margin: 14px 0 6px; font-size: 13px; color: var(--muted); font-weight: 600;
  display: flex; align-items: center; gap: 10px; }
.ta-cov { font-weight: 400; font-size: 12px; padding: 1px 8px; border-radius: 999px;
  background: rgba(255, 159, 10, .15); color: #ff9f0a; }
.ta-cov.done { background: rgba(52, 199, 89, .15); color: #4cd964; }
.ta-chips { display: flex; gap: 6px; flex-wrap: wrap; }
.ta-chip { border: 1px solid var(--line); background: none; color: inherit; border-radius: 999px;
  padding: 5px 12px; font-size: 13px; cursor: pointer; }
.ta-chip:hover { border-color: var(--accent, #0a84ff); }
.ta-chip.on { border-color: #0a84ff; background: rgba(10, 132, 255, .12); color: #64a8ff; }
.ta-tools { display: flex; gap: 8px; align-items: center; margin: 4px 0 2px; }
.ta-tools .f-input { flex: 1 1 auto; }
/* All fourteen, no inner scroller — the dialog already scrolls, and a list that
   scrolls inside a page that scrolls is two scrollbars for one finger. */
.ta-list { display: grid; gap: 0; margin-top: 8px; }
.ta-row { display: flex; align-items: center; justify-content: space-between; gap: 12px;
  padding: 9px 8px 9px 6px; border-bottom: 1px solid var(--line); border-left: 3px solid transparent; }
/* A row nobody has answered says so twice: an amber edge and the words. Amber
   because unanswered reads as "please ask" to a guest — it is the state that
   still needs somebody. */
.ta-row.open { border-left-color: #ff9f0a; background: rgba(255, 159, 10, .05); }
.ta-row > .ta-ico { flex: 0 0 auto; width: 22px; height: 22px; color: var(--muted); }
.ta-row.answered > .ta-ico { color: inherit; }
.ta-text { flex: 1 1 auto; min-width: 0; display: grid; gap: 1px; }
.ta-found { font-size: 12px; color: var(--muted); line-height: 1.3; }
.ta-open-note { font-size: 11.5px; color: #ff9f0a; white-space: nowrap; }
.ta-row.answered .ta-open-note { display: none; }
.ta-sub { font-weight: 400; font-size: 12px; color: var(--muted); margin-left: auto; text-align: right; }
.ta-big .ta-chip { display: inline-flex; align-items: center; gap: 7px; padding: 8px 12px 8px 10px; font-size: 13.5px; }
.ta-chip .ta-ico { opacity: .8; }
.ta-chip.on { font-weight: 600; }
.ta-chip .ta-tick { display: none; }
.ta-chip.on .ta-tick { display: inline; }
/* The diets on one line, the two badges on the next. */
.ta-gap { flex-basis: 100%; height: 0; }
.ta-st { display: inline-flex; align-items: center; gap: 6px; padding: 6px 11px; font-size: 12.5px; }
.ta-st i { font-style: normal; font-weight: 700; width: 12px; text-align: center; opacity: .65; }
.ta-st.on i { opacity: 1; }
@media (max-width: 720px) {
  .ta-row { flex-wrap: wrap; }
  .ta-states { width: 100%; justify-content: flex-end; }
  .ta-open-note { display: none; }
  .ta-sub { margin-left: 0; text-align: left; flex-basis: 100%; }
}
.ta-row:last-child { border-bottom: 0; }
.ta-name { font-size: 13.5px; }
.ta-states { display: flex; gap: 4px; }
.ta-st { border: 1px solid var(--line); background: none; color: var(--muted); border-radius: 8px;
  padding: 4px 10px; font-size: 12px; cursor: pointer; white-space: nowrap; }
.ta-st:hover { color: inherit; }
.ta-st.ta-bad.on { border-color: #ff453a; background: rgba(255, 69, 58, .14); color: #ff6961; }
.ta-st.ta-warn.on { border-color: #ff9f0a; background: rgba(255, 159, 10, .14); color: #ffb340; }
.ta-st.ta-ok.on { border-color: #34c759; background: rgba(52, 199, 89, .14); color: #4cd964; }

/* --- Menu tags (#200): the whole menu's diets and allergens, as a sheet ------
   Sixteen columns is a wide table on purpose — the card around it scrolls
   sideways, which is the one place in the back office where that is right,
   because the alternative is sixteen questions stacked per dish and three
   hundred dishes of scrolling.
   The variables here are the APP's (--surface-2, --border, --green…), not the
   public site's. Mixing the two silently yields invalid CSS and a control that
   falls back to black — v5.277.0 lost an afternoon to exactly that. */
.mt-table th.mt-h { width: 44px; padding: 6px 2px; vertical-align: bottom; }
.mt-table th.mt-h span {
  display: block; writing-mode: vertical-rl; transform: rotate(180deg);
  font-size: 12px; font-weight: 600; white-space: nowrap; color: var(--muted);
}
.mt-table td.mt-cell { padding: 4px 2px; text-align: center; }
.mt-table td.mt-name { min-width: 200px; }
.mt-b {
  width: 34px; height: 30px; border-radius: 8px; cursor: pointer;
  border: 1px solid var(--border); background: var(--surface-2);
  color: var(--muted); font-size: 15px; line-height: 1;
}
.mt-b:hover { border-color: var(--primary); }
.mt-b.bad { border-color: #ff453a; background: rgba(255, 69, 58, .14); color: #ff6961; }
.mt-b.warn { border-color: #ff9f0a; background: rgba(255, 159, 10, .14); color: #ffb340; }
.mt-b.good { border-color: #34c759; background: rgba(52, 199, 89, .14); color: #4cd964; }
.mt-tags { display: flex; flex-wrap: wrap; gap: 4px 10px; padding: 6px 8px; }
.mt-tag { display: inline-flex; align-items: center; gap: 5px; font-size: 12.5px;
  color: var(--muted); white-space: nowrap; }
.mt-tag input { width: auto; margin: 0; }
.mt-sug { margin-top: 6px; }

/* --- Menu setup (#201) -------------------------------------------------------
   A category with a date on it, and the two answers for a dish that is off.
   Colours come from the app's own tokens — the public site's --ink/--paper set
   is a different palette and mixing them produces CSS that simply does not
   apply (learned in the Apple rollout, v5.24.0). */
.sec-when {
  display: inline-block; margin-left: 8px; padding: 2px 8px; border-radius: 999px;
  font-size: 12px; font-weight: 600; letter-spacing: .01em;
  background: rgba(62, 207, 142, .16); color: var(--green, #3ecf8e);
}
.sec-when.waiting { background: rgba(245, 166, 35, .18); color: var(--amber, #f5a623); }
/* The row wraps, so the date editor drops onto its own line instead of
   squeezing the buttons into tall columns beside it. */
.sec-row { flex-wrap: wrap; align-items: center; }
.sec-when-edit {
  display: flex; align-items: flex-end; gap: 10px; flex-wrap: wrap;
  flex: 0 0 100%; padding: 10px 0 4px;
}
.sec-when-edit .field { flex: 1 1 220px; margin: 0; }

.ms-choice {
  display: flex; gap: 12px; align-items: flex-start; cursor: pointer;
  padding: 12px 14px; margin-bottom: 10px; border-radius: 12px;
  border: 1px solid var(--border, #2a2f3a); background: var(--surface-2, #1e222b);
}
.ms-choice.on { border-color: var(--primary, #4f8cff); background: rgba(79, 140, 255, .10); }
.ms-choice input { margin-top: 3px; width: 16px; height: 16px; accent-color: var(--primary, #4f8cff); }
.ms-choice > span { display: flex; flex-direction: column; gap: 3px; }
.ms-choice .hint { margin: 0; }

/* The message log (#201). **`white-space: normal` is not cosmetic here.** The
   global `th, td { white-space: nowrap }` at the top of this file pushes any
   long cell behind a horizontal scroll, and the reason a message did not arrive
   is the one cell on this table somebody has to read. The import preview shipped
   with exactly this bug and only a screenshot caught it. */
.msg-why { white-space: normal; max-width: 320px; color: var(--muted); font-size: 12.5px; }
/* The filter row. `input, select { width: 100% }` above is right for a form
   and wrong for a toolbar — five controls each claiming the full width is five
   rows of one control. These sit on one line and wrap only when they must. */
.msg-filters .input { width: auto; min-width: 150px; }
.msg-filters input[type="date"] { min-width: 145px; }
/* The channel-order picker is a sentence, not a form field, so it takes the
   width of its longest answer rather than the width of the card. */
.msg-order .input { width: auto; min-width: 240px; }

/* ---- The ticket, as paper (printer preview modal, v5.375.0) --------------
   Deliberately white in both themes: it is a photograph of a thermal roll,
   not a surface of the app. */
.paper-wrap { display: flex; justify-content: center; padding: 6px 0 2px; }
.paper {
  background: #fdfdf9; color: #222; width: min(320px, 100%);
  padding: 18px 16px 0; border-radius: 3px;
  box-shadow: 0 6px 18px rgba(0, 0, 0, .35);
  font-family: ui-monospace, 'SF Mono', Menlo, Consolas, monospace;
  font-size: 13px; line-height: 1.45;
}
.paper-line { white-space: pre-wrap; word-break: break-word; }
.paper-line.wide { font-size: 17px; font-weight: 700; letter-spacing: .04em; }
.paper-line.big { font-size: 20px; font-weight: 700; line-height: 1.6; }
.paper-line.small { font-size: 11px; color: #555; text-align: center; }
.paper-rule { border-top: 1px dashed #999; margin: 8px 0; }
.paper-qr {
  width: 84px; height: 84px; margin: 10px auto 4px; border: 2px solid #222;
  display: flex; align-items: center; justify-content: center;
  font-weight: 700; color: #222; letter-spacing: .1em;
}
.paper-cut { border-top: 2px dashed #bbb; margin-top: 14px; height: 12px; }

/* Printer connection truth + credentials (v5.376.0) */
.prn-live { margin: 6px 0 2px; font-size: 13px; line-height: 1.5; }
.prn-live.ok { color: var(--green); }
.prn-live.bad { color: var(--red); }
.prn-cred { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin: 6px 0; }
.prn-cred span { flex: 0 0 90px; color: var(--muted); font-size: 13px; }
.prn-cred code { word-break: break-all; font-size: 12px; flex: 1 1 160px; }

/* Hub venue name when the sidebar has pinned one venue (v5.378.0) */
.hub-venue-name { font-weight: 700; font-size: 17px; }

/* The × on a picked add-on tile in the item chooser (v5.379.0) */
.posm-flowtile { position: relative; }
.posm-flowtile .fl-x {
  position: absolute; top: 4px; right: 6px; font-style: normal;
  width: 22px; height: 22px; line-height: 22px; border-radius: 50%;
  background: rgba(0, 0, 0, .35); color: #fff; font-size: 15px;
  text-align: center; z-index: 1;
}

/* --- Paper tickets (v5.396.0) ------------------------------------------------
   Jamal, with a photograph of a coffee-bar KDS: "can you make it more like
   this" — white tickets on the dark board, a plain dark order number, the
   chips and the counting clock (which kdschips.js already drew from the same
   photograph) sitting on paper instead of on slate.

   Done as a re-skin, not a rewrite: the ticket's components read their colours
   from the custom properties below wherever they already used var(--text)/
   var(--muted)/var(--surface-2), so ticking, courses, the footer button and
   the source/dine/delivery colour systems all keep working. Only rules with
   hard-coded dark paint are overridden by name.

   What deliberately KEEPS its colour on the paper: the warn/late headers (an
   order going late must still shout across a pass), the parked green, rush and
   cancelled banners, the delivery band, buzzer chips, and any venue-set source
   colours — those override the paper exactly as they overrode the slate. */
#kds-board .kt, .kexample .kt {
  --text: #16181d;
  --muted: #6d7686;
  --surface: #fff;
  --surface-2: #f2f4f7;
  --border: #e4e7ee;
  background: #fff;
  border-color: var(--k-card-edge);
  box-shadow: var(--k-card-shadow);
  color: #16181d;
}
/* The header is paper too — in the photo the number sits straight on the
   card. The in-time tone keeps a thin green edge on the clock side of the
   state strip below rather than repainting the whole bar. */
#kds-board .kt .kt-head, .kexample .kt .kt-head {
  background: #fff;
  color: #16181d;
  border-bottom: 1px solid #eef0f4;
}
#kds-board .kt.ok .kt-head, .kexample .kt.ok .kt-head { background: #fff; color: #16181d; }
/* Jamal's header rule for the paper board (2026-08-28): *"the top bar should
   be white, unless the order is past the expected time, then it should turn
   red at the top, so they know its late."* So the amber halfway house goes —
   warn wears paper like ok, its clock pill still carries the amber — and only
   LATE repaints the bar. Parked keeps its green (his own earlier ask: a made
   order waiting to go out reads green across the pass) and cancelled keeps
   its dark red. */
#kds-board .kt.warn .kt-head, .kexample .kt.warn .kt-head { background: #fff; color: #16181d; }
#kds-board .kt.warn .kt-more { background: rgba(0, 0, 0, .07); }
#kds-board .kt.warn .kt-head:hover { filter: none; background: #fafbfc; }
#kds-board .kt.late .kt-head, .kexample .kt.late .kt-head { background: var(--red); color: #fff; }
#kds-board .kt.parked .kt-head, .kexample .kt.parked .kt-head { background: #12794b; color: #fff; }
#kds-board .kt.cancelled .kt-head, .kexample .kt.cancelled .kt-head { background: #6b1418; color: #fff; }
#kds-board .kt .kt-more, .kexample .kt .kt-more { background: rgba(0, 0, 0, .07); }
#kds-board .kt .kt-head:hover .kt-more { background: rgba(0, 0, 0, .13); }
#kds-board .kt.warn .kt-more, #kds-board .kt.late .kt-more,
#kds-board .kt.parked .kt-more, #kds-board .kt.cancelled .kt-more {
  background: rgba(255, 255, 255, .18);
}
#kds-board .kt .kt-head:hover, .kexample .kt .kt-head:hover { filter: none; background: #fafbfc; }
#kds-board .kt.warn .kt-head:hover, #kds-board .kt.late .kt-head:hover,
#kds-board .kt.parked .kt-head:hover { filter: brightness(1.08); }
/* The picked-up number chip: grey ink on paper. The buzzer stays amber. */
#kds-board .kt .kt-pick, .kexample .kt .kt-pick { background: rgba(0, 0, 0, .08); }
#kds-board .kt .kt-pick.buzzer, .kexample .kt .kt-pick.buzzer {
  background: var(--amber, #f5a623); color: #14171d;
}
/* The state strip on paper: a light band, its dot keeping the tone's colour. */
#kds-board .kt .kt-state, .kexample .kt .kt-state {
  background: #f2f4f7; color: #57606f; border-bottom: 1px solid #e9ebf1;
}
#kds-board .kt.parked .kt-state, .kexample .kt.parked .kt-state {
  background: #e7f6ee; color: #12794b; border-bottom-color: #cdebdb;
}
/* Modifiers read as quiet grey lines under their dish, as in the photo —
   still indented, no longer italic slate. */
#kds-board .kt .kt-line em, .kexample .kt .kt-line em { color: #7a8494; font-style: normal; }
#kds-board .kt .kt-line em.note, .kexample .kt .kt-line em.note { color: var(--amber); }
#kds-board .kt .kt-line em.alg, .kexample .kt .kt-line em.alg { color: #b91c1c; font-weight: 700; }
/* The hall board's kitchen strip, kept dark on purpose: on a white card it is
   now the single loudest thing, which is right — it is where to walk. */
/* The customer line wears a small person mark, as in the photo. */
.kt-who { display: flex; align-items: center; gap: 6px; }
.kt-who svg { flex: 0 0 auto; opacity: .65; }
/* The board behind the paper goes near-black, so the white cards carry the
   contrast the photograph has. */
.pos-kds-board { background: var(--k-ground); }

/* One-of-N settings as cards that light up — Toast's pattern, kept on Jamal's
   verdict (2026-08-30): "you select one or two or three options, and they have
   icons. The icons get highlighted… it's more obvious what's on at a time."
   The chosen card carries the accent; the rest sit back. */
.kset-cards { display: flex; gap: 8px; flex-wrap: wrap; justify-content: flex-end; }
.kset-card { display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 4px; padding: 8px 12px; min-width: 70px; min-height: 52px; border-radius: 10px;
  cursor: pointer; border: 1.5px solid var(--line, #2a2f38); background: transparent;
  color: inherit; font: inherit; font-size: 12.5px; line-height: 1.25; opacity: .72; }
.kset-card svg { display: block; }
.kset-card.on { border-color: #3b82f6; color: #3b82f6; opacity: 1;
  background: rgba(59, 130, 246, .09); font-weight: 700; }

/* Tabs — the open-tabs sheet. A row is the name, the shape of the bill, and
   the age; past twelve hours the age goes red and says so, because a tab that
   hides its age is Toast's open-since-two-weeks list. */
.pos-tabs { display: flex; flex-direction: column; gap: 8px; }
.pos-tab-row { display: grid; grid-template-columns: 1fr auto; gap: 2px 10px;
  text-align: left; padding: 10px 12px; border-radius: 10px; cursor: pointer;
  border: 1px solid var(--line, #2a2f38); background: transparent; color: inherit; font: inherit; }
.pos-tab-row:hover { border-color: #3b82f6; }
.pos-tab-name { font-weight: 800; font-size: 15px; }
.pos-tab-age { grid-row: 1 / 3; align-self: center; font-size: 12.5px; opacity: .8; }
.pos-tab-age.stale { color: #e5484d; font-weight: 700; opacity: 1; }
.pos-tab-sub { font-size: 12px; opacity: .65; }

/* --- The room, drawn on the till (T5 slice 2, 2026-08-30) ------------------
   Slice 1 gave the register a grid of squares. `tablemap.js` has known each
   table's shape, seat count and position since 2026-08-25 and the back office
   has had a drag editor for it just as long, so a waiter handed "table 14"
   still had to know where 14 was. This is the actual room.

   It reuses `.tm-top`, `.tm-chair` and `.tm-num` — the editor's own classes —
   deliberately: a manager arranging the room and a waiter reading it must not
   be looking at two different pictures. Only the states are new. */
.pos-tbl-tabs { display: flex; gap: 6px; overflow-x: auto; padding: 2px 0 10px; }
.pos-tbl-tab {
  flex: none; display: flex; align-items: center; gap: 6px;
  padding: 6px 11px; border-radius: 999px; cursor: pointer; font-size: 12.5px;
  border: 1px solid var(--line, #2a2f38); background: transparent; color: inherit; font-weight: 600;
}
.pos-tbl-tab span { opacity: .55; font-weight: 700; }
.pos-tbl-tab.on { border-color: #3b82f6; background: #1a2537; }
.pos-tbl-tab.on span { opacity: .85; }

.pos-tm-wrap { width: 100%; }
/* The viewBox is the grid itself, so a cell is a unit and nothing in the
   drawing has to know how wide the till is. */
.pos-tm-svg { display: block; width: 100%; height: auto; }
.pos-tm { cursor: pointer; }
.pos-tm .tm-num { pointer-events: none; }
.pos-tm-sub { font-size: .8px; font-weight: 600; fill: var(--muted); text-anchor: middle; }
.pos-tm:hover .tm-top { stroke: #3b82f6; }
/* Seated, in the same green the register uses everywhere else for "this is
   live" — and the chairs go with it, because a full table reads as a full
   table from across a room before any number is legible. */
.pos-tm.seated .tm-top { fill: #12241a; stroke: #1f8f4e; }
.pos-tm.seated .tm-chair { fill: #1f8f4e; }
.pos-tm.seated .pos-tm-sub { fill: #7bdca4; }
/* Sitting far too long — the tabs sheet's own judgement, carried through. */
.pos-tm.stale .tm-top { fill: #2a1618; stroke: #e5484d; }
.pos-tm.stale .tm-chair { fill: #e5484d; }
.pos-tm.stale .pos-tm-sub { fill: #ff9ea1; }
/* Out of use for guests scanning it, still seatable by a waiter. */
.pos-tm.off .tm-top { opacity: .45; }
.pos-tm.off .tm-num { opacity: .5; }

/* --- One line per sound (2026-08-30) ---------------------------------------
   Jamal, with three phone screenshots of the Sound section: *"Make this much
   more compact, test/sound picker/label all on one line."* It was a `kset-row`,
   which wraps by design — a label, then a full-width select, then a Play — so
   eight sounds took twenty-four lines of a phone.

   This row deliberately does NOT wrap. It is four things whose widths are
   known: a fixed icon column, a label that truncates, a picker sized to its
   content, and a round Play. Nothing here can grow enough to need a second
   line, which is the whole point. */
.snd-row {
  display: flex; align-items: center; gap: 6px;
  padding: 7px 0; border-bottom: 1px solid var(--border);
}
/* Every pixel spent on chrome here is a pixel taken off the event name, which
   is the one thing on the row that has to be read. The shared 30px icon column
   is narrowed and the row's own gaps tightened so "An order is cancelled" fits
   where it was being cut to "An order is cance…". */
.snd-row .kset-ic { width: 22px; align-self: center; margin-top: 0; }
/* The label is the only thing allowed to give ground, and it gives it by
   truncating rather than by wrapping — a two-line label would put this row
   back where it started. */
.snd-what {
  flex: 1 1 auto; min-width: 0; font-size: 13.5px; font-weight: 600;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
/* The picker. A button rather than a select, because a select cannot show the
   chosen sound's own glyph — which was the second half of the same request. */
.snd-pick {
  flex: 0 0 auto; display: flex; align-items: center; gap: 5px;
  max-width: 46vw; padding: 6px 6px 6px 8px; border-radius: 9px;
  border: 1px solid var(--line, #2a2f38); background: transparent;
  color: inherit; font: inherit; font-size: 12.5px; cursor: pointer;
}
.snd-pick:hover { border-color: #3b82f6; }
.snd-pick > span {
  min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-weight: 600;
}
.snd-pick svg { flex: none; }
.snd-pick svg:last-child { opacity: .5; transform: rotate(90deg); }
/* Round, because it does one thing and the word "Play" was costing more width
   than the icon it replaced. */
.snd-play {
  flex: none; width: 30px; height: 30px; display: flex;
  align-items: center; justify-content: center; padding: 0;
  border-radius: 50%; cursor: pointer;
  border: 1px solid var(--line, #2a2f38); background: transparent; color: inherit;
}
.snd-play:hover { border-color: #3b82f6; }
.snd-play svg { margin-left: 1px; }   /* a triangle looks left-heavy centred */

/* The chooser. Tapping a row plays it AND picks it — auditioning and choosing
   are one gesture, the way a phone picks a ringtone. */
.snd-list { display: flex; flex-direction: column; }
.snd-opt {
  display: flex; align-items: center; gap: 11px; width: 100%;
  padding: 9px 4px; text-align: left; cursor: pointer;
  border: 0; border-bottom: 1px solid var(--border);
  background: transparent; color: inherit; font: inherit;
}
.snd-opt:last-child { border-bottom: 0; }
.snd-opt:hover { background: rgba(255,255,255,.04); }
.snd-opt-ic { flex: none; width: 26px; display: flex; justify-content: center; color: var(--muted); }
.snd-opt.on .snd-opt-ic { color: #4ade80; }
.snd-opt-txt { flex: 1 1 auto; min-width: 0; }
.snd-opt-txt strong { display: block; font-size: 14px; font-weight: 700; }
.snd-opt-txt em { display: block; font-style: normal; font-size: 11.5px; color: var(--muted); margin-top: 1px; }
.snd-opt-tick { flex: none; width: 18px; color: #4ade80; }

/* --- The room, on the till (T5 slice 1, 2026-08-30) ------------------------
   A waiter reads a floorplan by its shape, so the squares are square and the
   number is the biggest thing on each. Seated is green — the same green the
   rest of the register uses for "this is live" — and free is the plain outline
   every other tappable thing here wears, so an empty room reads as empty at a
   glance rather than as a wall of buttons.

   `auto-fill` rather than a column count: a bar with four tables and a
   restaurant with a hundred are the same screen, and a fixed grid would leave
   one of them with four squares stretched across a metre of till. */
.pos-tbl-area { margin: 14px 0 8px; font-size: 12px; letter-spacing: .08em;
  text-transform: uppercase; opacity: .6; font-weight: 800; }
.pos-tbl-area:first-child { margin-top: 4px; }
.pos-tbl-grid { display: grid; gap: 8px;
  grid-template-columns: repeat(auto-fill, minmax(92px, 1fr)); }
.pos-tbl { display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 2px; aspect-ratio: 1 / 1; min-height: 78px; padding: 6px;
  border-radius: 12px; cursor: pointer; text-align: center;
  border: 1px solid var(--line, #2a2f38); background: transparent; color: inherit; font: inherit; }
.pos-tbl:hover { border-color: #3b82f6; }
.pos-tbl b { font-size: 21px; font-weight: 800; line-height: 1.1; }
.pos-tbl span { font-size: 11.5px; opacity: .6; }
.pos-tbl em { font-size: 11.5px; font-style: normal; opacity: .85; font-weight: 700; }
/* Seated. */
.pos-tbl.on { border-color: #1f8f4e; background: #12241a; }
.pos-tbl.on span, .pos-tbl.on em { opacity: .9; }
/* Sitting far too long — the tabs sheet's own judgement, carried through so
   the two screens cannot disagree about which bill is worrying. */
.pos-tbl.on.stale { border-color: #e5484d; background: #2a1618; }
.pos-tbl.on.stale em { color: #ff9ea1; }
/* Out of use for guests scanning it, but still seatable by a waiter — refusal
   1 in tableseat.js. Dimmed, never disabled. */
.pos-tbl.snoozed b { opacity: .55; }
.pos-tbl.snoozed::after { content: 'snoozed'; font-size: 10px; opacity: .5;
  letter-spacing: .04em; }
/* A bill whose table has been renamed away. Wider, because it carries a name
   rather than a number. */
.pos-tbl.lost { aspect-ratio: auto; min-height: 62px; border-color: #b8860b; background: #241f12; }
.pos-tbl.lost b { font-size: 15px; }

/* --- Tickets left on the pass (T4 slice 4, 2026-08-30) ---------------------
   The count rides on the Recent button because that is where they can be put
   back from — a chef should not have to know to look. Amber rather than red:
   nothing is wrong yet, something has just been sitting. */
.pos-kds-tool.has-stale, .pos-kds-filter.has-stale { position: relative; }
.pos-kds-tool.has-stale::after, .pos-kds-filter.has-stale::after {
  content: attr(data-stale);
  position: absolute; top: -6px; right: -6px; min-width: 17px; height: 17px;
  padding: 0 4px; border-radius: 999px; background: #f0ad4e; color: #1b1300;
  font-size: 11px; font-weight: 800; line-height: 17px; text-align: center;
}
.recent-head { margin: 2px 0 4px; font-size: 13px; letter-spacing: .04em;
  text-transform: uppercase; color: var(--k-muted); }

/* --- The revenue hub (#236) ------------------------------------------------
   A screen about money somebody else is about to have taken off them, so the
   thing it says loudest is that nothing has been taken yet. */
.rh-note { background: rgba(200,150,40,.10); border-color: #6b5a22; margin-bottom: 14px; }
/* Which hat you are wearing: a 1-of-N pick, so a segmented row rather than
   three cards. The first pass stacked three full-width cards with their
   explanations, which pushed every actual charge below the fold on a phone and
   rendered the unselected two at a contrast nobody could read. The explanation
   belongs to the one in use, once, underneath. */
.rh-hats { display: flex; gap: 6px; flex-wrap: wrap; margin-bottom: 6px; }
/* An unselected one still has to look like a button. With only var(--line) and
   var(--card) it had no visible surface at all and read as a heading — two of
   the three looked like section titles somebody had left in. */
.rh-hat { flex: 0 1 auto; padding: 9px 13px; border-radius: 10px; font-size: 13.5px;
  font-weight: 650; border: 1px solid #2a3550; background: rgba(255,255,255,.045);
  color: #cfd7e6; cursor: pointer; }
.rh-hat:hover { border-color: #3b6ff5; color: #e7ecf7; }
.rh-hat.on { border-color: #3b6ff5; background: rgba(59,111,245,.18); color: #8fb4ff; }
.rh-hat-what { margin: 0 0 14px; }

.rh-tot { display: flex; flex-wrap: wrap; gap: 18px 28px; }
.rh-tot > div { min-width: 100px; }
.rh-k { display: block; font-size: 12px; opacity: .65; margin-bottom: 2px; }
.rh-tot b { font-size: 20px; font-variant-numeric: tabular-nums; }
.rh-bad { color: #e8734a; }

.rh-stall { margin: 14px 0; border-color: #6b3a22; }
.rh-stall-row { display: flex; flex-wrap: wrap; gap: 4px 12px; justify-content: space-between;
  padding: 8px 0; border-top: 1px solid var(--line); }

.rh-bar { display: flex; gap: 10px; align-items: center; margin: 14px 0; flex-wrap: wrap; }
.rh-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: 12px; }
.rh-wide { grid-column: 1 / -1; }
.rh-actions { display: flex; gap: 10px; justify-content: flex-end; margin-top: 12px; flex-wrap: wrap; }
.rh-plan { margin: 14px 0; border-color: #2b4a8f; }

/* The floor modes, as cards whose whole body is the explanation — the
   toast-parity rule: a short 1-of-N choice is cards, not a dropdown. */
.rh-modes { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  gap: 10px; margin: 12px 0; }
.rh-mode { text-align: left; padding: 12px; border-radius: 12px; border: 1px solid var(--line);
  background: var(--card); cursor: pointer; }
.rh-mode b { display: block; margin-bottom: 4px; }
.rh-mode span { font-size: 12px; opacity: .7; }
.rh-mode.on { border-color: #3b6ff5; box-shadow: inset 0 0 0 1px #3b6ff5; }

.pill.rh-open { background: rgba(200,150,40,.16); color: #e0b95a; }
.pill.rh-part { background: rgba(59,111,245,.16); color: #8fb4ff; }
.pill.rh-settled { background: rgba(31,157,97,.16); color: #6fe0ac; }
.pill.rh-held, .pill.rh-disputed { background: rgba(232,115,74,.16); color: #e8734a; }
.pill.rh-written_off { background: rgba(140,150,170,.16); color: #97a3b8; }

/* Rule C: it has to read on a phone. The table becomes rows that wrap rather
   than a horizontal scroller nobody finds the right-hand end of. */
@media (max-width: 720px) {
  /* A reflowed table row with no headers is a column of unlabelled numbers —
     "£2000.00" twice, once the charge and once what is left, with nothing
     saying which. Every cell carries its own label instead. */
  .rh-tbl thead { display: none; }
  .rh-tbl tr { display: block; padding: 12px 0; border-top: 1px solid var(--line); }
  .rh-tbl td { display: flex; justify-content: space-between; gap: 12px;
    border: 0; padding: 3px 0; text-align: left; }
  .rh-tbl td.r { text-align: right; }
  .rh-tbl td[data-l]:not([data-l=""])::before {
    content: attr(data-l); opacity: .6; font-size: 12.5px; flex: 0 0 auto; }
  .rh-tbl td[data-l=""] { justify-content: flex-end; padding-top: 6px; }
  .rh-tot b { font-size: 17px; }
}

/* --- What the hall has charged this kitchen (#237) -------------------------
   A kitchen reading its own money screen, so the loudest thing is what is
   coming off and why. Never a state machine, never an unexplained number. */
.mn-charges .mn-ch-h { margin: 18px 0 8px; font-size: 13px; text-transform: uppercase;
  letter-spacing: .04em; opacity: .6; }
.mn-ch-list { display: flex; flex-direction: column; gap: 8px; }
.mn-ch { border: 1px solid var(--line); border-radius: 12px; padding: 11px 13px;
  background: rgba(255,255,255,.03); }
.mn-ch.od { border-color: #6b3a22; }
.mn-ch-top { display: flex; justify-content: space-between; align-items: baseline; gap: 12px; }
.mn-ch-amt { font-weight: 800; font-variant-numeric: tabular-nums; white-space: nowrap; }
.mn-ch-sub { display: flex; justify-content: space-between; align-items: center;
  gap: 10px; flex-wrap: wrap; margin-top: 5px; font-size: 12.5px; opacity: .75; }
.mn-ch-q { color: #e0b95a; font-size: 12.5px; }
.mn-ch-note { margin-top: 7px; padding-top: 7px; border-top: 1px solid var(--line);
  font-size: 12.5px; opacity: .8; font-style: italic; }
.mn-ch-says { margin: 2px 0 12px 42px; }
/* A planned take is not money that has left, so it does not wear the same icon
   as one that has. */
.mn-ico-plan { opacity: .55; }
.mn-ch-warn { border: 1px solid #6b5a22; background: rgba(200,150,40,.10);
  border-radius: 10px; padding: 9px 11px; margin-top: 12px; }


/* A charge the kitchen has queried. Loud on the hub, and deliberately not a
   freeze — Jamal's call: recovery carries on until the hall says otherwise. */
.pill.rh-queried { background: rgba(200,150,40,.16); color: #e0b95a; margin-left: 6px; }
.rh-vat { display: block; max-width: 200px; margin-top: 12px; }

/* --- How hands-on a group is (#247, v5.489.0) --------------------------------
   The stance picker: three cards whose icons light up, then the eight areas
   individually. `.kset-card` is shared with the till's settings on purpose —
   the same question asked the same way in both places (Jamal, 2026-08-30:
   "you select one or two or three options, and they have icons. The icons get
   highlighted… it's more obvious what's on at a time"). */
.gr-sheet .kset-card { text-align: center; }
.gr-areas { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; }
@media (max-width: 640px) { .gr-areas { grid-template-columns: 1fr; } }
.gr-area { display: flex; align-items: flex-start; gap: 9px; padding: 10px 11px;
  border: 1.5px solid var(--line, #2a2f38); border-radius: 10px; cursor: pointer;
  font-size: 13px; line-height: 1.3; }
.gr-area:hover { border-color: #3b82f6; }
.gr-area.on { border-color: #3b82f6; background: rgba(59, 130, 246, .08); }
.gr-area input { margin-top: 2px; flex: none; }
.gr-area .hint { font-size: 11.5px; }
/* The kitchen's row. "Set apart" is the one worth noticing at a glance — it is
   the exception somebody made on purpose and will later wonder about. */
.gr-note { margin-top: 3px; font-size: 11.5px; }
.gr-note.apart { color: #3b82f6; font-weight: 600; }
/* What the group changed, on the kitchen's own dashboard. Unread carries the
   accent; read sits back but stays — a record a vendor can clear out of
   existence is one they cannot go back to when a customer complains. */
.gr-changes { display: flex; flex-direction: column; gap: 6px; }
.gr-change { padding: 8px 10px; border-radius: 8px; font-size: 13px;
  border: 1px solid var(--line, #2a2f38); opacity: .7; }
.gr-change.new { opacity: 1; border-color: #3b82f6; background: rgba(59, 130, 246, .07); }
/* The stance in a table cell — capped, because the full sentence ran off the
   right edge and carried the row's own buttons with it. */
.gr-cell { max-width: 230px; }
.gr-cell .hint { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
/* A boxed note inside the stance sheet. `.notice` was used first and is not a
   class this app has, so both of these rendered as plain body copy — including
   the one warning that you are about to set a single kitchen apart from the
   rest, which is the sentence that most needed to look like a warning. */
.gr-notice { padding: 9px 11px; border-radius: 9px; font-size: 13px; line-height: 1.4;
  border: 1px solid var(--line, #2a2f38); background: rgba(127, 127, 127, .06); }
.gr-notice.warn { border-color: #d99a2b; background: rgba(217, 154, 43, .09); }

/* --- The company menu: what a push would do, kitchen by kitchen (#245) -------
   One row per kitchen, and three states that have to be distinguishable at a
   glance: changing, already matching, and held by somebody above. The last is
   the one that must never read as "fine" — it is a kitchen that will keep the
   old price. */
.mm-rows { display: flex; flex-direction: column; gap: 6px; }
.mm-row { display: grid; grid-template-columns: minmax(120px, 1fr) 2fr; gap: 10px;
  align-items: center; padding: 9px 11px; border-radius: 9px;
  border: 1px solid var(--line, #2a2f38); font-size: 13px; }
.mm-row.same { opacity: .55; }
.mm-row.blocked { border-color: #d99a2b; background: rgba(217, 154, 43, .07); }
.mm-site { font-weight: 700; }
.mm-what { display: flex; flex-wrap: wrap; gap: 4px 12px; align-items: center; }
.mm-chg { display: inline-flex; gap: 6px; align-items: baseline; }
.mm-lab { opacity: .6; font-size: 11.5px; }
.mm-from { opacity: .65; text-decoration: line-through; }
.mm-to { font-weight: 700; color: #3b82f6; }
.mm-held { color: #d99a2b; font-weight: 600; }
.mm-pick { display: inline-flex; gap: 7px; align-items: center; cursor: pointer; }
@media (max-width: 640px) { .mm-row { grid-template-columns: 1fr; gap: 3px; } }

/* --- Every kitchen, ranked (#245, v5.493.0) ---------------------------------
   Three states have to be distinguishable at a glance and one of them must not
   look like an emergency: a branch that has taken nothing at 9am is grey, not
   red, or the screen gets ignored by 10. */
.est-bar { margin: 0 0 12px; }
.est-summary { margin: 0 0 10px; }
.est-attention { margin: 0 0 14px; }
.est-pos { display: inline-block; min-width: 20px; opacity: .5; font-variant-numeric: tabular-nums; }
.est-flags { display: flex; flex-wrap: wrap; gap: 5px; margin-top: 4px; }
.est-flag { font-size: 11.5px; padding: 1px 7px; border-radius: 999px;
  border: 1px solid var(--line, #2a2f38); }
.est-flag.amber { color: #d99a2b; border-color: #d99a2b; }
.est-flag.grey { opacity: .6; }
tr.est-amber td { background: rgba(217, 154, 43, .05); }
/* A sortable heading has to look like one, and the live sort has to be obvious
   — otherwise the table silently reorders and nobody knows why. */
.est-sort { cursor: pointer; user-select: none; }
.est-sort:hover { color: #3b82f6; }
.est-sort.on { color: #3b82f6; font-weight: 800; }
.est-sort.on::after { content: ' ↓'; }
.est-caveat { color: #d99a2b; font-size: 11px; font-weight: 600; }

/* ---------------------------------------------------------------------------
   **A kitchen screen has no mouse, and `:hover` sticks to whatever you touched
   last** (v5.505.0).

   Jamal, 2026-08-31, with a photo of a ticket: *"the double tap should be a
   button of the whole ticket, not just the bottom one. It lets you double tap
   anywhere, but it has multiple buttons which makes it look bad, like the one
   you can see I pressed."*

   The line he pressed was wearing `.kt-line:hover { background: var(--surface-2) }`.
   On a touch screen a tap leaves the hover state applied until something else
   is touched, so one row of the ticket sat in a grey box looking like a pressed
   button while the rows around it did not. Nothing was styled as several
   buttons — one row was stuck looking like one.

   The whole stylesheet had **zero** hover guards, and the KDS is the one screen
   that is always glass. These are the ticket's ten, neutralised where the
   device cannot hover; the pointer versions above are untouched, so a desktop
   board keeps its feedback.

   Scoped to the ticket rather than swept across the file: a blanket change to
   every hover rule in ten thousand lines is not something to ship off one
   photo. --------------------------------------------------------------------- */
@media (hover: none) {
  .kt-line:hover { background: none; }
  .kt-line:hover .kt-tick { border-color: #4a5060; }
  /* `.done` still wins — it is state, not feedback, and it is drawn below. */
  .kt-line.done:hover { background: rgba(46, 160, 87, .10); }
  .kt-line.done:hover .kt-tick { border-color: var(--green); }
  .kt-head:hover { filter: none; }
  .kt-head:hover .kt-more { background: rgba(255, 255, 255, .12); opacity: .6; }
  .kt-btn:hover { background: inherit; color: inherit; }
  .kt-btn.go:hover { filter: none; }
  .kt-btn.cancel:hover { background: #232830; }
  .kt.parked .kt-btn:hover { background: #1f9d61; }
  .kt.srcbtn .kt-btn:hover { filter: none; }
  .kt.picking:hover { border-color: transparent; }

  /* **And the ticket itself answers the tap.** Jamal's other half: the card is
     the control, so pressing it should look like pressing it — one surface
     responding, rather than whichever row happened to be under the finger. */
  .kt:active { filter: brightness(1.06); }
}

/* The receipt question on the completed-payment screen (v5.508.0). Only drawn
   when the venue switched it on — see POS_TOGGLES.receipt_ask_after. */
.pos-paid-ask { margin: 14px 0 10px; padding: 12px; border: 1px solid var(--border);
  border-radius: 10px; background: var(--surface-2); }
.pos-paid-askq { font-weight: 700; font-size: 15px; text-align: center; margin-bottom: 10px; }
.pos-paid-askrow { display: flex; gap: 8px; }
.pos-paid-askrow .btn { flex: 1; justify-content: center; min-height: 44px; }
.pos-paid-askmail { width: 100%; box-sizing: border-box; margin-top: 8px; min-height: 44px; }

/* What the middle step actually is, under each order type on the kitchen
   settings sheet (v5.509.0). */
.kset-steps { margin: 8px 0 2px; padding: 8px 10px; border-radius: 8px;
  background: var(--surface-2); border: 1px solid var(--border); }
.kset-steps-cap { font-size: 10.5px; font-weight: 700; letter-spacing: .07em;
  text-transform: uppercase; color: var(--muted); margin-bottom: 6px; }
.kset-step { display: flex; align-items: baseline; gap: 10px;
  padding: 5px 0; border-top: 1px solid var(--border); }
.kset-step:first-of-type { border-top: 0; }
.kset-step em { font-style: normal; font-size: 12px; color: var(--muted); flex: 1 1 auto; }
.kset-step strong { font-size: 13.5px; text-align: right; flex: 0 0 auto; }

/* A machine's guess about allergens, waiting for a person to agree with it.
   Sits above the kitchen's own answers because it is the thing needing doing,
   and is visibly a suggestion rather than a setting — dashed edge, quieter
   ground — so nobody reads it as something the venue has already said. */
.ta-ai { margin: 10px 0 14px; }
.ta-ai-idle { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.ta-ai-card { border: 1px dashed var(--border); border-radius: 10px;
  padding: 12px 14px; background: color-mix(in srgb, var(--surface) 82%, transparent); }
.ta-ai-sum { margin: 0 0 8px; font-size: 14px; font-weight: 600; }
.ta-ai-rows { display: grid; gap: 6px; margin: 8px 0; }
.ta-ai-row { display: flex; align-items: flex-start; gap: 8px; font-size: 14px; }
.ta-ai-row input { width: auto; margin-top: 2px; flex: none; }
.ta-ai-clash { margin: 6px 0; padding-left: 18px; font-size: 13px; color: var(--amber); }
.ta-ai-clash li.bad { color: var(--red); font-weight: 600; }

/* **The Menu screen's warnings, one line each** (v5.619.0). Jamal: *"way too
   big… take up one line, be really simple, and then maybe have drop-downs."*
   A row is an icon, the sentence, the button that fixes it and a chevron; the
   paragraphs live under the row and open on tap. Amber keeps marking the nags
   with teeth (allergens, no menu, cost); the rest are plain. */
.menu-alerts { display: flex; flex-direction: column; gap: 6px; margin: 0 0 14px; }
.menu-alert { border: 1px solid var(--border); border-radius: 10px; background: var(--surface); }
.menu-alert.warn { border-left: 3px solid #e8a33d; }
.menu-alert-row { display: flex; align-items: center; gap: 8px; padding: 4px 8px 4px 4px; }
.menu-alert-head {
  flex: 1 1 auto; min-width: 0; display: flex; align-items: center; gap: 10px;
  background: none; border: 0; color: var(--text); font: inherit; font-size: 13.5px; font-weight: 600;
  padding: 6px 6px; text-align: left; cursor: pointer; border-radius: 8px;
}
.menu-alert-head:hover { background: var(--surface-2); }
.menu-alert-ico { flex: 0 0 auto; display: inline-flex; color: var(--muted); }
.menu-alert.warn .menu-alert-ico { color: #e8a33d; }
.menu-alert-txt { flex: 1 1 auto; min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.menu-alert-chev { flex: 0 0 auto; color: var(--muted); font-size: 12px; transition: transform .14s ease; }
.menu-alert.open .menu-alert-chev { transform: rotate(180deg); }
.menu-alert-act { flex: 0 0 auto; display: flex; align-items: center; gap: 6px; }
.menu-alert-body { padding: 2px 14px 12px 42px; }
.menu-alert-body .hint { margin: 0 0 8px; }
.menu-alert-body .hint:last-child { margin-bottom: 0; }
@media (max-width: 720px) {
  .menu-alert-row { flex-wrap: wrap; }
  .menu-alert-txt { white-space: normal; }
  .menu-alert-act { width: 100%; padding: 0 0 6px 42px; }
  .menu-alert-body { padding-left: 14px; }
}

/* **When it shows** (showwhen.js, v5.621.0) — the schedule block on the
   section sheet and the item editor. Hidden switch, Always / Only at these
   times, a row per weekday, Copy to every day. */
.sec-sheet-when { margin: 12px 0 0; padding: 12px; border: 1px solid var(--border); border-radius: 10px; }
.sec-sheet-when .field-label { display: block; font-size: 12.5px; font-weight: 600; color: var(--muted); margin-bottom: 8px; }
.shw-hidden { display: flex; align-items: center; gap: 8px; padding: 8px 10px; border-radius: 8px; border: 1px solid var(--border); background: var(--surface-2); cursor: pointer; }
.shw-hidden.on { border-color: rgba(201, 162, 74, .5); background: rgba(201, 162, 74, .1); }
.shw-hidden input { width: 16px; height: 16px; margin: 0; }
.shw-mode { display: flex; gap: 16px; flex-wrap: wrap; margin: 10px 0 6px; font-size: 13.5px; }
.shw-mode label { display: inline-flex; align-items: center; gap: 6px; cursor: pointer; }
.shw-days { display: flex; flex-direction: column; gap: 4px; margin-top: 6px; }
.shw-day { display: grid; grid-template-columns: 72px 1fr 24px 1fr auto; align-items: center; gap: 6px; padding: 3px 0; opacity: .6; }
.shw-day.on { opacity: 1; }
.shw-daylbl { display: inline-flex; align-items: center; gap: 6px; font-weight: 600; font-size: 13px; cursor: pointer; }
.shw-daylbl input { width: 15px; height: 15px; margin: 0; }
.shw-day input[type=time] { min-width: 0; padding: 5px 6px; font: inherit; font-size: 13px; border-radius: 7px; border: 1px solid var(--border); background: var(--surface-2); color: var(--text); }
.shw-to { text-align: center; color: var(--muted); font-size: 12px; }
.shw-copy { white-space: nowrap; font-size: 11.5px; padding: 3px 8px; }
.shw-says { margin: 10px 0 0; font-size: 13px; font-weight: 600; color: var(--text); }
.menu-vis.off-hours { color: var(--muted); border-color: var(--border); background: var(--surface-2); }
@media (max-width: 600px) {
  .shw-day { grid-template-columns: 62px 1fr 18px 1fr; }
  .shw-copy { grid-column: 1 / -1; justify-self: start; }
}

/* **The item editor's Basics tab** (v5.622.0): where it shows first, names, the
   tile drawn live, the photo as one block, when it shows. */
.mb { display: flex; flex-direction: column; gap: 12px; }
.mb-block { border: 1px solid var(--border); border-radius: 10px; padding: 12px 14px; }
.mb-h { font-size: 12.5px; font-weight: 700; color: var(--muted); text-transform: uppercase; letter-spacing: .04em; margin: 0 0 10px; display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap; }
.mb-h .hint { text-transform: none; letter-spacing: 0; font-weight: 500; }
.mb-toggle { display: flex; align-items: center; gap: 10px; padding: 9px 12px; border-radius: 9px; border: 1px solid var(--border); background: var(--surface-2); cursor: pointer; }
.mb-toggle.on { border-color: rgba(52, 199, 89, .45); background: rgba(52, 199, 89, .08); }
.mb-toggle input { width: 18px; height: 18px; margin: 0; }
.mb-chips { display: flex; flex-wrap: wrap; gap: 8px; margin: 10px 0 12px; }
.mb-chip { display: inline-flex; align-items: center; gap: 6px; padding: 5px 10px; border-radius: 999px; border: 1px solid var(--border); font-size: 12.5px; color: var(--muted); }
.mb-chip svg { width: 14px; height: 14px; }
.mb-chip b { color: var(--text); font-weight: 600; }
.mb-chip.on { color: #34c759; border-color: rgba(52, 199, 89, .45); background: rgba(52, 199, 89, .08); }
.mb-chip.off { color: #c9a24a; border-color: rgba(201, 162, 74, .45); background: rgba(201, 162, 74, .08); }
.mb-where h4 { margin: 12px 0 4px; }
.mb-tile-row { display: flex; gap: 16px; align-items: flex-start; flex-wrap: wrap; }
.mb-tile { flex: 0 0 auto; }
.mb-photo { display: flex; align-items: center; gap: 14px; flex-wrap: wrap; }
.mb-photo .qh-thumb { flex: 0 0 auto; }
.mb-photo-acts { display: flex; gap: 8px; flex-wrap: wrap; }
.mb-photo-acts .btn input[type=file] { display: none; }
.mb .form-grid { margin: 0; }
/* The Preview tab, apart on the right and outlined — a door, not a sibling. */
.mi-tab-preview { margin-left: auto; border: 1px solid var(--border); border-radius: 999px; padding: 6px 12px; align-self: center; }
.mi-tab-preview.active { border-color: var(--primary); background: rgba(79, 140, 255, .1); }
/* Nutrition: calories and the typed-by-hand list. */
.ta-kcal { display: grid; grid-template-columns: 120px auto; align-items: center; gap: 8px 10px; margin: 6px 0 14px; }
.ta-kcal input { width: 120px; }
.ta-kcal-unit { color: var(--muted); font-weight: 600; }
.ta-kcal .hint { grid-column: 1 / -1; margin: 0; }
.ta-byhand { display: flex; flex-direction: column; gap: 6px; margin: 6px 0 4px; }
.ta-byhand-on { display: flex; align-items: center; gap: 8px; cursor: pointer; }
.ta-byhand-on input { width: 16px; height: 16px; margin: 0; }
/* The not-before date in the schedule block. */
.shw-from { display: flex; flex-direction: column; gap: 6px; margin: 12px 0 0; font-size: 13px; font-weight: 600; }
.shw-from input { width: min(100%, 260px); padding: 6px 8px; font: inherit; border-radius: 7px; border: 1px solid var(--border); background: var(--surface-2); color: var(--text); }


/* --- Ask HelchPOS (v5.668.0) ------------------------------------------------
   Jamal, 2026-09-09, from three Toast IQ screenshots. The bar sits at the top
   of the Dashboard; the panel comes in down the right on a desktop and takes
   the bottom of the screen on a phone, which is UX rule 9 — a quick action is
   a bottom sheet on a handset, not a column squeezed to nothing. */
.ask-bar { margin-bottom: 16px; }
.ask-form { display: flex; align-items: center; gap: 8px; }
.ask-spark { color: var(--primary); display: flex; }
.ask-form input {
  flex: 1; min-width: 0; border: 1px solid var(--border); border-radius: 8px;
  padding: 9px 11px; font-size: 13.5px; background: var(--surface); color: var(--text);
}
.ask-form input:focus { outline: 2px solid var(--primary); outline-offset: -1px; }
.ask-beta {
  font-size: 9.5px; text-transform: uppercase; letter-spacing: .06em; font-weight: 700;
  color: var(--primary); border: 1px solid var(--primary); border-radius: 999px;
  padding: 2px 6px; white-space: nowrap;
}
.ask-starters { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 10px; }
.ask-chip {
  border: 1px solid var(--border); background: var(--surface); color: var(--text);
  border-radius: 999px; padding: 5px 11px; font-size: 12px; cursor: pointer;
}
.ask-chip:hover { border-color: var(--primary); color: var(--primary); }

.ask-panel {
  position: fixed; right: 0; top: 0; bottom: 0; width: min(420px, 92vw);
  background: var(--surface); border-left: 1px solid var(--border);
  box-shadow: -8px 0 28px rgba(0, 0, 0, .12); z-index: 60;
  display: flex; flex-direction: column;
}
.ask-panel-head {
  display: flex; align-items: center; justify-content: space-between; gap: 8px;
  padding: 14px 16px; border-bottom: 1px solid var(--border);
}
.ask-panel-head h3 { display: flex; align-items: center; gap: 8px; margin: 0; font-size: 14.5px; }
.ask-x { background: none; border: 0; font-size: 22px; line-height: 1; color: var(--muted); cursor: pointer; }
.ask-turns { flex: 1; overflow-y: auto; padding: 14px 16px; }
.ask-turn + .ask-turn { margin-top: 18px; border-top: 1px solid var(--border); padding-top: 18px; }
.ask-q { font-weight: 600; font-size: 13.5px; margin-bottom: 8px; }
.ask-a { font-size: 13.5px; line-height: 1.55; white-space: pre-wrap; }
.ask-bad { color: var(--red); }
.ask-thinking { display: flex; align-items: center; gap: 8px; color: var(--muted); font-size: 13px; }
.ask-dots { display: inline-flex; gap: 3px; }
.ask-dots i {
  width: 5px; height: 5px; border-radius: 50%; background: var(--primary);
  animation: ask-blink 1.1s infinite ease-in-out;
}
.ask-dots i:nth-child(2) { animation-delay: .15s; }
.ask-dots i:nth-child(3) { animation-delay: .3s; }
@keyframes ask-blink { 0%, 80%, 100% { opacity: .25; } 40% { opacity: 1; } }
@media (prefers-reduced-motion: reduce) { .ask-dots i { animation: none; opacity: .6; } }
/* The prose placeholder, while part two is still being written. It carries the
   same font-size and line-height as .ask-a and a min-height of one line, so the
   figures below it barely move when the real sentence replaces it. */
.ask-writing {
  display: flex; align-items: center; gap: 8px;
  /* flex, so the pre-wrap the answer needs cannot put the dots on their own
     line; one line of reserved height, which is what a short answer occupies. */
  white-space: normal; color: var(--muted); min-height: 21px;
}
.ask-figs { margin-top: 10px; }
.ask-figs summary { font-size: 12px; color: var(--muted); cursor: pointer; }
.ask-figs .dash-table { margin-top: 8px; }
.ask-list { margin-top: 10px; }
.ask-list-h {
  font-size: 10.5px; text-transform: uppercase; letter-spacing: .04em;
  color: var(--muted); margin-bottom: 4px;
}
.ask-foot { padding: 10px 16px; border-top: 1px solid var(--border); margin: 0; font-size: 11.5px; }

/* UX rule 9 on a handset: a sheet from the bottom, not a sliver down the side. */
@media (max-width: 640px) {
  .ask-panel {
    left: 0; right: 0; top: auto; width: auto; height: 78vh;
    border-left: 0; border-top: 1px solid var(--border); border-radius: 14px 14px 0 0;
    box-shadow: 0 -8px 28px rgba(0, 0, 0, .18);
  }
}

/* --- Performance heatmap (v5.721.0) ---------------------------------------
   The week as 7 x 24. Revly's grid in our language.

   The ramp is one hue at five lightnesses rather than a rainbow: the eye reads
   "more" off saturation reliably and off hue not at all, and a red-to-green
   scale is invisible to the commonest form of colour blindness — on a screen
   whose entire job is comparing squares.

   `hm-shut` is the decision the whole card turns on: an hour nobody ordered in
   is drawn as a faint outline with nothing in it, NOT as the darkest square.
   A closed kitchen is not a bad hour. */
.hm-wrap { --hm-gap: 3px; display: flex; flex-direction: column; gap: var(--hm-gap);
  overflow-x: auto; padding-bottom: 4px; }
.hm-row { display: grid; grid-template-columns: 56px repeat(7, minmax(44px, 1fr));
  gap: var(--hm-gap); align-items: stretch; min-width: 360px; }
.hm-hour { font-size: 11px; color: var(--muted, #8b93a7); display: flex;
  align-items: center; justify-content: flex-end; padding-right: 6px; white-space: nowrap; }
.hm-col { font-size: 11px; color: var(--muted, #8b93a7); text-align: center; padding-bottom: 2px; }
.hm-head { position: sticky; top: 0; }
.hm-cell { display: flex; align-items: center; justify-content: center;
  min-height: 22px; border-radius: 5px; font-size: 10.5px; font-variant-numeric: tabular-nums;
  color: #fff; background: transparent; }
.hm-b0 { background: rgba(79, 140, 255, 0.18); color: var(--text, #e6e9ef); }
.hm-b1 { background: rgba(79, 140, 255, 0.36); color: var(--text, #e6e9ef); }
.hm-b2 { background: rgba(79, 140, 255, 0.56); }
.hm-b3 { background: rgba(79, 140, 255, 0.78); }
.hm-b4 { background: rgba(79, 140, 255, 1); }
/* Nothing sold. Not a zero, not the worst hour — an outline and no number. */
.hm-shut { background: transparent; border: 1px dashed rgba(139, 147, 167, 0.22); }
/* Traded, but too few orders for the average to mean anything. */
.hm-thin { background: rgba(139, 147, 167, 0.14); color: var(--muted, #8b93a7); }
/* The shortlist. An outline rather than another colour, so it stacks on the
   ramp instead of competing with it. */
.hm-pick { outline: 2px solid #f59e0b; outline-offset: -2px; }
.hm-legend { display: flex; align-items: center; gap: 6px; flex-wrap: wrap;
  margin-top: 12px; font-size: 11px; color: var(--muted, #8b93a7); }
.hm-legend i { width: 34px; height: 12px; border-radius: 3px; display: inline-block; }
.hm-key { display: inline-flex; align-items: center; gap: 5px; margin-left: 10px; }
.hm-key i { width: 14px; }
.hm-metric { padding: 6px 10px; border-radius: 8px; font-size: 13px;
  width: auto; min-width: 140px; flex: 0 0 auto; }

/* An hour nobody traded in all week. Kept — it is how you read the shape of
   the trading day — but at a third of the height, because eleven shut rows at
   full size were half the card saying nothing. */
.hm-row-dead .hm-cell { min-height: 8px; }
.hm-row-dead .hm-hour { font-size: 9.5px; opacity: 0.45; }

/* A phone reads the grid by scrolling it sideways inside its own box — the
   page itself must never scroll horizontally. */
@media (max-width: 640px) {
  .hm-row { grid-template-columns: 44px repeat(7, minmax(38px, 1fr)); }
  .hm-cell { font-size: 9px; min-height: 20px; }
}

/* --- Platform breakdown (v5.723.0) ----------------------------------------
   The stacked trend. Slices are drawn in the declared channel order so the
   stack cannot reshuffle between reloads, and each slice keeps its platform's
   colour from CHANNEL_COLOURS — the one table the legend, this chart and the
   table all read, so they cannot disagree about which one is Deliveroo. */
.pm-wrap { display: flex; align-items: flex-end; gap: 8px; height: 230px;
  overflow-x: auto; padding: 4px 0 0; }
.pm-col { display: flex; flex-direction: column; align-items: center; justify-content: flex-end;
  gap: 4px; min-width: 46px; flex: 1 1 0; height: 100%; }
.pm-total { font-size: 10.5px; color: var(--muted, #8b93a7); font-variant-numeric: tabular-nums;
  white-space: nowrap; }
.pm-bar { width: 100%; max-width: 54px; display: flex; flex-direction: column-reverse;
  border-radius: 5px 5px 0 0; overflow: hidden; min-height: 2px; }
.pm-slice { display: block; width: 100%; }
.pm-x { font-size: 10.5px; color: var(--muted, #8b93a7); white-space: nowrap; }

/* A period that has not finished. Hatched and labelled rather than simply
   shorter — thirteen days of a month beside thirty-day neighbours reads as a
   crash, and it is a calendar. */
.pm-part { opacity: 0.72;
  background-image: repeating-linear-gradient(45deg,
    rgba(255,255,255,0.18) 0 3px, transparent 3px 7px); }
.pm-flag { font-size: 9.5px; color: var(--muted, #8b93a7); font-style: italic; }

@media (max-width: 640px) {
  .pm-wrap { height: 190px; gap: 5px; }
  .pm-col { min-width: 38px; }
  .pm-total, .pm-x { font-size: 9px; }
}
