/* TILL / EPOS (V24) — full-screen service UI.
 *
 * Touch-first: the reference devices are small Samsung tablets and an
 * iPhone 16 Pro Max. Every tappable thing is >=44px, nothing relies on hover,
 * and the layout never scrolls horizontally.
 *
 * The till deliberately does NOT use the app shell — no sidebar, no bottom
 * app nav. It owns the viewport while service is running.
 */

.till-body {
  margin: 0;
  overflow: hidden;                 /* the panes scroll, the page never does */
  background: var(--bg, #0b0c0c);
  overscroll-behavior: none;
}
.till-body.sheet-open { overflow: hidden; }

/* ⚠ THIRD LAYER against a swipe leaving the till. Chrome and Android turn a
   horizontal overscroll into a history navigation; `contain` keeps the gesture
   inside the app. Cheap, declarative, and it covers the browsers where the JS
   guard's touch events behave differently. */
.till-body, .till-app { overscroll-behavior-x: contain; }

.till-app {
  position: fixed;
  inset: 0;
  /* REINSTATED 2026-08-19 (it was tried and removed under the OLD wrapper,
     where the whole window sat 62pt down and height could not help): now that
     the wrapper anchors behind the clock, the remaining failure is the layout
     viewport under-reporting by ~62pt, which inset:0 inherits. dvh tracks the
     real window; top:0 + height wins over bottom:0, old WebViews drop it. */
  height: 100dvh;
  min-height: 100%;
  /* A FLEX column, mirroring the staff shell (.sidebar): position:fixed + inset:0
     with NO explicit height, so the bottom:0 anchor tracks iOS Safari's VISIBLE
     viewport and the nav ends flush at the visible bottom. The GRID this replaced
     resolved its 1fr track against the taller LAYOUT viewport, which pinned the
     nav above the real bottom and left a ~56px strip of .till-body showing under
     the bar in Safari (Filip, 2026-08); an explicit height — even 100dvh — breaks
     the same anchor, so there is none. Flex also drops a hidden printer strip for
     free: a display:none flex item just reflows, where a hidden GRID row used to
     slide .till-main into the wrong track and collapse it to 0px. Child order in
     till.html IS the layout order (top · printer strip · main · nav). */
  display: flex;
  flex-direction: column;
  background: var(--bg, #0b0c0c);
}

/* ---------------------------------------------------------------- top bar */
.till-top {
  display: flex;
  align-items: center;
  gap: 10px;
  position: relative;   /* anchors the tab-filter menu (V26.91) */
  /* Inside the zoom island the status inset is divided so it renders REAL —
     the Dynamic Island does not shrink with the UI. */
  padding: calc(env(safe-area-inset-top) / var(--till-chrome, 1) + 8px) 12px 8px;
  /* V26.80: iOS paints the status strip with the page THEME COLOUR
     (#0B0C0C), NOT pure black — proven by the dashboard, whose #0B0C0C bar
     blends perfectly. V26.79's #000 guess still mismatched, just darker.
     Match the strip exactly and the clock/battery area reads as part of
     the till's top bar. */
  background: var(--bg, #0B0C0C);
  border-bottom: 1px solid var(--border, #262a2a);
}
.till-exit, .till-operator {
  display: inline-flex; align-items: center; gap: 6px;
  min-height: 44px; padding: 0 14px;      /* 40px was under the touch minimum */
  border-radius: 999px; border: 1px solid var(--border, #2a2f2f);
  background: transparent; color: var(--text, #e9e6e1);
  font: 600 13px/1 Inter, system-ui, sans-serif; cursor: pointer;
}
.till-exit:active, .till-operator:active { background: rgba(255,255,255,.06); }
.till-operator { border-color: var(--gold, #C9A961); color: var(--gold, #C9A961); }
.till-title {
  /* V28.0 (design review #8): was text-align:center, but flex centring is only
     optical when the flanks match — on Tabs the filter pill + a long operator
     chip pushed it 40-70px off true centre, and it jumped when the pill hid on
     leaving Tabs. Left-aligned after the burger it is honest and stable. */
  flex: 1; text-align: left; min-width: 0;
  font: 600 16px/1.2 'Space Grotesk', Inter, sans-serif; color: var(--text, #e9e6e1);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

/* ------------------------------------------------------------------ panes */
.till-main { flex: 1; min-height: 0; overflow: hidden; position: relative; }
.till-screen {
  position: absolute; inset: 0;
  overflow-y: auto; -webkit-overflow-scrolling: touch;
  padding: 12px 12px 20px;
}
.till-screen[hidden] { display: none; }
.till-screen-head {
  display: flex; align-items: center; justify-content: space-between; gap: 12px; margin-bottom: 12px;
}
.till-screen-head h2 { margin: 0; font: 600 18px/1.2 'Space Grotesk', sans-serif; }
.till-empty { color: var(--text-muted, #8b9090); padding: 28px 8px; text-align: center; font-size: 14px; }

/* ---- Screen-level docked primary action ----
   The flex column + flex:1 list keeps the dock pinned to the bottom even when
   the screen is nearly empty (an empty tabs list is the common case at the
   start of service); position:sticky keeps it there once the list overflows.
   Both are needed — flex alone drops with short content once it scrolls, and
   sticky alone sits directly under the content when there is little of it. */
/* :not([hidden]) is REQUIRED. An ID selector is (1,0,0) and beats
   `.till-screen[hidden] { display:none }` at (0,2,0), so `display:flex` here
   re-showed the screen the nav had just hidden — the Tabs empty-state text bled
   through behind the menu grid. Screens are absolutely positioned and stacked,
   so nothing clipped it. */
#screenTabs:not([hidden]),
#screenMenu:not([hidden]) { display: flex; flex-direction: column; }
/* ⚠ The top bar must NOT grow. #itemGrid is `flex: 1 0 auto` and takes the free
   space; without an explicit shrink/grow of 0 here a sticky flex child can be
   stretched by the same rules, and the search pill would gain height on a short
   menu. */
#screenMenu .menu-top { flex: 0 0 auto; }
/* align-content:start is REQUIRED alongside flex:1. Without it the grid's rows
   stretch to fill the height the flex growth just gave it, which is what turned
   a single open tab into a full-screen card. Same reason as #itemGrid below. */
/* align-content:end is what makes the list fill from the bottom up, so the
   newest tab lands nearest the thumb (Filip, 2026-07-30). */
#screenTabs #tabsList { flex: 1 0 auto; align-content: end; }
/* align-content:start so a short result set sits at the TOP of the free space
   and the dock stays at the bottom, instead of the rows stretching to fill. */
#screenMenu #itemGrid { flex: 1 0 auto; align-content: start; }
/* FILL FROM THE BOTTOM UP (Filip, 2026-08-02: "all groups should populate from
   the bottom towards the top"). Same reasoning as the tabs list: on a phone held
   one-handed the bottom of the screen is thumb reach and the top is not, so a
   short grid should sit beside the dock rather than stranded under the title.
   ⚠ ID selectors, because #screenMenu #itemGrid above is (2,0,0) and a plain
   .cat-tiles rule loses to it — the reason the earlier align-content fix looked
   like it should work and did nothing.
   ⚠ Safe when the content overflows: the grid is `flex: 1 0 auto`, so it grows
   to its content and .till-screen does the scrolling. With no free space left,
   align-content has nothing to distribute and cannot push rows off the top. */
#screenMenu #itemGrid.cat-tiles,
#screenMenu #itemGrid.item-grid:not(.sectioned):not(.searching) { align-content: end; }
/* The sectioned list is display:block, so align-content does not reach it.
   `margin-top:auto` on the first child is the bottom-align that does NOT clip
   the top when the content is taller than the box — justify-content:flex-end
   would. */
#screenMenu #itemGrid.sectioned { display: flex; flex-direction: column; }
#screenMenu #itemGrid.sectioned > :first-child { margin-top: auto; }
.till-screen-dock {
  position: sticky; bottom: 0; z-index: 6;
  /* Anchors the filter popover, which drops upward. */
  position: -webkit-sticky; position: sticky;
  display: flex; flex-direction: column; gap: 9px;
  margin-top: 14px; padding-top: 12px;
  background: linear-gradient(to top, var(--bg, #0b0c0c) 64%, transparent);
}
/* ---- Tabs dock: a filter pill and a + button, nothing more ----
   Three sort chips took a whole row of the busiest screen for a control used
   once a shift. Collapsed into one pill that says the current order, with the
   choices in a popover that drops UP so the thumb never covers the list. */
.till-screen-dock { position: relative; }
.tabs-dock-row { display: flex; gap: 8px; align-items: center; }
.tabs-filter-btn {
  /* V26.91: sized for the top bar it now lives in. */
  display: inline-flex; align-items: center; gap: 6px;
  min-height: 44px; padding: 0 13px; border-radius: 999px;
  border: 1px solid var(--border, #2a2f2f); background: transparent;
  color: var(--text-muted, #8b9090); font: 600 12.5px/1 Inter, sans-serif; cursor: pointer;
}
.tabs-filter-btn[hidden] { display: none; }
.tabs-filter-btn:active { background: var(--bg-hover, #1d2024); }
/* + is the primary action, so it keeps brand red and the largest target. */
.tabs-add-btn {
  margin-left: auto; flex: 0 0 auto;
  width: 56px; height: 56px; border-radius: 18px;
  border: 1px solid var(--brand-red, #8A1915); background: var(--brand-red, #8A1915);
  color: var(--brand-cream, #F6E8D8); display: grid; place-items: center; cursor: pointer;
  box-shadow: 0 8px 22px rgba(0, 0, 0, .45);
}
.tabs-add-btn:active { transform: scale(.96); }
.tab-add-label { display: none; }
/* NO TABS OPEN: the + stops being a corner affordance and becomes the screen's
   one instruction (Filip, 2026-08-02 — "the button should be bigger and should
   display open a new tab"). Full width, so it sits directly under the "No open
   tabs" line rather than diagonally away from it. */
.tabs-dock-row.is-empty .tabs-add-btn {
  margin-left: 0; width: 100%; height: 62px; border-radius: 16px;
  display: flex; align-items: center; justify-content: center; gap: 10px;
}
.tabs-dock-row.is-empty .tab-add-label {
  display: inline; font: 600 16px/1 Inter, sans-serif; letter-spacing: -.01em;
}
/* Sits it just above the button instead of centred in a screen of black. */
.tabs-grid:has(.till-empty) { align-content: end; }
.till-empty { margin: 0 0 4px; }

.tabs-filter-pop {
  /* V26.91: anchored under the TOP BAR now (the filter lives beside the ⋮),
     so the menu drops DOWN instead of up. .till-top is position:relative. */
  position: absolute; left: 10px; top: calc(100% + 6px); bottom: auto; z-index: 40;
  min-width: 190px; padding: 6px;
  background: var(--bg-elevated, #15171a); border: 1px solid var(--border-strong, #3a3f47);
  border-radius: 14px; box-shadow: 0 18px 44px rgba(0, 0, 0, .6);
}
.tabs-filter-pop[hidden] { display: none; }
.tabs-filter-pop button {
  display: block; width: 100%; text-align: left;
  min-height: 44px; padding: 0 12px; border: 0; border-radius: 10px;
  background: transparent; color: var(--text, #e9e6e1);
  font: 500 14px/1 Inter, sans-serif; cursor: pointer;
}
.tabs-filter-pop button.on { color: var(--gold, #C9A961); font-weight: 700; }
.tabs-filter-pop button:active { background: var(--bg-hover, #1d2024); }
/* Two groups in one menu — Sort and Show answer different questions, and an
   undivided list of seven reads as seven mutually-exclusive options. */
.tfp-head {
  margin: 8px 0 2px; padding: 0 12px;
  color: var(--text-muted, #8b9090);
  font: 700 10px/1 Inter, sans-serif; letter-spacing: .09em; text-transform: uppercase;
}
.tfp-head:first-child { margin-top: 2px; }
.till-empty-act { margin-top: 12px; }

/* ---- Tab card: one comprehensive window ---- */
.tc-head { display: flex; align-items: baseline; gap: 8px; }
/* ⚠ WAS 34px AT EVERY WIDTH. The V26.27 audit measured 0 controls below 44px
   across the app; this one was added afterwards, with the rebuilt tab card, and
   never went through it. Negative margins keep the header height unchanged, so
   the target grows without the card growing — which is why it was small in the
   first place. Found by measuring the till at the Carbon's viewport. */
.tc-more {
  flex: 0 0 auto; width: 44px; height: 44px; margin: -9px -11px -9px 0;
  border: 0; background: none; border-radius: 999px;
  color: var(--text-muted, #8b9090); display: grid; place-items: center; cursor: pointer;
}
.tc-more:active { background: rgba(255,255,255,.07); }
/* Four facts on one line, wrapping only if they must: covers, server, when it
   opened (with elapsed), and when the food actually went in. */
.tc-facts {
  display: flex; flex-wrap: wrap; gap: 3px 10px; margin: 3px 0 7px;
  font-size: 11px; color: var(--text-muted, #8b9090);
}
.tc-facts span { white-space: nowrap; }

/* Stage row. Outline = waiting, filled = eating; the pair shares an icon so
   eight states need only five glyphs. */
/* ---- Service stage: ONE chip, in words ----
   This replaced .tab-stages/.stg — eight icon-only buttons per card. "Waiting
   mains" and "On mains" shared the plate glyph and differed ONLY by dashed vs
   solid border, so the row carried eight states in a code nobody can read on a
   first shift, at ~34px each on a tablet. Now the stage is spelled out with how
   long it has been there, and tapping opens a sheet of labelled rows. */
.tc-stage {
  overflow: hidden;   /* the pill/chevron must never paint past the border */
  margin-top: 9px; width: 100%; min-height: 44px;
  display: flex; align-items: center; gap: 8px; padding: 0 11px;
  border: 1px solid var(--gold, #C9A961); border-radius: 11px;
  background: rgba(201, 169, 97, .13); color: var(--gold, #C9A961);
  font: 600 13px/1.2 Inter, sans-serif; text-align: left; cursor: pointer;
}
.tc-stage-t { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.tc-stage-c { flex: 0 0 auto; opacity: .75; }
.tc-stage .held {
  flex: 0 0 auto; font-style: normal; font-size: 11px; font-weight: 700;
  padding: 2px 7px; border-radius: 999px;
  background: var(--danger, #EF4444); color: #fff;
}
/* WAITING reads as waiting: quieter fill, dashed edge. The DIFFERENCE no longer
   has to carry the meaning, because the words are right there. */
.tc-stage.wait { border-style: dashed; background: rgba(201, 169, 97, .05); }
/* An unpaid bill is the one stage a manager must spot across a room. */
.tc-stage.bill {
  border-color: var(--danger, #EF4444); background: rgba(239, 68, 68, .14);
  color: var(--danger, #EF4444);
}
.tc-stage:active { transform: scale(.985); }

/* Stage sheet rows — full width, 52px, current one ticked. */
.stage-row {
  width: 100%; min-height: 52px; display: flex; align-items: center; gap: 12px;
  padding: 0 14px; margin-bottom: 8px;
  border: 1px solid var(--border, #262a2a); border-radius: var(--t1-r-row, 12px);
  background: var(--t1-surface-2, #16191a); color: var(--text, #e9e6e1);
  font: 600 15px/1.2 Inter, sans-serif; text-align: left; cursor: pointer;
}
.stage-row span { flex: 1; min-width: 0; }
.stage-row svg { flex: 0 0 auto; color: var(--text-subtle, #6d7373); }
.stage-row.on {
  border-color: var(--gold, #C9A961); background: rgba(201, 169, 97, .14);
  color: var(--gold, #C9A961);
}
.stage-row.on svg { color: var(--gold, #C9A961); }
.stage-row:active { transform: scale(.99); }
.menu-row.danger .menu-row-t { color: var(--danger, #EF4444); }
.till-screen-dock .btn {
  width: 100%; min-height: 54px; border-radius: 15px;
  font-size: 15.5px; font-weight: 600; letter-spacing: -0.01em;
  box-shadow: 0 8px 26px rgba(0, 0, 0, .45);
}
/* The menu dock holds ONE thing now: the basket strip. Search and the way back
   moved to .menu-top at the top of the screen (Filip, 2026-08-02) — with two
   docks competing for the bottom, the strip was the one that scrolled away, and
   the strip is the one that has to be pinned above the nav.
   Opaque rather than a fade, because the item grid scrolls underneath it and a
   half-visible row of food names reads as a rendering fault. */
/* ⚠ FIXED, NOT STICKY (Filip, 2026-08-02: "the basket should function as a
   floating snapped ball and should be visible regardless of scrolling").
   Sticky was correct in theory and did not hold: the dock is the last child of
   a flex column whose middle child (#itemGrid, `flex: 1 0 auto`) grows past the
   viewport, so on a long list — 87 spirits — it sat at the end of the content
   and you had to scroll to reach it. The one control that has to be reachable
   at any moment is the one that was hardest to reach.
   Anchored above the nav via --till-nav-h, which till.js measures from the nav
   itself: the height changes with the safe-area inset and the nav is hidden
   entirely while the keyboard is open, so a hardcoded offset is wrong twice.
   ⚠ Fixed inside .till-app is safe ONLY while no ancestor animates transform —
   see trap #6. The arrival animation deliberately touches content blocks only. */
.till-menu-dock {
  position: fixed; left: 0; right: 0; bottom: var(--till-nav-h, 62px);
  z-index: 8;
  display: flex; flex-direction: column; gap: 9px;
  padding: 8px 12px 10px;
  background: var(--bg, #0b0c0c);
  box-shadow: 0 -10px 22px 6px var(--bg, #0b0c0c);
}
/* Reserve the strip's height so the last row of items is not sitting under it.
   Set only while the strip is actually showing — most of the time there is no
   basket and the grid should use the full screen. Expanding the peek overlays
   the grid on purpose: it is a glance, and it closes again. */
/* Measured, not a guess: the strip is one height collapsed and another with a
   peek open, and a constant left the last row of items hidden behind it
   (Filip, 2026-08-02). --menu-dock-h comes from a ResizeObserver in till.js. */
#screenMenu.has-basket { padding-bottom: var(--menu-dock-h, 84px); }

/* ------------------------------------------------------------------- tabs */
/* 250px minimum: the card now carries a facts row and eight stage buttons,
   which will not fit the 160px column the old two-line card used. */
.tabs-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(250px, 1fr)); gap: 10px; }
/* COMPACT two-line card: name + total on the first line, covers/items + server
   on the second. It was a four-row stack at min-height 96px, and once the list
   was told to fill the space above the dock the rows STRETCHED — one tab became
   a full-height card with its fields flung apart. align-content:start on the
   grid below is what stops that; the two-line layout is what makes it small. */
/* SINGLE COLUMN. This was `1fr auto` for the old two-line card, and the richer
   card has four block children — head, facts, stages, label — so a two-column
   template dealt them alternately into columns and threw the facts and the stage
   label OUTSIDE the card. .tc-head does its own row layout with flex. */
.tab-card {
  display: grid;
  grid-template-columns: 1fr;
  /* min-width:0 — a grid item's default min-width:auto refuses to shrink
     below its content, so a long stage chip pushed the whole card OUT of its
     column (Filip, 2026-08-19). overflow:hidden backstops the border. */
  min-width: 0; overflow: hidden;
  align-content: start;
  gap: 0; padding: 11px 13px; text-align: left;
  background: var(--surface, #141616); border: 1px solid var(--border, #262a2a);
  border-radius: var(--t1-r-row, 12px); /* V28.1: onto the row token (V26.63 already ran it at 12px) */
  color: var(--text, #e9e6e1); cursor: pointer;
  -webkit-touch-callout: none; -webkit-user-select: none; user-select: none;
}
.tab-card.current { border-color: var(--gold, #C9A961); box-shadow: 0 0 0 1px var(--gold, #C9A961) inset; }
.tab-card:active { transform: scale(.99); }
.tab-name {
  /* flex:1 moved here from the deleted .tc-open wrapper — without it the name
     does not grow and .tab-total stops being pushed to the right edge. */
  flex: 1;
  font: 600 15.5px/1.2 'Space Grotesk', sans-serif;
  min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.tab-meta { font-size: 11.5px; color: var(--text-muted, #8b9090); }
.tab-meta .held { color: var(--gold, #C9A961); font-style: normal; }
.tab-total {
  font: 700 16px/1.2 Inter, sans-serif; font-variant-numeric: tabular-nums;
  justify-self: end; margin: 0;
}
.tab-by {
  font-size: 10.5px; color: var(--text-muted, #8b9090); opacity: .8;
  justify-self: end; text-align: right;
  max-width: 90px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

/* ------------------------------------------------------------------- menu */

/* V26.92 (Filip): ONE tile size — fixed grid rows make every tile identical
   regardless of name length, and the type steps down for a denser, calmer
   grid. Long names clamp to two lines rather than growing their tile. */
.item-grid {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(128px, 1fr));
  gap: 8px; grid-auto-rows: 84px;
}
.item-btn {
  position: relative; display: grid; align-content: space-between; gap: 4px;
  min-height: 0; padding: 9px 10px; text-align: left; overflow: hidden;
  background: var(--surface, #141616); border: 1px solid var(--border, #262a2a);
  border-radius: var(--t1-r-row, 12px); /* V28.1: 13px matched no token */
  color: var(--text, #e9e6e1); cursor: pointer;
}
.item-btn:active { transform: scale(.98); border-color: var(--gold, #C9A961); }
.item-name {
  font: 600 12.5px/1.25 Inter, sans-serif;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
  overflow: hidden;
}
.item-price { font: 700 12.5px/1 Inter, sans-serif; color: var(--gold, #C9A961); font-variant-numeric: tabular-nums; }
.item-tag {
  position: absolute; top: 8px; right: 8px; font-size: 9px; letter-spacing: .04em;
  text-transform: uppercase; color: var(--gold, #C9A961);
}
/* 86'd: unmistakable, and the button genuinely cannot be pressed. */
.item-btn.unavailable { opacity: .5; cursor: not-allowed; border-style: dashed; }
.item-btn.unavailable .item-price { color: var(--text-muted, #8b9090); text-decoration: line-through; }
.item-86 {
  position: absolute; inset: auto 0 0 0; padding: 3px 0; text-align: center;
  background: var(--danger, #EF4444); color: #fff; font: 700 9.5px/1 Inter, sans-serif;
  letter-spacing: .06em; text-transform: uppercase;
  /* V28.1: inner radius = tile 12px − 1px border, so the red strip can never
     poke past the tile corner (the Carbon block re-tightens it to 9px). */
  border-radius: 0 0 11px 11px;
}

/* ------------------------------------------------------------------- bill */
.bill-head { display: flex; justify-content: space-between; align-items: flex-start; gap: 12px; margin-bottom: 14px; }
.bill-head h2 { margin: 0; font: 600 19px/1.2 'Space Grotesk', sans-serif; }
.bill-head p { margin: 3px 0 0; font-size: 12px; color: var(--text-muted, #8b9090); }
/* (V27.7: dead .bill-rename rule deleted — rename lives in the bill-actions
   sheet since V26.64 and nothing renders the class; it carried a 36px height
   that would resurface under the 44px floor if the class were ever reused.) */
.bill-sec { margin-bottom: 16px; }
.bill-sec h3 {
  margin: 0 0 8px; font: 600 11px/1 Inter, sans-serif; letter-spacing: .07em;
  text-transform: uppercase; color: var(--text-muted, #8b9090);
}
.bill-sec h3 em { font-style: normal; color: var(--gold, #C9A961); }

.bill-line {
  display: grid; grid-template-columns: 34px 1fr auto; gap: 10px; align-items: start;
  padding: 10px 0; border-bottom: 1px solid var(--border, #1f2323);
}
.bill-line.held { border-left: 2px solid var(--gold, #C9A961); padding-left: 8px; }
.bill-line.voided { opacity: .55; }
.bill-line.voided .bl-name { text-decoration: line-through; }
.bl-qty { font: 700 14px/1.4 Inter, sans-serif; font-variant-numeric: tabular-nums; color: var(--text-muted, #8b9090); }
.bl-name { font: 600 13.5px/1.35 Inter, sans-serif; }
.bl-name em { font-style: normal; color: var(--gold, #C9A961); font-weight: 500; }
.bl-mods, .bl-note, .bl-void, .bl-override { font-size: 11.5px; line-height: 1.4; margin-top: 2px; }
.bl-mods { color: var(--text-muted, #8b9090); }
.bl-note { color: var(--gold, #C9A961); }
.bl-void, .bl-override { color: var(--danger, #EF4444); }
.bl-right { display: grid; gap: 6px; justify-items: end; }
.bl-total { font: 700 14px/1 Inter, sans-serif; font-variant-numeric: tabular-nums; }
.bl-actions { display: flex; gap: 4px; }
.bl-actions button {
  width: 34px; height: 34px; border-radius: 9px;
  border: 1px solid var(--border, #2a2f2f); background: transparent;
  color: var(--text, #e9e6e1); font-size: 15px; cursor: pointer;
}
.bl-actions button:active { background: rgba(255,255,255,.08); }
.bl-void-btn {
  min-height: 30px; padding: 0 10px; border-radius: 8px;
  border: 1px solid var(--danger, #EF4444); background: transparent;
  color: var(--danger, #EF4444); font-size: 11.5px; cursor: pointer;
}
/* Send and Fire are the two highest-consequence buttons in the app — they put
   food in front of a guest. Given the docked-primary treatment (54px, 15px
   radius, lifted) so they read as the decisive action, not one option of six. */
.send-btn {
  width: 100%; margin-top: 10px; min-height: 54px;
  font-size: 15.5px; font-weight: 600; border-radius: 15px;
  box-shadow: 0 8px 26px rgba(0, 0, 0, .45);
}

.bill-totals {
  margin: 16px 0; padding: 12px 14px; border-radius: var(--t1-r-row, 12px); /* V28.1 */
  background: var(--surface, #141616); border: 1px solid var(--border, #262a2a);
}
.bt-row { display: flex; justify-content: space-between; gap: 12px; padding: 5px 0; font-size: 13.5px; }
.bt-row span:last-child { font-variant-numeric: tabular-nums; }
.bt-row.disc { color: var(--gold, #C9A961); }
.bt-row.bt-total {
  border-top: 1px solid var(--border, #262a2a); margin-top: 6px; padding-top: 10px;
  font: 700 17px/1 Inter, sans-serif;
}
.bt-row em { font-style: normal; color: var(--danger, #EF4444); font-size: 11px; }

.bill-actions { display: grid; grid-template-columns: repeat(auto-fit, minmax(108px, 1fr)); gap: 8px; }
.bill-actions .btn { min-height: 48px; font-size: 13px; }   /* app control height */
.bill-actions .danger { border-color: var(--danger, #EF4444); color: var(--danger, #EF4444); }

/* -------------------------------------------------------------- bottom nav */
/* Same principle as the main app bar (V24.9): take the home-indicator inset
   MINUS 12pt rather than all of it, so the labels sit lower and more centred
   while staying clear of the indicator and the curved corners. --nav-safe is
   defined in styles.css, which the till also loads. */
/* Filip, 2026-08-02: "the buttons can be aligned to center vertically... brought
   a little bit closer together just due to the rounded edges of, for example,
   iPhones and other tablets."
   A 4×1fr grid across the full width puts the OUTER two buttons directly into
   the corner radius, where a phone clips them and a thumb reaching the corner
   is least accurate. The bar still spans edge to edge (the background has to,
   or it reads as a floating pill) — only the button row is inset, and capped so
   it stays a row of buttons on a tablet instead of four items flung apart. */
.till-nav {
  display: grid; grid-template-columns: repeat(4, 1fr); align-items: center;
  background: var(--surface, #141616); border-top: 1px solid var(--border, #262a2a);
  padding-inline: max(20px, env(safe-area-inset-left)) max(20px, env(safe-area-inset-right));
  /* The safe inset is padding INSIDE the bar, so content sits above the home
     indicator. Matching it at the top is what actually centres the row — without
     this the buttons ride high and the bar looks bottom-weighted. --nav-safe is
     the inset minus 12pt (see styles.css), so the top match is capped to keep
     the bar from growing tall on a device with a large inset. */
  padding-top: min(var(--nav-safe, 0px), 10px);
  /* Divided inside the island: the home-indicator clearance stays REAL. */
  padding-bottom: calc(var(--nav-safe, env(safe-area-inset-bottom)) / var(--till-chrome, 1));
}
.till-nav-btn {
  /* V26.79: matched to the staff bottom bar exactly (icon 21, label 9.5px/600,
     5px between them) and pulled tighter vertically — Filip wants the two
     apps' navs to read as one system. */
  position: relative; display: grid; justify-items: center; align-content: center; gap: 5px;
  padding: 5px 2px 4px; min-height: 46px; background: none; border: 0;
  border-top: 2px solid transparent;
  color: var(--text-muted, #8b9090); font: 600 9.5px/1 Inter, sans-serif; cursor: pointer;
  /* Keeps the four together and centred once there is width to spare, rather
     than stretching each to a quarter of an iPad. */
  width: 100%; max-width: 128px; justify-self: center;
}
/* On a phone the four share the width evenly and the 20px inset is what keeps
   them out of the corner radius. Give them more room than that and even a capped
   button still sits centred in its own quarter — on a tablet that is four items
   flung to the corners of a 1024px bar. Past 600px the track itself is fixed and
   centred, so they stay a group. */
@media (min-width: 600px) {
  .till-nav { grid-template-columns: repeat(4, 132px); justify-content: center; }
}
.till-nav-btn.active { color: var(--brand-red, #d1495b); border-top-color: var(--brand-red, #d1495b); }
.till-badge {
  position: absolute; top: 4px; right: 22%;
  min-width: 17px; height: 17px; padding: 0 4px; border-radius: 999px;
  background: var(--brand-red, #8f1d2b); color: #fff;
  font: 700 10px/17px Inter, sans-serif; font-style: normal; text-align: center;
}

/* ----------------------------------------------------------------- sheets */
/* justify-content matters even though the sheet is width:100%: the moment a
   sheet carries a max-width (see .till-sheet-sm) the flex default of
   flex-start pins it to the LEFT edge and leaves dead space — and the bottom
   nav showing — down the right-hand side. Centred at every width instead. */
.till-sheet-wrap {
  position: fixed; inset: 0; z-index: 1400;
  display: flex; align-items: flex-end; justify-content: center;
  /* Lift the sheet ONTO the keyboard. auth.js publishes --kb from
     visualViewport. Without this the sheet is anchored to the bottom of the
     LAYOUT viewport — behind the keyboard — so iOS scrolls the page to bring
     the focused field into view. That is the weird jump when tapping the table
     number or covers: the sheet lurched up and left a black gap above it. With
     the sheet already clear of the keyboard, iOS has nothing to scroll. */
  padding-bottom: var(--kb, 0px);
  transition: padding-bottom 160ms ease;
}
.till-sheet-wrap[hidden] { display: none; }
.till-sheet-backdrop { position: absolute; inset: 0; background: rgba(0,0,0,.62); }
.till-sheet {
  position: relative; width: 100%; display: flex; flex-direction: column;
  /* Shrink by the keyboard height too, or a tall sheet lifted by the wrap above
     would push its own head off the top of the screen. */
  max-height: min(88dvh, calc(100dvh - var(--kb, 0px) - 24px));
  background: var(--surface, #141616); border-radius: 20px 20px 0 0;
  border-top: 1px solid var(--border, #2a2f2f);
  padding-bottom: env(safe-area-inset-bottom);
  animation: sheetUp .18s ease-out;
}
@keyframes sheetUp { from { transform: translateY(14px); opacity: .6; } to { transform: none; opacity: 1; } }
@media (prefers-reduced-motion: reduce) { .till-sheet { animation: none; } }
@media (min-width: 760px) {
  .till-sheet-wrap { align-items: center; }
  .till-sheet { max-width: 560px; border-radius: 18px; }
  /* The 440px cap belongs to the CENTRED desktop/tablet sheet only. It used to
     sit outside this query, so on a phone the small sheets (new tab, manager
     passcode) were capped mid-screen and — before the centring fix above —
     hugged the left edge with the bottom nav visible beside them. A bottom
     sheet on a phone is full-bleed. */
  .till-sheet-sm { max-width: 440px; }
}
.sheet-head {
  display: flex; align-items: flex-start; justify-content: space-between; gap: 12px;
  padding: 16px 16px 10px; border-bottom: 1px solid var(--border, #262a2a);
}
.sheet-head h3 { margin: 0; font: 600 17px/1.25 'Space Grotesk', sans-serif; }
.sheet-desc { margin: 4px 0 0; font-size: 12px; line-height: 1.45; color: var(--text-muted, #8b9090); }
.sheet-x {
  flex: 0 0 auto; width: 34px; height: 34px; border-radius: 9px; border: 0;
  background: rgba(255,255,255,.06); color: var(--text, #e9e6e1); font-size: 20px; cursor: pointer;
}
.sheet-body { overflow-y: auto; padding: 14px 16px; -webkit-overflow-scrolling: touch; }
.sheet-group { margin-bottom: 16px; }
.sheet-group h4 {
  margin: 0 0 8px; font: 600 11px/1 Inter, sans-serif; letter-spacing: .07em;
  text-transform: uppercase; color: var(--text-muted, #8b9090);
}
.sheet-group h4 .req { font-style: normal; color: var(--brand-red, #d1495b); letter-spacing: 0; }
.opt-row { display: flex; flex-wrap: wrap; gap: 8px; }
.opt {
  /* 44px: variant and modifier chips are tapped for nearly every item that has
     options, so they were the smallest high-traffic target in the till at 42. */
  display: inline-flex; align-items: center; gap: 6px; min-height: 44px; padding: 0 14px;
  border-radius: 11px; border: 1px solid var(--border, #2a2f2f); background: transparent;
  color: var(--text, #e9e6e1); font: 500 13px/1 Inter, sans-serif; cursor: pointer;
}
.opt em { font-style: normal; color: var(--gold, #C9A961); font-size: 12px; }
.opt.on { border-color: var(--gold, #C9A961); background: rgba(201,169,97,.12); }
.sheet-note, .ask-field input, .ask-field select {
  width: 100%; min-height: 46px; padding: 0 12px; border-radius: 11px;
  border: 1px solid var(--border, #2a2f2f); background: var(--bg, #0b0c0c);
  color: var(--text, #e9e6e1);
  font-size: 16px;                   /* 16px or iOS zooms the viewport (trap #11) */
}
/* V26.90 — "apply to how many" chips (edit of a multi-unit line). */
.sheet-split { padding: 10px 16px 12px; border-top: 1px solid var(--border, #262a2a); }
.sheet-split h4 {
  margin: 0 0 8px; font: 600 11px/1 Inter, sans-serif;
  letter-spacing: 0.1em; text-transform: uppercase; color: var(--text-muted, #8b9090);
}
.sheet-split[hidden] { display: none; }

.sheet-foot {
  display: flex; align-items: center; gap: 10px; padding: 12px 16px 16px;
  border-top: 1px solid var(--border, #262a2a);
}
.sheet-foot .btn { flex: 1; min-height: 50px; }
.ask-cancel { flex: 0 0 auto; min-width: 96px; }
.qty-stepper {
  display: flex; align-items: center; gap: 2px;
  border: 1px solid var(--border, #2a2f2f); border-radius: 11px; overflow: hidden;
}
.qty-stepper button {
  width: 46px; height: 48px; border: 0; background: transparent;
  color: var(--text, #e9e6e1); font-size: 20px; cursor: pointer;
}
.qty-stepper button:active { background: rgba(255,255,255,.08); }
.qty-stepper span {
  min-width: 34px; text-align: center; font: 700 16px/1 Inter, sans-serif; font-variant-numeric: tabular-nums;
}
/* Body copy for a confirm — askSheet doubles as the till's dialog, so this is
   the prose case where the field cases are inputs. Sized to be read at a
   glance mid-service, not skimmed past. */
.ask-msg {
  margin: 0; padding: 2px 0 4px;
  font: 500 14.5px/1.55 Inter, sans-serif; color: var(--text-muted, #b6bcbc);
}
.ask-field { display: block; margin-bottom: 12px; }
.ask-field span {
  display: block; margin-bottom: 6px; font-size: 12px; color: var(--text-muted, #8b9090);
}

/* ------------------------------------------------------------------ toast */
.till-toast {
  position: fixed; left: 50%; bottom: calc(80px + env(safe-area-inset-bottom));
  transform: translateX(-50%); z-index: 1600;
  max-width: min(92vw, 460px); padding: 11px 16px; border-radius: 11px;
  background: #1c2320; border: 1px solid #2f6b4f; color: #dff3e6;
  font: 500 13px/1.4 Inter, sans-serif; text-align: center;
  box-shadow: 0 8px 26px rgba(0,0,0,.45);
}
.till-toast.bad { background: #2a1a1a; border-color: #7a2b25; color: #f6d9d6; }
.till-toast[hidden] { display: none; }

/* Waiting-to-fire: sent to the bill but held back from the station until the
   waiter calls the course away. Visually distinct from both held (basket) and
   away (on the bill) so nobody mistakes "waiting" for "gone". */
.bill-waiting .bill-line { border-left: 2px solid #6a8fd8; padding-left: 8px; }
.bill-sec.bill-waiting h3 { color: #8fabe4; }
.fire-btn {
  width: 100%; margin-top: 10px; min-height: 54px;
  font-size: 15.5px; font-weight: 600; border-radius: 15px;
  text-transform: capitalize; box-shadow: 0 8px 26px rgba(0, 0, 0, .45);
}
.fire-btn em { font-style: normal; opacity: .75; margin-left: 6px; }

/* Nobody has claimed the till: make it obvious, because until someone does,
   every sale is credited to the device account rather than a person. */
.till-operator.is-device {
  border-color: var(--danger, #EF4444);
  color: var(--danger, #EF4444);
}

/* Options sheet rows (V24.19) */
.till-menu {
  display: inline-flex; align-items: center; justify-content: center;
  width: 44px; height: 44px; flex: 0 0 auto;   /* was 40px — touch minimum */
  border-radius: 999px; border: 1px solid var(--border, #2a2f2f);
  background: transparent; color: var(--text-muted, #8b9090); cursor: pointer;
}
.till-menu:active { background: rgba(255,255,255,.06); }
.menu-row {
  display: grid; gap: 2px; width: 100%; text-align: left;
  padding: 14px 14px; margin-bottom: 8px; border-radius: 12px;
  background: rgba(255,255,255,.04); border: 1px solid var(--border, #262a2a);
  color: var(--text, #e9e6e1); cursor: pointer; text-decoration: none;
}
.menu-row:active { background: rgba(255,255,255,.09); }
.menu-row-t { font: 600 14.5px/1.2 Inter, sans-serif; }
.menu-row-s { font-size: 12px; color: var(--text-muted, #8b9090); }
.menu-row-danger { border-color: rgba(179,38,30,.4); }
.menu-row-danger .menu-row-t { color: var(--danger, #EF4444); }

/* ============================================================================
   V25.1 — TILL: docked actions and navigation
   ----------------------------------------------------------------------------
   The bill's action row was a grid of eight equal buttons — every action
   given identical weight, so the one you need forty times a shift (Send) sat
   among Cancel sale and Reprint. Service work needs a clear primary.
   ========================================================================= */

/* Send is the primary action and now docks, so it is in the same place on
   every tab rather than wherever the list happens to end. */
.send-btn {
  position: sticky; bottom: 8px; z-index: 5;
  width: 100%; min-height: 56px; margin-top: 12px;
  border-radius: 15px; font-size: 16px; font-weight: 600;
  box-shadow: 0 8px 26px rgba(0, 0, 0, .5);
}

/* Everything else is secondary and reads as such: two rows of quiet controls
   rather than eight competing blocks. */
.bill-actions {
  display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px;
  margin-top: 16px; padding-top: 14px;
  border-top: 1px solid var(--border, #262a2a);
}
.bill-actions .btn {
  min-height: 46px; padding: 0 8px;
  border-radius: 12px; font-size: 12.5px; font-weight: 600;
  background: rgba(255, 255, 255, .04);
  border: 1px solid rgba(255, 255, 255, .09);
  color: var(--text, #e9e6e1);
}
.bill-actions .btn:active { background: rgba(255, 255, 255, .09); }
/* Payment is the one secondary action that carries weight — it spans the row
   and takes the brand colour, so it is never confused with Reprint. */
.bill-actions #payBtn {
  grid-column: 1 / -1;
  min-height: 52px; font-size: 14.5px;
  background: var(--brand-red, #8f1d2b); border-color: var(--brand-red, #8f1d2b); color: #fff;
}
.bill-actions .danger {
  background: transparent;
  border-color: rgba(179, 38, 30, .42);
  color: var(--danger, #EF4444);
}

/* Fire is a decision, not a menu item — full width and unmistakable. */
.fire-btn {
  position: sticky; bottom: 8px; z-index: 5;
  min-height: 54px; border-radius: 15px; font-size: 15.5px;
  box-shadow: 0 8px 26px rgba(0, 0, 0, .5);
}

/* Bottom nav gets the same glass treatment as the app's docked bar. */
.till-nav {
  background: rgba(20, 22, 22, .82);
  -webkit-backdrop-filter: blur(14px);
  backdrop-filter: blur(14px);
}

/* Item grid: denser on a tablet, where a waiter is scanning not reading. */
@media (min-width: 700px) {
  .item-grid { grid-template-columns: repeat(auto-fill, minmax(118px, 1fr)); }
  .tabs-grid { grid-template-columns: repeat(auto-fill, minmax(290px, 1fr)); }
}

/* Inputs inside till sheets follow the app's field treatment. */
.till-sheet input, .till-sheet select, .sheet-note {
  min-height: 48px; border-radius: var(--t1-r-ctl, 11px);
  border: 1px solid rgba(255, 255, 255, .14);
  background: rgba(0, 0, 0, .3);
  font-size: 16px;
}
.till-sheet input:focus, .till-sheet select:focus, .sheet-note:focus {
  outline: none; border-color: var(--gold, #C9A961);
}

/* ---- Menu search (V25.2) ---- */
/* Search PILL, matching the CRM's .tb-search — Filip's reference for a search
   box he trusts. Lives in .menu-top at the TOP of the screen again as of
   2026-08-02 — the bottom belongs to the basket strip now. 48px control
   height, fully elliptical. */
.menu-search {
  display: flex; align-items: center; gap: 9px;
  margin: 0; padding: 0 8px 0 14px;
  height: 48px; border-radius: 999px;
  background: var(--bg-elevated, rgba(255, 255, 255, .05));
  border: 1px solid var(--border, rgba(255, 255, 255, .10));
  color: var(--text-muted, #8b9090);
  transition: border-color 140ms ease;
}
.menu-search:focus-within { border-color: var(--gold, #C9A961); }
/* The pill draws the control; the field inside carries no chrome of its own,
   or the global 48px/11px field treatment puts a rounded rectangle inside the
   pill and overlaps it — the exact double-box Filip flagged in the CRM. */
.menu-search input {
  flex: 1; min-width: 0; height: 100%;
  min-height: 0; padding: 0;
  background: transparent; border: none; border-radius: 0;
  box-shadow: none; outline: none;
  color: var(--text, #e9e6e1);
  font-size: 16px; font-family: inherit;   /* 16px or iOS zooms on focus */
}
.menu-search input:focus {
  background: transparent; border: none; box-shadow: none;
}
/* (V27.7: a duplicate .menu-search input block re-declared min-height:44px
   here and, being later at equal specificity, overflowed the 40px Carbon
   pill by 4px. The chrome-free declaration above is the real one.) */
.menu-search input::-webkit-search-cancel-button { display: none; }
.menu-search button {
  width: 30px; height: 30px; flex: 0 0 auto; border: 0; border-radius: 999px;
  background: rgba(255, 255, 255, .09); color: var(--text, #e9e6e1);
  font-size: 17px; line-height: 1; cursor: pointer;
}

/* Results grouped by category, so a broad word does not read as one flat wall. */
/* Both drop the outer grid so each section owns its own .item-grid-inner.
   Two names for one behaviour on purpose: `searching` is a flat result list,
   `sectioned` is a group browsed by choice — they diverge the moment either
   wants its own spacing. */
.item-grid.searching, .item-grid.sectioned { display: block; }
.search-group { margin-bottom: 16px; }
.search-group h4 {
  margin: 0 0 8px; font: 600 10.5px/1 Inter, sans-serif;
  letter-spacing: .08em; text-transform: uppercase; color: var(--text-muted, #8b9090);
}
/* Allergens in the item sheet. Recorded codes read as words — "G" is not an
   answer to give a guest. */
/* Spice: a scale, drawn as one. Four equal columns so it reads left-to-right as
   increasing heat, with rising bars doing the work before the label is read. */
.spice-row { display: grid; grid-template-columns: repeat(4, 1fr); gap: 8px; }
/* The custom sheet adds a "Not set" option, so it is five across on a phone —
   at 2 columns the labels stay readable where 5 in a row would not. */
#customSpice { grid-template-columns: repeat(auto-fit, minmax(84px, 1fr)); }
.spice-btn {
  display: grid; justify-items: center; align-content: center; gap: 7px;
  min-height: 62px; padding: 8px 4px; border-radius: var(--t1-r-ctl, 11px);
  border: 1px solid var(--t1-hairline-str, rgba(255, 255, 255, .14));
  background: var(--t1-surface, rgba(255, 255, 255, .038));
  color: var(--text, #e9e6e1); font: 600 12px/1 Inter, sans-serif; cursor: pointer;
}
.sp-bars { display: flex; align-items: flex-end; gap: 2px; height: 14px; }
/* The bars grow in height as well as filling, so the shape of the control
   carries the scale even in greyscale or for a colour-blind reader. */
.sb {
  width: 4px; border-radius: 1px;
  background: var(--t1-hairline-str, rgba(255, 255, 255, .16));
}
.sb:nth-child(1) { height: 5px; }
.sb:nth-child(2) { height: 8px; }
.sb:nth-child(3) { height: 11px; }
.sb:nth-child(4) { height: 14px; }
.sb.fill { background: var(--gold, #C9A961); }
.spice-btn.on {
  border-color: var(--brand-red, #d1495b); background: rgba(209, 73, 91, .14);
}
.spice-btn.on .sb.fill { background: var(--brand-red, #d1495b); }
/* An option this dish cannot take. Visibly present and visibly unavailable —
   hiding it makes a waiter think the till is broken when a guest asks for mild. */
.spice-btn.is-off, .opt.is-off {
  opacity: .38; cursor: not-allowed;
  text-decoration: line-through; text-decoration-thickness: 1px;
}
.spice-btn.is-off .sb { background: var(--t1-hairline, rgba(255, 255, 255, .1)); }
.spice-why {
  margin: 8px 0 0; color: var(--text-muted, #8b9090);
  font: 500 11.5px/1.4 Inter, sans-serif;
}
.sp-n { text-align: center; }
@media (max-width: 380px) { .sp-n { font-size: 11px; } }

.sheet-allergens { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; }
.alg-lead {
  color: var(--text-muted, #8b9090);
  font: 700 10px/1 Inter, sans-serif; letter-spacing: .09em; text-transform: uppercase;
}
/* Reads as instruction, not decoration — this row is the one a waiter is meant
   to act on rather than skim. */
.alert-lead {
  margin: 12px 0 8px; color: var(--text-muted, #8b9090);
  font: 500 12px/1.4 Inter, sans-serif;
}
/* The 14 are a reference list, so they read quieter than the prep alerts until
   one is chosen — a wall of 14 equally-loud chips is a wall nobody reads.
   ⚠ Quieter via TYPE AND PADDING ONLY. The height stays on the 44px floor set
   by the V26.27 touch audit; shrinking a chip a waiter taps mid-service to make
   a list look tidier is how that audit's findings got there in the first place. */
.alg-chip { padding: 0 11px; font-size: 12px; font-weight: 500; }
.alg {
  padding: 7px 11px; border-radius: 999px;
  border: 1px solid rgba(239, 68, 68, .45); background: rgba(239, 68, 68, .10);
  color: #F0A2A2; font: 600 12.5px/1 Inter, sans-serif;
}
/* ⚠ Deliberately NOT styled as reassurance. Most items have no codes recorded,
   and a calm grey "none" would read as "safe" — this has to read as an
   instruction to go and check. */
.sheet-allergen-none {
  margin: 0; padding: 9px 11px; border-radius: var(--t1-r-ctl, 11px);
  border: 1px dashed var(--gold, #C9A961); background: rgba(201, 169, 97, .07);
  color: var(--gold, #C9A961); font: 500 12.5px/1.45 Inter, sans-serif;
}

/* Kitchen alerts. Chips, because mid-service nobody types "NUT ALLERGY"
   consistently and a ticket reading "nut alergy" is one a chef has to stop and
   interpret. Selected chips go red — this is the one control in the sheet whose
   state has a safety consequence, so it should not look like the modifier
   chips beside it. */
.alert-row { display: flex; flex-wrap: wrap; gap: 8px; }
.alert-chip {
  min-height: 44px; padding: 0 13px; border-radius: 999px;
  border: 1px solid var(--t1-hairline-str, rgba(255, 255, 255, .14));
  background: var(--t1-surface, rgba(255, 255, 255, .038));
  color: var(--text, #e9e6e1); font: 600 12.5px/1 Inter, sans-serif; cursor: pointer;
}
.alert-chip.on {
  border-color: var(--danger, #EF4444); background: rgba(239, 68, 68, .16);
  color: #FFC9C9;
}

/* WHERE THE NEXT TAP LANDS, and a peek at what is in it (Filip, 2026-08-02).
   Sits at the top of the menu dock — above search and the nav — so it is in the
   same place whether it is naming the open tab or holding a basket taken before
   one was picked. Tapping the left side expands the list; the primary on the
   right is its own target, so committing is never one mis-tap away from a peek. */
/* SEARCH AND THE WAY BACK, AT THE TOP (Filip, 2026-08-02). Sticky rather than
   static: scrolling 87 spirits should not take the search with it, and the
   bottom of the screen now belongs to the basket strip. */
/* ⚠ `top: -12px`, NOT 0. .till-screen has 12px of padding-top, and this bar
   carries `margin-top: -12px` to bleed full width — so `top: 0` pinned its
   MARGIN box to the scrollport and left the border box 12px lower, with a 12px
   window of items scrolling visibly above it. That is the overlap in Filip's
   photo: a bottle of water sitting on top of the back button. Measured, not
   inferred: bar top 73 against screen top 61.
   ONE ROW: an arrow and the search field. The category name was already on the
   screen the arrow returns you to, so spending a whole row restating it pushed
   the menu down for nothing. */
.menu-top {
  position: sticky; top: -12px; z-index: 7;
  display: flex; align-items: center; gap: 8px;
  margin: -12px -12px 10px; padding: 12px 12px 10px;
  background: var(--bg, #0b0c0c);
  border-bottom: 1px solid var(--t1-hairline, rgba(255, 255, 255, .07));
}
.menu-top .menu-back[hidden] { display: none; }
/* Icon only. Square, 48px to match the search pill beside it. */
.menu-top .menu-back {
  flex: 0 0 auto; width: 48px; height: 48px; padding: 0;
  display: grid; place-items: center; border-radius: 999px;
}
.menu-top .menu-back span { display: none; }
.menu-top .menu-search { flex: 1 1 auto; min-width: 0; }

.menu-basket {
  display: grid; grid-template-columns: minmax(0, 1fr) auto;
  align-items: center; gap: 8px; width: 100%; margin-bottom: 8px;
  padding: 6px 8px 6px 12px;
  border: 1px solid var(--gold, #C9A961); border-radius: var(--t1-r-ctl, 11px);
  background: rgba(201, 169, 97, .10); color: var(--text, #e9e6e1);
}
/* ⚠ `display:grid` on the class BEATS the user-agent [hidden] rule — same
   specificity, and till.css loads later. Without this the empty strip renders
   as a gold bar above the search whenever there is nothing to show, which is
   most of the time. Documented trap, hit here once already. */
.menu-basket[hidden] { display: none; }
.menu-basket.on-tab {
  border-color: var(--t1-hairline-str, rgba(255, 255, 255, .14));
  background: var(--t1-surface, rgba(255, 255, 255, .038));
}
.mb-head {
  display: flex; align-items: center; gap: 8px; min-width: 0;
  min-height: 44px; padding: 0; border: 0; background: none;
  color: inherit; font: 600 13px/1 Inter, sans-serif; text-align: left; cursor: pointer;
}
.mb-head:disabled { cursor: default; }
.mb-lead {
  flex: 0 0 auto; color: var(--text-muted, #8b9090);
  font: 700 10px/1 Inter, sans-serif; letter-spacing: .09em; text-transform: uppercase;
}
/* The name takes the space and clips last — a long table name must never push
   the total or the primary off the row. */
.mb-name { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.mb-count { flex: 0 0 auto; color: var(--gold, #C9A961); font-variant-numeric: tabular-nums; }
.mb-quiet { color: var(--text-muted, #8b9090); font-weight: 500; }
.mb-chev {
  flex: 0 0 auto; color: var(--text-muted, #8b9090);
  transition: transform 160ms ease;
}
.menu-basket.open .mb-chev { transform: rotate(180deg); }
.mb-go {
  flex: 0 0 auto; min-height: 40px; padding: 0 13px; border-radius: 9px;
  border: 1px solid var(--brand-red, #8A1915); background: var(--brand-red, #8A1915);
  color: var(--brand-cream, #F6E8D8); font: 600 13px/1 Inter, sans-serif;
  white-space: nowrap; cursor: pointer;
}
.menu-basket.on-tab .mb-go {
  border-color: var(--t1-hairline-str, rgba(255, 255, 255, .14));
  background: transparent; color: var(--text-muted, #8b9090);
}
.mb-go:active { transform: scale(.97); }

/* THE PEEK. Collapsed to zero rather than removed, so opening and closing is a
   movement the eye can follow instead of the dock jumping. Capped at 40vh: it
   is a glance at the basket, not the basket screen — the whole point is that
   the menu behind it stays usable. */
/* ⚠ NOT A max-height TRANSITION. The first version animated max-height from 0,
   and the open state was therefore only ever as reliable as the transition
   actually running — measured stuck at 0px with an INLINE max-height:320px
   overriding it, because a throttled tab never advances the interpolation. A
   panel that is open or shut must not depend on an animation finishing.
   display does the state; opacity/transform do the movement, and if those are
   skipped the panel is still open. */
.mb-list { grid-column: 1 / -1; display: none; }
.menu-basket.open .mb-list {
  /* V26.92 (Filip): expanded = the WHOLE order, full height — the strip
     grows upward to just under the top bar, scrollable and editable. */
  display: block; max-height: calc(100dvh - 195px); overflow-y: auto; -webkit-overflow-scrolling: touch;
  margin-top: 2px; border-top: 1px solid var(--t1-hairline, rgba(255, 255, 255, .09));
  animation: mbOpen 170ms cubic-bezier(.2, .75, .25, 1) both;
}
@keyframes mbOpen {
  from { opacity: 0; transform: translateY(-4px); }
  to   { opacity: 1; transform: none; }
}
@media (prefers-reduced-motion: reduce) {
  .menu-basket.open .mb-list { animation: none; }
}
.mb-row {
  display: flex; align-items: center; gap: 9px; width: 100%;
  min-height: 48px; padding: 7px 4px;
  border-bottom: 1px solid var(--t1-hairline, rgba(255, 255, 255, .06));
  color: var(--text, #e9e6e1); font: 500 13px/1.3 Inter, sans-serif;
}
.mb-row:last-child { border-bottom: 0; }
.mb-acts { display: flex; gap: 4px; flex: 0 0 auto; }
.mb-acts button {
  min-width: 34px; min-height: 34px; display: grid; place-items: center;
  border-radius: 8px; border: 1px solid var(--t1-hairline-str, rgba(255, 255, 255, .14));
  background: var(--t1-surface, rgba(255, 255, 255, .038));
  color: var(--text, #e9e6e1); font: 600 15px/1 Inter, sans-serif; cursor: pointer;
}
.mb-acts button:active { background: var(--bg-hover, #1d2024); }
/* Mirrors the ticket and the bill: an alert is inverted wherever it appears. */
.mb-alert {
  display: block; margin-top: 3px; padding: 2px 6px; border-radius: 5px;
  background: var(--danger, #EF4444); color: #fff;
  font: 700 10.5px/1.4 Inter, sans-serif; width: fit-content;
}
.mb-note {
  display: block; margin-top: 2px; font-style: italic;
  font-size: 11px; color: var(--text-muted, #8b9090);
}
.mb-q { flex: 0 0 auto; color: var(--text-muted, #8b9090); font-variant-numeric: tabular-nums; }
.mb-n { flex: 1; min-width: 0; }
.mb-n em {
  display: block; font-style: normal; margin-top: 2px;
  font-size: 11.5px; color: var(--text-muted, #8b9090);
}
.mb-p { flex: 0 0 auto; font-variant-numeric: tabular-nums; color: var(--gold, #C9A961); }

/* Rows in the tab-picker sheet carry a total on the right. */
.sr-meta { margin-left: auto; color: var(--text-muted, #8b9090); font-style: normal; font-variant-numeric: tabular-nums; }
.basket-head { padding: 4px 2px 12px; }
.basket-head h3 { margin: 0 0 2px; font: 600 17px/1.2 'Space Grotesk', sans-serif; }

/* Sub-sections inside one menu group (Filip, 2026-08-02: "they will have their
   own title heading with a little divider and items listed underneath it").
   Same type treatment as a search group so the two read as one system, plus a
   rule — the divider is what stops "Non Vegetarian" and "Vegetarian & Vegan"
   reading as one continuous list of 22 dishes. */
.menu-sub { margin-bottom: 18px; }
.menu-sub h4 {
  display: flex; align-items: center; gap: 10px;
  margin: 0 0 10px; font: 700 10.5px/1 Inter, sans-serif;
  letter-spacing: .09em; text-transform: uppercase; color: var(--gold, #C9A961);
}
/* The rule is drawn with a BORDER, not a background — on this screen either
   works, but the print sheets were bitten by exactly that (V26.16) and one
   habit across the codebase is worth more than the two characters saved. */
.menu-sub h4::after {
  content: ''; flex: 1; height: 0; border-top: 1px solid var(--t1-hairline, rgba(255, 255, 255, .09));
}
/* The first heading sits directly under the grid's top edge, where the extra
   space above reads as a gap rather than as separation. */
.menu-sub:first-child h4 { margin-top: 0; }

.item-grid-inner {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(148px, 1fr)); gap: 10px;
}
@media (min-width: 700px) {
  .item-grid-inner { grid-template-columns: repeat(auto-fill, minmax(132px, 1fr)); }
}

/* ---- The iOS keyboard (V26.11) ----
   .till-app is position:fixed; inset:0 (a flex column) — its inset:0 bottom tracks
   the Safari toolbar, but NOT the on-screen keyboard. So everything docked
   at the bottom (the tabs dock, the category strip, the menu search) sat BEHIND
   the keyboard, and iOS then scrolled the page to reveal the focused field,
   which is the lurching Filip reported. styles.css already makes this trade for
   the CRM dock and the app nav; till.css only ever consumed --kb inside
   .till-sheet-wrap, so the till itself was left out.

   auth.js publishes --kb from visualViewport and sets body.kb-open, and it IS
   loaded on till.html. Adjusting `bottom` (never a transform — trap #6: a
   transform on an ancestor of a position:fixed element makes it a containing
   block and the dock renders off-screen) shrinks the shell so its grid
   recomputes and the dock lands on top of the keyboard. */
body.kb-open .till-app { bottom: var(--kb, 0px); }
/* Reclaim the 72px nav while typing — the same trade styles.css makes for
   .nav-menu. Nobody needs to change screen mid-keystroke, and on a 440px phone
   with the keyboard up that 72px is the difference between seeing the field and
   not. */
body.kb-open .till-nav { display: none; }

/* ---- Category tiles (V26.12) ----
   Filip: "they should all be visible without me having to scroll on an iPhone,
   and they should potentially be bigger... revealing a submenu bottom-up."

   Replaces .cat-strip/.cat-chip — a single horizontal scroller holding 12 food
   or 23 drinks categories, with labels cut off at the viewport edge, so reaching
   one category meant blind-swiping past twenty others.

   grid-auto-rows: minmax(58px, 1fr) is the whole trick. The rows DIVIDE the
   available height so every tile is visible with no scrolling at the real counts
   (23 drinks / 3 columns = 8 rows, ~80px each on a 440x956 phone), but they never
   squeeze below the 58px touch floor — past that the container scrolls rather
   than shipping targets nobody can hit. #itemGrid is already flex:1 inside the
   flex-column screen, so the height is definite and 1fr resolves. */
/* ⚠ `grid-auto-rows: minmax(76px, 1fr)` IS WHAT MADE THE TILES 640px TALL.
   A 1fr row absorbs all the free space itself, so `align-content` has nothing
   left to distribute and cannot save it — the fix people reach for first does
   nothing here. That rule was tuned when this grid held 23 drink categories and
   the constraint was "fit without scrolling on an iPhone"; grouping cut it to
   6 food and 7 drinks tiles, so each row stretched to a third of the screen with
   a 19px icon marooned in the middle of it.
   Rows are a fixed comfortable height now and the grid starts at the top. */
.cat-tiles {
  display: grid;
  /* TWO columns on a phone, not three. With at most 7 tiles per tab there is no
     scrolling to avoid any more, so the width goes into the tile: a ~200px
     target instead of ~133px, and titles like "While You Wait" on one line
     instead of clamped to two. */
  grid-template-columns: repeat(2, 1fr);
  grid-auto-rows: minmax(118px, auto);
  gap: 10px;
  align-content: start;
  overflow-y: auto;
}
@media (min-width: 560px) {
  .cat-tiles { grid-template-columns: repeat(3, 1fr); }
}
@media (min-width: 700px) {
  /* A tablet has room for more columns — and once there are more columns there
     are fewer rows, so 1fr stretches each tile to fill the height. 23 tiles in 6
     columns is 4 rows, which rendered 184px tiles that were nearly all empty
     space. Above 700px the fit-without-scrolling constraint is not binding
     (that was Filip's requirement "on an iPhone"), so pin a comfortable row
     height and let the grid start at the top instead. */
  .cat-tiles {
    grid-template-columns: repeat(auto-fill, minmax(172px, 1fr));
    grid-auto-rows: 132px;
    align-content: start;
  }
}
.cat-tile {
  position: relative; min-width: 0;
  display: grid; grid-template-rows: auto auto auto; align-content: center;
  justify-items: center; gap: 9px;
  padding: 14px 10px 15px; text-align: center;
  background: var(--t1-surface, #141616);
  border: 1px solid var(--t1-hairline, #262a2a);
  border-radius: var(--t1-r-card, 14px);
  color: var(--text, #e9e6e1); cursor: pointer;
  -webkit-touch-callout: none; -webkit-user-select: none; user-select: none;
  overflow: hidden;
}
/* A tinted wash keyed to the category's tone, strongest at the top-left where
   the icon sits. Enough to make each tile its own object at a glance without
   turning the grid into a paint chart — the mark and the label still carry the
   meaning, the colour just makes the tile findable a second time. */
.cat-tile::before {
  content: ''; position: absolute; inset: 0;
  background: radial-gradient(120% 90% at 12% 0%, var(--tone-wash) 0%, transparent 62%);
  pointer-events: none;
}
.cat-tile > * { position: relative; }
/* Icon, title and count all scaled up together (Filip). The mark is the thing
   read first at arm's length on a moving floor, so it takes the biggest jump —
   19px inside a 30px chip was a detail, not a signpost. */
.ct-i {
  display: grid; place-items: center;
  width: 52px; height: 52px; border-radius: 15px;
  background: var(--tone-chip); color: var(--tone-ink);
}
.ct-i svg { width: 30px; height: 30px; }
.ct-t {
  font: 600 15.5px/1.25 Inter, sans-serif; letter-spacing: -.005em;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
}
.ct-n {
  font: 600 10px/1 Inter, sans-serif; font-style: normal;
  color: var(--text-subtle, #6d7373); letter-spacing: .09em; text-transform: uppercase;
}
@media (min-width: 560px) {
  .ct-i { width: 46px; height: 46px; border-radius: 14px; }
  .ct-i svg { width: 27px; height: 27px; }
  .ct-t { font-size: 14px; }
}
/* Category tones. Muted and desaturated on purpose: this sits behind food
   photography-free text at 440px, and saturated blocks would fight the gold and
   burgundy the rest of the app is built from. */
.tone-red     { --tone-chip: rgba(180,58,48,.20);  --tone-ink: #E08076; --tone-wash: rgba(180,58,48,.11); }
.tone-rose    { --tone-chip: rgba(196,84,120,.20); --tone-ink: #E58BAC; --tone-wash: rgba(196,84,120,.10); }
.tone-gold    { --tone-chip: rgba(201,169,97,.22); --tone-ink: #D9BC77; --tone-wash: rgba(201,169,97,.11); }
.tone-amber   { --tone-chip: rgba(198,132,58,.20); --tone-ink: #DFA466; --tone-wash: rgba(198,132,58,.10); }
.tone-plum    { --tone-chip: rgba(140,84,168,.20); --tone-ink: #BC94D6; --tone-wash: rgba(140,84,168,.10); }
.tone-sky     { --tone-chip: rgba(74,142,190,.20); --tone-ink: #7FB8DE; --tone-wash: rgba(74,142,190,.10); }
.tone-green   { --tone-chip: rgba(76,150,110,.20); --tone-ink: #7CC79A; --tone-wash: rgba(76,150,110,.10); }
.tone-brown   { --tone-chip: rgba(150,110,80,.22); --tone-ink: #C39A76; --tone-wash: rgba(150,110,80,.11); }
.tone-neutral { --tone-chip: rgba(160,160,160,.16); --tone-ink: #A6ADAD; --tone-wash: rgba(160,160,160,.07); }
.cat-tile:active { transform: scale(.97); }

/* The way back out of a category. Full width in the dock, because the browser
   back button would leave the till entirely. */
.menu-back {
  width: 100%; min-height: 46px; margin-bottom: 8px;
  display: flex; align-items: center; gap: 7px; padding: 0 13px;
  background: var(--t1-surface-2, #16191a);
  border: 1px solid var(--t1-hairline, #262a2a);
  border-radius: var(--t1-r-ctl, 11px);
  color: var(--text, #e9e6e1); font: 600 14px/1 Inter, sans-serif;
  text-align: left; cursor: pointer;
}
.menu-back svg { flex: 0 0 auto; color: var(--gold, #C9A961); }
.menu-back span { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.menu-back:active { transform: scale(.99); }
/* The [hidden] ATTRIBUTE is a UA rule at specificity 0 and loses to
   `.menu-back { display: flex }` above — so the back control rendered at level 1
   where there is nothing to go back to. Same bug as `#screenTabs { display:flex }`
   beating `.till-screen[hidden]` (V25.14.1) and it is now the third instance in
   this file's history. ANY rule that sets `display` on an element that is ever
   toggled with [hidden] needs this companion. */
.menu-back[hidden] { display: none; }

/* ===================== SPLIT BILL (V26.14) =====================
   Filip: "it could literally split the screen in half, and you would move items
   from left hand side to right hand side. And on the top, you would choose the
   name of the new tab on the top right and on the top left would be the name of
   the old tab."

   A full screen, because a sheet cannot hold two lists, two headers and two
   totals — .till-sheet caps at min(88dvh, ...) and the old UI nested a 40vh
   scroller inside it. z-index 1300 sits BELOW the sheet layer (1400) so the
   manager-passcode prompt can still appear over this, and above .till-nav so
   tapping "Food" cannot silently discard staged work. */
.split-wrap[hidden] { display: none; }
.split-wrap {
  position: fixed; top: 0; left: 0; right: 0;
  /* Rides the iOS keyboard the way .till-sheet-wrap does — the name input at
     the top would otherwise sit under it. */
  bottom: var(--kb, 0px);
  z-index: 1300;
  display: grid;
  grid-template-rows: auto auto auto 1fr auto;
  background: var(--bg, #0b0c0c);
}

/* Shared 3-column geometry so the divider lines up down the whole screen. A 1px
   hairline, NOT two bordered cards: Filip rejected full rings twice, and two
   1px rings would eat 4px of a 200px column. */
.split-heads, .split-tots, .split-lists {
  display: grid; grid-template-columns: 1fr 1px 1fr; column-gap: 9px; padding: 0 10px;
}
.split-div { background: var(--t1-hairline, #262a2a); }

.split-heads { padding-top: calc(env(safe-area-inset-top) + 8px); padding-bottom: 8px; }
.split-head { display: grid; gap: 3px; min-width: 0; align-content: start; }
.sh-eyebrow {
  font: 700 10px/1 Inter, sans-serif; letter-spacing: .09em;
  text-transform: uppercase; color: var(--text-muted, #8b9090);
}
.sh-name {
  font: 600 15px/1.25 'Space Grotesk', sans-serif; color: var(--text, #e9e6e1);
  min-height: 44px; display: flex; align-items: center;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* 16px or iOS zooms the viewport on focus — trap #11. Typing here IS the naming
   interaction; there is no separate dialog. */
.sh-input {
  min-height: 44px; padding: 0 11px; font-size: 16px; font-weight: 600;
  background: var(--t1-surface-2, #16191a); color: var(--gold, #C9A961);
  border: 1px solid var(--gold, #C9A961); border-radius: var(--t1-r-ctl, 11px);
  min-width: 0; width: 100%;
}

.split-tots { padding-bottom: 9px; align-items: start; }
.split-tot { display: grid; gap: 2px; min-width: 0; }
.split-tot.right { text-align: right; }
.st-big { font: 700 21px/1.1 Inter, sans-serif; font-variant-numeric: tabular-nums; color: var(--text, #e9e6e1); }
.split-tot.right .st-big { color: var(--gold, #C9A961); }
.st-sub, .st-disc {
  font: 500 11px/1.3 Inter, sans-serif; color: var(--text-muted, #8b9090);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.st-disc { color: var(--gold, #C9A961); }
/* Sits ON the divider between the two totals. Amber when the halves stop summing
   — a rounding penny or a bug, and the waiter should meet it here, not at the
   card machine. */
.split-sum {
  grid-column: 2; justify-self: center; align-self: end;
  white-space: nowrap; font: 600 10.5px/1 Inter, sans-serif;
  color: var(--text-subtle, #6d7373); background: var(--bg, #0b0c0c);
  padding: 3px 0; transform: translateY(4px);
}
.split-sum.off { color: #E0A33E; }

.split-chips {
  display: flex; gap: 7px; padding: 0 10px 8px; overflow-x: auto;
  scrollbar-width: none;
}
.split-chips::-webkit-scrollbar { display: none; }
.split-chip {
  flex: 0 0 auto; min-height: 38px; padding: 0 14px; border-radius: 999px;
  border: 1px solid var(--t1-hairline-str, #2a2f2f); background: transparent;
  color: var(--text-muted, #8b9090); font: 600 12.5px/1 Inter, sans-serif; cursor: pointer;
}
.split-chip:active { transform: scale(.97); }

/* The only scrolling region. min-height:0 is mandatory — a 1fr grid row
   overflows without it. FLEX for the lists, not grid, so the "flex:1 on a grid
   stretches its rows" trap cannot bite. */
.split-lists { min-height: 0; }
.split-list {
  display: flex; flex-direction: column; gap: 8px;
  overflow-y: auto; min-height: 0; -webkit-overflow-scrolling: touch;
  padding: 0 0 12px;
}
.split-hint {
  margin: auto 0; padding: 20px 12px; text-align: center;
  font: 500 12.5px/1.5 Inter, sans-serif; color: var(--text-subtle, #6d7373);
}

/* At 440px each column is ~200px, leaving ~182px of content — so the money goes
   on its OWN line rather than a third column. */
.split-row {
  display: grid; grid-template-columns: auto 1fr; gap: 9px; align-items: start;
  min-height: 52px; padding: 8px 9px; text-align: left; cursor: pointer;
  background: var(--t1-surface-2, #16191a);
  border: 1px solid var(--t1-hairline, #262a2a);
  border-left: 2px solid transparent;
  border-radius: var(--t1-r-row, 12px); color: var(--text, #e9e6e1);
  -webkit-touch-callout: none; -webkit-user-select: none; user-select: none;
}
/* A partially-moved line is the one state you cannot infer from position alone,
   so it gets the gold left edge — same language as .bill-line.held. */
.split-row.partial { border-left-color: var(--gold, #C9A961); }
.sr-q {
  min-width: 26px; padding: 3px 6px; border-radius: 7px;
  background: var(--t1-surface, #141616); color: var(--gold, #C9A961);
  font: 700 12.5px/1.2 Inter, sans-serif; text-align: center; font-variant-numeric: tabular-nums;
}
.sr-q em { font-style: normal; font-weight: 500; color: var(--text-subtle, #6d7373); }
.sr-body { display: grid; gap: 2px; min-width: 0; }
/* Two lines then ellipsis: "Chicken Tikka Masala" will not fit one 149px line
   and truncating a dish name mid-service is not acceptable. */
.sr-n {
  font: 600 12.5px/1.25 Inter, sans-serif;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
}
.sr-m {
  font: 500 10.5px/1.3 Inter, sans-serif; color: var(--text-muted, #8b9090);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.sr-p { font: 700 12.5px/1.2 Inter, sans-serif; font-variant-numeric: tabular-nums; text-align: right; }
.split-row:active { transform: scale(.98); }

/* NOT .mobile-action-bar — that class is pointer-events:none with only .btn
   re-enabled (trap #10), which would kill the name input and the chips. */
.split-dock {
  display: grid; gap: 8px; padding: 10px;
  padding-bottom: max(env(safe-area-inset-bottom), 10px);
  background: var(--bg, #0b0c0c); border-top: 1px solid var(--t1-hairline, #262a2a);
}
.split-dock-row { display: flex; gap: 8px; }
.split-dock-row .btn { flex: 1; min-height: 48px; }
.split-go {
  width: 100%; min-height: 56px; border-radius: 15px;
  font-size: 15.5px; font-weight: 600;
  box-shadow: 0 8px 26px rgba(0, 0, 0, .5);
}
.split-go:disabled { opacity: .45; box-shadow: none; }

/* The toast sits at bottom:80px, which lands exactly on the primary. */
body.split-open .till-toast { bottom: calc(150px + env(safe-area-inset-bottom)); }

@media (min-width: 700px) {
  /* Don't let a 1280px tablet become two 600px columns of 52px rows. */
  .split-wrap > * { max-width: 1040px; margin-inline: auto; width: 100%; }
  .split-heads, .split-tots, .split-lists, .split-chips, .split-dock { padding-inline: 16px; }
  .sh-name, .sh-input { min-height: 48px; }
  .split-row { min-height: 56px; }
  /* One dock row: a tablet on a stand is still reached from below, so it stays
     at the bottom rather than moving to a desktop-style corner. */
  .split-dock { grid-template-columns: 1fr; }
}

/* ===================== BILL DOCK (V26.21) =====================
   Filip: the bill's totals and actions "should be displayed as different icons,
   and a little bit compressed, like a little menu just above the navigation
   bar. Same for the subtotal, service and total."

   It replaced NINE equal-weight text buttons in document order — with "Cancel
   sale" sitting immediately beside "Close tab", two identically weighted
   controls where one ends a paid bill and the other destroys it — and a primary
   that sat at the END of a long scroll, so a 25-line table meant scrolling the
   whole bill to reach Take payment. */
#screenBasket:not([hidden]) { display: flex; flex-direction: column; }
/* ⚠ THE DOCK COULD NOT PIN WITHOUT THIS. .bill-dock uses `margin-top:auto` to
   sit at the bottom, which needs a flex COLUMN PARENT WITH HEIGHT — and
   #basketBody was a plain div sized to its content, so on a short basket the
   buttons floated directly under the last line with the screen empty beneath
   them. #screenBasket was already a flex column; its only child never grew. */
#basketBody { flex: 1; display: flex; flex-direction: column; min-height: 0; }
/* The dock is fixed, so it no longer occupies flow space — reserve its measured
   height or the last bill line sits under it. Measured, not guessed: the dock
   grows with the action row wrapping and with a longer totals line. */
#screenBasket { padding-bottom: var(--bill-dock-h, 150px); }
/* SNAP THE BILL TO THE BOTTOM (Filip, 2026-08-02: "all the menu options should
   be snapping to the bottom"). A tab with seven lines left "Send to kitchen"
   stranded mid-screen with a screen of black under it, and the Send button is
   the one control on that screen you are reaching for.
   ⚠ `margin-top: auto` on the first child, NOT `justify-content: flex-end` —
   flex-end clips the TOP of a bill longer than the screen, and a 25-line table
   is the exact case where that matters. */
#basketBody > :first-child { margin-top: auto; }

/* ONE ROW: total, then both buttons (Filip). Three stacked blocks made the
   waiter read down the screen to find the total and then back up to act on it;
   in a row the number sits beside the button that uses it.
   Not 1fr 1fr for the buttons: equal width would give "Clear basket" the same
   visual claim as the action you actually came here for. */
/* The pencil sits in the same row as − + ✕, which are text buttons, so it needs
   its glyph centred rather than baseline-aligned with them. */
.bl-actions [data-pact="edit"] { display: inline-grid; place-items: center; }
.bl-actions [data-pact="edit"] svg { display: block; }

/* Mirrors the ticket: the alert is inverted on paper, so it is inverted here.
   A waiter reading the tab back should see the same thing the chef will. */
.bl-alert {
  display: inline-block; margin-top: 4px; padding: 3px 7px; border-radius: 6px;
  background: var(--danger, #EF4444); color: #fff;
  font: 700 11.5px/1.35 Inter, sans-serif;
}
/* The alert field is the loud one, so it says so before anything is typed. */
.sheet-alert { border-color: rgba(239, 68, 68, .5) !important; }
.sheet-group h4 .hint {
  font: 500 10px/1 Inter, sans-serif; font-style: normal; letter-spacing: 0;
  text-transform: none; color: var(--text-subtle, #6d7373); margin-left: 6px;
}

/* The quantity reads as typeable, because it is. Without this it looks like a
   label between two buttons and nobody discovers it. */
#qtyVal {
  /* 48px to fill the stepper pill's height — at 44 the dashed box floated with
     2px of air above and below the 48px − / + segments. */
  min-width: 44px; min-height: 48px; display: inline-grid; place-items: center;
  border-radius: 9px; cursor: pointer;
  border: 1px dashed var(--t1-hairline-str, rgba(255, 255, 255, .18));
}
#qtyVal:active { background: var(--bg-hover, #1d2024); }

/* MANAGER KEYPAD. Borrows .lock-dots/.lock-pad from the launcher stylesheet so
   the two cannot drift — it is deliberately the same object in the same colours,
   because a manager should recognise it instantly on somebody else's phone.
   till.html loads styles.css and till.css only, so the few rules it needs are
   restated here rather than pulling lock.css into the till. */
/* ⚠ THIS SHEET HAS NO .sheet-body WRAPPER, so it does not inherit that class's
   `padding: 14px 16px`. Without the horizontal padding restated here the reason
   block ran the full 436px of the sheet — label clipped at the left edge, input
   jammed against x=0 — while the dots and keypad sat correctly centred beside
   it, which is what made it look like an alignment bug rather than a missing
   gutter. Measured: body 0→436 against a head inset to 16px. */
.pin-body {
  /* V26.92: even rhythm — title, dots, keys and reason on consistent beats.
     The (usually empty) error line reserves almost nothing, so the dots no
     longer float in a hole between the title and the pad. */
  display: grid; justify-items: center; gap: 10px;
  padding: 12px 16px 10px;
}
.lock-dots {
  display: flex; justify-content: space-between; align-self: center;
  width: 168px; flex: 0 0 auto; margin-top: 2px;
}
.pin-body .dot {
  width: 13px; height: 13px; border-radius: 999px;
  background: transparent; border: 1.5px solid var(--text-muted, #8b9090);
  transition: background 140ms ease, transform 140ms ease;
}
.pin-body .dot.on { background: var(--text, #e9e6e1); border-color: var(--text, #e9e6e1); transform: scale(1.08); }
.lock-err {
  min-height: 4px; margin: 0; font-size: 12px; line-height: 1.35;
  color: var(--danger, #d9564c); text-align: center;
}
.lock-pad {
  display: grid; grid-template-columns: repeat(3, 1fr);
  gap: 12px 22px; margin: 0; width: min(272px, 74vw);
}
.lock-pad button {
  aspect-ratio: 1; border-radius: 999px; border: 1px solid rgba(255, 255, 255, .14);
  background: rgba(255, 255, 255, .05); color: var(--text, #e9e6e1);
  font: 400 25px/1 Inter, sans-serif; cursor: pointer;
  display: grid; place-items: center;
  transition: background 90ms ease, transform 90ms ease;
}
.lock-pad button:active { background: rgba(255, 255, 255, .16); transform: scale(.94); }
/* V26.91 (Filip): the empty bottom-right cell read as misalignment.
   iOS convention — 0 centred, delete bottom-RIGHT. DOM order is unchanged;
   explicit grid placement does the swap. */
.lock-pad button[data-k="0"] { grid-column: 2; grid-row: 4; }
.lock-pad button[data-k="del"] { grid-column: 3; grid-row: 4; }
.lock-pad button[data-k="del"] {
  background: transparent; color: var(--gold, #C9A961); border-color: rgba(201, 169, 97, .32);
}
.lock-pad button[data-k="del"]:active { background: rgba(201, 169, 97, .12); }
/* Optional, and it says so — a required box is a box that gets "asdf" in it. */
/* Capped to the keypad's width so the field lines up with the control above it
   rather than being the one full-bleed element on the sheet. */
.pin-reason { display: block; width: min(272px, 100%); margin-top: 2px; }
.pin-reason input { width: 100%; }
.pin-reason span {
  display: block; margin-bottom: 6px;
  font: 700 9.5px/1 Inter, sans-serif; letter-spacing: .09em; text-transform: uppercase;
  color: var(--text-muted, #8b9090);
}
.pin-reason em {
  font-style: normal; letter-spacing: 0; text-transform: none;
  font-weight: 500; color: var(--text-subtle, #6d7373);
}

/* Per-line discount. Gold, because it takes money off — it should not read like
   the neutral quantity controls beside it. */
.bl-pct {
  min-width: 40px; min-height: 40px; border-radius: 9px;
  border: 1px solid rgba(201, 169, 97, .4); background: rgba(201, 169, 97, .10);
  color: var(--gold, #C9A961); font: 700 14px/1 Inter, sans-serif; cursor: pointer;
}
.bl-pct:active { background: rgba(201, 169, 97, .2); }
.bl-disc {
  margin-top: 3px; color: var(--gold, #C9A961);
  font: 600 11.5px/1.35 Inter, sans-serif;
}

/* The custom-item tile. Gold and dashed rather than a category tone: it is not
   a section of the menu, it is the exception to it.
   Full width at the foot of the grid — as wide as two tiles side by side. A
   wide tile laid out like a tall one is a lot of empty middle, so this one runs
   horizontally: mark, label, then the caveat that matters most about it. */
.ct-custom {
  grid-column: 1 / -1;
  grid-template-columns: auto 1fr auto; grid-template-rows: auto;
  justify-items: start; align-items: center;
  min-height: 68px; padding: 10px 16px; text-align: left;
  border-style: dashed;
}
.ct-custom .ct-i { width: 42px; height: 42px; border-radius: 12px; }
.ct-custom .ct-i svg { width: 24px; height: 24px; }
.ct-custom .ct-n { justify-self: end; }

/* Floor numbers — three chips above the tab list, manager+ only.
   Scrolls sideways rather than wrapping: a wrapped row would push the tab cards
   down the screen on a phone, and the tabs are what this screen is for. */
.floor-chips {
  display: flex; gap: 8px; margin: 0 0 10px;
  overflow-x: auto; scrollbar-width: none; -webkit-overflow-scrolling: touch;
}
.floor-chips::-webkit-scrollbar { display: none; }
.floor-chips[hidden] { display: none; }
/* ⚠ 116px each plus gaps needs 390px, and the myPOS Carbon gives the page ~356.
   The strip scrolls, so nothing was lost — but TOTAL, the number a manager
   actually glances for, sat off the right edge on the device this is going onto.
   Below 400px the tiles tighten so all three fit; above it nothing changes. */
.fc {
  flex: 1 0 auto; min-width: 116px; padding: 8px 12px;
  border-radius: var(--t1-r-ctl, 11px);
  border: 1px solid var(--t1-hairline, rgba(255, 255, 255, .09));
  background: var(--t1-surface, rgba(255, 255, 255, .038));
}
.fc em {
  display: block; font: 700 10px/1 Inter, sans-serif; font-style: normal;
  letter-spacing: .09em; text-transform: uppercase; color: var(--text-subtle, #6d7373);
}
.fc b {
  display: block; margin: 4px 0 2px;
  font: 700 18px/1 'Space Grotesk', sans-serif;
  color: var(--gold, #C9A961); font-variant-numeric: tabular-nums;
}
.fc span { display: block; font: 500 10.5px/1.3 Inter, sans-serif; color: var(--text-muted, #8b9090); }
@media (max-width: 400px) {
  .floor-chips { gap: 4px; }
  .fc { min-width: 0; padding: 8px 8px; }
  .fc b { font-size: 16.5px; }
  /* The sub-line is what sets the width -- "4 covers · £3.70/cover" is the
     widest string on the strip and drove the overflow on its own. */
  .fc span { font-size: 9.3px; }
}

/* A basket line that is not on the menu. Marked, because "Deposit — Sharma
   party" beside real dishes should not read as something a waiter could have
   mistyped from the catalogue. */
.bl-custom {
  font-style: normal; font-size: 10px; font-weight: 700; letter-spacing: .06em;
  text-transform: uppercase; color: var(--gold, #C9A961);
  border: 1px solid rgba(201, 169, 97, .4); border-radius: 4px; padding: 1px 5px; margin-left: 6px;
}

.pend-row {
  display: grid; grid-template-columns: auto auto minmax(0, 1fr);
  align-items: center; gap: 8px;
}
.pend-tot { text-align: left; padding-right: 2px; }
.pend-tot em {
  display: block; font: 700 9px/1 Inter, sans-serif; font-style: normal;
  letter-spacing: .1em; text-transform: uppercase; color: var(--text-subtle, #6d7373);
}
.pend-tot b {
  display: block; margin-top: 3px;
  font: 700 21px/1 'Space Grotesk', sans-serif;
  color: var(--gold, #C9A961); font-variant-numeric: tabular-nums;
}
.pend-row .btn {
  width: 100%; min-height: 52px; border-radius: 14px;
  font-size: 14px; padding-inline: 12px; white-space: nowrap;
}
/* Under ~360px the three-up row would crush the primary; the total steps onto
   its own line rather than the buttons shrinking below a usable width. */
@media (max-width: 359px) {
  .pend-row { grid-template-columns: auto minmax(0, 1fr); }
  .pend-tot { grid-column: 1 / -1; display: flex; align-items: baseline; gap: 8px; }
  .pend-tot b { margin-top: 0; }
}
#basketBody {
  flex: 1; min-height: 0; overflow-y: auto; -webkit-overflow-scrolling: touch;
  /* Flex column + margin-top:auto on the dock, AND position:sticky on it. Both
     are needed and they cover different cases: sticky pins the dock once the
     bill is long enough to scroll, margin-top:auto puts it at the bottom when
     the bill is SHORT. With sticky alone a two-line tab left the dock floating
     370px up the screen, halfway between the last item and the nav — which is
     the one place Filip asked for it not to be. Same pairing as the tabs dock. */
  display: flex; flex-direction: column;
}

.bill-dock {
  /* ⚠ `margin-top: auto` USED TO SIT ABOVE THE SHORTHAND, WHICH RESET IT TO
     12px. It had therefore never worked; the tab bill only looked pinned
     because its content is long enough to fill the screen, and the basket —
     which is often two lines — exposed it by floating mid-screen with the nav
     far below. Same class of bug as the two min-heights in V26.27: a shorthand
     after a longhand is not an override, it is a silent erase. `auto` is folded
     INTO the shorthand now so it cannot be separated from it again. */
  /* ⚠ FIXED ABOVE THE NAV, not sticky. Sticky offsets are measured from the
     SCROLLPORT'S PADDING BOX, so `bottom: 0` left the dock 30px clear of the nav
     however the margins were tuned — against 9px between its own rows, which is
     what Filip measured by eye ("the height difference should be just as much as
     there is between the buttons and the take payment button").
     Anchored to --till-nav-h, the same measured variable the menu dock uses, so
     the two docks sit identically. The 9px below the buttons is now padding, so
     the spacing inside and under the dock is literally the same number. */
  position: fixed; left: 0; right: 0; bottom: var(--till-nav-h, 62px); z-index: 6;
  margin: 0; padding: 10px 14px 9px;
  display: grid; gap: 9px;
  background: var(--bg, #0b0c0c);
  border-top: 1px solid var(--t1-hairline-str, #2a2f2f);
  box-shadow: 0 -14px 30px rgba(0, 0, 0, .45);
}
.bd-tot { display: flex; align-items: flex-end; justify-content: space-between; gap: 12px; }
.bd-lines { display: flex; flex-wrap: wrap; gap: 2px 12px; min-width: 0; }
.bd-lines span {
  font: 500 11.5px/1.5 Inter, sans-serif; color: var(--text-muted, #8b9090);
  white-space: nowrap;
}
.bd-lines b { color: var(--text, #e9e6e1); font-weight: 600; font-variant-numeric: tabular-nums; }
.bd-lines .disc b { color: var(--gold, #C9A961); }
.bd-lines .off { text-decoration: line-through; opacity: .65; }
/* The one number the guest asks for, and the one the waiter reads out. */
.bd-due { flex: 0 0 auto; text-align: right; }
.bd-due em {
  display: block; font: 700 9.5px/1 Inter, sans-serif; font-style: normal;
  letter-spacing: .1em; text-transform: uppercase; color: var(--text-subtle, #6d7373);
}
.bd-due b {
  display: block; margin-top: 2px;
  font: 700 26px/1 'Space Grotesk', sans-serif;
  color: var(--gold, #C9A961); font-variant-numeric: tabular-nums;
}
.bd-pay {
  width: 100%; min-height: 56px; border-radius: 15px;
  font-size: 15.5px; font-weight: 600;
  box-shadow: 0 8px 26px rgba(0, 0, 0, .45);
}
/* One scrolling row of icons. Labelled — an icon-only row is the same mistake
   the tab-card stage strip made, and these are destructive controls. */
.bd-acts {
  display: flex; gap: 6px; overflow-x: auto; scrollbar-width: none;
  padding-bottom: 2px; min-width: 0;
}
.bd-acts::-webkit-scrollbar { display: none; }
.bill-act {
  flex: 1 0 auto; min-width: 62px; min-height: 52px;
  display: grid; place-items: center; gap: 3px; padding: 6px 8px;
  background: var(--t1-surface, #141616);
  border: 1px solid var(--t1-hairline, #262a2a);
  border-radius: var(--t1-r-ctl, 12px);
  color: var(--text-muted, #8b9090); cursor: pointer;
}
.bill-act svg { width: 18px; height: 18px; }
.bill-act span { font: 600 9.5px/1 Inter, sans-serif; letter-spacing: .02em; white-space: nowrap; }
.bill-act:active { transform: scale(.96); }
/* Cancel is last, separated, and the only tinted one. Quiet destructive — the
   V25.7 rule: a filled red button outranks the primary beside it. */
.bill-act.is-danger {
  margin-left: 6px; color: var(--danger, #EF4444);
  border-color: rgba(239, 68, 68, .45);
}
@media (min-width: 700px) {
  .bd-acts { flex-wrap: wrap; overflow: visible; }
  .bill-act { flex: 0 1 auto; min-width: 88px; }
}

/* ---- Turn-time bar (V26.22) ----
   How long the table has left, under its stage chip. Three states because that
   is the number of decisions a manager makes with it: fine / nearly done / over.
   Colour is reinforcement — the words say it too, so this works for a colour
   -blind manager and in the corner of the eye at equal measure. */
.turn { display: flex; align-items: center; gap: 8px; margin-top: 7px; }
.turn-track {
  flex: 1; min-width: 0; height: 5px; border-radius: 999px;
  background: rgba(255, 255, 255, .07); overflow: hidden;
}
.turn-track i {
  display: block; height: 100%; border-radius: 999px;
  background: var(--turn-c); transition: width .6s ease;
}
.turn-l {
  flex: 0 0 auto; font: 600 10.5px/1 Inter, sans-serif;
  color: var(--turn-c); font-variant-numeric: tabular-nums;
}
.turn.ok   { --turn-c: #6E9E7E; }
.turn.soon { --turn-c: #E0A33E; }
.turn.over { --turn-c: var(--danger, #EF4444); }
/* Over time is the one state that should catch the eye from across a room. */
.turn.over .turn-track i { background: repeating-linear-gradient(
  45deg, var(--danger, #EF4444) 0 4px, rgba(239,68,68,.55) 4px 8px); }

/* ---- Printer health strip (V26.24) ----
   Not dismissible: this is not a notification, it is a statement about whether
   the last order reached the kitchen. Sits between the top bar and the content
   so it displaces rather than covers — an overlay here would hide a tab card. */
.printer-strip[hidden] { display: none; }
.printer-strip {
  display: flex; align-items: center; gap: 9px;
  padding: 9px 14px;
  background: rgba(239, 68, 68, .14);
  border-bottom: 1px solid rgba(239, 68, 68, .5);
  color: var(--danger, #EF4444);
  font: 500 12.5px/1.35 Inter, sans-serif;
}
.printer-strip svg { flex: 0 0 auto; }
.printer-strip b { font-weight: 700; }
.printer-strip span { min-width: 0; }

/* ---- Item tiles, quick-add and the two-variant sheet (V26.25) ---- */
.item-btn { min-height: 92px; }
.item-foot { display: flex; align-items: center; justify-content: space-between; gap: 8px; }
/* Diet marks. Already in the catalogue, shown nowhere until now — and "is it
   vegan" is asked at the table constantly in an Indian restaurant. VGO is kept
   distinct from VG on purpose: "vegan on request" is a different answer. */
.item-diet { display: flex; gap: 4px; flex: 0 0 auto; }
.dt {
  font: 700 8.5px/1 Inter, sans-serif; font-style: normal; letter-spacing: .04em;
  padding: 3px 5px; border-radius: 5px;
}
.dt-v   { background: rgba(76,150,110,.20);  color: #7CC79A; }
.dt-vg  { background: rgba(76,150,110,.30);  color: #8FD9AC; }
.dt-vgo { background: rgba(76,150,110,.13);  color: #6E9E7E; }
/* A one-tap item SAYS it is one tap. Otherwise the two behaviours are
   indistinguishable until you commit, and a waiter cannot build a rhythm
   around a control that sometimes opens a sheet and sometimes does not. */
.item-quick {
  position: absolute; top: 8px; right: 9px;
  width: 20px; height: 20px; border-radius: 999px;
  display: grid; place-items: center;
  background: rgba(201,169,97,.16); color: var(--gold, #C9A961);
  font: 700 14px/1 Inter, sans-serif;
}
.item-btn.is-quick:active { border-color: var(--gold, #C9A961); background: rgba(201,169,97,.10); }
/* Reserve the corner. The badge is absolutely positioned, so a two-line name
   ("Special Tandoori Mix Grill") ran straight underneath it — the + landed on
   top of the words. Same for the 86 tag, which shares the corner. */
.item-btn.is-quick .item-name { padding-right: 24px; }
.item-btn .item-tag ~ .item-name { padding-right: 24px; }

/* Two variants, nothing else to answer: two big buttons that ADD, rather than
   select-then-confirm. Full width and 68px because this is the single most
   repeated interaction on the drinks side of the menu. */
.pair-row { display: grid; gap: 10px; }
.pair-btn {
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  width: 100%; min-height: 68px; padding: 0 18px;
  background: var(--t1-surface-2, #16191a);
  border: 1px solid var(--gold, #C9A961); border-radius: 15px;
  color: var(--text, #e9e6e1); cursor: pointer; text-align: left;
}
.pb-n { font: 600 16px/1.2 Inter, sans-serif; }
.pb-p { font: 700 18px/1 'Space Grotesk', sans-serif; color: var(--gold, #C9A961); font-variant-numeric: tabular-nums; }
.pair-btn:active { transform: scale(.985); background: rgba(201,169,97,.12); }
.pair-more {
  width: 100%; min-height: 44px; margin-top: 10px;
  background: transparent; border: 0; color: var(--text-muted, #8b9090);
  font: 500 13px/1 Inter, sans-serif; cursor: pointer;
}
/* In pair mode the footer stepper and Add button are noise — the two buttons
   ARE the action, and leaving a disabled "Add · £0.00" under them just asks
   which one is the real control. */
.pair-mode .sheet-foot { display: none; }

/* The shell is a flex column now (see .till-app), so DOM order is the layout and
   a hidden printer strip simply drops out and the rest reflow — no explicit row
   placement, and the old grid collapse (a display:none row reshuffling the tracks
   until .till-main went to 0px) cannot happen. */
/* Never a flex item: it only ever holds a position:fixed dock. */
#billDockHost  { display: contents; }

/* ════════════════════════════════════════════════════════════════════════
   V26.63 — BASKET + TABS REWORK (Filip's screenshots, 2026-08-07)
   Appended, so everything here wins the cascade over the blocks above.
   ═══════════════════════════════════════════════════════════════════════ */

/* ---- Compressed tab cards: more tables per screen on a small tablet ---- */
.tab-card { padding: 8px 11px 9px; border-radius: var(--t1-r-row, 12px); }
.tab-name, .tab-total { font-size: 14.5px; }
.tc-facts { margin: 2px 0 0; gap: 2px 8px; font-size: 10.5px; }
/* 40px, deliberately under the 44 floor: Filip asked for density on the small
   tablets, the chip is full-width so the surface is still large, and the words
   stay 12.5px. The gain is a whole extra card per screen at 640px tall. */
.tc-stage { margin-top: 6px; min-height: 40px; font-size: 12.5px; padding: 0 10px; border-radius: 10px; }
.turn { margin-top: 5px; }
.tabs-grid { gap: 9px; }

/* ---- + New lives in the nav now ---- */
.till-nav { grid-template-columns: repeat(5, 1fr);
  padding-inline: max(10px, env(safe-area-inset-left)) max(10px, env(safe-area-inset-right)); }
@media (min-width: 600px) { .till-nav { grid-template-columns: repeat(5, 118px); justify-content: center; } }
.till-nav-new .tn-plus {
  width: 24px; height: 24px; margin: -2px 0 -1px; border-radius: 999px;
  background: var(--brand-red, #8A1915); color: var(--brand-cream, #F6E8D8);
  display: grid; place-items: center;
}
.till-nav-new:active .tn-plus { transform: scale(.92); }
/* The old floating + is gone from the markup; its empty-state variant with it. */
.tabs-add-btn { display: none; }

/* ---- Sticky bill header ---- */
/* ⚠ top: -12px, NOT 0. The header bleeds the screen's 12px padding with
   negative margins, and sticky pins the MARGIN box — top:0 here is the same
   12px-window bug the menu top bar had (V26.53). */
.bill-head {
  position: sticky; top: -12px; z-index: 5;
  margin: -12px -12px 12px; padding: 10px 12px 9px;
  background: rgba(11, 12, 12, .95);
  -webkit-backdrop-filter: blur(12px); backdrop-filter: blur(12px);
  border-bottom: 1px solid var(--border, #262a2a);
  align-items: center;
}
.bh-main { flex: 1; min-width: 0; }
.bill-head h2 { font-size: 17px; }
.bh-main .turn { margin-top: 5px; }
.bh-more { margin: 0; }   /* tc-more's negative margins are for the tab card */

/* ---- Bill line actions: one size, and nothing off the edge ---- */
/* minmax(0,1fr) is the overflow fix: a bare 1fr cannot shrink below its
   content's min size, and the right column was being pushed past the screen
   edge — Filip's screenshot shows "Voi" clipped mid-word. */
.bill-line { grid-template-columns: 30px minmax(0, 1fr) auto; gap: 8px; }
.bl-right { min-width: 0; }
.bl-actions { flex-wrap: wrap; justify-content: flex-end; }
.bl-actions button, .bl-actions .bl-pct {
  width: 40px; height: 40px; min-width: 40px; min-height: 40px;
  padding: 0; border-radius: 9px; font-size: 14px;
}
.bl-actions .bl-void-btn { width: auto; padding: 0 12px; font-size: 11.5px; }

/* ---- The dock: totals left, Save + Pay right ---- */
.bill-dock.bd2 { display: flex; align-items: center; gap: 10px; padding: 9px 14px 8px; }
.bd2-left { flex: 1 1 auto; min-width: 0; display: grid; gap: 0; }
.bd2-left > span {
  font: 500 10.5px/1.5 Inter, sans-serif; color: var(--text-muted, #8b9090);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.bd2-left > span b { color: var(--text, #e9e6e1); font-weight: 600; font-variant-numeric: tabular-nums; }
.bd2-left > span.disc b { color: var(--gold, #C9A961); }
.bd2-left > span.off { text-decoration: line-through; opacity: .65; }
.bd2-total { display: flex; align-items: baseline; gap: 6px; margin-top: 1px; }
.bd2-total em {
  font: 700 10px/1 Inter, sans-serif; font-style: normal;
  letter-spacing: .09em; text-transform: uppercase; color: var(--text-subtle, #6d7373);
}
.bd2-total b {
  font: 700 19px/1.1 'Space Grotesk', sans-serif;
  color: var(--gold, #C9A961); font-variant-numeric: tabular-nums;
}
.bd2-btns { flex: 0 0 auto; display: flex; gap: 8px; }
/* Save IS "send to kitchen & bar". Filled brand red while there is anything to
   save; disabled (never hidden) otherwise, so Pay never shifts under a thumb. */
.bd-save {
  min-height: 48px; padding: 0 16px; border-radius: 12px;
  border: 1px solid var(--brand-red, #8A1915);
  background: var(--brand-red, #8A1915); color: var(--brand-cream, #F6E8D8);
  font: 600 14px/1 Inter, sans-serif; white-space: nowrap;
}
.bd-save:disabled {
  background: transparent; border-color: var(--border, #2a2f2f);
  color: var(--text-subtle, #6d7373); opacity: .8;
}
.bd2 .bd-pay2 {
  width: auto; min-height: 48px; padding: 0 15px; border-radius: 12px;
  font-size: 14px; font-weight: 600; white-space: nowrap; box-shadow: none;
}
@media (max-width: 400px) {
  .bill-dock.bd2 { gap: 7px; }
  .bd-save { padding: 0 12px; }
  .bd2 .bd-pay2 { padding: 0 11px; font-size: 13px; }
  .bd2-total b { font-size: 17px; }
}

/* ---- The actions sheet the dock row became ---- */
.bd-acts-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px; }
.bd-acts-grid .bill-act { min-width: 0; min-height: 64px; }
.bd-acts-grid .bill-act svg { width: 20px; height: 20px; }
.bd-acts-grid .bill-act span { font-size: 10.5px; }
.bd-acts-grid .bill-act.is-danger { margin-left: 0; }

/* ---- V26.64: identity row joins the dock; sticky header retired ---- */
.bill-dock.bd2 { display: grid; gap: 7px; }
.bd2-id { display: flex; align-items: center; gap: 9px; min-width: 0; }
.bd2-back {
  flex: 0 0 auto; width: 42px; height: 42px; border-radius: 11px;
  border: 1px solid var(--border, #2a2f2f); background: transparent;
  color: var(--text, #e9e6e1); display: grid; place-items: center; cursor: pointer;
}
.bd2-back:active { background: rgba(255,255,255,.07); }
.bd2-who { flex: 1; min-width: 0; }
.bd2-who b {
  display: block; font: 600 14.5px/1.15 'Space Grotesk', sans-serif;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.bd2-who > span {
  display: block; margin-top: 1px; font: 500 10.5px/1.3 Inter, sans-serif;
  color: var(--text-muted, #8b9090);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.bd2-turnwrap .turn { margin-top: 3px; }
.bd2-money { display: flex; align-items: center; gap: 10px; min-width: 0; }
/* The retired sticky header — markup no longer renders it (V26.64). */

/* ── V26.65 — CARBON TOUCH-FLOOR FIXES (2026-08-08) ─────────────────────────
   From measuring every till surface in a true 360×640 viewport (myPOS Carbon,
   iframe harness — Chrome's window cannot go that narrow). Four controls sat
   under the 44px touch floor. Deliberately NOT raised: the 40px bill-line
   buttons and the 40px tab-card stage chip — both are compressions Filip
   specced on 2026-08-07 — and the peek's row buttons go to 40, not 44, to
   match that same line-button family (same job, same size). */
.sheet-x { width: 44px; height: 44px; border-radius: 11px; }
.mb-go { min-height: 44px; }
.mb-acts button { min-width: 40px; min-height: 40px; }
.split-chip { min-height: 44px; }
.bd2-back { width: 44px; height: 44px; }

/* ── V26.69 — CARBON DENSITY (2026-08-09) ───────────────────────────────────
   The till's real device is the 360×640 myPOS Carbon, and Filip flagged the
   phone-tuned scale as reading oversized there. This layer tightens type,
   padding and tile heights on NARROW screens only (≤400px) — the same gate the
   floor-summary already uses — so the 440px phone keeps its comfortable scale.
   Only type, spacing and non-critical heights shrink; primary tap targets stay
   ≥44px, secondary pills sit at 40px (Filip's established small-screen density).
   Measured live in a true 360×640 frame, not eyeballed. */
@media (max-width: 400px) {
  /* Header 61 → ~42px */
  .till-top { padding: calc(env(safe-area-inset-top) + 4px) 9px 4px; gap: 6px; }
  .till-title { font-size: 13.5px; }
  .till-exit, .till-operator { min-height: 36px; padding: 0 10px; font-size: 11px; }

  /* Screen gutters tight */
  .till-screen { padding: 8px 8px 14px; }
  /* .menu-top's -12px bleed is tuned to the 12px gutters above — retune it to
     the 8px gutters here or the sticky search bar overflows 4px per side. */
  .menu-top { margin: -8px -8px 10px; padding: 8px 8px 8px; top: -8px; }
  /* Filter pill compresses with the operator chip beside it. */
  .tabs-filter-btn { min-height: 36px; }

  /* Menu item tiles — densest, most-scrolled surface. 92 → ~48px:
     ~13 rows now fill the viewport where 5 did. Text pulled close together. */
  .item-grid { gap: 6px; }
  .item-btn { min-height: 48px; padding: 6px 8px; gap: 2px; border-radius: 10px; }
  /* V28.1 — the 86 banner follows the tightened 10px tile corner (10 − 1px
     border), instead of poking past it at 11px (review till-screens #9). */
  .item-86 { border-radius: 0 0 9px 9px; }
  .item-name { font: 600 11px/1.1 Inter, sans-serif; }
  .item-price { font-size: 11.5px; }
  .item-foot { font-size: 8px; }
  .item-tag { font-size: 7.5px; top: 5px; right: 5px; }

  /* Category tiles — 132 → ~84px; the mark still leads the tile */
  .cat-tile { gap: 5px; padding: 8px 8px 9px; border-radius: 11px; }
  .ct-i { width: 34px; height: 34px; border-radius: 10px; }
  .ct-i svg { width: 20px; height: 20px; }
  .ct-t { font-size: 11.5px; line-height: 1.15; }
  .ct-n { font-size: 8.5px; }

  /* Search pill 48 → 40px */
  .menu-search { height: 40px; }
  .menu-search input { font-size: 13px; }

  /* Section headings pulled tight to their rows */
  .menu-sec, .msec, .item-sec, .menu-sec-head { margin-bottom: 5px; }

  /* Tab cards — tighter, lines pulled together */
  .tab-card { padding: 6px 9px; border-radius: 10px; }
  .tab-name { font-size: 12.5px; line-height: 1.15; }
  .tab-total { font-size: 13px; }
  .tab-meta { font-size: 9.5px; }
  .tab-by { font-size: 8.5px; }
  .tc-facts { font-size: 9px; }
  .tc-stage { margin-top: 5px; min-height: 36px; font-size: 11px; }
  .tabs-grid { gap: 6px; }

  /* Floor summary — smaller value + tighter card */
  .fc { padding: 6px 8px; }
  .fc b { font-size: 13px; margin: 2px 0 1px; }
  .fc em { font-size: 8px; }
  .fc span { font-size: 8.3px; line-height: 1.25; }
}

/* ── V26.71 — CRAFT POLISH (2026-08-12) — impeccable + ui-ux-pro-max audit ──
   The till already clears the density/touch/consistency bar (V26.65/69). This
   pass is the "precise details" layer an Operate-mode brand lives in, and the
   browser-surface theming the craft-floor names the cheapest signal that a UI
   was built rather than assembled. Polish only — no layout or size change. */
/* Text selection: brand gold, never the browser's default blue. Scoped to the
   till (till.css only loads here). */
::selection { background: rgba(201, 169, 97, .30); color: #fff; }
/* Every number in the till reads as tabular data — the tab facts line, the
   covers/server/per-cover meta, and the floor-summary sub-lines — so figures
   stay column-stable as values change (the numerals the craft-floor flags). */
.tc-facts, .tab-meta, .fc span { font-variant-numeric: tabular-nums; }

/* ═══ Per-DEVICE display scale (2026-08-19) ═══════════════════════════════
   Set by the Display size slider in the ⋮ options sheet; till.js applies it
   before first paint. It zooms the CONTENT surfaces only — the nav, the
   docks and the sheets keep their tuned sizes (the staff app's Display size
   makes the same trade, and it is what keeps --till-nav-h / --kb /
   --menu-dock-h out of the zoomed subtree, so no dock measurement needs
   compensating). zoom, never transform — trap #6. */
:root {
  --till-scale: 0.675;   /* rebased default: the new "100%" */
  /* Chrome floor: the top bar and nav scale UP with the slider but never
     below their 100% size — below that the nav icons get too small to hit. */
  /* V26.79 (Filip): pinned, matching the staff app's --chrome-scale — the
     two bottom navs must be the SAME physical size, independent of each
     app's display slider. Was max(var(--till-scale), 0.675). */
  --till-chrome: 0.675;
}
/* ⚠ ZOOM ISLANDS, not a root zoom — iOS mis-anchors position:fixed inside a
   zoomed root (measured on device 2026-08-19: the nav lifted off the bottom
   at 80%). Fixed CONTAINERS (.till-app, the menu/bill docks) stay unzoomed
   so their anchors hold; their in-flow children carry the zoom; env()/safe
   terms inside islands divide by the scale (the island and home bar are
   physical). The dock-height vars (--till-nav-h etc) are MEASURED rects —
   visual px — consumed by unzoomed fixed containers, so they need no
   compensation: a zoomed nav simply measures smaller. */
.till-top, .till-nav, .printer-strip { zoom: var(--till-chrome, 1); }
.tabs-grid, .item-grid, #basketBody, .floor-chips, .till-orders { zoom: var(--till-scale, 1); }
.till-screen-dock > *, #billDockHost .bill-dock > * { zoom: var(--till-scale, 1); }

/* The slider row in the options sheet — a static row, not a button. */
.menu-row-static { cursor: default; }
.menu-row-static:active { background: rgba(255,255,255,.04); }
.menu-scale-val {
  font-style: normal; font-size: 12px; margin-left: 7px;
  color: var(--gold, #C9A961); font-variant-numeric: tabular-nums;
}
#tillScale {
  width: 100%; margin-top: 10px; min-height: 46px;
  accent-color: var(--gold, #C9A961);
}

/* V26.94 — the Hold-mains messenger row in the stage sheet: amber, set apart
   from the stages above it (it prints a slip, it does not move the table). */
.stage-row.stage-hold {
  margin-top: 8px; border-top: 1px solid var(--border, #262a2a);
  color: var(--gold, #C9A961);
}

/* V26.96 — collection orders on the Tabs screen. */
.till-orders { display: grid; gap: 8px; margin-bottom: 10px; }
.till-orders[hidden] { display: none; }
.till-order {
  display: flex; align-items: center; gap: 12px; width: 100%;
  min-height: 56px; padding: 8px 14px; text-align: left;
  background: var(--surface, #141616); border: 1px solid var(--border, #262a2a);
  border-radius: var(--t1-r-row, 12px); /* V28.1 */
  color: var(--text, #e9e6e1); cursor: pointer;
}
.till-order.is-new { border-color: rgba(196, 58, 52, 0.55); }
.till-order .to-when { font: 700 15px/1 'Space Grotesk', sans-serif; color: var(--gold, #C9A961); min-width: 52px; }
.till-order .to-who { flex: 1; min-width: 0; font: 600 14px/1.2 Inter, sans-serif; }
.till-order .to-who em { display: block; font: 400 11.5px/1.3 Inter, sans-serif; font-style: normal; color: var(--text-muted, #8b9090); margin-top: 2px; }
.till-order .to-next { font: 600 12.5px/1 Inter, sans-serif; color: var(--brand-red, #d1495b); white-space: nowrap; }

/* ===== V26.98 — Manager's day · 86 board ================================ */
/* Day sheet: four KPI tiles, then labelled rows. Structure by weight and
   hairlines, never by filled colour — the V25 surface language. */
.day-loading, .day-none { color: var(--text-muted, #8b9090); font: 400 13.5px/1.4 Inter, sans-serif; padding: 10px 2px; }
.day-kpis { display: grid; grid-template-columns: repeat(2, 1fr); gap: 8px; margin-bottom: 4px; }
.day-kpi { background: var(--t1-surface, rgba(255,255,255,.038)); border: 1px solid var(--t1-hairline, rgba(255,255,255,.08));
  border-radius: var(--t1-r-row, 12px); padding: 10px 12px; display: flex; flex-direction: column; gap: 3px; }
.day-kpi b { font: 700 19px/1.1 'Space Grotesk', sans-serif; color: var(--gold, #C9A961); }
.day-kpi span { font: 500 11px/1.2 Inter, sans-serif; color: var(--text-muted, #8b9090); text-transform: uppercase; letter-spacing: .04em; }
.day-h { margin: 14px 0 4px; font: 700 11px/1.2 Inter, sans-serif; text-transform: uppercase; letter-spacing: .07em; color: var(--text-muted, #8b9090); }
.day-row { display: flex; align-items: center; justify-content: space-between; gap: 10px; padding: 8px 2px;
  border-bottom: 1px solid var(--t1-hairline, rgba(255,255,255,.08)); font: 400 13.5px/1.3 Inter, sans-serif; }
.day-row:last-child { border-bottom: 0; }
.day-row b { font: 600 13.5px/1.2 'Space Grotesk', sans-serif; }
.day-ex { display: flex; align-items: center; gap: 8px; padding: 7px 2px; font: 400 12.5px/1.3 Inter, sans-serif;
  border-bottom: 1px solid var(--t1-hairline, rgba(255,255,255,.08)); }
.day-ex:last-child { border-bottom: 0; }
.day-ex-t { color: var(--brand-red, #d1495b); font-weight: 600; text-transform: capitalize; white-space: nowrap; }
.day-ex-w { flex: 1; min-width: 0; color: var(--text-muted, #8b9090); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* 86 board: full-width rows, 44px floor, the unavailable state reads by
   colour AND label so it survives greyscale. */
.b86-search { padding: 0 16px 10px; }
.b86-search input { width: 100%; min-height: 44px; font-size: 16px; border-radius: 999px;
  background: var(--t1-surface, rgba(255,255,255,.038)); border: 1px solid var(--t1-hairline-str, rgba(255,255,255,.14));
  color: inherit; padding: 0 16px; outline: none; }
.b86-search input:focus { border-color: var(--gold, #C9A961); }
.b86-row { display: flex; align-items: center; justify-content: space-between; gap: 10px; width: 100%;
  min-height: 44px; padding: 8px 12px; margin-bottom: 6px; text-align: left;
  background: var(--t1-surface, rgba(255,255,255,.038)); border: 1px solid var(--t1-hairline, rgba(255,255,255,.08));
  border-radius: var(--t1-r-row, 12px); color: inherit; font: 500 14px/1.25 Inter, sans-serif; cursor: pointer; }
.b86-row .b86-state { font: 700 11.5px/1 Inter, sans-serif; letter-spacing: .04em; color: var(--success, #4caf7d);
  border: 1px solid currentColor; border-radius: 999px; padding: 5px 10px; white-space: nowrap; }
.b86-row.off { border-color: rgba(209, 73, 91, 0.5); }
.b86-row.off .b86-name { color: var(--text-muted, #8b9090); text-decoration: line-through; }
.b86-row.off .b86-state { color: var(--brand-red, #d1495b); }

/* V26.99 — table-level allergy. The one control whose state is a safety
   consequence: red, loud, and identical everywhere it appears. */
.sheet-tab-allergy { background: var(--brand-red, #d1495b); color: #fff; font: 700 12.5px/1.35 Inter, sans-serif;
  letter-spacing: .03em; border-radius: var(--t1-r-row, 12px); padding: 9px 12px; margin-bottom: 10px; }
.tc-allergy { color: var(--brand-red, #d1495b); font-weight: 700; }

/* V27.7 — design audit: the bill screen, bill dock and menu-basket dock were
   the only till surfaces with no width cap on ≥700px — a 25-line bill read as
   a 1024px-wide receipt with the price a screen away from the dish. The split
   screen already caps at 1040px; these centre at 760px. A fixed element with
   left:0/right:0 + max-width + margin-inline:auto centres without transform
   (trap #6 stays respected). */
@media (min-width: 700px) {
  #basketBody, .bill-dock { max-width: 760px; margin-inline: auto; width: 100%; }
  .till-menu-dock { max-width: 760px; margin-inline: auto; }
}

/* V28.0 — POINTER FEEDBACK (design review, till #3). till.css carried ~40
   :active rules and zero :hover: correct on the Carbon, but on the 1024px
   tablets in desktop mode and any desktop browser every control shows
   cursor:pointer with no response — dead next to the staff app. Gated
   pointer-only so touch devices never get sticky hover. Backgrounds mirror
   each control's :active state one step quieter; no transforms (hover must
   never move layout), and stateful variants (.current/.on/.off/.is-new) are
   excluded so hover can't outrank a state ring by source order. */
@media (hover: hover) and (pointer: fine) {
  .till-exit:hover, .till-operator:hover, .till-menu:hover,
  .tc-more:hover, .sheet-x:hover { background: rgba(255, 255, 255, .04); }
  .tabs-filter-btn:hover, .tabs-filter-pop button:hover,
  .mb-acts button:hover, #qtyVal:hover, .bd2-back:hover { background: var(--bg-hover, #1d2024); }
  .tab-card:not(.current):hover, .till-order:not(.is-new):hover,
  .b86-row:not(.off):hover, .cat-tile:hover, .split-row:hover,
  .menu-back:hover { border-color: var(--t1-hairline-str, rgba(255, 255, 255, .18)); }
  .item-btn:hover { border-color: rgba(201, 169, 97, .45); }
  .menu-row:hover { background: rgba(255, 255, 255, .06); }
  .menu-row-static:hover { background: rgba(255, 255, 255, .03); }
  .bl-actions button:hover, .qty-stepper button:hover,
  .bill-actions .btn:hover, .bill-act:hover { background: rgba(255, 255, 255, .06); }
  .stage-row:not(.on):hover, .opt:not(.on):hover, .split-chip:not(.on):hover,
  .alert-chip:not(.on):hover, .spice-btn:not(.on):hover,
  .pair-btn:hover { background: rgba(255, 255, 255, .05); }
  .bl-pct:hover { background: rgba(201, 169, 97, .12); }
  .lock-pad button:hover { background: rgba(255, 255, 255, .10); }
  .mb-go:hover, .tc-stage:hover, .till-nav-new:hover .tn-plus { filter: brightness(1.1); }
  .till-nav-btn:not(.active):hover { color: var(--text, #e9e6e1); }
}
