/* ===========================================================
   SRUHU POS 2.0
   One stylesheet, no build step, no framework.
   Responsive by default: every screen works in portrait and
   landscape, on a phone, tablet or till monitor.
   =========================================================== */

:root {
  --paper: #f4f5f0;
  --surface: #ffffff;
  --surface-2: #eef0e8;
  --ink: #181a12;
  --ink-dim: #666b5a;
  --line: #dde0d3;
  --green: #2f6d4f;
  --green-tint: #2f6d4f14;
  --amber: #d9820b;
  --amber-tint: #d9820b17;
  --red: #b3261e;
  --red-tint: #b3261e12;
  --radius: 10px;
  --font: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
  --mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --paper: #14160f;
    --surface: #1c1f17;
    --surface-2: #23271d;
    --ink: #eceee4;
    --ink-dim: #9aa08c;
    --line: #333829;
    --green: #6cc08f;
    --green-tint: #6cc08f1a;
    --amber: #e8a33d;
    --amber-tint: #e8a33d1a;
    --red: #f2837b;
    --red-tint: #f2837b14;
  }
}

* { box-sizing: border-box; }

body {
  margin: 0;
  background: var(--paper);
  color: var(--ink);
  font-family: var(--font);
  font-size: 15px;
  line-height: 1.5;
  -webkit-text-size-adjust: 100%;
}

a { color: var(--green); }

/* ---------- Layout ---------- */
.wrap { max-width: 1200px; margin: 0 auto; padding: 16px; }
.wrap-wide { max-width: 1500px; margin: 0 auto; padding: 16px; }

/* ---------- Top bar ---------- */
.topbar {
  background: var(--surface);
  border-bottom: 1px solid var(--line);
  position: sticky;
  top: 0;
  z-index: 50;
}
.topbar-inner {
  max-width: 1500px;
  margin: 0 auto;
  padding: 10px 16px;
  display: flex;
  align-items: center;
  gap: 14px;
  flex-wrap: wrap;
}
.brand {
  font-weight: 800;
  letter-spacing: -0.02em;
  font-size: 17px;
  text-decoration: none;
  color: var(--ink);
  white-space: nowrap;
}
.brand span { color: var(--green); }

.nav { display: flex; gap: 4px; flex-wrap: wrap; flex: 1; }
.nav a {
  padding: 7px 12px;
  border-radius: 8px;
  text-decoration: none;
  color: var(--ink-dim);
  font-size: 14px;
  font-weight: 600;
  white-space: nowrap;
}
.nav a:hover { background: var(--surface-2); color: var(--ink); }
.nav a.active { background: var(--green-tint); color: var(--green); }

.topbar-right { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.whoami { font-size: 13px; color: var(--ink-dim); text-align: right; line-height: 1.3; }
.whoami strong { display: block; color: var(--ink); font-size: 13.5px; }

/* ---------- Buttons ---------- */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  padding: 9px 15px;
  border-radius: 9px;
  border: 1px solid var(--line);
  background: var(--surface);
  color: var(--ink);
  font-size: 14px;
  font-weight: 600;
  font-family: inherit;
  cursor: pointer;
  text-decoration: none;
  min-height: 40px;
}
.btn:hover { background: var(--surface-2); }
.btn-primary { background: var(--green); border-color: var(--green); color: #fff; }
.btn-primary:hover { filter: brightness(1.08); background: var(--green); }
.btn-accent { background: var(--amber); border-color: var(--amber); color: #fff; }
.btn-accent:hover { filter: brightness(1.08); background: var(--amber); }
.btn-danger { color: var(--red); border-color: var(--red); background: transparent; }
.btn-danger:hover { background: var(--red-tint); }
.btn-sm { padding: 5px 10px; font-size: 13px; min-height: 32px; }
.btn-lg { padding: 13px 22px; font-size: 16px; min-height: 50px; }
.btn-block { width: 100%; }
.btn:disabled { opacity: 0.5; cursor: not-allowed; }

/* ---------- Cards ---------- */
.card {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 18px;
}
.card + .card { margin-top: 14px; }
.card-title {
  margin: 0 0 12px;
  font-size: 15px;
  font-weight: 700;
  letter-spacing: -0.01em;
}
.card-sub { color: var(--ink-dim); font-size: 13px; margin: -8px 0 12px; }

.grid { display: grid; gap: 14px; }
.grid-2 { grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); }
.grid-3 { grid-template-columns: repeat(auto-fit, minmax(230px, 1fr)); }
.grid-4 { grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); }

/* ---------- Stats ---------- */
.stat { background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius); padding: 16px; }
.stat-label { font-size: 12px; text-transform: uppercase; letter-spacing: 0.04em; color: var(--ink-dim); font-weight: 700; }
.stat-value { font-size: 26px; font-weight: 800; letter-spacing: -0.02em; margin-top: 4px; }
.stat-note { font-size: 12.5px; color: var(--ink-dim); margin-top: 2px; }
.stat-value.green { color: var(--green); }
.stat-value.amber { color: var(--amber); }

/* ---------- Tables ---------- */
.table-scroll { overflow-x: auto; -webkit-overflow-scrolling: touch; }
table { width: 100%; border-collapse: collapse; font-size: 14px; }
th {
  text-align: left;
  font-size: 11.5px;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--ink-dim);
  font-weight: 700;
  padding: 8px 10px;
  border-bottom: 1px solid var(--line);
  white-space: nowrap;
}
td { padding: 9px 10px; border-bottom: 1px solid var(--line); }
tr:last-child td { border-bottom: none; }
.num { text-align: right; font-variant-numeric: tabular-nums; }
.mono { font-family: var(--mono); font-size: 13px; }

/* ---------- Forms ---------- */
label { display: block; }
.field { margin-bottom: 13px; }
.field-label { font-size: 12.5px; font-weight: 700; color: var(--ink-dim); margin-bottom: 5px; display: block; }
input[type="text"], input[type="password"], input[type="number"], input[type="date"],
input[type="email"], input[type="tel"], select, textarea {
  width: 100%;
  padding: 10px 12px;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: var(--paper);
  color: var(--ink);
  font-size: 15px; /* 16px-ish stops iOS zooming on focus */
  font-family: inherit;
}
input:focus, select:focus, textarea:focus { outline: 2px solid var(--green); outline-offset: 1px; }
.form-row { display: flex; gap: 10px; flex-wrap: wrap; }
.form-row > * { flex: 1; min-width: 140px; }

/* ---------- Alerts ---------- */
.alert { padding: 11px 14px; border-radius: 9px; font-size: 14px; margin-bottom: 14px; }
.alert-error { background: var(--red-tint); color: var(--red); border: 1px solid var(--red); }
.alert-ok { background: var(--green-tint); color: var(--green); border: 1px solid var(--green); }
.alert-warn { background: var(--amber-tint); color: var(--amber); border: 1px solid var(--amber); }

/* ---------- Badges ---------- */
.badge {
  display: inline-block;
  padding: 2px 8px;
  border-radius: 20px;
  font-size: 11.5px;
  font-weight: 700;
  background: var(--surface-2);
  color: var(--ink-dim);
  white-space: nowrap;
}
.badge-green { background: var(--green-tint); color: var(--green); }
.badge-amber { background: var(--amber-tint); color: var(--amber); }
.badge-red { background: var(--red-tint); color: var(--red); }

.muted { color: var(--ink-dim); }
.small { font-size: 13px; }
.tiny { font-size: 12px; }
.right { text-align: right; }
.spread { display: flex; justify-content: space-between; align-items: center; gap: 10px; flex-wrap: wrap; }
.stack { display: flex; flex-direction: column; gap: 10px; }
.row { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; }

/* ===========================================================
   BILLING SCREEN
   Two panes on a wide screen, stacked on a phone.
   =========================================================== */
/* ---------- The till ----------

   THE PROBLEM THIS FIXES
   ----------------------
   A 123-dish menu drawn as tiles is several screens tall. The page grew
   with it, so the bill being built — the one thing the cashier is actually
   looking at — was pushed below the fold, and taking an order meant
   scrolling away from the order.

   Now the two columns scroll INDEPENDENTLY inside the window. The menu
   scrolls; the bill stays put and always visible, however long the menu is.
   ---------------------------------------------------------------------- */
.pos {
  display: grid;
  /* 380px, not 340. At 340 a bill line had about 110px for the dish name,
     so "Peanut Butter Sandwich (Banana)" and "...(Classic)" both truncated
     to "Peanut Butter ..." — two different dishes reading identically on
     the bill the cashier is checking. The menu list gives up the width
     more cheaply than the order does. */
  grid-template-columns: minmax(0, 1fr) 380px;
  gap: 12px;
  align-items: stretch;
  /* A starting guess only. public/js/billing-layout.js measures the real
     space left under the header and sets this exactly — the header wraps
     onto a second line as the navigation grows, and a hardcoded number
     silently pushes the bill off the bottom of a laptop when it does. */
  height: calc(100vh - 168px);
  min-height: 380px;
}

.pos-menu { display: flex; flex-direction: column; min-height: 0; }

/* The only part that scrolls on the left. */
#item-grid, .item-grid { overflow-y: auto; min-height: 0; flex: 1; padding-right: 4px; }

.menu-toolbar { display: flex; align-items: center; gap: 10px; margin-bottom: 10px; }
.menu-toolbar .cat-tabs { margin-bottom: 0; padding-bottom: 6px; flex: 1; min-width: 0; }

.density { display: flex; gap: 4px; flex: 0 0 auto; }
.density-btn {
  padding: 6px 11px; border: 1px solid var(--line); border-radius: 7px;
  background: var(--surface); color: var(--ink-dim); cursor: pointer;
  font-family: inherit; font-size: 12.5px; font-weight: 600;
}
.density-btn.is-on { background: var(--green); border-color: var(--green); color: #fff; }

/* COMPACT LIST — the laptop default.
   One thin row per dish, so four or five times as many fit in the same
   height and the bill never leaves the screen. */
.item-grid.as-list { display: block; }
.item-grid.as-list .item-btn {
  display: grid;
  grid-template-columns: 62px minmax(0, 1fr) auto;
  align-items: center;
  gap: 10px;
  min-height: 0;
  padding: 6px 10px;
  margin-bottom: 3px;
  border-radius: 7px;
  text-align: left;
}
.item-grid.as-list .item-btn .code { font-size: 11.5px; }
.item-grid.as-list .item-btn .name {
  font-size: 13.5px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.item-grid.as-list .item-btn .price { font-size: 13.5px; font-weight: 700; text-align: right; }

@media (max-width: 1000px) {
  /* Below a laptop the two columns stack, so a fixed height would trap
     everything in a short scrolling box. Let the page scroll instead. */
  .pos { height: auto; min-height: 0; grid-template-columns: 1fr; }
  #item-grid, .item-grid { overflow: visible; max-height: none; flex: none; }
  .cart { max-height: none; position: static; }
  .cart .cart-lines { overflow: visible; max-height: none; }
}

/* ---------- Channel bar ---------- */
.channel-bar {
  display: flex;
  align-items: center;
  gap: 12px;
  flex-wrap: wrap;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 10px 14px;
  margin-bottom: 14px;
}
.channel-label {
  font-size: 11.5px;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  font-weight: 700;
  color: var(--ink-dim);
}
.channel-tabs { display: flex; gap: 6px; flex-wrap: wrap; align-items: center; }
.channel-group {
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--ink-dim);
  font-weight: 700;
  padding-left: 8px;
  border-left: 1px solid var(--line);
  margin-left: 4px;
}
.channel-tab {
  padding: 7px 14px;
  border-radius: 8px;
  border: 1px solid var(--line);
  background: var(--surface);
  font-size: 13.5px;
  font-weight: 600;
  text-decoration: none;
  color: var(--ink-dim);
  white-space: nowrap;
}
.channel-tab:hover { background: var(--surface-2); color: var(--ink); }
.channel-tab.active { background: var(--green); border-color: var(--green); color: #fff; }
.channel-tab.active.ch-zomato { background: #cb202d; border-color: #cb202d; }
.channel-tab.active.ch-swiggy { background: #fc8019; border-color: #fc8019; }
.channel-tab.active.ch-commission { background: var(--amber); border-color: var(--amber); }

.customer-chip {
  font-size: 12.5px;
  line-height: 1.45;
  padding: 7px 10px;
  margin-top: 6px;
  border-radius: 8px;
  background: var(--green-tint);
  color: var(--ink);
}

.item-btn.soldout { opacity: 0.45; cursor: not-allowed; }
.item-btn.soldout:hover { border-color: var(--line); background: var(--surface); }

/* Deliberately compact. Every pixel this takes is a pixel the item list
   and the bill do not get, and on a 768px-tall laptop that is the
   difference between seeing the total and scrolling for it. */
.quickcode-bar {
  background: var(--surface);
  border: 2px solid var(--green);
  border-radius: var(--radius);
  padding: 8px 10px;
  margin-bottom: 10px;
}
.quickcode-bar input {
  font-size: 17px;
  font-weight: 700;
  font-family: var(--mono);
  text-transform: uppercase;
  padding: 8px 12px;
}
.quickcode-hint {
  font-size: 11.5px; color: var(--ink-dim); margin-top: 4px;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
/* On a short screen the hint is the first thing to go. */
@media (max-height: 800px) {
  .quickcode-hint { display: none; }
  .quickcode-bar { padding: 6px 8px; }
  .quickcode-bar input { font-size: 16px; padding: 7px 10px; }
}

.cat-tabs { display: flex; gap: 6px; overflow-x: auto; padding-bottom: 8px; margin-bottom: 10px; }
.cat-tab {
  padding: 7px 14px;
  border-radius: 20px;
  border: 1px solid var(--line);
  background: var(--surface);
  font-size: 13.5px;
  font-weight: 600;
  white-space: nowrap;
  cursor: pointer;
  color: var(--ink-dim);
  font-family: inherit;
}
.cat-tab.active { background: var(--green); border-color: var(--green); color: #fff; }

.item-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(132px, 1fr)); gap: 7px; }
/* ====================================================================
   A TILE, AND NOTHING OUTSIDE IT

   The price used to be drawn two pixels BELOW the bottom edge of its own
   tile. With overflow visible that put it on top of the tile underneath,
   so every tile carried a ghost of the one above it — visible the moment
   the neighbour lit up on hover. 33 of 40 tiles on a normal menu did it.

   The cause was `flex: 1` on the name. That is `flex: 1 1 0%`: the name
   sized itself from the leftover space instead of from its own text, so a
   two-line dish name was handed a one-line box and simply spilled out of
   it, taking the price with it.

   Now the name sizes from its content and is clamped to two lines, the
   price is a fixed row that always has its own space, and the tile clips
   anything that somehow still tries to escape. A tile can only ever draw
   inside itself.
   ==================================================================== */
.item-btn {
  text-align: left;
  padding: 9px 10px;
  border-radius: var(--radius);
  border: 1px solid var(--line);
  background: var(--surface);
  cursor: pointer;
  font-family: inherit;
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  gap: 4px;
  /* Two lines of name plus a price plus padding. Every tile is the same
     height, so the grid reads as a grid rather than a ragged wall. */
  min-height: 74px;
  color: var(--ink);
  /* The guarantee. Whatever happens to the text inside, it cannot be
     painted over the neighbouring tile. */
  overflow: hidden;
}
.item-btn:hover { border-color: var(--green); background: var(--green-tint); }
.item-btn:active { transform: scale(0.98); }
.item-btn .code {
  font-family: var(--mono);
  font-size: 11px;
  font-weight: 700;
  color: var(--green);
  letter-spacing: 0.03em;
}
.item-btn .name {
  font-size: 13.5px;
  font-weight: 600;
  line-height: 1.25;
  /* Sized from its own text, not from whatever room is left over. */
  flex: 0 1 auto;
  min-height: 0;
  /* Two lines, then an ellipsis. A dish whose name is longer than that is
     still fully readable in List view and on the bill — and a tile that
     grew to fit it would make its whole row taller and push the rest of
     the menu off the screen. */
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
/* The price never shares in the squeeze: it is the number the cashier is
   checking, so it gets its own row and keeps it. */
.item-btn .price { font-size: 14px; font-weight: 700; flex: 0 0 auto; }

/* ====================================================================
   THE BILL COLUMN — ONE LAYOUT, NOT SEVERAL STACKED ON EACH OTHER

   This used to be three separate attempts fighting each other: the column
   scrolled, the item list inside it ALSO scrolled, and the totals and the
   Save button were sticky panels floating over both at z-index 1 and 2.
   The result at 1366x768 was an item list 56 pixels tall — a slit — with
   the quantity buttons sliced in half by the totals panel sitting on top
   of them. Every fix added another layer instead of removing one.

   THE RULE, AND THERE IS ONLY ONE:
   The column is a flex column of fixed height. Exactly ONE child scrolls:
   the list of items on the bill. Nothing is sticky, nothing has a
   z-index, and nothing is ever drawn on top of anything else.
   ==================================================================== */
.cart {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  display: flex;
  flex-direction: column;
  /* Exactly the height of the .pos grid row. Not max-height: the column
     must claim its full height so the item list has room to grow into,
     rather than collapsing to the height of its own content. */
  height: 100%;
  min-height: 0;
  /* The column itself NEVER scrolls. If it did, the item list would be
     scrolling inside a second scrollbar and the cashier would drag the
     wrong one. */
  overflow: hidden;
}
.cart-head { padding: 8px 12px; border-bottom: 1px solid var(--line); flex: 0 0 auto; }

/* THE ONLY PART OF THE BILL THAT SCROLLS.
   min-height:0 is what lets it shrink; flex:1 is what makes it take every
   pixel the fixed rows below do not need. A floor of 96px means that even
   on a cramped screen three lines of the order are readable — below that
   the payment block gives way instead (see the short-screen rules). */
.cart-body {
  flex: 1 1 auto;
  overflow-y: auto;
  padding: 4px 10px;
  /* A floor, so the order is never squeezed to nothing by a channel that
     happens to ask for three extra fields. Below this the payment block
     gives way instead — see the short-screen rules. */
  min-height: 96px;
}

/* The bill lines are now a plain block INSIDE that scroller. They must not
   scroll on their own: a scrollbar inside a scrollbar is how a cashier
   ends up dragging the wrong one. */
.cart-lines {
  min-height: 0;
}
.cart-totals, .cart-actions, .pay-block { flex: 0 0 auto; }

/* The payment block can itself get tall (split has two panes). Cap it and
   let it scroll rather than let it shove the buttons out of view. */
.pay-block { max-height: 42vh; overflow-y: auto; }
.cart-empty { padding: 30px 16px; text-align: center; color: var(--ink-dim); font-size: 14px; }
/* ONE ROW PER ITEM: [− 2 +] [ Dish name / ₹120 each ] [₹240] [×]
   The stepper leads because it is what gets touched most; the money ends
   the row because that is where the eye goes to check a bill. */
.cart-line {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto auto;
  gap: 0 8px;
  padding: 5px 4px;
  border-bottom: 1px solid var(--line);
  align-items: center;
}
.cart-line:last-child { border-bottom: none; }
.cart-line .cl-name { font-size: 12.5px; font-weight: 600; line-height: 1.22; min-width: 0; }
.cart-line .cl-name-text {
  display: block;
  /* A long dish name shortens rather than wrapping onto a second line and
     making this row taller than its neighbours. The full name is on the
     menu button that was just pressed, and in the printed bill. */
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.cart-line .cl-total { font-weight: 700; font-variant-numeric: tabular-nums; font-size: 13.5px; }
.cart-line .cl-controls { display: flex; align-items: center; gap: 3px; }
.qty-btn {
  width: 30px; height: 30px;
  border-radius: 7px;
  border: 1px solid var(--line);
  background: var(--surface);
  font-size: 16px;
  font-weight: 700;
  cursor: pointer;
  color: var(--ink);
  line-height: 1;
}
.qty-btn:hover { background: var(--surface-2); }
.qty-val { min-width: 20px; text-align: center; font-weight: 700; font-variant-numeric: tabular-nums; }
.cl-rate { display: block; font-size: 11px; font-weight: 500; color: var(--ink-dim); }
/* The stepper and the remove cross: small enough for a one-line row, still
   comfortably bigger than the 24px that makes a button hard to hit. */
.cart-line .qty-btn { width: 26px; height: 26px; font-size: 14px; }

.cart-totals { padding: 12px 16px; border-top: 1px solid var(--line); background: var(--surface-2); }
.total-row { display: flex; justify-content: space-between; font-size: 13.5px; padding: 2px 0; }
.total-row.grand { font-size: 20px; font-weight: 800; padding-top: 8px; margin-top: 6px; border-top: 1px solid var(--line); }
.cart-actions { padding: 12px 16px; display: grid; gap: 8px; }

/* Portrait phones: stack the panes, put the cart on top */
@media (max-width: 900px) {
  .pos { grid-template-columns: 1fr; }
  .cart { position: static; max-height: none; order: -1; }
  .cart-lines { max-height: 260px; }
}

/* Landscape phone: shorter cart so the item grid stays usable */
@media (max-height: 520px) and (orientation: landscape) {
  .cart { max-height: calc(100vh - 70px); max-height: calc(100dvh - 70px); }
  .cart-lines { max-height: 40vh; }
}

/* ===========================================================
   KITCHEN DISPLAY
   =========================================================== */
.kot-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(260px, 1fr)); gap: 14px; }
.kot-card { background: var(--surface); border: 2px solid var(--line); border-radius: var(--radius); overflow: hidden; }
.kot-card.new { border-color: var(--amber); }
.kot-card.preparing { border-color: var(--green); }
.kot-card.ready { border-color: var(--ink-dim); opacity: 0.85; }
.kot-head { padding: 10px 14px; background: var(--surface-2); display: flex; justify-content: space-between; align-items: center; }
.kot-num { font-weight: 800; font-size: 17px; }
.kot-items { padding: 10px 14px; }
.kot-item { font-size: 16px; font-weight: 600; padding: 5px 0; border-bottom: 1px solid var(--line); }
.kot-item:last-child { border-bottom: none; }
.kot-item .q { color: var(--green); font-weight: 800; }
.kot-item .note { display: block; font-size: 13px; color: var(--amber); font-weight: 600; }
.kot-foot { padding: 10px 14px; display: flex; gap: 8px; }

/* ===========================================================
   PRINT PREVIEW
   =========================================================== */
.receipt {
  font-family: var(--mono);
  font-size: 12px;
  line-height: 1.35;
  white-space: pre;
  background: #fff;
  color: #000;
  padding: 14px;
  border: 1px solid var(--line);
  border-radius: 6px;
  overflow-x: auto;
  display: inline-block;
}

/* ===========================================================
   PRINTING

   THE BUG THIS FIXES
   ------------------
   Hiding the navigation was not enough. Without an @page size the browser
   lays the receipt out on A4, so Ctrl+P produced a sheet of mostly white
   paper with a small block of text at the top — which on a thermal printer
   means metres of blank roll, and in "Save as PDF" means a screenshot of a
   page rather than a bill.

   The @page rule lives in views/print.ejs, because the paper width is a
   setting (80mm or 58mm) and @page cannot be switched by a CSS selector.
   =========================================================== */
@media print {
  /* Everything is hidden, then the receipt alone is put back. Hiding by
     class means every new screen has to remember to opt out; this way a
     screen that forgets prints nothing extra by default.

     Scoped to pages that actually contain a receipt — otherwise printing
     any other screen in the app would come out blank, which is a worse bug
     than the one being fixed. */
  /* Everything except the receipt is REMOVED, not merely made invisible.
     visibility:hidden leaves the text in the printed document — off the
     page but still in the file, and still at the mercy of a driver that
     decides to render it. display:none takes it out entirely.

     This works because the receipt is a direct child of <body> on the
     print page, so hiding its siblings never hides the receipt itself. */
  body.page-receipt > *:not(.receipt) { display: none !important; }

  .topbar, .no-print, nav, header, footer, .btn, form { display: none !important; }

  html, body {
    background: #fff !important;
    margin: 0 !important;
    padding: 0 !important;
    width: auto !important;
  }
  .wrap, .wrap-wide {
    padding: 0 !important;
    margin: 0 !important;
    max-width: none !important;
    text-align: left !important;
  }

  /* Pulled to the very top-left of the roll: a thermal printer has no
     margins to centre inside. */
  .receipt {
    position: static !important;
    border: none !important;
    border-radius: 0 !important;
    padding: 0 !important;
    margin: 0 !important;
    background: #fff !important;
    color: #000 !important;
    font-size: 11px !important;
    line-height: 1.3 !important;
    display: block !important;
    width: auto !important;
    overflow: visible !important;
    /* Never break a receipt across two pieces of paper. */
    page-break-inside: avoid;
  }
}

/* ---------- Login ---------- */
.login-wrap {
  min-height: 100vh;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 20px;
}
.login-card { width: 100%; max-width: 380px; }
.login-brand { text-align: center; margin-bottom: 20px; }
.login-brand h1 { font-size: 26px; font-weight: 800; letter-spacing: -0.03em; margin: 0; }
.login-brand h1 span { color: var(--green); }
.login-brand p { color: var(--ink-dim); font-size: 13.5px; margin: 4px 0 0; }

/* ---------- Permission matrix ---------- */
.perm-group { margin-bottom: 18px; }
.perm-group h3 { font-size: 13px; text-transform: uppercase; letter-spacing: 0.04em; color: var(--ink-dim); margin: 0 0 8px; }
.perm-row { display: flex; align-items: flex-start; gap: 10px; padding: 8px 10px; border-radius: 8px; }
.perm-row:hover { background: var(--surface-2); }
.perm-row input[type="checkbox"] { width: 18px; height: 18px; margin-top: 2px; flex-shrink: 0; accent-color: var(--green); }
.perm-row .perm-text { flex: 1; }
.perm-row .perm-key { font-family: var(--mono); font-size: 11.5px; color: var(--ink-dim); }

/* ---------- Bill format editor ----------
   Two columns on a desktop: controls on the left, the paper on the right,
   with the paper pinned so it stays visible while you work down the form.
   On a phone the paper moves to the top, because the whole point of the
   screen is watching the bill change. */
.bf-layout { display: grid; grid-template-columns: minmax(0, 1fr) 380px; gap: 16px; align-items: start; }
.bf-layout > div:last-child { position: sticky; top: 74px; }

@media (max-width: 900px) {
  /* minmax(0, 1fr), NOT 1fr — the same trap written up twice further down
     this file, caught a third time on the one page nobody had opened at
     phone width. `1fr` is `minmax(auto, 1fr)`, and `auto` means
     min-content; the min-content of this column is the receipt preview,
     a <pre> of 80mm monospace that cannot reflow. So the column refused
     to be narrower than the paper, the page became 394px wide inside a
     390px phone, and the whole thing slid left and right. */
  .bf-layout { grid-template-columns: minmax(0, 1fr); }
  .bf-layout > div { min-width: 0; }
  .bf-layout > div:last-child { position: static; order: -1; }
}

.bf-paper-wrap {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 14px;
}

/* Real receipt paper is warm white and never follows a dark theme, so the
   preview does not either — otherwise you judge the contrast of something
   that will be printed in black on white. */
.bf-paper {
  background: #fffdf7;
  color: #14150f;
  border: 1px solid #e6e2d4;
  border-radius: 6px;
  padding: 14px 10px;
  margin: 0;
  overflow-x: auto;
  white-space: pre;
  font-family: var(--mono);
  line-height: 1.35;
  box-shadow: inset 0 0 0 1px #fff, 0 1px 3px rgba(0,0,0,0.08);
}
.bf-font-small  { font-size: 10px; }
.bf-font-medium { font-size: 11.5px; }
.bf-font-large  { font-size: 13.5px; }

.bf-toggles { display: grid; grid-template-columns: repeat(auto-fill, minmax(190px, 1fr)); gap: 2px; }
.bf-toggle {
  display: flex; align-items: center; gap: 9px;
  padding: 7px 9px; border-radius: 8px; cursor: pointer; font-size: 13.5px;
}
.bf-toggle:hover { background: var(--surface-2); }
.bf-toggle input[type="checkbox"] { width: 17px; height: 17px; flex-shrink: 0; accent-color: var(--green); }

/* ---------- Back-dating panel on the till ----------
   Collapsed by default. The normal case is a sale happening right now,
   and a date field sitting open invites a stray keystroke that quietly
   puts a bill on the wrong day. */
.backdate { border: 1px solid var(--line); border-radius: 8px; padding: 9px 11px; }
.backdate > summary { cursor: pointer; color: var(--ink-dim); list-style-position: inside; }
.backdate[open] > summary { color: var(--ink); margin-bottom: 4px; }

/* ---------- Taking payment at the till ----------
   Sits directly under the totals, because the sequence at the counter is
   read the total, take the money, save. */
.pay-block { border-top: 1px solid var(--line); margin-top: 12px; padding-top: 12px; }

.pay-modes { display: grid; grid-template-columns: repeat(3, 1fr); gap: 6px; margin-bottom: 10px; }
.pay-mode {
  padding: 11px 8px; border: 1px solid var(--line); border-radius: 9px;
  background: var(--surface); color: var(--ink); font-size: 14px; font-weight: 600;
  cursor: pointer; font-family: inherit;
}
.pay-mode:hover { background: var(--surface-2); }
.pay-mode.is-on { background: var(--green); border-color: var(--green); color: #fff; }

.pay-pane { margin-bottom: 8px; }
.quick-notes { display: flex; gap: 6px; flex-wrap: wrap; margin-top: 8px; }

/* The verdict line. Sized and coloured to be read at a glance from
   standing height, because it is the thing that decides whether the
   drawer opens. */
.pay-verdict {
  margin-top: 10px; padding: 10px 12px; border-radius: 9px;
  font-size: 15px; font-weight: 700; text-align: center; min-height: 20px;
}
.pay-verdict:empty { display: none; }
.pay-verdict.is-ok     { background: var(--green-tint); color: var(--green); }
.pay-verdict.is-change { background: var(--amber-tint); color: var(--amber); font-size: 18px; }
.pay-verdict.is-warn   { background: var(--amber-tint); color: var(--amber); }
.pay-verdict.is-short  { background: var(--red-tint);   color: var(--red);   font-size: 17px; }

/* ---------- The payment QR ----------
   Bottom LEFT and deliberately narrow: the cashier still has to read the
   bill on the right while the customer scans. */
.qr-panel {
  position: fixed; left: 16px; bottom: 16px; z-index: 70; width: 232px;
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--radius); padding: 12px;
  box-shadow: 0 8px 28px rgba(0,0,0,0.18);
}
.qr-head { display: flex; align-items: center; justify-content: space-between; margin-bottom: 8px; }
/* White plate behind the code: a QR on a dark background does not scan. */
.qr-img { width: 100%; height: auto; display: block; background: #fff; padding: 8px; border-radius: 6px; }
.qr-amount { margin: 8px 0 0; text-align: center; font-size: 19px; font-weight: 800; }

@media (max-width: 720px) {
  .qr-panel { left: 8px; right: 8px; width: auto; bottom: 8px; }
  .qr-img { max-width: 220px; margin: 0 auto; }
}

/* ---------- Money tiles on the reports screen ----------
   Billed and collected side by side, because the gap between them is the
   number that matters and it should not need arithmetic to see. */
.money-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); gap: 10px; }
.money-tile {
  border: 1px solid var(--line); border-radius: 9px; padding: 11px 13px;
  display: flex; flex-direction: column; gap: 2px; background: var(--surface-2);
}
.money-tile.is-good { background: var(--green-tint); border-color: var(--green); }
.money-tile.is-bad  { background: var(--red-tint);   border-color: var(--red); }
.money-label { font-size: 11.5px; text-transform: uppercase; letter-spacing: 0.04em; color: var(--ink-dim); }
.money-value { font-size: 20px; font-weight: 800; }
.money-tile.is-good .money-value { color: var(--green); }
.money-tile.is-bad  .money-value { color: var(--red); }
.money-note { font-size: 11px; color: var(--ink-dim); }

/* ---------- Staff access ----------
   The QR is the whole point of the screen, so it gets the space. */
.connect-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(210px, 1fr)); gap: 12px; }
.connect-card {
  border: 1px solid var(--line); border-radius: var(--radius);
  padding: 12px; background: var(--surface-2);
}
.connect-card.is-best { border-color: var(--green); background: var(--green-tint); }
/* White plate behind the code: a QR on a dark background will not scan. */
.connect-qr {
  display: block; width: 100%; max-width: 200px; height: auto; margin: 0 auto;
  background: #fff; padding: 8px; border-radius: 8px;
}
.connect-url {
  margin: 10px 0 0; text-align: center; font-size: 14.5px; font-weight: 700;
  word-break: break-all;
}


/* ---------- Density switch ----------
   These were connected to nothing at all: a control that looks like it
   works and does not. List is now the default and the choice is
   remembered per browser. */
.density-btn.is-on {
  background: var(--green); border-color: var(--green); color: #fff;
}

/* On a short laptop screen, give the space back to the list and the bill. */
@media (max-height: 800px) {
  .cart-head { padding: 8px 12px; }
  .cart-totals { padding: 9px 13px; }
  .cart-actions { padding: 9px 13px; gap: 6px; }
  .total-row.grand { font-size: 18px; padding-top: 6px; margin-top: 4px; }
  .pay-block { max-height: 34vh; }
  .pay-mode { padding: 8px 6px; font-size: 13px; }
  .menu-toolbar { margin-bottom: 7px; }
}


/* ====================================================================
   THE TILL, MADE TO FIT A LAPTOP

   WHAT WAS WRONG
   --------------
   The bill column was 523px tall on a 1366x768 laptop and held 775px of
   content, with overflow:hidden. So the total, the payment box and the
   Save button were CLIPPED — not merely below the fold, but unreachable,
   because nothing in that column scrolled. The page itself then scrolled
   as well, which is why the bill had to be hunted for by hand.

   THE RULE NOW
   ------------
   The bill column is a flex column. Exactly one part of it scrolls — the
   list of items on the bill. The header, the totals, the payment box and
   the Save button are fixed, so they are on screen at every window size.
   ==================================================================== */

/* Fixed. Kept deliberately short: only the two fields used on every bill.
   The column layout itself is defined once, further up — deliberately not
   repeated here, because two definitions of the same thing is how the
   nested-scrollbar mess got in. */


/* The amount and its "Exact" button belong on ONE line. As a flex .row the
   button wrapped underneath, which cost 38px of the bill's height for no
   reason at all. A grid cannot wrap. */
.pay-amount-row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  gap: 6px;
  align-items: center;
}
.pay-amount-row > input { min-width: 0; }

/* Order type, table and Clear on one row. Clear used to sit up beside the
   channel badge; when that badge is hidden on a short window the button
   would have gone with it, so it moved down here where it is always
   present and always the same distance from the cashier's hand. */
.cart-row-main {
  display: grid;
  grid-template-columns: minmax(0, 1.2fr) minmax(0, 1fr) auto;
  gap: 6px;
  align-items: center;
}
.cart-row-main > select,
.cart-row-main > input { min-width: 0; font-size: 13.5px; padding: 7px 10px; }

/* Two fields to a row, and allowed to be narrow — the generic .form-row
   reserves 140px per field, which in a 340px column forces a third field
   onto its own line and wastes vertical space the bill needs. */
.cart-row2 {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 8px;
  margin-top: 8px;
}
.cart-row2 > * { min-width: 0; font-size: 13.5px; padding: 7px 10px; }
.cart-row2 > *:only-child { grid-column: 1 / -1; }

/* Everything that is not needed on every bill. */
.cart-more { margin-top: 8px; }
.cart-more > summary {
  cursor: pointer; color: var(--ink-dim); padding: 4px 0;
  list-style-position: inside;
}
.cart-more[open] > summary { color: var(--ink); }
.cart-more-body { padding-bottom: 2px; }

/* Tighter buttons: three full-width buttons stacked came to 140px, which
   is a quarter of the column on a small laptop. */
.cart-actions { padding-top: 10px; }
.cart-actions .btn-lg { padding: 11px 14px; font-size: 15px; }

/* SHORT SCREENS. Under about 800px of window there is no room for
   generosity, so the paddings come in rather than the Save button going
   off the bottom. */
@media (min-width: 1001px) and (max-height: 820px) {
  .cart-head .spread { margin-bottom: 2px; }
  .cart-row2 { gap: 6px; margin-top: 6px; }
  .cart-row2 > * { padding: 6px 9px; font-size: 13px; }
  .pay-block { margin-top: 8px; padding-top: 8px; }
  .pay-mode { padding: 8px 6px; font-size: 13.5px; }
  .pay-verdict { margin-top: 7px; padding: 8px 10px; font-size: 14px; }
  .cart-totals { padding-top: 8px; }
  .cart-actions { padding-top: 8px; }
  .cart-actions .btn-lg { padding: 10px 14px; }
  .quick-notes { margin-top: 6px; }
}


/* Short laptop screens: the bill must fit, so the padding gives way first.
   Measured against a 1366x768 window, which is the most common laptop in
   Indian shops — everything below had to come out of 278px of overflow. */
@media (min-width: 1001px) and (max-height: 900px) {
  .cart { padding: 10px 12px; }
  .cart-head .spread { margin-bottom: 0; }

  /* Totals: the small lines shrink, TOTAL stays big — it is the number the
     customer is told and the one the cashier checks. */
  .cart-totals { padding-top: 8px; margin-top: 8px; }
  .cart-totals .total-row { padding: 2px 0; font-size: 13px; }
  .cart-totals .total-row.grand { padding: 6px 0 2px; font-size: 18px; }

  .pay-block { margin-top: 8px; padding-top: 8px; }
  .pay-modes { margin-bottom: 7px; }
  .pay-pane { margin-bottom: 4px; }
  .pay-pane .field-label { margin-bottom: 2px; }
  .quick-notes { margin-top: 5px; }
  .quick-notes .btn { padding: 4px 9px; }

  .cart-actions { padding-top: 8px; gap: 7px; }
  .cart-actions .btn-lg { padding: 10px 14px; font-size: 15px; }
  .cart-actions .btn { padding: 7px 10px; font-size: 13px; }

  .cart-more { margin-top: 6px; }
  .cart-more > summary { padding: 2px 0; font-size: 12.5px; }
}




/* The Save button and the totals are the last two rows of the flex
   column, so they are always on screen without being sticky.

   They USED to be sticky, at z-index 2 and 1, floating over the item
   list. That is what put a panel on top of the quantity buttons and cut
   the last bill line in half. A sticky element inside a scrolling column
   does not make room for itself — it covers what is underneath. Rows in a
   flex column do make room for themselves, which is the whole point. */
.cart-actions { padding-bottom: 2px; }


/* ====================================================================
   THE ITEM LIST, TIDIED

   From a screenshot at 1366x768: every row was shrink-to-fit, so a menu
   of 123 dishes rendered as a ragged column of different-width pills with
   the four-letter codes wrapping onto a second line. It read as clutter
   and wasted most of the left column.
   ==================================================================== */
.item-grid.as-list .item-btn {
  width: 100%;
  /* A wider code cell so PBSB and MFJS stay on one line — a wrapped code
     makes every row a different height, which is what made the list look
     disordered. */
  grid-template-columns: 58px minmax(0, 1fr) auto;
}
.item-grid.as-list .item-btn .code {
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  font-weight: 700;
}
/* Rows the same height, so the eye runs straight down the list. */
.item-grid.as-list .item-btn { min-height: 34px; }

.cart-totals { border-top: 1px solid var(--line); margin-top: 0; }

/* WHEN THE ORDER IS LONGER THAN THE BOX.
   A row cut in half at the bottom edge reads like something is sitting on
   top of it — which, before this rewrite, it was. A fading edge says
   "there is more below, scroll" instead, and the count in the header says
   how much more. */
.cart-body { scrollbar-width: thin; }
.cart-count { font-size: 12px; color: var(--ink-dim); font-weight: 600; }

/* ---------- The "More" navigation menu ----------
   Opens on click rather than hover: a hover menu on a till is opened by
   accident every time someone reaches across the screen. */
.nav-more { position: relative; }
.nav-more-btn {
  font-family: inherit; font-size: inherit; font-weight: 600;
  background: none; border: none; cursor: pointer;
  color: var(--ink-dim); padding: 7px 10px; border-radius: 7px;
}
.nav-more-btn:hover { background: var(--surface-2); color: var(--ink); }
.nav-more-btn[aria-expanded="true"] { background: var(--surface-2); color: var(--ink); }

/* THE DESKTOP DROPDOWN HAD NO CEILING, AND THAT IS WHERE THE REAL FAULT WAS.
 *
 * The phone panel was fixed first, and the owner said it was still wrong —
 * because he was on a laptop. This rule had `max-height: none` and
 * `overflow: visible`, so seventeen links simply hung off the bottom of the
 * window with nothing to scroll:
 *
 *     1366x768 laptop (625px of page)  Backup and Audit 63px below the edge
 *     1280x720 laptop (580px of page)  three links off, and clicking
 *                                      Backup genuinely failed
 *     1440x900 and larger              fine, which is why it was missed
 *
 * A bug that only appears on the smaller half of laptops is one the person
 * who builds on a big screen never sees and the shop sees every day.
 */
.nav-more-menu {
  display: none;
  position: absolute; top: 100%; right: 0; z-index: 90;
  min-width: 180px; padding: 6px;
  background: var(--surface); border: 1px solid var(--line);
  border-radius: 10px; box-shadow: 0 10px 28px rgba(0,0,0,0.16);

  /* Never taller than the window it hangs in. The bar is about 45px, so
     this leaves a margin below the last row rather than ending flush
     against the edge of the screen. */
  max-height: calc(100vh - 64px);
  max-height: calc(100dvh - 64px);   /* see the note on the phone panel */
  overflow-y: auto;
  overscroll-behavior: contain;
}

/* Same scrolling shadow as the phone panel: solid covers attached `local`
   travel with the content, soft shadows attached `scroll` stay pinned to
   the edges, so the hint appears only while there is actually more to see.
   A background, never a mask — see the note at .topbar::after. */
.nav-more-menu {
  background-image:
    linear-gradient(to bottom, var(--surface) 30%, rgba(255, 255, 255, 0)),
    linear-gradient(to top,    var(--surface) 30%, rgba(255, 255, 255, 0)),
    radial-gradient(farthest-side at 50% 0,    rgba(0, 0, 0, 0.28), rgba(0, 0, 0, 0)),
    radial-gradient(farthest-side at 50% 100%, rgba(0, 0, 0, 0.28), rgba(0, 0, 0, 0));
  background-position: top, bottom, top, bottom;
  background-size: 100% 20px, 100% 20px, 100% 11px, 100% 11px;
  background-repeat: no-repeat;
  background-attachment: local, local, scroll, scroll;
  background-color: var(--surface);
}

.nav-more.is-open .nav-more-menu { display: block; }
.nav-more-menu a { display: block; padding: 8px 10px; border-radius: 7px; white-space: nowrap; }
.nav-more-menu a:hover { background: var(--surface-2); }

/* A SHORT LAPTOP GETS TWO COLUMNS, FOR THE SAME REASON A SHORT PHONE DOES.
 *
 * Scrolling is the fallback, not the answer. Seventeen rows at 37px is
 * 643px and does not fit the 625px a 1366x768 laptop gives a web page;
 * nine rows is 333px and fits everything still being sold.
 *
 * Keyed on HEIGHT, because height is what ran out — a wide 1920x600 window
 * has exactly the same problem as a 1280x720 one.
 *
 * This sits AFTER `.nav-more.is-open .nav-more-menu { display: block }`
 * deliberately. Written above it, the two rules have equal specificity and
 * the later `block` wins, so the panel stays one column and the fix does
 * nothing — which is precisely what happened on the first attempt. */
@media (min-width: 1001px) and (max-height: 820px) {
  .nav-more.is-open .nav-more-menu {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 1px 4px;
    min-width: 340px;
  }
}

@media (max-width: 1000px) {
  /* On a phone the bar already wraps and scrolls; a dropdown there just
     hides things people are looking for. */
  .nav-more-menu { display: block; position: static; min-width: 0; padding: 0;
                   background: none; border: none; box-shadow: none; }
  .nav-more-menu a { display: inline-block; padding: 7px 10px; }
  .nav-more-btn { display: none; }
}


/* ====================================================================
   TILL DENSITY — buying height for the bill

   Every pixel spent on page furniture above the till is a pixel the
   order list does not get. On a 1366x768 laptop the header and the
   channel strip together were eating 183px before the till even began,
   and the bill column below was left with room for one item.

   These rules apply only on the billing screen (it is the only page with
   a .pos grid) and only when the window is short enough to need them, so
   a big desktop monitor keeps the roomier spacing.
   ==================================================================== */
@media (min-width: 1001px) and (max-height: 900px) {
  body.page-till .topbar-inner { padding: 5px 16px; }
  body.page-till .wrap-wide { padding: 8px 14px; }

  body.page-till .channel-bar { padding: 5px 10px; margin-bottom: 8px; gap: 8px; }
  body.page-till .channel-tab { padding: 4px 11px; font-size: 13px; }

  /* The bill column's own header: the channel is already named in the
     strip directly above it, in colour and at three times the size, so
     repeating it here is a badge that costs height and says nothing. */
  body.page-till .cart-head { padding: 6px 10px; }
  body.page-till .cart-channel-echo { display: none; }

  /* Subtotal / Discount / Tax pair up into two columns instead of
     running down three separate lines. TOTAL keeps a full-width line of
     its own — it is the number that gets read out loud. */
  body.page-till .cart-totals {
    padding: 7px 12px;
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 0 12px;
  }
  body.page-till .cart-totals .total-row.grand { grid-column: 1 / -1; }
  body.page-till .cart-totals .total-row { font-size: 13px; padding: 1px 0; }
  body.page-till .cart-totals .total-row.grand { font-size: 18px; padding-top: 5px; margin-top: 4px; }
  body.page-till .cart-totals input { padding: 4px 8px; font-size: 13px; }

  body.page-till .cart-actions { padding: 8px 12px; gap: 6px; }
  body.page-till .cart-actions .btn-lg { padding: 10px 14px; font-size: 15px; }
  body.page-till .cart-actions .btn { padding: 6px 10px; font-size: 13px; }

  body.page-till .pay-block { padding-top: 6px; }
  body.page-till .pay-mode { padding: 6px 6px; font-size: 13.5px; }

  /* The amount box carries its own label as placeholder text on a short
     window, so the separate label line comes out. Screen readers still
     get the <label>; it is moved off-screen, not deleted. */
  body.page-till .pay-pane > .field-label {
    position: absolute; width: 1px; height: 1px;
    overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap;
  }

  /* Quick note buttons are a convenience, not part of taking money. On a
     laptop they cost three bill lines, so they fold away with everything
     else that is not needed to complete the sale. */
  body.page-till .quick-notes { display: none; }
}


/* ====================================================================
   VERY SHORT WINDOWS — a netbook, or a laptop with a taskbar and a tall
   browser toolbar.

   This block MUST come after the general till-density block above. Both
   media queries match a 1024x640 screen, the selectors have the same
   weight, and in CSS the last one wins — so when these rules lived higher
   up the file the roomier ones quietly overrode them and the column
   overflowed by exactly the amount these rules were meant to save.
   ==================================================================== */
/* Very short windows — a netbook, or a laptop with a taskbar and a big
   browser toolbar. The item notes and the tip lines go entirely. */
@media (min-width: 1001px) and (max-height: 700px) {
  .cart-totals .total-row.grand { font-size: 17px; }
  .pay-verdict { padding: 6px 8px; font-size: 13.5px; }
  .pay-verdict.is-change { font-size: 15px; }
  .quick-notes { display: none; }
  /* Even here the bill keeps three readable lines. On a window this short
     it is everything else that gives way, not the order itself — the
     cashier can scroll a payment pane, but an order they cannot see is an
     order they cannot check against the plates going out.

     The per-unit price is the first thing to go: on a netbook it is worth
     more to see three dishes than to see what one of them costs each,
     and the rate is still on the menu button and on the printed bill.
     Dropping it takes a bill line from 40px to about 26px, which is a
     third dish visible for nothing. */
  .cart-line .cl-rate { display: none; }
  .cart-line { padding: 3px 4px; }
  .cart-line .qty-btn { width: 24px; height: 24px; }
  /* Three lines of the order, guaranteed. Everything below is trimmed
     by a pixel or two each so this floor can be met without the column
     itself overflowing. */
  .cart-body { min-height: 93px; }
  .pay-block { max-height: 30vh; padding-top: 4px; }
  body.page-till .topbar-inner { padding: 3px 16px; }
  body.page-till .channel-label { display: none; }

  /* The line count is a convenience; at this height the order itself is
     not, so the count goes and the order keeps the pixels. */
  body.page-till .cart-count { display: none; }
  body.page-till .cart-head { padding: 4px 9px; }
  body.page-till .cart-actions { padding: 5px 10px; gap: 5px; }
  body.page-till .cart-actions .btn-lg { padding: 8px 12px; font-size: 14.5px; }
  body.page-till .cart-actions .btn { padding: 5px 9px; font-size: 12.5px; }
  body.page-till .cart-totals { padding: 4px 10px; }
  body.page-till .cart-totals .total-row.grand { font-size: 17px; padding-top: 3px; margin-top: 3px; }
  body.page-till .pay-mode { padding: 5px 5px; font-size: 13px; }
  body.page-till .pay-verdict { padding: 5px 8px; font-size: 13px; }
  body.page-till .channel-bar { padding: 4px 9px; margin-bottom: 6px; }
  body.page-till .wrap-wide { padding: 6px 12px; }
}


/* The one-line answer when Ctrl+P has no bill to print. Without it the
   keypress appears to do nothing, which is indistinguishable from the
   shortcut being broken — the complaint this whole mechanism exists to
   answer. */
.print-shortcut-msg {
  position: fixed;
  left: 50%;
  bottom: 24px;
  transform: translateX(-50%) translateY(10px);
  background: #23303a;
  color: #fff;
  padding: 11px 18px;
  border-radius: 9px;
  font-size: 14px;
  font-weight: 600;
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.25);
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.15s ease, transform 0.15s ease;
  z-index: 200;
}
.print-shortcut-msg.is-on { opacity: 1; transform: translateX(-50%) translateY(0); }
@media print { .print-shortcut-msg { display: none !important; } }


/* ====================================================================
   THE HEADER ON A NARROW SCREEN

   Adding a second branch adds an outlet switcher to the header, and on a
   1024px netbook that was enough to push the whole bar onto a second
   line — 29 pixels taken straight out of the bill below it, on the one
   screen size that had none to spare. A chain feature must not make the
   till worse for the branch that uses it.

   So below 1150px the header gives up ornament rather than height: the
   navigation tightens, the switcher stops trying to show a long
   restaurant name in full, and the role subtitle goes. Nothing is
   removed — everything is still reachable, just smaller.
   ==================================================================== */
@media (max-width: 1150px) {
  .topbar-inner { gap: 8px; flex-wrap: nowrap; }
  .nav { min-width: 0; flex-wrap: nowrap; overflow: hidden; }
  .nav > a { padding: 6px 7px; font-size: 13px; }
  .nav-more-btn { padding: 6px 7px; font-size: 13px; }

  .topbar-right { flex-wrap: nowrap; gap: 6px; }
  /* A long branch name is truncated rather than allowed to set the width
     of the whole header. The full name is on every screen below it. */
  .topbar-right select {
    max-width: 130px; font-size: 12.5px !important; padding: 5px 7px !important;
    text-overflow: ellipsis;
  }
  /* The person knows their own job title. The name is enough here. */
  .whoami { font-size: 12px; }
  .whoami > :not(strong) { display: none; }
  .topbar-right .btn { padding: 5px 9px; font-size: 12.5px; }
}


/* ====================================================================
   WHERE THE KEYBOARD IS

   The till runs on four arrow keys, which is only faster than a mouse if
   the cashier can see which section they are in without looking for a
   cursor. The current section is outlined; the individual control inside
   it keeps the ordinary focus ring.
   ==================================================================== */
.kbd-here {
  box-shadow: 0 0 0 2px var(--green), 0 0 0 5px var(--green-tint);
  border-radius: var(--radius);
}
.pos .kbd-here { position: relative; z-index: 1; }
/* Never on paper. */
@media print { .kbd-here { box-shadow: none !important; } }

/* The arrow-key hint, small enough to ignore once it is learned. */
.kbd-hint {
  font-size: 11.5px;
  color: var(--ink-dim);
  margin-top: 6px;
}
.kbd-hint kbd {
  font-family: var(--mono);
  font-size: 11px;
  background: var(--surface-2);
  border: 1px solid var(--line);
  border-radius: 4px;
  padding: 1px 5px;
}

/* ====================================================================
   THE SRUHU CALENDAR — subscriptions, the rider's round, and the
   customer's own page.

   Three audiences, one stylesheet, and only one of them is staff. The
   customer's page has to stand up on its own on a five-year-old phone
   opened from a WhatsApp link, so nothing here depends on a recent CSS
   feature: no :has(), no container queries, no subgrid. A colour block
   and a number is the whole design, because the question being answered
   is "did my salad come today" and that must be legible at arm's length
   in sunlight.

   THE FOUR COLOURS MEAN THE SAME THING ON ALL THREE SCREENS
     green  it was handed over
     red    the day passed and it did not arrive
     amber  called off in advance
     plain  still to come
   Colour is never the only carrier: every cell also says the word, because
   about one man in twelve cannot tell the green from the red.
   ==================================================================== */

/* ---------- shared day colours ---------- */
.is-green { background: var(--green-tint); }
.is-red { background: var(--red-tint); }
.is-amber { background: var(--amber-tint); }
.is-plain { background: var(--surface); }

/* ---------- the customer's page ---------- */
.cal-wrap { max-width: 720px; margin: 0 auto; padding: 14px; }
.cal-head {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  gap: 12px;
  flex-wrap: wrap;
  margin-bottom: 14px;
}
.cal-head h1 { margin: 0; font-size: 22px; }
.cal-sub { margin: 4px 0 0; color: var(--ink-dim); font-size: 13.5px; }

.cal-legend { display: flex; flex-wrap: wrap; gap: 4px 12px; font-size: 12.5px; color: var(--ink-dim); }
.cal-legend span { display: inline-flex; align-items: center; gap: 5px; }
.dot { width: 10px; height: 10px; border-radius: 50%; display: inline-block; }
.dot-green { background: var(--green); }
.dot-red { background: var(--red); }
.dot-amber { background: var(--amber); }
.dot-plain { background: var(--line); }

/* The four counts. A subscriber's whole month in one glance. */
.cal-tally, .rider-tally {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(84px, 1fr));
  gap: 8px;
  margin-bottom: 14px;
}
.cal-stat {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 10px 12px;
  text-align: center;
}
.cal-stat strong { display: block; font-size: 22px; line-height: 1.1; }
.cal-stat span { font-size: 11.5px; color: var(--ink-dim); }
.cal-stat.is-bad { border-color: var(--red); background: var(--red-tint); }
.cal-stat.is-bad strong { color: var(--red); }

.cal-nav { display: flex; align-items: center; justify-content: center; gap: 14px; margin-bottom: 10px; }

/*  THE MONTH GRID.

    Seven equal columns, which is one of the few things a fixed grid is
    genuinely right for — a week is always seven days wide. Weeks start on
    Monday because that is how a delivery week is planned and talked about,
    not because of any calendar convention.  */
.cal-grid {
  display: grid;
  grid-template-columns: repeat(7, minmax(0, 1fr));
  gap: 5px;
}
.cal-dow {
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.4px;
  color: var(--ink-dim);
  text-align: center;
  padding-bottom: 2px;
}
.cal-cell {
  border: 1px solid var(--line);
  border-radius: 8px;
  min-height: 74px;
  padding: 5px 5px 6px;
  display: flex;
  flex-direction: column;
  gap: 2px;
  overflow: hidden;
}
.cal-cell.is-empty { border: 0; background: none; min-height: 0; }
/* A day this person does not take at all — not a missed day, just not theirs. */
.cal-cell.is-off { background: var(--paper); border-style: dashed; }
.cal-num { font-size: 12.5px; font-weight: 700; }
/*  THE MARK.

    A tick, a cross or a dash, centred and large enough to read at arm's
    length. It is what the square says to somebody who cannot tell the green
    from the red, and it is also what survives a black-and-white printout or
    a phone in bright sun with the brightness turned down.  */
.cal-mark {
  flex: 1 1 auto;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 17px;
  line-height: 1;
}
.cal-cell.is-green .cal-mark { color: var(--green); }
.cal-cell.is-red .cal-mark { color: var(--red); }
.cal-cell.is-amber .cal-mark { color: var(--amber); }
.cal-cell.is-plain .cal-mark { color: var(--line); }
.cal-cell.is-today { box-shadow: inset 0 0 0 2px var(--green); }

/*  THE DAYS IN WORDS.

    Everything that needs reading lives here rather than in a 45-pixel
    square: the dish, what happened, the photo, and the button to call a day
    off. Rows, not a table — this is read on a phone.  */
.cal-h2 { font-size: 14px; margin: 20px 0 8px; }
.cal-list { list-style: none; margin: 0; padding: 0; }
.cal-row {
  display: flex;
  align-items: center;
  gap: 10px;
  border: 1px solid var(--line);
  border-left-width: 4px;
  border-radius: 8px;
  padding: 9px 11px;
  margin-bottom: 6px;
}
.cal-row.is-green { border-left-color: var(--green); }
.cal-row.is-red { border-left-color: var(--red); }
.cal-row.is-amber { border-left-color: var(--amber); }
.cal-row.is-plain { border-left-color: var(--line); }
.cal-when { flex: 0 0 auto; font-size: 12.5px; color: var(--ink-dim); min-width: 92px; }
.cal-what { flex: 1 1 auto; min-width: 0; font-size: 13.5px; }
.cal-word { display: block; font-style: normal; font-size: 11.5px; color: var(--ink-dim); }
.cal-foot { font-size: 12.5px; color: var(--ink-dim); margin: 16px 0 0; line-height: 1.5; }

/*  Four counts, two by two on a narrow phone. Auto-fit put three on the
    first row and left the fourth stranded on its own.  */
@media (max-width: 430px) {
  .cal-tally { grid-template-columns: repeat(2, 1fr); }
  .cal-when { min-width: 74px; }
  /* Shorter squares on a phone. Nothing is read inside them any more, so
     they only have to be big enough to see the pattern of the month
     without five rows of them filling the whole screen. */
  .cal-cell { min-height: 54px; }
  .cal-mark { font-size: 15px; }
}

/* ---------- the restaurant's week grid ---------- */
.subs-grid th, .subs-grid td { vertical-align: top; }
.subs-grid th.is-today { background: var(--green-tint); }
.subs-dow { display: block; font-size: 11px; text-transform: uppercase; color: var(--ink-dim); }
.subs-date { display: block; font-size: 12.5px; }
/* How many salads that day. The kitchen's number, so it is the big one. */
.subs-count { display: block; font-size: 15px; font-weight: 700; }
.subs-cell { min-width: 96px; position: relative; }
.subs-item { display: block; font-size: 12px; line-height: 1.3; }
.subs-status { display: block; color: var(--ink-dim); text-transform: capitalize; }
.subs-cell.is-off { background: var(--paper); }
.subs-x {
  position: absolute;
  top: 2px;
  right: 2px;
  font: inherit;
  font-size: 13px;
  line-height: 1;
  background: none;
  border: 0;
  color: var(--ink-dim);
  cursor: pointer;
  padding: 3px 5px;
  border-radius: 4px;
}
.subs-x:hover { background: var(--red-tint); color: var(--red); }
.subs-edit-row td { background: var(--paper); padding-top: 6px; padding-bottom: 10px; }

/* ---------- the rider's round ---------- */
/*  One thumb, at a gate, in sunlight, with a bag in the other hand. Rows
    rather than a table, nothing to scroll sideways, and the one button that
    matters is full width and 50px tall.  */
.rider-stop {
  background: var(--surface);
  border: 1px solid var(--line);
  border-left: 4px solid var(--amber);
  border-radius: var(--radius);
  padding: 12px 13px;
  margin-bottom: 10px;
}
.rider-stop.is-done { border-left-color: var(--green); background: var(--green-tint); }
.rider-stop.is-off { border-left-color: var(--line); opacity: 0.65; }
.rider-name { font-size: 16.5px; }
.rider-addr { font-size: 13.5px; line-height: 1.4; margin-top: 2px; }
.rider-note {
  font-size: 12.5px;
  background: var(--amber-tint);
  border-radius: 6px;
  padding: 5px 8px;
  margin-top: 6px;
}
.rider-actions { display: flex; gap: 6px; flex-shrink: 0; }
.rider-deliver { margin-top: 10px; }
/*  THE ONE BUTTON A RIDER PRESSES ALL MORNING.

    Sized past the ordinary .btn height on purpose. It is pressed with a
    thumb, at a gate, often one-handed while holding a bag, and a mis-tap
    costs a walk back. 52px clears the 44px that both Apple and Google give
    as the floor for a touch target, with room for a glove or a wet screen. */
.rider-cam {
  text-align: center;
  cursor: pointer;
  min-height: 52px;
  font-size: 15.5px;
}
.rider-done-row { display: flex; gap: 8px; align-items: center; margin-top: 8px; flex-wrap: wrap; }
.rider-msg { font-size: 12.5px; color: var(--ink-dim); margin: 6px 0 0; min-height: 1px; }
.rider-msg.is-bad { color: var(--red); }

/* Nothing on any of these three screens is meant for paper. */
@media print {
  .cal-skip, .subs-x, .rider-deliver, .rider-actions { display: none !important; }
}

/* Seven checkboxes instead of a typed "1,2,3,4,5,6". */
.day-pick { display: inline-flex; gap: 3px; flex-wrap: nowrap; }
.day-pick label {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 28px;
  height: 32px;
  border: 1px solid var(--line);
  border-radius: 6px;
  cursor: pointer;
  font-size: 12px;
  background: var(--surface);
  position: relative;
  margin: 0;
}
/* The box itself is covered by its own label, which is the target. */
.day-pick input { position: absolute; opacity: 0; width: 100%; height: 100%; margin: 0; cursor: pointer; }
.day-pick input:checked + span { color: #fff; }
.day-pick label:has(input:checked) { background: var(--green); border-color: var(--green); }
/*  :has() is not load-bearing here — an older browser simply shows the tick
    instead of the filled box, and the control still works. Nothing that
    reaches paper or money depends on it. */
.day-pick input:focus-visible + span { outline: 2px solid var(--green); outline-offset: 3px; }

/* The per-subscriber edit form, folded until wanted. */
.subs-edit > summary { cursor: pointer; font-size: 13px; color: var(--ink-dim); padding: 2px 0; }
.subs-edit[open] > summary { color: var(--ink); font-weight: 600; }

/* A package about to run out, on the dashboard. One line each, because the
   only question is who and when. */
.renew-line {
  display: flex;
  align-items: baseline;
  gap: 10px;
  flex-wrap: wrap;
  padding: 7px 10px;
  border-left: 4px solid var(--line);
  border-radius: 6px;
  margin-bottom: 5px;
  font-size: 13.5px;
}
.renew-line.is-warn { border-left-color: var(--amber); background: var(--amber-tint); }
.renew-line.is-over { border-left-color: var(--red); background: var(--red-tint); }
.renew-line span { color: var(--ink-dim); font-size: 12.5px; }
.renew-when { margin-left: auto; }

/* ---------- where the customer stands on what they paid for ---------- */
.cal-status {
  border-radius: var(--radius);
  padding: 10px 13px;
  font-size: 13.5px;
  line-height: 1.5;
  margin-bottom: 12px;
  border: 1px solid var(--line);
}
.cal-status.is-ok { background: var(--green-tint); border-color: var(--green); }
.cal-status.is-warn { background: var(--amber-tint); border-color: var(--amber); }
.cal-status.is-over { background: var(--red-tint); border-color: var(--red); }

/* A day square is a link now — it must not look like body text. */
a.cal-cell { text-decoration: none; color: inherit; }
a.cal-cell:hover { border-color: var(--green); }
/* A dot in the corner means there is a photo waiting behind this day. */
.cal-cam { position: absolute; top: 4px; right: 5px; font-size: 7px; color: var(--green); }
.cal-cell { position: relative; }

.cal-back { font-size: 13px; margin: 0 0 10px; }

/* ---------- one day, in full ---------- */
.day-card {
  border: 1px solid var(--line);
  border-left-width: 5px;
  border-radius: var(--radius);
  padding: 16px;
  background: var(--surface);
}
.day-card.is-green { border-left-color: var(--green); }
.day-card.is-red { border-left-color: var(--red); }
.day-card.is-amber { border-left-color: var(--amber); }
.day-card.is-plain { border-left-color: var(--line); }
.day-status {
  font-size: 12px;
  text-transform: uppercase;
  letter-spacing: 0.6px;
  color: var(--ink-dim);
}
.day-meal { font-size: 20px; font-weight: 700; margin: 4px 0 10px; line-height: 1.3; }
.day-extra {
  background: var(--amber-tint);
  border-radius: 8px;
  padding: 9px 11px;
  font-size: 13px;
  margin-bottom: 10px;
}
.day-extra span { display: block; color: var(--ink-dim); font-size: 12.5px; margin-top: 2px; }
.day-facts { display: grid; grid-template-columns: auto 1fr; gap: 4px 14px; margin: 0 0 12px; font-size: 13.5px; }
.day-facts dt { color: var(--ink-dim); }
.day-facts dd { margin: 0; }
.day-missed { font-size: 13px; color: var(--red); margin: 10px 0 0; line-height: 1.5; }

/*  THE PHOTO.

    Full width and unclipped. It is the one thing on this page somebody
    opened the day to look at, and cropping it to a neat square is exactly
    how you cut the doormat out of the evidence.  */
.day-proof { margin: 0; }
.day-proof img {
  width: 100%;
  height: auto;
  display: block;
  border-radius: var(--radius);
  border: 1px solid var(--line);
  background: var(--surface-2);
}
.day-proof figcaption { font-size: 12.5px; color: var(--ink-dim); margin-top: 7px; }

/* A subscriber no longer in the book, still listed but visibly retired. */
tr.is-dim > td { opacity: 0.55; }

/* A special request, on the owner's week grid. */
.subs-extra {
  display: inline-block;
  font-size: 11px;
  font-weight: 700;
  color: var(--amber);
}

/* Today only, and louder than the standing note beneath it. */
.rider-special {
  font-size: 13px;
  background: var(--amber-tint);
  border: 1px solid var(--amber);
  border-radius: 6px;
  padding: 6px 9px;
  margin-top: 6px;
}
.rider-special span { display: block; font-weight: 700; color: var(--amber); margin-top: 2px; }

/* The dish name in these lists is a link to the day. It should read as the
   dish, not as a web link — the whole row is tappable anyway. */
.cal-what a { color: inherit; text-decoration: none; }
.cal-what a:hover { text-decoration: underline; }

/* ====================================================================
   DEMO PASSES

   A guest is on the real software, on a real server, with a clock
   running. The bar is loud on purpose: the owner is standing next to
   them and both of them need to see how long is left without asking.
   ==================================================================== */
.demo-bar {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
  background: var(--ink);
  color: #fff;
  padding: 7px 14px;
  font-size: 13px;
}
.demo-bar.is-ending { background: var(--red); }
.demo-dot {
  width: 8px; height: 8px; border-radius: 50%;
  background: #7ee2a8; display: inline-block;
}
.demo-bar.is-ending .demo-dot { background: #fff; }
.demo-who { opacity: 0.8; }
.demo-time { font-variant-numeric: tabular-nums; }
.demo-bar .btn { background: rgba(255,255,255,0.12); color: #fff; border-color: rgba(255,255,255,0.3); }
.demo-bar .btn:hover { background: rgba(255,255,255,0.22); }
.demo-bar form { margin: 0; }
@media print { .demo-bar { display: none !important; } }

/* The guest's sign-in and the goodbye page: one card, nothing else. */
.demo-gate {
  min-height: 100vh;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 20px;
}
.demo-gate .card { max-width: 380px; width: 100%; }

/* The credentials, shown once. */
.fresh-pass { border: 2px solid var(--green); }
.pass-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(190px, 1fr)); gap: 10px; }
.pass-grid > div {
  background: var(--paper);
  border-radius: 8px;
  padding: 9px 11px;
}
.pass-grid span { display: block; font-size: 11.5px; color: var(--ink-dim); }
.pass-grid strong { font-size: 14px; word-break: break-all; }
/* The password is the one thing that cannot be recovered — make it the
   thing the eye lands on. */
.pass-word { font-size: 18px !important; letter-spacing: 1px; }


/* ====================================================================
   PHONES AND TABLETS HELD UPRIGHT

   The owner's report was that the software is perfect on a tablet in
   landscape and falls apart the moment the tablet is turned, and that on a
   phone almost nothing is usable. Measuring every screen at 390x844,
   360x740, 768x1024 and 1024x768 found three causes, and confirmed that
   landscape really is clean — so these rules only need to repair the
   narrow case without touching the one that already works.

   1. THE NAVIGATION WAS CLIPPED, NOT SCROLLED.
      `.nav` carried `overflow: hidden` below 1150px. On a phone that hid
      24 of the 24 links and on a tablet in portrait it hid 18, including
      Audit — which is why the audit trail could not be reached at all.
      `hidden` does not mean "shrink to fit", it means "cut off and provide
      no way back". It needed to scroll.

   2. THE TILL WAS 1594px WIDE INSIDE A 390px PHONE.
      `.pos` already collapses to one column below 1000px, so that was not
      it. A grid item defaults to `min-width: auto`, meaning it refuses to
      become narrower than its own contents — so the single column inflated
      to the width of the widest thing inside it and dragged the page with
      it. Every container in that chain now gets `min-width: 0`, which is
      what lets a grid or flex child actually shrink.

   3. SMALL OVERFLOWS ELSEWHERE — a table or a toolbar a few pixels wider
      than the screen. Enough to make the whole page slide sideways, which
      is what makes a layout feel broken even when everything is present.
   ==================================================================== */

/* ---- 1. The navigation scrolls instead of hiding what it cannot fit ---- */
@media (max-width: 1150px) {
  .nav {
    overflow-x: auto;
    overflow-y: hidden;
    scrollbar-width: none;               /* the bar itself is the affordance */
    scroll-snap-type: x proximity;

    /* NO `-webkit-overflow-scrolling: touch` HERE. IT WAS HERE, AND IT IS
       THE SAME TRAP AS THE mask-image ONE WRITTEN UP AT .topbar::after.

       On iOS WebKit that property makes the element a containing block for
       its `position: fixed` descendants. The More panel IS a fixed
       descendant of this bar, so on an iPhone it was being confined to a
       ~40px scrolling strip: drawn in the wrong place, clipped, or sitting
       there looking fine and swallowing every tap.

       The owner reported exactly that — "I cannot click on the More button
       and nothing appears" — while every Chromium test passed, because
       Chrome has no such behaviour. One engine's quirk, invisible to the
       other engine's tests.

       It was only ever there for momentum scrolling, which iOS has done by
       default for `overflow: auto` since iOS 13. So it buys nothing on any
       phone in use and costs the whole menu on Safari. */
  }
  .nav::-webkit-scrollbar { display: none; }
  .nav > a, .nav .nav-more-menu a { scroll-snap-align: start; flex: 0 0 auto; }

  /* NO MASK ON THE BAR. THIS COST AN HOUR, SO IT IS WRITTEN DOWN.
     
     The fading right edge was first done with `mask-image` on .nav. It
     looked right and quietly broke the More menu: a masked element becomes
     the CONTAINING BLOCK for its `position: fixed` descendants, exactly as
     `transform` and `filter` do. The menu panel was therefore confined to a
     358x40px scrolling strip instead of the viewport — it still drew in the
     right place, but taps passed straight through it to the page behind.
     Visible, correctly positioned, and completely dead.
     
     No amount of z-index fixes that, because it is not a stacking problem.
     Five separate overrides were tried and only moving the panel out of the
     header worked, which is what pointed at the containing block.
     
     The fade is now a pseudo-element on the header instead. The panel is
     not a descendant of it, so nothing is trapped. */
  .topbar { position: sticky; }
  .topbar::after {
    content: '';
    position: absolute;
    right: 0;
    bottom: 4px;
    width: 26px;
    height: 34px;
    pointer-events: none;        /* never swallow a tap on the last link */
    background: linear-gradient(to right, transparent, var(--surface));
  }
}

/* ---- 2. Let every column actually become narrow ---- */
@media (max-width: 1000px) {
  .pos, .pos > *, .pos-menu, .menu-toolbar, .cart { min-width: 0; }
  .pos-menu > *, .cart > * { min-width: 0; max-width: 100%; }
  .quickcode-bar, .quickcode-bar input { max-width: 100%; box-sizing: border-box; }
  .item-grid, .item-grid.as-list { min-width: 0; }
  .item-grid .item-btn { min-width: 0; max-width: 100%; }
  .item-grid .item-btn .name { min-width: 0; overflow-wrap: anywhere; }
}

/* ---- 3. Nothing slides sideways ---- */
@media (max-width: 1000px) {
  /* NO `overflow-x: hidden` ON body HERE, DELIBERATELY.
     
     It was the obvious belt-and-braces against sideways scrolling and it
     caused a worse bug than it prevented. Setting overflow-x on body makes
     the computed overflow `hidden auto`, which turns body into a scroll
     container — and a scroll container on body breaks hit-testing for
     `position: fixed` children. The More menu opened, the links were drawn
     in the right place, and taps went straight through them to the page
     behind. "Visible but not clickable" is the hardest kind of broken to
     report and the easiest to ship.
     
     The real overflow causes are fixed above — the nav scrolls, grid items
     may shrink, and columns use minmax(0, 1fr). Hiding the symptom is no
     longer needed, and the layout probe fails the build if one comes back. */

  /* A table is the one thing that genuinely needs to stay wide. It gets its
     own scroller rather than pushing the page out. */
  .table-scroll, .card .table-scroll { overflow-x: auto; -webkit-overflow-scrolling: touch; }
  table { max-width: 100%; }

  /* Toolbars and filter rows wrap rather than running off the edge. */
  .menu-toolbar, .channel-bar, .filters, .toolbar, .card-head, .row-actions {
    flex-wrap: wrap;
  }

  /* An input with a fixed width is the commonest few-pixel overflow. */
  input, select, textarea { max-width: 100%; box-sizing: border-box; }
}

/* ---- A phone is not a small tablet ---- */
@media (max-width: 560px) {
  /* Two columns of anything is one column too many at this width.

     minmax(0, 1fr), NOT 1fr. They look identical and are not: `1fr` is
     shorthand for `minmax(auto, 1fr)`, and `auto` as a minimum means
     min-content — so a column holding a wide table refuses to be narrower
     than that table. The reports grid was 370px wide with a 496px column
     inside it, and the cards hung 116px off the right of the screen. It is
     the same min-width:auto trap as on the grid items above, wearing a
     different hat. */
  .grid, .grid-2, .grid-3, .grid-4, .stat-row, .cards-2, .cards-3 {
    grid-template-columns: minmax(0, 1fr) !important;
  }
  .item-grid { grid-template-columns: minmax(0, 1fr) !important; }

  /* And the cards themselves must be allowed to be as narrow as the column. */
  .grid > *, .grid-2 > *, .grid-3 > *, .grid-4 > * { min-width: 0; }
  .wrap, .wrap-wide { padding-left: 10px; padding-right: 10px; }
  .card { padding: 12px; }

  /* Buttons a thumb can actually hit.
     44px, not 40. The rest of the application already uses 44 — it is the
     size a fingertip reliably lands on — and writing 40 here quietly pulled
     the rider's camera button down to 43px, which the calendar layout test
     caught. A min-height is a floor, so picking a lower one than the app's
     own standard can only do harm. */
  .btn, .cat-tab, .item-btn { min-height: 44px; }

  /* The order list is what the cashier is checking — give it the room. */
  .cart { order: -1; }
  .cart-lines { max-height: 45vh; }
}

/* The quick-code hint clips itself with an ellipsis, which on a desktop
   hides a few characters and on a phone hides the whole hint — the cashier
   is left with an input box and no clue what to type into it. On a narrow
   screen it wraps onto a second line instead, which costs 14 pixels and
   keeps the thing that teaches the shortcut. */
@media (max-width: 700px) {
  .quickcode-hint {
    white-space: normal;
    overflow: visible;
    text-overflow: clip;
    line-height: 1.45;
  }
  .quickcode-hint .mono { white-space: nowrap; }
}

/* ====================================================================
   ON A NARROW SCREEN THE NAVIGATION GETS ITS OWN ROW

   Making the bar scrollable stopped links being lost, but measuring what
   the scrolling window was actually worth gave this:

       small phone  360px    5px of 566px visible   (1%)
       phone        390px   35px of 566px visible   (6%)
       large phone  430px   75px of 566px visible  (13%)
       tablet 768px         413px of 566px visible  (73%)

   One per cent is a letter at a time. The bar was sharing a flex row with
   the logo and the branch/user controls, which took 307 of the 390 pixels
   and left the navigation with the crumbs — and because the "More" menu is
   flattened inline at this width, those crumbs then wrapped into a column
   615 PIXELS TALL. On an 844px phone the header was three quarters of the
   screen before any content appeared.

   So below 900px the header becomes two rows: identity and controls on the
   first, the navigation across the full width of the second. `display:
   contents` dissolves the More wrapper so its links join the same single
   scrolling line instead of stacking into a block.
   ==================================================================== */
@media (max-width: 900px) {
  .topbar-inner {
    flex-wrap: wrap;
    row-gap: 6px;
    align-items: center;
  }
  .brand        { flex: 0 0 auto; }
  .topbar-right { flex: 1 1 auto; justify-content: flex-end; min-width: 0; }

  .nav {
    order: 10;          /* always the second row, whatever the source order */
    flex: 1 0 100%;
    width: 100%;
    min-width: 0;
    flex-wrap: nowrap;  /* one line that scrolls, never a wrapped block */
    padding-bottom: 2px;
  }

  /* THE "MORE" MENU STAYS A MENU HERE.
     
     The old rule flattened it inline below 1000px, reasoning that "the bar
     already wraps and scrolls, and a dropdown just hides things people are
     looking for". The bar did not scroll — it was clipped — so that traded
     a hidden menu for lost links.
     
     Now that the bar genuinely scrolls, flattening is the worse of the two:
     all 24 links in one line is 1,889px, which is five phone-screens of
     swiping to reach Audit. Keeping the menu puts the seven everyday links
     in reach and the rest one tap away. */
  .nav-more     { display: inline-block; position: relative; flex: 0 0 auto; }
  .nav-more-btn { display: inline-block; }

  /* The panel drops below the bar as a sheet, scrolling on its own if the
     list is longer than the screen. Anchored to the right so it cannot run
     off the edge it was opened from. */
  .nav-more-menu {
    display: none;
    position: fixed;
    left: 8px; right: 8px;
    top: auto;

    /* 60vh WAS THE BUG, AND IT LOOKED LIKE A SENSIBLE NUMBER.
     *
     * On an owner's install the menu holds seventeen links — 743px of
     * them. 60vh of an 844px phone is 506px, so the panel showed twelve
     * and a HALF rows and stopped. Backup and Audit were inside a box
     * that scrolled perfectly well, and nobody could tell, because a
     * list cut off mid-row with no scrollbar reads as a list that has
     * ended. Below the cut the page's own content starts, so a thumb
     * aiming at the next item scrolls the bills table behind it instead.
     *
     * The owner reported this twice as "I cannot reach the backup". He
     * was right both times, and both times the measurement said the link
     * was reachable — because a robot scrolls a container it can see in
     * the DOM, and a person scrolls what looks scrollable.
     *
     * The panel opens about 87px down, so this takes nearly all of what
     * is left. Seventeen links now fit outright on a normal phone and
     * there is nothing to scroll at all. */
    /* vh FIRST, THEN dvh. On iOS Safari `100vh` is the height the page
       WOULD have if the toolbars were hidden — taller than what you can
       actually see. A panel sized from it hangs its last rows underneath
       the Safari toolbar, which is precisely where Backup and Audit live.
       `dvh` is the height that is really visible, and the plain vh line
       above it is what an older iOS falls back to. */
    max-height: calc(100vh - 96px);
    max-height: calc(100dvh - 96px);
    overflow-y: auto;

    /* And when the list IS longer than the screen — a small phone, or an
     * install with more links — the scroll stays inside the panel instead
     * of handing the gesture to the page behind once it hits the end. */
    overscroll-behavior: contain;

    z-index: 200;
    padding: 6px;
    background: var(--surface);
    border: 1px solid var(--line);
    border-radius: 10px;
    box-shadow: 0 12px 32px rgba(0,0,0,0.22);
  }

  /* A SHADOW THAT SAYS "THERE IS MORE BELOW", AND GOES AWAY AT THE END.
   *
   * Two pairs of backgrounds. The solid covers are attached `local`, so
   * they travel with the content; the soft shadows are attached `scroll`,
   * so they stay pinned to the panel's edges. At the top of the list the
   * cover has scrolled away and the bottom shadow shows. Scroll to the
   * end and the bottom cover slides over its shadow and hides it.
   *
   * It is a background, NOT a mask and NOT a filter. Both of those turn
   * an element into the containing block for `position: fixed`
   * descendants, which is the trap that made this very menu visible but
   * untappable once before. The note at .topbar::after has that story. */
  .nav-more-menu {
    background-image:
      linear-gradient(to bottom, var(--surface) 30%, rgba(255, 255, 255, 0)),
      linear-gradient(to top,    var(--surface) 30%, rgba(255, 255, 255, 0)),
      radial-gradient(farthest-side at 50% 0,    rgba(0, 0, 0, 0.30), rgba(0, 0, 0, 0)),
      radial-gradient(farthest-side at 50% 100%, rgba(0, 0, 0, 0.30), rgba(0, 0, 0, 0));
    background-position: top, bottom, top, bottom;
    /* Deliberately heavy. A faint hint is what we had — a half-row with no
       scrollbar — and it was read as "the list ends here" twice. When the
       list does fit, the solid covers sit over both shadows and nothing
       shows, so this can be as loud as it needs to be. */
    background-size: 100% 22px, 100% 22px, 100% 13px, 100% 13px;
    background-repeat: no-repeat;
    background-attachment: local, local, scroll, scroll;
    background-color: var(--surface);
  }
  .nav-more.is-open .nav-more-menu { display: block; }
  .nav-more-menu a {
    display: block;
    padding: 11px 12px;     /* a thumb, not a mouse */
    border-radius: 7px;
    white-space: nowrap;
  }
}

/* ====================================================================
   A SHORT PHONE GETS THE MENU IN TWO COLUMNS

   Seventeen links at a thumb-sized 43px each is 743px of list. A tall
   phone has room for that and shows the lot. A 640px phone does not, and
   no amount of shadow at the cut edge beats simply not cutting.

   Two columns turn seventeen rows into nine — 387px — which fits with
   room to spare on anything still being sold. The panel is 344px wide at
   360px screen width, so each column is about 165px and the longest
   label on it ("Staff access") is 95px. Nothing wraps and nothing is
   hidden.

   Keyed on HEIGHT, not width, because height is what ran out.
   ==================================================================== */
@media (max-width: 1000px) and (max-height: 780px) {
  .nav-more-menu {
    display: none;             /* still opened by .is-open below */
    grid-template-columns: 1fr 1fr;
    gap: 2px 4px;
  }
  .nav-more.is-open .nav-more-menu { display: grid; }
  .nav-more-menu a {
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;   /* a longer label on a future screen degrades
                                  to an ellipsis rather than breaking the grid */
  }
}

/* ====================================================================
   THE ROW THAT WAS JUST SAVED

   Two dishes on this menu are called "Papaya Chikoo" and "Papaya Chikoo
   Date", and four more differ only by (MED) or (LG). After a save the page
   reloads and looks identical, so "did that take?" is a real question. The
   row says so for two seconds.
   ==================================================================== */
tr.just-saved > td {
  background: var(--green-tint, rgba(16,160,90,0.14));
  transition: background 1.2s ease-out;
}
tr.is-retired > td { opacity: 0.55; }
tr.is-retired input, tr.is-retired select { opacity: 0.8; }


/* ====================================================================
   A WIDE TABLE ON A PHONE: `.as-cards`

   The menu list is name, category, two codes, price, tax, status and
   buttons — eight columns of inputs. On a 390px screen that is about 48px
   each, which is not a table, it is a column of truncated boxes. The
   horizontal scroller keeps it honest on a tablet, but on a phone the only
   workable shape is one card per row.

   Each cell carries a data-label, so the heading travels with the value
   instead of being stranded in a <thead> the person can no longer see.

   Put `as-cards` on the card wrapping any table with more than about five
   columns. It is used by the menu list, the partner list, a partner's
   client list and the monthly commission breakdown — the four tables wide
   enough to be unusable on a phone otherwise.
   ==================================================================== */
@media (max-width: 700px) {
  .as-cards table,
  .as-cards thead,
  .as-cards tbody,
  .as-cards tr,
  .as-cards td { display: block; width: 100%; }

  .as-cards thead { display: none; }

  .as-cards tr {
    border: 1px solid var(--line);
    border-radius: var(--radius);
    padding: 10px 12px;
    margin-bottom: 10px;
    background: var(--surface);
  }

  .as-cards td {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 4px 0;
    border: none;
    text-align: left !important;
  }

  /* The heading for this value, carried on the cell itself. */
  .as-cards td[data-label]::before {
    content: attr(data-label);
    flex: 0 0 82px;
    font-size: 11.5px;
    text-transform: uppercase;
    letter-spacing: 0.03em;
    font-weight: 700;
    color: var(--ink-dim);
  }
  .as-cards td[data-label='']::before { content: none; }

  /* Inputs take the rest of the row rather than their desktop widths. */
  .as-cards td input,
  .as-cards td select {
    flex: 1 1 auto;
    min-width: 0;
    width: auto !important;
    text-align: left !important;
  }
  /* Except the two codes, which sit side by side and are short. */
  .as-cards td[data-label='Codes'] { flex-wrap: wrap; }
  .as-cards td[data-label='Codes'] input { flex: 1 1 70px; width: auto !important; }

  /* The buttons get their own full-width row at the bottom of the card. */
  .as-cards td.actions {
    justify-content: flex-start;
    gap: 8px;
    padding-top: 8px;
    margin-top: 4px;
    border-top: 1px solid var(--line);
  }
  .as-cards td.actions form { display: inline; }
  .as-cards td.actions .btn { flex: 1 1 auto; }

  /* No sideways scroller is needed once it is cards, and leaving one makes
     the whole card drag around under a thumb. */
  .as-cards .table-scroll { overflow-x: visible; }
}

/* The line under a phone box that says what is wrong with the number.
   Silent until there is something to say — a field that nags before you
   have finished typing is a field people learn to ignore. */
.phone-hint {
  min-height: 0;
  color: var(--ink-dim);
  margin-top: 3px;
  line-height: 1.3;
}
.phone-hint.is-bad { color: var(--red, #c0392b); font-weight: 600; }
input[aria-invalid="true"] { border-color: var(--red, #c0392b); }

/* ====================================================================
   STAT TILES GO TWO ACROSS ON A PHONE

   Everything else on a phone becomes one column, and that is right for a
   card holding a table. A stat tile holds a word and a number. Stacked one
   per row, the four figures at the top of the partner page — clients,
   orders, spend, commission — pushed the search box and the actual report
   most of a screen down, so the first thing a phone showed was four big
   numbers and nothing to do with them.

   Two across fits 150px columns at 360px wide, which is enough for
   "₹13,721" at this size. Only grids explicitly marked `grid-stats` are
   affected; a grid of cards keeps its single column.
   ==================================================================== */
@media (max-width: 560px) {
  .grid-stats { grid-template-columns: repeat(2, minmax(0, 1fr)) !important; }
  .grid-stats .stat { padding: 10px 11px; }
  .grid-stats .stat-value { font-size: 20px; line-height: 1.2; }
  .grid-stats .stat-label { font-size: 10.5px; }
}

/* A card that folds. Used for the partner's order lines, which are the
   audit trail rather than the answer — two hundred rows of it should not
   stand between a phone and the report above it. The summary is given a
   thumb-sized target and keeps its disclosure triangle, because a heading
   that can be opened has to look different from one that cannot.

   NOT display:flex on the summary — a flex summary loses its disclosure
   triangle in Chrome, and a heading that opens has to look like one. */
details.card > summary {
  cursor: pointer;
  min-height: 44px;
  padding: 10px 0 6px;
  margin: 0;
}
details.card[open] > summary { margin-bottom: 4px; }


/* ====================================================================
   FITMENT: THE SCREENS BEHIND A CLICK

   The earlier mobile work covered about twenty top-level pages. The owner
   asked the obvious next question — what about the bill being edited, a
   subscriber's page, the rider's round, the menu tiles? — and the answer
   was that none of them had ever been opened at phone width. Forty-three
   screens were then opened at four sizes each. What follows is what that
   found, and nothing else; every rule here has a measurement behind it.
   ==================================================================== */

/* ---- 1. The List/Tiles toggle was eating the category row ----------
   On a 390px phone the toggle took 103px of the toolbar, leaving the
   category tabs 258px to hold 2,372px of categories. They scrolled, but
   the last one was always sliced off mid-word against the toggle, which
   reads as broken rather than as scrollable. Below a laptop the toggle
   gets its own line and the tabs get the whole width. */
@media (max-width: 1000px) {
  .menu-toolbar { flex-wrap: wrap; align-items: stretch; }
  .menu-toolbar .cat-tabs { flex: 1 1 100%; order: 1; }
  .menu-toolbar .density  { flex: 0 0 auto; order: 2; margin-left: auto; }
}

/* ---- 2. A fingertip is 44px on a tablet too -------------------------
   The 44px floor was written for `max-width: 560px`, so a tablet held
   upright — 768px, and the device the shop actually puts on the counter —
   kept 32px buttons. Thirty-two pixels is a mouse target. Every control
   in the sweep that came back under 40px was on a touch screen wider than
   560px: Open, Filter, Delete, Remove, Access, Enable, the date ranges,
   the week switchers, the List/Tiles toggle. */
@media (max-width: 1000px) {
  .btn, .btn-sm, .cat-tab, .item-btn, .density-btn, .channel-tab,
  button[type="submit"], select {
    min-height: 44px;
  }
  /* A bill number in a card row is a link people tap, not prose. */
  .as-cards td a { display: inline-block; padding: 11px 2px; }
  /* Padding has to grow with the box or the label floats off centre. */
  .btn-sm { padding-top: 8px; padding-bottom: 8px; }
  .density-btn { padding: 10px 14px; }
}

/* ---- 3. Tile names were being cut through the middle of a letter ----
   `-webkit-line-clamp: 2` was already there and working. The tile was the
   problem: 74px is right for a one-line name, and a two-line name plus
   its code and price needs 96px. The name clamped, the TILE clipped, and
   seventeen pixels of "Peanut Butter Sandwich (Banana)" went under the
   edge. Wider columns mean fewer names need the second line at all. */
@media (max-width: 1000px) {
  .item-grid:not(.as-list) { grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); }
  .item-grid:not(.as-list) .item-btn { min-height: 96px; }
}

/* ---- 4. The bill preview made the whole page scroll sideways --------
   The receipt is 80mm of monospace and cannot be made narrower without
   ceasing to be a preview of the paper. At 394px in a 390px screen the
   entire page slid left and right, which is the single thing that makes
   a layout feel broken. It keeps its real width and scrolls inside its
   own box instead. */
@media (max-width: 1000px) {
  .bf-paper-wrap {
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
    max-width: 100%;
  }
  /* The paper keeps its true width inside that scroller. Shrinking it to
     fit would reflow the columns and show a layout the printer will never
     produce, which defeats the point of a preview. */
  .bf-paper { max-width: none; }
}

/* ---- 5. Ten-and-a-half-pixel text on a phone ------------------------
   Stat labels, table headings and badges sit at 10.5-11.5px, which is
   fine on a laptop a foot from your face and not fine on a phone held at
   arm's length in a kitchen. Twelve is the floor on a touch screen. The
   desktop sizes are left alone — this is a reading-distance problem, not
   a design one. */
@media (max-width: 1000px) {
  .tiny, .stat-label, th, .badge, .card-sub, small { font-size: 12px; }
  .small { font-size: 12.5px; }
  /* The two-up stat tiles set their own smaller label size further up, and
     being more specific they won. On a phone held at arm's length in a
     kitchen, 10.5px is not a label, it is a smudge. */
  .grid-stats .stat-label { font-size: 12px; }
  .channel-tab, .cat-tab, .code, .quickcode-hint,
  .money-label, .money-note, .subs-dow, .channel-label, .channel-group, kbd {
    font-size: 12px;
  }

  /* NOT the receipt preview. .bf-paper is 80mm of monospace drawn at the
     size the thermal printer will actually print it; enlarging it to be
     comfortable on a phone would show a layout that never comes out of
     the machine, which is the one thing a preview must not do. */
  .bf-paper, .bf-paper * { font-size: inherit; }

  /* A dish row in List view is the thing a cashier taps a hundred times a
     shift. 34px is right for a mouse on a laptop and wrong for a thumb. */
  .item-grid.as-list .item-btn { min-height: 44px; }
}

/* ---- 6. Three last things the sweep turned up -----------------------
   The keyboard hint explains arrow keys and Enter. A phone has neither,
   and the explanation costs sixty pixels of a screen that is mostly bill.
   It stays on anything with a keyboard.

   The payment mode buttons and the rider's counters were simply missed by
   the earlier passes: 33px and 11.5px on the two screens that are used
   standing up, one-handed, in a kitchen or on a bike. */
@media (max-width: 1000px) {
  .kbd-hint { display: none; }
  .pay-mode { min-height: 44px; }
  .cal-stat span { font-size: 12px; }
}
@media (max-width: 1000px) {
  /* The quick code is deliberately the smallest thing on a dish row so the
     name gets the room. Half a pixel under the floor is still under it. */
  .item-grid.as-list .item-btn .code { font-size: 12px; }
}

/* ---- The week grid on a touch screen --------------------------------
   Found by the fitment sweep the first time it ran actually logged in.
   For weeks it had been photographing the login page and reporting that
   everything fitted, which is true and useless.

   Two faults, both on the Salads week grid:

     - the call-off cross is a 19px target. It is absolutely positioned in
       the corner of a cell, so the 44px floor for buttons never reached
       it. Nineteen pixels is a mouse target; a thumb hits the cell beside
       it and cancels the wrong person's lunch.
     - "+₹120" — the mark that somebody paid extra for a special request —
       was 11px. On the owner's phone, in a kitchen, that is the figure
       that decides what he charges.

   The grid is dense on purpose and stays dense on a laptop. This only
   grows the two things a finger has to hit and a person has to read. */
@media (max-width: 1000px) {
  .subs-x {
    min-width: 44px;
    min-height: 44px;
    top: 0;
    right: 0;
    padding: 0;
    font-size: 17px;
    display: flex;
    align-items: center;
    justify-content: center;
  }
  /* The cross now covers the corner, so the cell needs room for the dish
     name beside it rather than underneath it. */
  .subs-cell { min-width: 132px; padding-right: 46px; }
  .subs-extra { font-size: 12.5px; }
}

/* ---- A badge that is a LINK is a tap target ---------------------------
   The Inventory screen lists the ingredients that have gone negative, and
   each one is a badge you tap to open it. A badge is 22px tall, which is
   right for a label and half the size of a fingertip.

   Only the ones that are links grow. A plain `span.badge` — "on sale",
   "hidden", "corrected" — is a label nobody taps, and giving those a 44px
   floor would push every table row apart for nothing.

   Found by the fitment sweep, but only once the database had stock in it:
   the page has nothing on it when nothing has gone negative, and a screen
   with nothing on it fits every phone ever made. */
@media (max-width: 1000px) {
  a.badge {
    min-height: 44px;
    padding: 0 12px;
    display: inline-flex;
    align-items: center;
    border-radius: 22px;
  }
  /* Wrapping would make them taller again and the row ragged; these are
     short names, so let a long one scroll inside the list instead. */
  a.badge { white-space: nowrap; }
}

/* ---- The date going on a bill, shown before it is saved --------------
   The till was stamping bills five and a half hours early and nothing on
   the screen said so. The preview is the fix: the date a bill WILL carry,
   in words, at all times. Amber when it has been set by hand, because a
   hand-dated tax invoice is a deliberate act and should not look like an
   ordinary one. */
.bd-preview {
  margin-top: 8px;
  padding: 10px 12px;
  border-radius: 8px;
  background: var(--surface-2);
  border: 1px solid var(--line);
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
}
.bd-preview .bd-label { font-size: 12px; color: var(--ink-dim); }
.bd-preview strong { font-size: 15px; }
.backdate.is-manual > summary { color: var(--amber); font-weight: 700; }
.backdate.is-manual .bd-preview { background: var(--amber-tint); border-color: var(--amber); }
/* A fingertip, on the screen where speed matters most. */
@media (max-width: 1000px) { .bd-quick .btn { min-height: 44px; } }

/* ===========================================================================
   THE FLOATING CUSTOMER BUTTON

   Everything here is `position: fixed` and sits in its own stacking context.
   Not one rule touches `.pos`, `.cart-*`, `.channel-*` or any other till
   class — the owner's requirement was that the billing screen must not move,
   and the only way to be sure of that is to never style it. Delete this
   block and the till is exactly what it was.
   ======================================================================== */
.sr-only {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}

.cust-fab {
  position: fixed;
  /* Default corner. Moved the moment the cashier drags it, and remembered
     per device — a phone and the counter screen want it in different places,
     and one of them is being used left-handed. */
  right: 18px;
  bottom: 86px;
  z-index: 60;
  width: 56px; height: 56px;
  border-radius: 50%;
  border: none;
  display: flex; align-items: center; justify-content: center;
  background: var(--accent, #2f6d4f);
  color: #fff;
  box-shadow: 0 6px 18px rgba(0, 0, 0, .34);
  cursor: grab;
  /* A drag must never select text or fire the browser's own scroll. */
  touch-action: none;
  -webkit-user-select: none; user-select: none;
  transition: transform .12s ease, box-shadow .12s ease;
}
.cust-fab:active { cursor: grabbing; transform: scale(.96); }
.cust-fab.is-dragging { box-shadow: 0 10px 26px rgba(0, 0, 0, .46); transition: none; }

/* A customer is attached to this bill. The button says so without being
   opened, because the one thing worse than no details is details nobody
   noticed were already there. */
.cust-fab-dot {
  position: absolute; top: 4px; right: 4px;
  width: 13px; height: 13px; border-radius: 50%;
  background: var(--amber, #e0a81c);
  border: 2px solid var(--bg, #14181c);
}

/* `display: flex` below beats the `hidden` attribute's own `display: none`,
   because a class selector outranks it. Without this one line the panel is
   never hidden at all: it covers the whole till from the moment the page
   loads, swallowing every tap, and no button can close it because the
   button is behind it. A browser test caught it; reading the CSS did not. */
.cust-sheet[hidden] { display: none !important; }

.cust-sheet {
  position: fixed; inset: 0; z-index: 70;
  background: rgba(0, 0, 0, .55);
  display: flex; align-items: flex-end; justify-content: center;
  padding: 12px;
  overflow-y: auto;
}
@media (min-width: 720px) { .cust-sheet { align-items: center; } }

.cust-sheet-card {
  width: 100%; max-width: 620px;
  background: var(--card, #1b2026);
  border: 1px solid var(--line, #2a313a);
  border-radius: 14px;
  padding: 16px;
  max-height: 92vh; overflow-y: auto;
  /* Clear of the home indicator on a phone. */
  padding-bottom: calc(16px + env(safe-area-inset-bottom, 0px));
}

.cust-grid {
  display: grid; grid-template-columns: 1fr; gap: 10px; margin-top: 12px;
}
@media (min-width: 560px) { .cust-grid { grid-template-columns: 1fr 1fr; } }
.cust-grid .field { margin-bottom: 0; }
.cust-wide { grid-column: 1 / -1; }

.cust-consent {
  display: flex; gap: 10px; align-items: flex-start;
  margin-top: 14px; padding: 10px;
  border: 1px solid var(--line, #2a313a); border-radius: 10px;
}
.cust-consent input { margin-top: 3px; flex: 0 0 auto; }
.cust-consent span { font-size: 13px; line-height: 1.45; }
.cust-consent .tiny { display: block; margin-top: 2px; }

/* ===========================================================================
   THE MANUAL MENU EDITOR — channel pickers and the category manager.

   Narrow by design: these sit inside a table cell on a computer and inside a
   stacked card on a phone, so the row has to be able to be 180px wide
   without the price box falling off the end.
   ======================================================================== */
.chan-pick-grid { display: flex; flex-direction: column; gap: 6px; margin-top: 4px; }

.chan-pick-row {
  display: flex; align-items: center; gap: 8px;
  padding: 6px 8px;
  border: 1px solid var(--line, #2a313a); border-radius: 8px;
  font-size: 13px; font-weight: 400;
  cursor: pointer;
}
.chan-pick-row input[type="checkbox"] { flex: 0 0 auto; margin: 0; }
.chan-pick-name { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; }
.chan-pick-price {
  flex: 0 0 82px; width: 82px;
  padding: 4px 6px; font-size: 13px; text-align: right;
}

/* One dish's four price lists, folded away until asked for. A table row with
   four tick boxes permanently open is unreadable at a hundred dishes. */
.item-chans { margin-top: 6px; }
.item-chans > summary {
  cursor: pointer; color: var(--accent, #2f6d4f);
  padding: 2px 0; list-style: revert;
}
.item-chans[open] > summary { margin-bottom: 6px; }
/* NO BACKGROUND OF ITS OWN.
   This set `background: var(--bg)`, which is the PAGE background — a dark
   value. On the light theme that painted a near-black strip under near-black
   text and the four channel names became invisible. Every measurement passed:
   the rows lined up, nothing overflowed, the panel was simply unreadable.
   Only looking at the screenshot showed it. The row now keeps its border and
   inherits whatever surface it is sitting on, which is correct in both
   themes by construction rather than by a value that has to be kept in step. */
.item-chans .chan-pick-row { background: transparent; color: inherit; }

.cat-edit {
  border: 1px solid var(--line, #2a313a); border-radius: 8px;
  padding: 8px 10px; margin-bottom: 8px;
}
.cat-edit > summary {
  cursor: pointer; display: flex; justify-content: space-between;
  align-items: center; gap: 10px; list-style: revert;
}
.cat-edit-body { margin-top: 10px; padding-top: 10px; border-top: 1px solid var(--line, #2a313a); }

/* ===========================================================================
   THE PRICE-LIST PANEL INSIDE A PHONE CARD.

   On a phone the item table becomes one card per dish, and `.as-cards td`
   turns each cell into a flex ROW whose inputs are told to stretch:

       .as-cards td input { flex: 1 1 auto; width: auto !important; }

   That rule is right for the name and price boxes it was written for, and
   badly wrong for this panel — it stretched the tick boxes, stretched the
   price boxes, and pushed the whole card wider than the screen, so the
   channel names scrolled off to the left and the prices off to the right.
   The panel was unusable on the one device the owner actually bills on.

   So the panel claims a full-width line of its own inside the cell, and its
   own controls go back to their intended sizes.
   ======================================================================== */
@media (max-width: 700px) {
  /* The status cell holds a badge AND this panel: let them stack. */
  .as-cards td { flex-wrap: wrap; }

  .as-cards td .item-chans {
    flex: 1 1 100%;
    width: 100%;
    min-width: 0;
    box-sizing: border-box;
  }

  .as-cards td .chan-pick-row {
    width: 100%;
    box-sizing: border-box;
  }

  /* A tick box is a tick box, not something to stretch across a card. */
  .as-cards td .chan-pick-row input[type="checkbox"] {
    flex: 0 0 auto !important;
    width: auto !important;
    min-width: 0;
  }

  /* The name takes what is left and truncates rather than pushing. */
  .as-cards td .chan-pick-name {
    flex: 1 1 auto;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }

  /* And the price keeps a fixed, readable width at the end of the row. */
  .as-cards td .chan-pick-price {
    flex: 0 0 78px !important;
    width: 78px !important;
    min-width: 78px;
    text-align: right !important;
  }
}

/* The three-state control in a price-list row: name, state, price. */
.chan-pick-state {
  flex: 0 0 104px; width: 104px;
  padding: 4px 6px; font-size: 12.5px;
}
@media (max-width: 700px) {
  .as-cards td .chan-pick-state {
    flex: 0 0 104px !important; width: 104px !important; min-width: 104px;
  }
  /* Name, state and price on one line is tight on a phone: let the name
     take its own line above the two controls rather than being squeezed
     to three illegible characters. */
  .as-cards td .chan-pick-row { flex-wrap: wrap; row-gap: 6px; }
  .as-cards td .chan-pick-name { flex: 1 1 100%; white-space: normal; }
}
