/* ==========================================================================
   POS terminal — the Business Central grammar on a handheld
   ==========================================================================
   A separate stylesheet, loaded only by the POS shell, because this surface
   has almost nothing in common with the desk portals. It runs full-screen on
   a handheld with a card reader and a printer, in daylight, on a forecourt,
   operated one-thumbed by someone with a queue behind them — and, since the
   Business Central port, on whatever else a site puts it on: a tablet in a
   cradle, a desk browser during training.

   The language, in one paragraph. Surfaces are flat — square panes with a
   hairline border, no radius, no drop shadow. The app bar is near-black and
   carries the product name and the site. Section headers are tinted in the
   accent and set in sentence case. Fields are square and focus with a 2px
   underline in the accent rather than a ring. Commands are flat until the
   one primary action, which keeps its fill. Status pills stay rounded, which
   is the one curve the reference keeps.

   What the forecourt still changes, concretely:

   * **No application chrome.** No rail, no search, no breadcrumbs. One task
     fills the screen and there is a single way back.
   * **Targets are large.** Keypad keys are 56px tall — past the 44px minimum
     — because the operator is standing, often in gloves.
   * **Type is bigger and heavier than the desk product** (bold pass,
     2026-09-16). The figure being confirmed is read aloud to a customer, so
     amounts are set large, heavy and tabular; titles, tile labels, option
     rows and keys all carry more weight than the desk, because the screen
     is read at arm's length in daylight, not studied.
   * **The shell fills the device.** No cap and no centred column: on a phone
     everything stacks, and past 760px the screen becomes two columns — what
     is being entered on the left, the keypad that enters it on the right.

   Colour comes from tokens.css. The `[data-portal="pos"]` block there carries
   the Business Central teal, so every `--brand-*` below resolves to it while
   the desk portals keep their green. `tests/test_handheld_skin.py` pins the
   grammar; `tests/test_layout.py` measures the layout.
   ========================================================================== */

/* The terminal fills the device; nothing behind it should scroll. */
.pos-body { margin: 0; background: var(--bg-app); }

.pos-shell {
  min-height: 100vh;
  min-height: 100dvh;
  display: flex;
  flex-direction: column;
  width: 100%;
  background: var(--bg-app);
}

/* ------------------------------------------------------------- app bar */

/* Near-black, the product name, the site. Business Central's bar carries the
   app launcher and the product; this one carries the way back and the way
   home, because a terminal has no launcher. */
.pos-bar,
.pos-signin-bar {
  display: flex;
  align-items: center;
  gap: var(--s-2);
  min-height: 48px;
  padding: 0 var(--s-2) 0 var(--s-3);
  padding-top: env(safe-area-inset-top);
  background: var(--chrome);
  color: var(--n-0);
  flex: none;
}
.pos-bar .spacer { flex: 1; }

.pos-brand {
  display: inline-flex;
  align-items: center;
  gap: var(--s-2);
  min-width: 0;
  color: var(--n-0);
  text-decoration: none;
}
.pos-brand-text { display: flex; flex-direction: column; min-width: 0; line-height: 1.25; }
.pos-brand-name { font-size: var(--fs-16); font-weight: var(--fw-bold); letter-spacing: -.01em; }
.pos-brand-site,
.pos-signin-id span {
  font-size: var(--fs-12);
  color: var(--chrome-text);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* The product mark: a square tile in the accent. Sized to the two lines of
   text beside it so the pair reads as one lockup. */
.pos-mark {
  display: grid;
  place-items: center;
  width: 36px;
  height: 36px;
  flex: none;
  background: var(--brand-400);
  color: var(--n-0);
  /* The pump, in the sprite's two tones; the fill reads harder on the
     bright tile than the .16 the glyphs default to. */
  --me-duo: .35;
}
.pos-signin-id { display: flex; flex-direction: column; min-width: 0; line-height: 1.3; }
.pos-signin-id strong { font-size: var(--fs-16); font-weight: var(--fw-bold); }
/* An uncommissioned terminal says so here rather than only failing later. */
.pos-signin-id .unbound { color: var(--chrome-accent); }

.pos-back,
.pos-home {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--s-1);
  min-height: 44px;
  min-width: 44px;
  padding: 0 var(--s-3);
  border: 0;
  background: transparent;
  color: var(--n-0);
  font-size: var(--fs-14);
  font-weight: var(--fw-semibold);
  text-decoration: none;
}
.pos-back:hover,
.pos-home:hover { background: var(--chrome-hover); text-decoration: none; }

/* The offline pill (B8): amber on the near-black bar, shown by the script
   while navigator.onLine is false. Square, like everything else here. */
.pos-offline {
  display: inline-flex;
  align-items: center;
  min-height: 28px;
  padding: 0 var(--s-2);
  margin-right: var(--s-2);
  background: var(--tone-amber-soft);
  color: var(--tone-amber-ink);
  font-size: var(--fs-12);
  font-weight: var(--fw-semibold);
  letter-spacing: .04em;
  text-transform: uppercase;
}

/* -------------------------------------------------------------- content */

.pos-main {
  flex: 1;
  padding: var(--s-4);
  padding-bottom: calc(var(--s-4) + env(safe-area-inset-bottom));
  display: flex;
  flex-direction: column;
  gap: var(--s-4);
  min-height: 0;
}

/* Large and light, the way the reference sets a document title, and the
   detail under it in the muted ink. */
/* Heavy, not merely large: the title is what an attendant reads to know
   which step they are on, from a metre away, with a queue. */
.pos-title {
  margin: 0;
  font-size: var(--fs-28);
  font-weight: var(--fw-heavy);
  letter-spacing: -.02em;
  line-height: 1.1;
}
.pos-sub { margin: var(--s-2) 0 0; font-size: var(--fs-14); font-weight: var(--fw-medium); color: var(--text-muted); }
.pos-sub.ref { font-family: var(--font-mono); letter-spacing: .04em; }

.pos-panel {
  background: var(--bg-surface);
  border: 1px solid var(--border);
  padding: var(--s-4);
}
.pos-panel.flush { padding: 0; overflow: hidden; }

/* ---------------------------------------------------------- attendant band */

/* Who is signed in. Filled in the accent, the way the reference's headline
   tiles are — one block of position under the dark bar, not another card. */
.pos-identity {
  background: var(--brand-700);
  color: var(--n-0);
  padding: var(--s-5) var(--s-4);
  border-bottom: 4px solid var(--brand-400);
}
.pos-identity h1 { margin: 0; font-size: var(--fs-22); font-weight: var(--fw-heavy); letter-spacing: -.02em; color: var(--n-0); }
.pos-identity .meta { display: block; margin-top: var(--s-1); font-size: var(--fs-13); font-weight: var(--fw-medium); color: var(--chrome-accent); }

/* ------------------------------------------------------------------ tiles */

/* Butted together with a hairline between them, so the menu reads as one
   grid rather than six cards competing for attention. The rule is an outline
   on each tile rather than a background showing through a gap: with the grid
   painted in the rule colour, every cell the tiles do not fill — one on a
   phone, three on a tablet — showed as a grey block. */
.pos-tiles {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 1px;
  padding: 1px;
}
.pos-tile {
  display: flex;
  flex-direction: column;
  gap: var(--s-2);
  padding: var(--s-4);
  min-height: 116px;
  outline: 1px solid var(--border);
  background: var(--bg-surface);
  color: var(--text);
  text-decoration: none;
  font-size: var(--fs-16);
  font-weight: var(--fw-bold);
  letter-spacing: -.01em;
}
.pos-tile:hover { background: var(--brand-50); text-decoration: none; }
.pos-tile .tile-ico {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 44px;
  height: 44px;
  background: var(--brand-50);
  color: var(--brand-700);
  /* The two-tone glyph fills a step harder in a tinted tile. */
  --me-duo: .28;
}
.pos-tile .tile-ico .ico { width: 24px; height: 24px; }
.pos-option .opt-ico { --me-duo: .24; }

/* A tone per task (2026-09-16), the driver app's six, so "Fleet card" and
   "Fuel voucher" are told apart by colour from across a counter and not only
   by reading. Square, like everything else here. */
.tile-ico.is-teal,   .opt-ico.is-teal   { background: var(--tone-teal-soft);   color: var(--tone-teal-ink); }
.tile-ico.is-amber,  .opt-ico.is-amber  { background: var(--tone-amber-soft);  color: var(--tone-amber-ink); }
.tile-ico.is-plum,   .opt-ico.is-plum   { background: var(--tone-plum-soft);   color: var(--tone-plum-ink); }
.tile-ico.is-indigo, .opt-ico.is-indigo { background: var(--tone-indigo-soft); color: var(--tone-indigo-ink); }
.tile-ico.is-lime,   .opt-ico.is-lime   { background: var(--tone-lime-soft);   color: var(--tone-lime-ink); }
.tile-ico.is-coral,  .opt-ico.is-coral  { background: var(--tone-coral-soft);  color: var(--tone-coral-ink); }
.pos-tile.wide { grid-column: 1 / -1; min-height: 0; flex-direction: row; align-items: center; }

/* --------------------------------------------------------------- options */

/* A list, ruled between rows, hovering in the accent tint. */
.pos-options { display: flex; flex-direction: column; }
.pos-option {
  display: flex;
  align-items: center;
  gap: var(--s-3);
  padding: var(--s-3) var(--s-4);
  min-height: 64px;
  border: 0;
  border-bottom: 1px solid var(--border);
  background: var(--bg-surface);
  color: var(--text);
  text-decoration: none;
  font-size: var(--fs-16);
  font-weight: var(--fw-semibold);
  text-align: left;
  width: 100%;
}
.pos-option:last-child { border-bottom: 0; }
.pos-option:hover { background: var(--brand-50); text-decoration: none; }

/* A choice list (B1's void reasons and supervisors): the same rows, each a
   label around a real radio — square, 22px, the accent when chosen — so it
   works with a thumb and without script. */
.pos-choices .pos-option { cursor: pointer; }
.pos-choices input[type="radio"] {
  flex: none;
  width: 22px;
  height: 22px;
  margin: 0;
  accent-color: var(--brand-600);
}
.pos-choices .pos-option:has(input:checked) { background: var(--brand-50); }
.pos-choices .pos-option:has(input:focus-visible) { box-shadow: inset var(--focus-ring); }
.pos-option .opt-ico {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 40px; height: 40px;
  flex: none;
  background: var(--brand-50);
  color: var(--brand-700);
}
.pos-option .opt-ico .ico { width: 20px; height: 20px; }
.pos-option .opt-body { flex: 1; min-width: 0; }
.pos-option .opt-meta { display: block; font-size: var(--fs-13); font-weight: var(--fw-regular); color: var(--text-subtle); }
.pos-option .opt-value { font-variant-numeric: tabular-nums; font-weight: var(--fw-heavy); font-size: var(--fs-18); }

/* An option the terminal cannot honour yet is stated, never offered. A dead
   control on a forecourt reads as a broken system. */
.pos-option[aria-disabled="true"] {
  opacity: .55;
  pointer-events: none;
}

/* --------------------------------------------------------------- display */

/* The figure the operator reads aloud. It is the field: square, and the 2px
   underline is the Fluent focus treatment, drawn on the pane because the
   input inside it is borderless. Tabular so digits do not shift while it is
   being typed. */
.pos-display {
  padding: var(--s-5) var(--s-4);
  text-align: center;
  background: var(--bg-surface);
  border: 1px solid var(--border);
  border-bottom: 2px solid var(--border-strong);
}
.pos-display:focus-within { border-bottom-color: var(--brand-600); }
/* The issuer prefix sits in front of the digits the attendant keys: the same
   line, the same size, quieter — it is context, not something they typed.
   Flex rather than a pseudo-element so the pair centres as one string. */
.pos-display.has-prefix {
  display: flex;
  align-items: baseline;
  justify-content: center;
  gap: var(--s-1);
}
.pos-display-prefix {
  font-size: var(--fs-40);
  font-weight: var(--fw-heavy);
  letter-spacing: -.03em;
  color: var(--text-muted);
  flex: none;
}
.pos-display.has-prefix .pos-display-input { width: auto; min-width: 0; flex: 1; text-align: left; }
/* The pane shows the focus; the browser's rounded ring inside a square
   pane read as a second, softer field. */
[data-portal="pos"] .pos-display input:focus,
[data-portal="pos"] .pos-display input:focus-visible { outline: none; box-shadow: none; border: 0; }
.pos-display .value,
.pos-display input {
  font-size: var(--fs-40);
  font-weight: var(--fw-heavy);
  font-variant-numeric: tabular-nums;
  letter-spacing: -.03em;
}
/* The input inside the display: borderless, centred, the display's own
   type. Was an inline style on seven inputs, which beat the sheet and kept
   the figure at the desk's size. A card number is a step smaller than an
   amount (sixteen digits have to fit a phone); a code is mono and upper. */
/* `input[type="text"]` in app.css outranks a lone class, which is why the
   inline style existed; the portal scope restores the order. */
[data-portal="pos"] input.pos-display-input {
  width: 100%;
  height: auto;
  padding: 0;
  border: 0;
  background: transparent;
  text-align: center;
}
.pos-display-input.is-number { font-size: var(--fs-28); font-weight: var(--fw-bold); letter-spacing: .04em; }
.pos-display-input.is-code {
  font-family: var(--font-mono);
  font-size: var(--fs-22);
  font-weight: var(--fw-bold);
  letter-spacing: .06em;
  text-transform: uppercase;
}
/* The running total of a sum being keyed (2026-09-29). Between the figure
   and its limit, and bigger than the limit: it is the number the attendant
   reads out, and the figure above it is only how they got there. */
.pos-sum {
  display: block;
  margin-top: var(--s-2);
  font-size: var(--fs-22);
  font-weight: var(--fw-bold);
  font-variant-numeric: tabular-nums;
  color: var(--brand-700);
}

.pos-display .limit { display: block; margin-top: var(--s-2); font-size: var(--fs-13); font-weight: var(--fw-medium); color: var(--text-subtle); }

/* ----------------------------------------------------------------- keypad */

.pos-keypad {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 1px;
  background: var(--border);
  border: 1px solid var(--border);
}
.pos-key {
  min-height: 64px;
  border: 0;
  background: var(--bg-surface);
  color: var(--text);
  font-size: var(--fs-22);
  font-weight: var(--fw-semibold);
  font-variant-numeric: tabular-nums;
  cursor: pointer;
}
.pos-key:hover { background: var(--brand-50); }
.pos-key:active { background: var(--brand-100); }
/* The one primary action keeps its fill, the same accent as every other
   primary action on the platform. */
.pos-key.action {
  background: var(--brand-600);
  color: var(--n-0);
  font-size: var(--fs-16);
  font-weight: var(--fw-bold);
}
.pos-key.action:hover { background: var(--brand-700); }
.pos-key[disabled] { opacity: .4; cursor: not-allowed; }

/* A figure somebody adds up gets a fourth column of signs; a PIN, a card
   number and a pump do not, and there the same twelve keys sit in three
   columns exactly as before. The sheet's pad is switched by the script as
   the focus moves between the two kinds of field. */
.pos-keypad.is-math,
.pos-sheet-pad.is-math { grid-template-columns: repeat(4, minmax(0, 1fr)); }
.pos-keypad:not(.is-math) .pos-key.is-sign,
.pos-sheet-pad:not(.is-math) .pos-key.is-sign { display: none; }
/* Tinted, so the column reads as a different kind of key from arm's length
   without becoming a second primary action. */
.pos-key.is-sign {
  background: var(--brand-50);
  color: var(--brand-700);
  font-size: var(--fs-22);
  font-weight: var(--fw-bold);
}
.pos-key.is-sign:hover { background: var(--brand-100); }
/* The pad's one action, under the keys rather than in the corner of them:
   with a sign column there is no corner cell left, and a full-width target
   is the easier tap anyway. */
.pos-key.is-wide { grid-column: 1 / -1; }


/* ------------------------------------------------------ reading a photo */
/* Reading a plate or an odometer off the camera (2026-09-28). The overlay
   covers the shell rather than sitting in the flow: the attendant is
   pointing a phone at a vehicle and everything else is in the way. Flat and
   square like the rest of the terminal. */
.pos-scan {
  margin-left: auto;
  display: inline-flex;
  align-items: center;
  gap: var(--s-1);
  padding: var(--s-1) var(--s-2);
  border: 1px solid var(--border);
  background: var(--bg-surface);
  color: var(--text);
  font-size: var(--fs-13);
  font-weight: var(--fw-medium);
}
.pos-scan:focus-visible { outline: none; box-shadow: inset var(--focus-ring); }
/* Reading the odometer and the plate from a photo (2026-10-07): two pickers
   drawn as the scan buttons are, each with a line beneath that says what
   was read. The picker is a label round a hidden file input, so the phone's
   camera opens on a tap; the picture is read on the device and discarded. */
.pos-photos { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--s-2) var(--s-3); }
.pos-photos .pos-field-head { grid-column: 1 / -1; }
.pos-photos-label { font-weight: var(--fw-medium); }
.pos-photo-slot { display: flex; flex-direction: column; gap: var(--s-1); min-width: 0; }
.pos-photo { display: flex; width: 100%; min-height: 44px; justify-content: center; }
.pos-photo:focus-within { box-shadow: inset var(--focus-ring); }
.pos-photo-status { min-height: 1.2em; font-size: var(--fs-12); color: var(--n-600); }
.pos-photo-status.is-ok { color: var(--pos-700); }
.pos-photo-status.is-bad { color: var(--neg-700); }

.pos-cam {
  position: fixed;
  inset: 0;
  z-index: 40;
  display: flex;
  flex-direction: column;
  gap: var(--s-2);
  padding: var(--s-3);
  padding-top: calc(env(safe-area-inset-top) + var(--s-3));
  background: var(--bg-surface);
}
.pos-cam[hidden] { display: none; }
.pos-cam-bar { display: flex; align-items: center; gap: var(--s-2); }
.pos-cam-title { flex: 1; font-size: var(--fs-18); font-weight: var(--fw-heavy); }
.pos-cam-view {
  position: relative;
  flex: 1;
  min-height: 0;
  background: var(--bg-sunken);
  border: 1px solid var(--border);
}
.pos-cam-video { width: 100%; height: 100%; object-fit: cover; display: block; }
/* The box the engine actually reads. Everything outside it is ignored: a
   forecourt is full of text and the engine has no reason to prefer the plate
   over the price board behind it. */
.pos-cam-guide {
  position: absolute;
  left: 8%;
  right: 8%;
  top: 50%;
  height: 22%;
  transform: translateY(-50%);
  border: 2px solid var(--brand-600);
}
.pos-cam-status { margin: 0; font-size: var(--fs-14); color: var(--text-muted); min-height: 1.4em; }
.pos-cam-reading {
  margin: 0;
  font-size: var(--fs-32);
  font-weight: var(--fw-heavy);
  font-variant-numeric: tabular-nums;
  letter-spacing: -.02em;
  min-height: 1.1em;
}
.pos-cam-actions { display: grid; gap: var(--s-2); }
/* One action at a time: capture while looking, then use it or take it again. */
.pos-cam[data-state="looking"] [data-cam-use],
.pos-cam[data-state="looking"] [data-cam-retake],
.pos-cam[data-state="reading"] [data-cam-capture],
.pos-cam[data-state="reading"] [data-cam-use],
.pos-cam[data-state="reading"] [data-cam-retake],
.pos-cam[data-state="read"] [data-cam-capture] { display: none; }
.pos-cam[data-state="read"] .pos-cam-guide { border-color: var(--border-strong); }

/* ------------------------------------------------------------- the sheet */

/* The keypad for the digit fields a screen draws no pad for — the pump, the
   odometer, a member's litres. One sheet in the shell, fixed where the
   device's keyboard would be (the script holds that down), revealed while
   such a field has focus. Flat like the pads above: a hairline grid on the
   rule colour, the accent as a rule along its top edge rather than a shadow.
   The main column takes the sheet's measured height as padding while it is
   up (the script sets `--pos-sheet-h`), so the button at the foot of a form
   can be scrolled clear of it. */
.pos-sheet {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 40;
  background: var(--bg-surface);
  border-top: 2px solid var(--brand-600);
  padding: 0 0 env(safe-area-inset-bottom);
}
.pos-sheet-bar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-3);
  min-height: 44px;
  padding: 0 var(--s-2) 0 var(--s-4);
}
.pos-sheet-for {
  font-size: var(--fs-13);
  color: var(--text-muted);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
/* The running total, on the sheet's own bar: a field served by the sheet
   sits in a panel with no display pane to draw it under. */
.pos-sheet-sum {
  margin-left: auto;
  padding-right: var(--s-3);
  font-size: var(--fs-14);
  font-weight: var(--fw-bold);
  font-variant-numeric: tabular-nums;
  color: var(--brand-700);
  white-space: nowrap;
}

.pos-sheet-done {
  min-height: 44px;
  padding: 0 var(--s-4);
  border: 0;
  background: none;
  color: var(--brand-700);
  font: inherit;
  font-size: var(--fs-14);
  font-weight: var(--fw-semibold);
  cursor: pointer;
}
/* Hairlines as an outline on each key rather than the rule colour showing
   through a gap (the tiles' trick): the withheld decimal cell then stays
   the surface colour instead of painting as a grey block. */
.pos-sheet-pad {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 1px;
  padding: 1px 0 0;
  background: var(--bg-surface);
  border-top: 1px solid var(--border);
}
.pos-sheet-pad .pos-key { outline: 1px solid var(--border); }
/* The decimal point for a whole number: withheld, but its cell kept, so the
   zero stays under the eight where a thumb expects it. */
.pos-key[data-void] { visibility: hidden; pointer-events: none; }
.pos-shell.is-keypad .pos-main {
  padding-bottom: calc(var(--pos-sheet-h, 300px) + var(--s-6));
}

/* -------------------------------------------------------------- PIN dots */

/* A PIN is never a password field: the browser would offer to save it on a
   shared handheld, and ignores autocomplete="off" on type="password". The
   field is numeric text, masked here; the keypad writes into it as before.
   (Firefox lacks -webkit-text-security; the terminal's PIN inputs sit
   off-screen behind the dots, so nothing is revealed there.) */
.pos-secret { -webkit-text-security: disc; }

.pin-dots {
  display: flex;
  justify-content: center;
  gap: var(--s-3);
  padding: var(--s-5) 0;
}
.pin-dot {
  width: 16px;
  height: 16px;
  border-radius: 50%;
  border: 2px solid var(--border-strong);
}
.pin-dot.filled { background: var(--brand-600); border-color: var(--brand-600); }

/* ------------------------------------------------------------- summary kv */

/* A field reads as a ledger line: what it is on the left, what it holds on
   the right, a dotted rule carrying the eye across the gap. This is what
   makes a Business Central card page recognisable at a glance. */
.pos-kv { margin: 0; }
.pos-kv > div {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: var(--s-3);
  padding: var(--s-2) 0;
  border-bottom: 1px dotted var(--border-strong);
  font-size: var(--fs-13);
}
.pos-kv > div:last-child { border-bottom: 0; }
.pos-kv dt { color: var(--text-muted); font-size: var(--fs-12); }
.pos-kv dd { margin: 0; text-align: right; font-variant-numeric: tabular-nums; font-weight: var(--fw-semibold); font-size: var(--fs-14); }
.pos-kv .strong dd { font-weight: var(--fw-heavy); font-size: var(--fs-22); }

/* ------------------------------------------------------------- verdict */

/* POS-009 — read at arm's length, from a forecourt, in daylight. */
.pos-verdict { text-align: center; padding: var(--s-6) var(--s-4); }
.pos-verdict .mark {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 64px; height: 64px;
  border-radius: 50%;
  margin-bottom: var(--s-3);
}
.pos-verdict.ok .mark { background: var(--pos-50); color: var(--pos-700); }
.pos-verdict.no .mark { background: var(--neg-50); color: var(--neg-700); }
/* The void band (B1) sits above the slip and prints with it. */
.pos-void-band h2 { letter-spacing: .08em; color: var(--neg-700); }
.pos-verdict h2 { margin: 0 0 var(--s-2); font-size: var(--fs-22); font-weight: var(--fw-heavy); letter-spacing: -.02em; }
.pos-verdict .headline {
  font-size: var(--fs-32);
  font-weight: var(--fw-heavy);
  font-variant-numeric: tabular-nums;
  letter-spacing: -.03em;
}
.pos-verdict .detail { font-size: var(--fs-13); color: var(--text-muted); }

/* ------------------------------------------------------------- receipt */

/* Deliberately monospace and narrow: this is what the thermal printer emits,
   so what is on screen should be what comes out of the slot. */
.pos-receipt {
  font-family: var(--font-mono);
  font-size: var(--fs-12);
  line-height: var(--lh-normal);
  background: var(--bg-surface);
  border: 1px solid var(--border);
  padding: var(--s-4);
}
/* No `white-space: pre-wrap` here. The slip is built from flex rows, not from
   preformatted text, so preserving whitespace only rendered the template's own
   newlines as blank lines and doubled the height of every row. */
.pos-receipt .head { text-align: center; margin-bottom: var(--s-3); }
.pos-receipt .row {
  display: flex;
  justify-content: space-between;
  gap: var(--s-3);
  padding: 1px 0;
}
.pos-receipt .row span:last-child { text-align: right; }
.pos-receipt .rule { border-top: 1px dashed var(--border-strong); margin: var(--s-3) 0; }
.pos-receipt .foot { text-align: center; margin-top: var(--s-3); color: var(--text-muted); }

/* -------------------------------------------------------------- actions */

.pos-actions {
  display: flex;
  flex-direction: column;
  gap: var(--s-2);
  margin-top: auto;
  padding-top: var(--s-4);
}
.pos-actions .btn { width: 100%; height: 56px; font-size: var(--fs-16); font-weight: var(--fw-bold); }
/* The terminal's primary: the accent fill, the one filled control on the
   screen. It was near-black, which read as "cancel" beside the accent keys. */
.btn-terminal {
  background: var(--brand-600);
  border-color: var(--brand-600);
  color: var(--n-0);
}
.btn-terminal:hover { background: var(--brand-700); border-color: var(--brand-700); }

/* Square corners on every inherited control, so what app.css provides
   belongs to the same language as what this sheet draws. */
[data-portal="pos"] .btn,
[data-portal="pos"] .alert,
[data-portal="pos"] .field input,
[data-portal="pos"] .field select,
[data-portal="pos"] .field textarea { border-radius: 0; }
[data-portal="pos"] .btn-secondary { background: var(--bg-surface); border-color: var(--border-strong); border-width: 2px; }
[data-portal="pos"] .btn-secondary:hover { background: var(--brand-50); border-color: var(--border-strong); color: var(--brand-700); }
[data-portal="pos"] .btn-subtle:hover { background: var(--brand-50); color: var(--brand-700); }

/* The Fluent field: square, a stronger rule, and focus is an underline. */
[data-portal="pos"] .field label {
  font-size: var(--fs-12);
  font-weight: var(--fw-regular);
  color: var(--text-muted);
}
[data-portal="pos"] .field input {
  height: 44px;
  border-color: var(--border-strong);
  background: var(--bg-surface);
}
[data-portal="pos"] .field input:focus {
  outline: none;
  border-color: var(--brand-600);
  box-shadow: inset 0 -2px 0 var(--brand-600);
}

/* Prefilled capture (2026-09-20): a label that is not a <label> (the
   plate is a choice), a hint under a field, the plate chips, and the
   "required" mark the card's control puts on the odometer. */
[data-portal="pos"] .field .field-label {
  display: block; margin-bottom: var(--s-1);
  font-size: var(--fs-12); font-weight: var(--fw-regular); color: var(--text-muted);
}
.pos-hint { margin: var(--s-1) 0 0; font-size: var(--fs-12); color: var(--text-muted); }
.pos-hint.is-warn { color: var(--tone-amber-ink); background: var(--tone-amber-soft); padding: var(--s-1) var(--s-2); }
.pos-field-head { display: flex; align-items: baseline; gap: var(--s-2); }
.pos-required { font-size: var(--fs-12); font-weight: var(--fw-semibold); color: var(--tone-amber-ink); }
.pos-chips { display: flex; flex-wrap: wrap; gap: var(--s-2); }
.pos-chip { position: relative; display: inline-flex; }
.pos-chip input { position: absolute; opacity: 0; width: 1px; height: 1px; }
.pos-chip span {
  display: inline-flex; align-items: center; min-height: 44px; padding: 0 var(--s-4);
  border: 2px solid var(--border-strong); background: var(--bg-surface);
  font-size: var(--fs-16); font-weight: var(--fw-semibold); font-variant-numeric: tabular-nums;
}
.pos-chip input:checked + span { border-color: var(--brand-600); background: var(--brand-50); color: var(--brand-700); }
.pos-chip input:focus-visible + span { box-shadow: inset var(--focus-ring); }

/* Quick amounts (2026-09-20): a row of thumb-height keys under the figure,
   and the other unit read back in the display's own margin. */
.pos-quick { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 1px; margin-top: var(--s-2); background: var(--border); border: 1px solid var(--border); }
.pos-quick-key {
  min-height: 48px; padding: 0 var(--s-2); border: 0; background: var(--bg-surface); color: var(--text);
  font-size: var(--fs-14); font-weight: var(--fw-semibold); font-variant-numeric: tabular-nums; cursor: pointer;
  touch-action: manipulation; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.pos-quick-key:hover { background: var(--brand-50); color: var(--brand-700); }
.pos-quick-key:focus-visible { box-shadow: inset var(--focus-ring); outline: 0; }
.pos-convert { display: block; margin-top: var(--s-1); font-size: var(--fs-13); color: var(--text-muted); font-variant-numeric: tabular-nums; }

/* Served today (item 9, 2026-09-20): a short row list above the tiles. */
.pos-served-head { margin: var(--s-3) 0 var(--s-1); font-weight: var(--fw-semibold); color: var(--text); }
.pos-served { margin-bottom: var(--s-4); border: 1px solid var(--border); background: var(--bg-surface); }

/* --------------------------------------------------------------- filters */

.pos-filters { display: flex; gap: var(--s-2); flex-wrap: wrap; }
/* Sized to the thumb, not to the label. These read as small chips but they
   are the only control on the screen an operator taps repeatedly while
   holding the handheld in one hand, and at 28px they were missed — which on a
   period filter means a customer being told the wrong week. Pills, because
   they are a set of alternatives rather than commands; the reference keeps
   its pills round. */
.pos-filter {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  padding: 0 var(--s-4);
  border: 2px solid var(--border-strong);
  border-radius: var(--r-pill);
  background: var(--bg-surface);
  color: var(--text);
  font-size: var(--fs-14);
  font-weight: var(--fw-semibold);
  text-decoration: none;
}
.pos-filter:hover { border-color: var(--brand-600); color: var(--brand-700); text-decoration: none; }
.pos-filter.is-active {
  background: var(--brand-600);
  border-color: var(--brand-600);
  color: var(--n-0);
}

/* A standing caveat under a list, not an alert — the scoping is normal and
   permanent, and styling it as a warning would train operators to ignore it. */
.pos-note {
  display: flex;
  align-items: center;
  gap: var(--s-2);
  margin: 0;
  color: var(--text-subtle);
  font-size: var(--fs-12);
  line-height: 1.4;
}
.pos-note .ico { flex: none; }

/* ----------------------------------------------------------------- list */

.pos-list { display: flex; flex-direction: column; }
.pos-item {
  padding: var(--s-3) var(--s-4);
  border-bottom: 1px solid var(--border);
  font-size: var(--fs-13);
}
.pos-item:last-child { border-bottom: 0; }
.pos-item .line { display: flex; justify-content: space-between; gap: var(--s-3); }
.pos-item .qty { font-weight: var(--fw-semibold); font-variant-numeric: tabular-nums; }
.pos-item .meta { color: var(--text-subtle); font-size: var(--fs-12); }
/* A category heading in the goods list (2026-10-21): the shelf's label,
   not a row that can be keyed. */
.pos-group-head { padding: var(--s-2) var(--s-4); background: var(--brand-50); }
.pos-group-head .meta { font-weight: var(--fw-semibold); text-transform: uppercase; letter-spacing: .04em; }
/* A row that opens its slip: the same row, tappable, the reprint one tap
   away rather than behind a typed reference. */
a.pos-item-link { display: block; color: inherit; text-decoration: none; }
a.pos-item-link:hover { background: var(--brand-50); text-decoration: none; }
a.pos-item-link .line:first-child .qty:last-child::after { content: " ›"; color: var(--text-subtle); }

/* ------------------------------------------------------------ sign-in */

/* One block, centred in whatever height the device gives us. Anchoring the
   keypad to the bottom instead put ~600px of white between the heading and
   the field on a tall handheld, which reads as a page that failed to render
   rather than as breathing room. The keypad still lands in the lower half of
   a phone, which is what thumb reach actually needs.

   Capped, and that is not the letterbox the shell gave up: a sign-in form is
   a form, and one that spans a 1440px screen is worse than one that sits in
   the middle of it. */
.pos-signin {
  max-width: 440px;
  margin: 0 auto;
  width: 100%;
  flex: 1;
  display: flex;
  align-items: center;
}
.pos-signin .pos-panel { width: 100%; }
.pos-signin .site {
  display: flex;
  justify-content: space-between;
  gap: var(--s-3);
  padding: var(--s-3);
  background: var(--bg-sunken);
  border: 1px solid var(--border);
  font-size: var(--fs-12);
}
.pos-signin .site strong { display: block; font-size: var(--fs-14); }

/* ---------------------------------------------------------- install invite */

/* Server-rendered but `hidden`, and only ever revealed by script that has
   established the browser can install, the terminal is not already installed,
   and nobody has just said no. Offered on the set-up and sign-in screens only
   — never over the keypad, where anything that is not the sale is an
   interruption. A flat pane with the accent rule on its leading edge, which is
   how the reference marks a notification. */
.pos-install {
  display: flex;
  flex-direction: column;
  gap: var(--s-3);
  margin: 0 0 var(--s-4);
  padding: var(--s-4);
  background: var(--bg-surface);
  border: 1px solid var(--border);
  border-left: 3px solid var(--brand-600);
}

/* `[hidden]` is display:none in the *user-agent* stylesheet, so any author
   rule setting display beats it — a component styled `display:flex` stays
   visible while its markup swears it is hidden. That is how iOS was once shown
   a Chromium-only Add button. Two classes of specificity, no !important. */
.pos-body .pos-install[hidden],
.pos-body .pos-install [hidden],
.pos-body .pos-offline[hidden] { display: none; }

.pos-install-lead { display: flex; gap: var(--s-3); align-items: flex-start; }
.pos-install-lead .ico { flex: none; color: var(--brand-700); margin-top: 2px; }
.pos-install-lead b { display: block; font-size: var(--fs-14); font-weight: var(--fw-semibold); }
.pos-install-lead p { margin: 2px 0 0; font-size: var(--fs-13); color: var(--text-muted); }
.pos-install-actions { display: flex; gap: var(--s-2); }
.pos-install-actions .btn { flex: 1 1 0; min-width: 0; height: 44px; }
.pos-install-steps {
  margin: 0;
  padding-left: 20px;
  display: grid;
  gap: 6px;
  font-size: var(--fs-13);
  color: var(--text-muted);
}
.pos-install-steps b { color: var(--text); font-weight: var(--fw-semibold); }
.pos-install-block { width: 100%; height: 44px; margin-top: var(--s-3); }

/* ------------------------------------------------------------- scanner */

/* A fixed-ratio window rather than whatever the camera reports, so the frame
   does not jump as the stream starts and the operator has a stable target. */
/* The sale's code: the QR at a size a phone reads from across a pump, the
   reference in words beneath it for a camera that will not. Flat and square
   like the rest of the terminal. */
.pos-sale { text-align: center; }
.pos-qr { display: flex; justify-content: center; padding: var(--s-3) 0; }
.pos-qr svg { width: min(60vw, 260px); height: auto; }
.pos-sale-ref { font-size: var(--fs-16); margin: var(--s-2) 0; letter-spacing: .04em; }
.pos-sale-ref strong { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: var(--fs-22); letter-spacing: .12em; }

.scanner {
  position: relative;
  aspect-ratio: 3 / 4;
  background: var(--n-900);
  overflow: hidden;
}
.scanner-view {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}
/* The aiming rectangle. Purely a hint — detection runs on the whole frame, so
   a voucher slightly outside it still scans. */
.scanner-frame {
  position: absolute;
  inset: 18% 12%;
  border: 2px solid var(--n-0);
  opacity: .85;
  pointer-events: none;
}
.scanner-status {
  position: absolute;
  left: 0; right: 0; bottom: 0;
  margin: 0;
  padding: var(--s-3);
  background: rgba(22, 27, 34, .72);
  color: var(--n-0);
  font-size: var(--fs-13);
  text-align: center;
}

/* ------------------------------------------------------------------- tap */

.tap-target {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--s-4);
  padding: var(--s-8) var(--s-4);
  background: var(--bg-surface);
  border: 1px solid var(--border);
  text-align: center;
}
.tap-ring {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 96px;
  height: 96px;
  border-radius: 50%;
  background: var(--brand-50);
  color: var(--brand-700);
  box-shadow: 0 0 0 0 rgba(0, 183, 195, .35);
  animation: tap-pulse 1.8s ease-out infinite;
}
/* The pulse says the terminal is listening. Someone holding a card against a
   dead-looking panel gives up after about two seconds. */
@keyframes tap-pulse {
  70% { box-shadow: 0 0 0 22px rgba(0, 183, 195, 0); }
  100% { box-shadow: 0 0 0 0 rgba(0, 183, 195, 0); }
}
@media (prefers-reduced-motion: reduce) {
  .tap-ring { animation: none; }
}
.tap-target[data-tap-state="found"] .tap-ring {
  background: var(--pos-50);
  color: var(--pos-700);
  animation: none;
}
.tap-target[data-tap-state="unavailable"] .tap-ring {
  background: var(--n-100);
  color: var(--n-500);
  animation: none;
}
.tap-status { margin: 0; font-size: var(--fs-14); color: var(--text-muted); }
/* On the find screen (2026-09-20) the ring sits above the number field,
   a row rather than a panel: the reader and the keypad are one way in. */
.tap-target-inline { flex-direction: row; gap: var(--s-3); padding: var(--s-3) var(--s-4); margin-bottom: var(--s-3); }
.tap-target-inline .tap-ring { width: 44px; height: 44px; }
.pos-countdown { font-variant-numeric: tabular-nums; opacity: .8; }

/* Off-screen, not hidden: a display:none input cannot hold focus, and the
   wedge reader types into whatever has it. */
.tap-input {
  position: absolute;
  left: -10000px;
  width: 1px;
  height: 1px;
  opacity: 0;
}

/* ================================================================ wide ===

   Past a phone the screen becomes two columns. What is being entered — the
   display, a summary, a list — takes the left; the keypad that enters it and
   the commands that finish it take the right, capped at the width a keypad is
   useful at. Everything without a keypad beside it spans both columns, so a
   list on a tablet is a full-width list rather than a phone column with the
   rest of the glass empty.

   Forms become `display: contents` so their children join the grid; the form
   still submits, it just stops being a box. Placement is `dense` so a keypad
   marked for column 2 backfills the row its display sits on rather than
   waiting for the next one. */
@media (min-width: 760px) {
  .pos-main {
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(320px, 520px);
    grid-auto-flow: row dense;
    align-content: start;
    align-items: start;
    gap: var(--s-4) var(--s-6);
    padding: var(--s-6);
  }
  .pos-main > form { display: contents; }
  .pos-main > *,
  .pos-main > form > * { grid-column: 1 / -1; }

  /* Where there is a keypad, what it types into stays on the left. */
  .pos-main:has(.pos-keypad) > :is(.pos-display, .pos-panel:not(.pos-input), .tap-target),
  .pos-main:has(.pos-keypad) > form > :is(.pos-display, .pos-panel:not(.pos-input), .tap-target) {
    grid-column: 1;
  }
  .pos-main > .pos-keypad,
  .pos-main > form > .pos-keypad,
  .pos-main > .pos-input,
  .pos-main > form > .pos-input,
  .pos-main:has(.pos-keypad) > .pos-actions,
  .pos-main:has(.pos-keypad) > form > .pos-actions {
    grid-column: 2;
  }
  .pos-keypad.mt-4 { margin-top: 0; }
  .pos-key { min-height: 72px; }

  /* The display fills the row its keypad sets the height of, with the figure
     centred in it, rather than sitting as a short strip beside a tall pad. */
  .pos-main:has(.pos-keypad) > .pos-display,
  .pos-main:has(.pos-keypad) > form > .pos-display {
    align-self: stretch;
    display: flex;
    flex-direction: column;
    justify-content: center;
  }

  /* Commands read as a row of flat commands, the primary filled, rather than
     as a column of screen-wide buttons. */
  .pos-actions {
    flex-direction: row;
    flex-wrap: wrap;
    margin-top: 0;
    padding-top: 0;
  }
  .pos-actions .btn { width: auto; min-width: 220px; }
  .pos-main:has(.pos-keypad) > .pos-actions,
  .pos-main:has(.pos-keypad) > form > .pos-actions { flex-direction: column; }
  .pos-main:has(.pos-keypad) > .pos-actions .btn,
  .pos-main:has(.pos-keypad) > form > .pos-actions .btn { width: 100%; }

  .pos-tiles { grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); }
  .pos-tile.wide { grid-column: 1 / -1; }

  /* The sheet keeps to the keypad's column rather than spanning a desk. */
  .pos-sheet {
    left: auto;
    right: var(--s-6);
    width: min(520px, calc(100% - 2 * var(--s-6)));
    border-left: 1px solid var(--border);
    border-right: 1px solid var(--border);
  }
  .pos-display .value,
  .pos-display input { font-size: var(--fs-48); }
  .pos-title { font-size: var(--fs-32); }
}

/* ================================================================ paper ===

   The receipt on the roll (2026-09-17). Only the slip prints: the bar, the
   verdict, the points form, the buttons and the keypad sheet are for the
   screen. 80mm thermal paper with a 4mm margin leaves 72mm of print; the
   slip is set in the same monospace at a size a thermal head renders
   cleanly, black on white whatever the screen's theme. */
@media print {
  @page { size: 80mm auto; margin: 4mm; }
  .pos-bar,
  .pos-signin-bar,
  .pos-verdict,
  .pos-panel:has(.pos-verdict),
  .pos-actions,
  .pos-sheet,
  .pos-keypad,
  .pos-input,
  .alert,
  form { display: none !important; }
  .pos-body, .pos-shell, .pos-main { background: var(--paper); min-height: 0; padding: 0; margin: 0; display: block; }
  .pos-receipt {
    width: 72mm;
    margin: 0;
    padding: 0;
    border: 0;
    font-size: 9.5pt;
    color: var(--paper-ink);
    background: var(--paper);
  }
  .pos-receipt .rule { border-top: 1px dashed var(--paper-ink); }
  .pos-receipt .foot { color: var(--paper-ink); }
  .print-only { display: block; }
}
/* The terminal's name goes on the paper, not the screen. */
.print-only { display: none; }

/* The cardholder app used to share this sheet. It has its own now
   (`me.css`): the terminal is operated by an attendant serving a queue and
   the app is read by its owner, and the shared rules kept pulling the app
   back towards the terminal's answers. Nothing `me-*` belongs here. */

/* ------------------------------------------------------------ dry goods */

/* FSS POS-01 (2026-09-19). A quantity between two thumb-sized buttons;
   the number is a real input so the keypad sheet can set five at once. */
.pos-basket-line .line { align-items: center; }
.pos-stepper { display: inline-flex; align-items: center; gap: var(--s-1); flex: none; }
.pos-stepper input {
  width: 3.2rem; height: 44px; text-align: center; font-size: var(--fs-16);
  font-weight: var(--fw-semibold); font-variant-numeric: tabular-nums;
  border: 1px solid var(--border); background: var(--bg-surface);
}
.pos-step {
  width: 44px; height: 44px; border: 1px solid var(--border);
  background: var(--bg-surface); color: var(--text); font-size: var(--fs-18); line-height: 1;
  cursor: pointer; touch-action: manipulation;
}
.pos-step:hover { background: var(--brand-50); }
.pos-step:focus-visible { box-shadow: inset var(--focus-ring); outline: 0; }
