:root {
  color-scheme: light;
  font-family: Geist, Inter, system-ui, sans-serif;
  color: #111827;
  background: #fffaf5;
  --orange: #ea580c;
  /*
   * The same accent, darkened until small text passes WCAG AA.
   *
   * `--orange` is the brand colour and stays exactly as specified, but measured
   * against this product's three real backgrounds — warm white `#fffaf5`, a 70%
   * white panel over it, and the 22% peach of a ready row — it lands at 3.43,
   * 3.52, and 3.15 to 1. That clears AA for large type only, and every text use
   * of the accent in this stylesheet is 11–18px: eyebrows, steps, links,
   * countdowns, badges, the Recipient code. So the accent splits by *size*
   * rather than by surface. `--orange` keeps large display type, borders, and
   * fills, where 3:1 is the applicable threshold; `--orange-text` takes anything
   * set small, at 4.99, 5.12, and 4.59 to 1.
   *
   * `#c2410c` is orange-700 from the same ramp and already in this file as a
   * border colour, so this darkens the accent rather than introducing a hue.
   */
  --orange-text: #c2410c;
  --peach: #fdba74;
  --rose: #e11d48;
  --surface: #191c21;
  --muted: #4b5563;
  --border: #e5e7eb;

  /*
   * The V2 presentation foundation (#90).
   *
   * These extend the palette above; they do not replace it. `--orange`,
   * `--orange-text`, `--peach`, `--rose`, `--surface`, `--muted`, `--border`
   * and the warm white `#fffaf5` remain the Auralis/Neuform language, and the
   * size split explained above still decides which of the two oranges a rule
   * may use — a V2 route setting the accent at 11-18px still takes
   * `--orange-text`.
   *
   * What is new here is only what every redesigned route needs to state the
   * same way: the minimum target, the control radius, the panel fill and its
   * border, the page gutter, the focus ring, and the refusal colour.
   */
  --v2-target: 44px;
  --v2-radius: 8px;
  --v2-panel: rgb(255 255 255 / 0.8);
  --v2-panel-border: rgb(229 231 235 / 0.9);
  --v2-gutter: clamp(20px, 6vw, 32px);
  /*
   * A focus indicator has to clear 3:1 against what it sits on (WCAG 2.2 AA,
   * 1.4.11). The 30%-alpha orange used by the older `:focus-visible` rule in
   * this file blends to about 1.5:1 over the warm white, so V2 focus uses the
   * solid darkened accent instead: `#c2410c` measures 5.06:1 over `#fffaf5`.
   * It is the same hue, so nothing about the language changes.
   */
  --v2-focus-ring: 3px solid var(--orange-text);
  --v2-focus-offset: 3px;
  /*
   * The refusal colour already used by `.error-text` and `.status.error`,
   * named rather than repeated. 7.8:1 over the warm white.
   */
  --v2-danger-text: #9f1239;
}

* {
  box-sizing: border-box;
}

body {
  min-height: 100vh;
  margin: 0;
  background:
    linear-gradient(100deg, rgb(255 255 255 / 0.96) 0 52%, transparent 78%),
    #fffaf5;
}

.atmosphere {
  position: fixed;
  z-index: -1;
  inset: -15% -18% auto 45%;
  height: 70vh;
  border-radius: 45% 55% 62% 38%;
  background:
    radial-gradient(circle at 62% 25%, #fef3c7 0 8%, transparent 30%),
    radial-gradient(circle at 70% 65%, #fda4af 0 12%, transparent 40%),
    radial-gradient(circle at 30% 60%, var(--peach) 0 18%, transparent 48%),
    linear-gradient(145deg, #fb923c, #fecdd3 58%, #fff7ed);
  filter: blur(18px);
  animation: breathe 12s ease-in-out infinite alternate;
}

header,
main {
  width: min(100% - 48px, 1280px);
  margin-inline: auto;
}

header {
  display: flex;
  /*
   * Deliberately does not wrap. Letting it wrap was tried as a reflow fix and
   * was both wrong and harmful: the reflow failure was a heading that could not
   * break, not a header that could not wrap, and wrapping put the Guest brand
   * on top of the session actions it is supposed to sit beside — which
   * browser/guest-workspace.spec.js catches by comparing their edges at 200%
   * zoom.
   */
  align-items: center;
  justify-content: space-between;
  padding-block: 28px;
}

/*
 * The brand is a real link home, so it is sized like a real target rather than
 * left as inline text that only clears 44px when it happens to wrap.
 */
.brand {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  color: inherit;
  font-size: 18px;
  letter-spacing: -0.04em;
  text-decoration: none;
  text-transform: uppercase;
}

.brand span {
  color: var(--orange-text);
}

/*
 * Visually hidden, still spoken (#163).
 *
 * One spelling of "hidden from sight, kept in the accessibility tree". The
 * recipe lives here once; each site keeps its own comment saying why that
 * text is stated for a reader and set elsewhere for a viewer. `clip` is
 * deprecated, so the clip is `clip-path: inset(50%)` everywhere.
 *
 * Sites adopt this by taking the `visually-hidden` class in markup when their
 * element belongs to one route. The selectors below cannot take the class
 * without touching markup on every route (shared live region, JS-built chips),
 * so they join the recipe here instead; each carries its reason beside it.
 */
.visually-hidden,
/* Desk/Operations: the shared live region is announced per route state, and
   Closed Event history scopes it to assistive-only copy via a class the
   workspace JS toggles — both live in markup every role shares. */
#closed-event-history ~ #status.history-announcement,
/* Operations routine announcements: the live region markup is shared by every
   route, so no route-markup class can express this state. */
.operations-page #status.status.visible:not(.error),
.operations-page:has(
    #operations-route-state[data-route-state="no-open-event"]:not([hidden])
  )
  #status.status,
.operations-page:has(#operations-session-ended:not([hidden])) #status.status,
.operations-page:has(.operations-offline) .connectivity-status,
/* All Stalls: the counts and separators are built by all-stalls.js, so the
   class would have to be added in script rather than route markup. */
.all-stalls-page .coverage-counts,
.all-stalls-page .chip-separator,
.all-stalls-page[data-stalls-selected] .monitoring-summary {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* The history-announcement site sits on the shared live region, whose base
   presentation animates transform and visibility; these resets hold the
   recipe steady there. They are scoped here so future adopters of the
   utility above do not inherit live-region coupling they do not need. */
#closed-event-history ~ #status.history-announcement {
  visibility: visible;
  transform: none;
  transition: none;
}

.role,
.eyebrow,
.step,
.event-meta {
  font-family: "JetBrains Mono", ui-monospace, monospace;
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
}

.role,
.hint,
.help {
  color: var(--muted);
}

.header-nav {
  display: flex;
  align-items: center;
  gap: 18px;
}

.settings-link {
  color: var(--surface);
  font-size: 14px;
  font-weight: 700;
  text-underline-offset: 4px;
}

main {
  padding-block: 70px 100px;
}

.intro {
  max-width: 720px;
}

.eyebrow {
  color: var(--orange-text);
}

h1 {
  margin: 16px 0;
  font-size: clamp(48px, 7vw, 88px);
  font-weight: 300;
  line-height: 0.98;
  letter-spacing: -0.065em;
}

h1 em {
  color: transparent;
  font-style: normal;
  background: linear-gradient(90deg, var(--orange), var(--rose));
  background-clip: text;
}

.intro > p:last-child {
  max-width: 590px;
  color: var(--muted);
  font-size: 18px;
  line-height: 1.65;
}

.workspace {
  display: grid;
  grid-template-columns: 1.15fr 0.85fr;
  gap: 20px;
  margin-top: 64px;
}

.panel {
  position: relative;
  padding: clamp(26px, 4vw, 48px);
  border: 1px solid rgb(229 231 235 / 0.9);
  background: rgb(255 255 255 / 0.8);
  box-shadow: 0 28px 70px -55px rgb(25 28 33 / 0.6);
  backdrop-filter: blur(20px);
}

.step {
  position: absolute;
  top: 20px;
  right: 24px;
  color: var(--orange-text);
}

h2 {
  margin: 0;
  font-size: clamp(24px, 3vw, 36px);
  font-weight: 400;
  letter-spacing: -0.04em;
}

.hint {
  margin: 10px 0 30px;
  max-width: 570px;
  line-height: 1.55;
}

form,
label {
  display: grid;
  gap: 8px;
}

form {
  gap: 20px;
}

label,
legend {
  font-size: 14px;
  font-weight: 600;
}

input,
select,
textarea {
  width: 100%;
  min-height: 46px;
  padding: 11px 13px;
  color: var(--surface);
  border: 1px solid #d1d5db;
  border-radius: 8px;
  background: rgb(255 255 255 / 0.88);
  font: inherit;
}

textarea {
  resize: vertical;
  font-family: "JetBrains Mono", ui-monospace, monospace;
  font-size: 12px;
  line-height: 1.5;
}

input:focus-visible,
select:focus-visible,
textarea:focus-visible,
button:focus-visible,
a:focus-visible {
  outline: 3px solid rgb(234 88 12 / 0.3);
  outline-offset: 2px;
}

fieldset {
  display: flex;
  gap: 20px;
  margin: 0;
  padding: 0;
  border: 0;
}

legend {
  margin-bottom: 10px;
}

.choice {
  display: flex;
  align-items: center;
  gap: 7px;
  font-weight: 400;
}

.choice input {
  width: 18px;
  min-height: 18px;
}

.help {
  margin: -12px 0 0;
  font-size: 12px;
  line-height: 1.5;
}

.form-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 18px;
}

button {
  min-height: 48px;
  padding: 0 24px;
  border: 0;
  font: inherit;
  cursor: pointer;
}

.primary {
  color: white;
  background: var(--surface);
}

.primary:hover {
  background: #000;
}

.clipped,
.event-state {
  clip-path: polygon(
    9px 0,
    100% 0,
    100% calc(100% - 9px),
    calc(100% - 9px) 100%,
    0 100%,
    0 9px
  );
}

.text-button {
  min-height: 40px;
  padding: 0;
  color: var(--orange-text);
  background: transparent;
}

.events {
  display: grid;
  grid-template-columns: minmax(190px, 0.35fr) 1fr;
  gap: 40px;
  margin-top: 64px;
  padding-top: 38px;
  border-top: 1px solid var(--border);
}

/*
 * Reflow, at the one width where a heading cannot give way.
 *
 * These display headings hold a floor — All Stalls keeps 40px at any width —
 * and a heading like "Runner <em>coverage</em>" is a single unbreakable word.
 * At 200% page zoom in a 390px viewport the effective viewport is 195 CSS px
 * and that word measures wider than it on a CI runner, so the whole page
 * scrolled sideways. A mid-word break at extreme zoom is ugly; requiring
 * horizontal scrolling to read a line of text is a WCAG 1.4.10 failure. The
 * break wins. At every ordinary width there is nothing to break and this rule
 * changes nothing.
 */
h1,
h2,
h3 {
  overflow-wrap: break-word;
}

[hidden] {
  display: none !important;
}

.service-setup {
  margin-top: 64px;
  padding-top: 38px;
  border-top: 1px solid var(--border);
}

.service-grid {
  display: grid;
  grid-template-columns: 0.75fr 1.25fr;
  gap: 20px;
  margin-top: 28px;
}

h3 {
  margin: 0;
  font-size: 20px;
  font-weight: 500;
  letter-spacing: -0.03em;
}

#board-list,
.menu-list,
.ingredient-list {
  display: grid;
  gap: 14px;
}

.board-row,
.menu-row,
.ingredient-row {
  display: grid;
  min-width: 0;
  min-inline-size: 0;
  gap: 14px;
  padding: 18px;
  border: 1px solid var(--border);
}

.board-row legend {
  padding-inline: 5px;
  color: var(--orange-text);
  font-family: "JetBrains Mono", ui-monospace, monospace;
  font-size: 11px;
  text-transform: uppercase;
}

.stall-choices {
  display: flex;
  flex-wrap: wrap;
  gap: 10px 18px;
}

.board-link {
  color: var(--orange-text);
  font-size: 14px;
  text-decoration: underline;
  text-underline-offset: 3px;
}

.staff-sign-in,
.staff-workspace {
  max-width: 1040px;
  margin-top: 48px;
}

.staff-workspace {
  display: grid;
  width: 100%;
  min-width: 0;
  gap: 24px;
}

.staff-workspace > *,
.active-stall {
  min-width: 0;
}

.active-stall {
  display: grid;
  grid-template-columns: 1fr auto;
  align-items: end;
  gap: 8px 20px;
  padding: 0 0 24px 20px;
  border-left: 6px solid var(--stall-color, var(--orange));
  border-bottom: 1px solid var(--border);
}

.queue-counts {
  display: flex;
  grid-column: 1 / -1;
  flex-wrap: wrap;
  gap: 8px 18px;
  color: var(--muted);
  font-family: "JetBrains Mono", ui-monospace, monospace;
  font-size: 12px;
  font-weight: 600;
}

.active-stall .eyebrow {
  grid-column: 1 / -1;
  margin: 0;
}

.capacity-controls {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 10px;
}

.capacity-controls legend {
  width: 100%;
  color: var(--muted);
}

/*
 * WCAG 2.2 AA target size (#39). This was 42px, and the role-scoped Capacity
 * State panel carried a 44px override to clear the gate on its own screens
 * while the shared rule stayed non-conforming. Raising the shared rule fixes
 * every surface that uses it and makes the override redundant.
 */
.capacity-controls button {
  min-height: 44px;
  border: 1px solid var(--border);
  border-radius: 8px;
  background: rgb(255 255 255 / 0.8);
}

.capacity-controls button[aria-pressed="true"] {
  color: white;
  border-color: var(--surface);
  background: var(--surface);
}

.fulfillment-queues {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 20px;
}

.queue-panel {
  min-width: 0;
  padding: clamp(22px, 3vw, 34px);
  border: 1px solid var(--border);
  background: rgb(255 255 255 / 0.78);
  backdrop-filter: blur(20px);
}

.queue-panel .eyebrow {
  margin-top: 0;
}

.queue-list {
  display: grid;
  gap: 12px;
  margin-top: 20px;
  color: var(--muted);
}

.queue-card {
  padding: 18px;
  color: var(--surface);
  border: 1px solid var(--border);
  border-left: 5px solid var(--orange);
  background: white;
}

.queue-card.new-arrival {
  border-color: var(--orange);
  background: #fff7ed;
  animation: queue-arrival 700ms ease-out;
}

.queue-card h4,
.queue-card p {
  margin: 0;
}

.queue-card h4 {
  font-size: 18px;
}

.queue-card ul {
  margin-bottom: 0;
  padding-left: 20px;
}

.queue-actions,
.line-cancellation {
  display: grid;
  gap: 10px;
  margin-top: 14px;
}

.line-cancellation {
  padding: 12px;
  border-left: 3px solid var(--border);
}

.queue-actions button,
.line-cancellation button {
  justify-self: start;
}

.ready-pending,
.handoff-pending,
.ready-badge {
  color: var(--orange-text);
  font-weight: 700;
}

.handoff-form {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  gap: 10px;
}

.active-stall h2 {
  font-size: clamp(36px, 6vw, 64px);
}

.stall-switcher {
  max-width: 420px;
}

.runner-alerts {
  padding: 24px;
  color: white;
  background: var(--surface);
}

#runner-alert-list {
  display: grid;
  gap: 12px;
}

.runner-alert {
  display: grid;
  grid-template-columns: 1fr auto;
  gap: 10px 18px;
  padding: 16px;
  border: 1px solid rgb(255 255 255 / 0.2);
}

.runner-alert h3,
.runner-alert p {
  margin: 0;
}

.runner-alert input {
  grid-column: 1 / 2;
}

.menu-row {
  grid-template-columns: 1.2fr 0.7fr;
}

.menu-row legend,
.ingredient-row legend,
.readiness-status .warning {
  color: var(--orange-text);
}

.menu-row .choice,
.menu-row .text-button {
  align-self: end;
}

.recipe-fields,
.stock-actions {
  display: grid;
  min-width: 0;
  gap: 12px;
}

.recipe-fields > *,
.stock-actions > * {
  min-width: 0;
}

.recipe-fields,
.stock-actions,
.stock-counts,
.ingredient-row > .hint {
  grid-column: 1 / -1;
}

.stock-actions {
  grid-template-columns: repeat(2, minmax(0, 1fr));
  padding-top: 14px;
  border-top: 1px solid var(--border);
}

.stock-alert {
  grid-column: 1 / -1;
  margin: 0;
  color: var(--rose);
  font-weight: 700;
}

.stock-counts {
  margin: 0;
  font-family: "JetBrains Mono", ui-monospace, monospace;
  font-weight: 700;
}

.enable-orders {
  color: var(--surface);
  border: 1px solid var(--orange);
  background: #fff7ed;
}

.readiness-status:not(:empty) {
  padding: 18px 20px;
  border-left: 4px solid var(--orange);
  background: rgb(255 247 237 / 0.8);
}

.readiness-status p {
  margin: 0;
}

.readiness-status ul {
  margin-bottom: 0;
}

.board-page {
  height: 100vh;
  overflow: hidden;
  color: #332317;
  background:
    radial-gradient(circle at 82% 8%, rgb(255 186 124 / 0.22), transparent 30%),
    #fff8ed;
}

/*
 * The one surface nobody holds: a TV on a wall, in landscape, read from twenty
 * feet by someone who will not walk closer. So it fills the screen rather than
 * centring a card in it — F2's real wall-mounted board carries roughly eight rows
 * per column edge to edge, where a centred column of tiles wastes the two thirds
 * of a 1920×1080 display that carries no information.
 */
.board-main {
  display: grid;
  /* One row per child — header, columns, info bar — with the slack given to the
     columns, so the orders begin under the title and the info bar sits on the
     floor. Naming fewer rows than there are children puts the `1fr` on the wrong
     element and the whole board drifts downward. */
  grid-template-rows: auto 1fr auto;
  box-sizing: border-box;
  width: 100%;
  height: 100vh;
  min-height: 0;
  overflow: hidden;
  padding: 30px clamp(34px, 4vw, 76px) 24px;
}

/* The board's name identifies the screen; it is not the thing being read. F2 gives
   its heading one line and spends everything else on orders, so all three
   identifying values share that line and the stall list absorbs the slack. */
.board-head {
  display: flex;

  /* The global `header` rule centres a 1280px column and pads it — right for a
     phone workspace inside a page, wrong here, where this is a band across a wall
     display and the board's own gutter is the only margin there should be. */
  width: 100%;
  margin-inline: 0;
  gap: clamp(16px, 2vw, 32px);
  align-items: end;
  justify-content: space-between;
  padding-block: 0;
  border-bottom: 2px solid #ef7926;
}

.board-main h1 {
  margin: 0 0 12px;
  color: #e96118;
  font-size: clamp(46px, 4.3vw, 76px);
  line-height: 0.95;
  letter-spacing: -0.04em;
}

/* Dark surface, so the accent inverts rather than darkens — same reason as the
   Guest's balance and Basket panels. At twenty feet (rule 24) a 3.30-to-1 label
   is the first thing to disappear. */
.board-head .eyebrow {
  margin: 0 0 8px;
  color: #765845;
  font-size: clamp(14px, 1.1vw, 20px);
  letter-spacing: 0.12em;
}

.board-stalls {
  overflow: hidden;
  margin: 0;
  color: #4f392c;
  font-family: "JetBrains Mono", ui-monospace, monospace;
  /* Truncated rather than wrapped: a board carrying eight stalls must not buy a
     second header line at the cost of a row of orders. */
  text-overflow: ellipsis;
  text-transform: uppercase;
  white-space: nowrap;
}

/*
 * Order Ready is wider than In Progress, and deliberately. A Guest crossing a
 * hall is looking for one question — is mine ready — and F2's board answers it
 * with the Ready column given the dominant share of the screen. Rule 24 forbids
 * leaning on colour to carry readiness, so the distinction is *size and position*
 * first: readable as a shape before any hue resolves, and still readable to
 * someone who cannot distinguish the two greens at all.
 */
.board-columns {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1.6fr);
  gap: clamp(32px, 4vw, 74px);
  align-content: start;
  min-height: 0;
  margin-top: clamp(24px, 3vh, 42px);
}

.board-columns > section {
  display: grid;
  grid-template-rows: auto 1fr auto;
  min-height: 0;
}

/*
 * The rotation marker: only present while Rule 26's twelve-row cap has forced a
 * second page, and set at the Ready heading's scale rather than the footer's —
 * at twenty feet the footer line it supplements was never read. It sits at the
 * foot of the column it describes, so "there is another page" is answered where
 * the Guest is already looking for their number.
 */
.board-pages {
  display: flex;
  gap: clamp(12px, 1.4vw, 26px);
  align-items: baseline;
  margin: clamp(12px, 1.8vh, 24px) 0 0;
  color: #d95012;
}

.board-pages-count {
  font-size: clamp(26px, 2.5vw, 46px);
  font-variant-numeric: tabular-nums;
  letter-spacing: -0.02em;
  white-space: nowrap;
}

.board-pages-note {
  color: #765845;
  font-size: max(18px, min(clamp(20px, 1.6vw, 30px), 3vh));
  white-space: nowrap;
}

/* Keep the manual page control at the marker's wall-readable scale. */
.board-next-page {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  margin-top: clamp(8px, 1vh, 16px);
  padding: clamp(8px, 1vw, 14px) clamp(16px, 2vw, 28px);
  font-size: clamp(18px, 1.8vw, 28px);
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  color: #fff8ed;
  background: #d95012;
  border: 2px solid #d95012;
  border-radius: 6px;
  cursor: pointer;
  white-space: nowrap;
}

.board-next-page:focus-visible {
  outline: 3px solid #d95012;
  outline-offset: 3px;
  background: #fff8ed;
  color: #d95012;
}

.board-next-page:hover {
  background: #e96118;
  border-color: #e96118;
}

.board-next-page:active {
  background: #b84210;
  border-color: #b84210;
}

.board-next-page[hidden] {
  display: none;
}

.board-columns > section > h2 {
  margin: 0 0 18px;
  color: #554238;
  font-size: clamp(24px, 2.3vw, 38px);
  letter-spacing: -0.025em;
}

/* The Ready heading earns the larger type for the same reason its column is
   wider: it is the answer the Guest walked over to read. */
.board-columns > section:last-child > h2 {
  color: #d95012;
  font-size: clamp(34px, 3.2vw, 54px);
}

/*
 * Rows share out the height the column actually has, rather than each taking its
 * natural height and the last few falling off the bottom edge. A twelfth row that
 * renders below the screen is invisible and unannounced — worse than pagination,
 * because the Guest has no way to know to wait for a rotation. `--rows` is set by
 * `board.js` from the rendered count, so the height is divided by what is really
 * there and a board with three orders does not stretch them to fill a TV.
 */
.board-orders {
  /* Replaced by `board.js` from the column's measured height. A guessed constant
     for the surrounding chrome was wrong by enough to clip the last row. */
  --row-height: 64px;

  /* How many stall groups stand side by side, set by `board.js` from the group
     count. Rule 26 pins the page at 12, and 12 rows in a single stack on a 1080p
     screen resolve to ~32px numerals — legible at arm's length, not from twenty
     feet, which is this surface's entire purpose. The rule wins over the reference,
     so the 12 gain width rather than losing members: two stalls beside each other
     are six rows apiece and roughly double the type. */
  --sub-columns: 1;

  /* Derived from *this* column's height, not the page's. `board.js` measures a
     column by setting `--row-height` on it and reading where the last row lands, so
     anything sized off a page-level variable does not move while the column is being
     measured — the number is the tallest thing in a row, and a row whose tallest
     element ignores the property makes the measurement meaningless. That is not a
     tidiness point: it is what made the fit return type at its floor. */
  /* A full page never reaches this ceiling — `--row-height * 0.62` is what binds at
     twelve rows — so it bounds only the sparse case. */
  --ready-number: max(
    28px,
    min(clamp(36px, 7vw, 132px), var(--row-height) * 0.62)
  );

  display: grid;
  grid-template-columns: repeat(var(--sub-columns), minmax(0, 1fr));
  align-content: start;
  align-items: start;
  /* Both bounds track the row, so enlarged type does not keep a dense board's gaps. */
  /* The 10px floor is the fit last resort lever, not a rule: rule 26 pins the
     page at 12 rows and rule 27 pins the text at 28px, so when a taller info bar
     leaves 12 legible rows without room, the only thing left that no rule
     protects is the space between them. board.js lowers --gap-floor rather than
     let a row render under the info bar. */
  gap: min(max(var(--gap-floor, 10px), calc(var(--row-height) * 0.16)), 64px);
  min-height: 0;

  /* A third of the height the fitted rows did not need, set by `board.js` after the
     fit. Measured slack, so it cannot push a row off-screen; on a full page it
     resolves under two pixels. */
  padding-top: var(--column-slack, 0px);
}

.board-orders section {
  display: grid;
  /* The query container for the row text below: a name and a heading are sized
     against the sub-column they occupy, not the width of the wall. */
  container-type: inline-size;
  align-content: start;
  gap: min(
    max(calc(var(--gap-floor, 10px) * 0.8), calc(var(--row-height) * 0.07)),
    40px
  );
  padding-left: clamp(10px, 1vw, 18px);
  border-left: 3px solid #dda981;
  min-width: 0;
}

/*
 * The 28px floor is rule 27 at twenty feet: on a full page the heading is the only
 * thing separating two stalls both showing order number 1, so it may never be the
 * text that shrinks. A dense row's `0.16` is well under 28, so `max()` holds it there.
 */
.board-orders h3 {
  margin: 0;
  color: #765845;
  font-family: "JetBrains Mono", ui-monospace, monospace;
  font-size: max(
    28px,
    min(clamp(28px, 9cqw, 56px), calc(var(--row-height) * 0.16))
  );
  letter-spacing: 0.08em;
  /* Wraps rather than running out over the next sub-column. Not truncated: rule 27
     wants the heading readable, and "CHAAT COR…" beside "CHAAT COU…" is not. */
  overflow-wrap: break-word;
  text-transform: uppercase;
}

/*
 * A row, not a tile. F2's board puts the name and the number on one line and
 * stacks eight of them per column, because a Guest scans a *list* — one saccade
 * down a left edge — where a grid of large tiles has to be read cell by cell.
 * Eight rows also mean the common case never paginates, and rule 26's rotation
 * stays the exception rather than the way the board normally behaves.
 */
.board-orders span {
  display: flex;
  gap: clamp(12px, 1.5vw, 28px);
  align-items: baseline;
  justify-content: space-between;
  padding: min(clamp(8px, 1.2vh, 16px), calc(var(--row-height) * 0.14))
    clamp(14px, 1.4vw, 24px);
  border: 2px solid #c8b6a8;
  border-radius: 4px;
  background: rgb(255 255 255 / 0.62);
  /* A flex item's `min-width` is `auto`, so without this a row grows to fit its whole
     name and overhangs the next sub-column instead of the name yielding. */
  min-width: 0;
}

.board-columns > section:first-child .board-orders span {
  flex-direction: column;
  gap: 2px;
  align-items: flex-start;
  justify-content: center;
}

.board-columns > section:first-child .board-orders small {
  max-width: 100%;
  font-size: max(15px, min(6cqw, 24px));
  text-align: left;
}

/*
 * Every size is bounded by the height its own row was given, so a full page
 * compresses instead of spilling past the bottom edge. The floor keeps a
 * compressed row legible: if 12 rows cannot be read at distance the answer is a
 * smaller page, not type nobody can see.
 *
 * In Progress is additionally capped *below* Order Ready's number, via a ceiling
 * `board.js` publishes on the page once the ready column has been fitted. A full
 * ready column beside a near-empty In Progress column would otherwise compress the
 * ready numbers under the in-progress ones and invert the one hierarchy this surface
 * has — the frame caught exactly that.
 */
.board-orders b {
  font-size: min(
    max(24px, min(clamp(28px, 4.6vw, 88px), var(--row-height) * 0.62)),
    var(--number-ceiling, 132px)
  );
  font-variant-numeric: tabular-nums;
  font-weight: inherit;
  line-height: 1.05;
  /* Never the thing that yields: order 12 clipped to "1" is a different order. */
  flex-shrink: 0;
}

/* Order Ready's numbers are larger, so the answer is legible from further back
   than the question. Size, not colour — rule 24. */
.board-columns > section:last-child .board-orders b {
  font-size: var(--ready-number);
}

.board-columns > section:last-child .board-orders span {
  position: relative;
  overflow: hidden;
  border: 0;
  border-radius: 0;
  color: #fffaf3;
  background: #be420e;
  clip-path: polygon(0 0, calc(100% - 18px) 0, 100% 18px, 100% 100%, 0 100%);
  box-shadow: inset 0 -3px 0 rgb(112 46 13 / 0.18);
}

/* Names stay subordinate to the number a volunteer calls across the hall, but at
   half the row height they remain readable from the wall-board viewing distance.
   `0.5` binds on a full page; sparse boards may grow to the reference's generous
   name scale rather than looking like metadata inside an otherwise empty row. */
.board-orders small {
  overflow: hidden;
  color: #765845;
  /* `cqw`, not `vw`: sized off the viewport, a name truncated to "Mi…" in the narrow
     In Progress column while the wide Ready column had room to spare. */
  font-size: max(18px, min(clamp(22px, 10cqw, 64px), var(--row-height) * 0.5));
  line-height: 1.2;
  text-align: right;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.board-orders[style*="--sub-columns: 3"] small {
  font-size: max(
    18px,
    min(clamp(20px, 7cqw, 42px), calc(var(--row-height) * 0.42))
  );
}

.board-columns > section:last-child .board-orders small {
  color: #fff4e7;
}

/*
 * The wait chip: how long this order has been Ready. It sits between the number
 * and the name, pushed against the name by `margin-inline: auto`, so the row's
 * one-line scan — number, then the far edge — passes through it without adding
 * a second line or a tile. Every size is bounded by `--row-height` like its
 * neighbours, so a full page compresses the chip with the row instead of
 * spilling.
 */
.board-columns > section:last-child .board-orders time.board-wait {
  margin-inline: auto clamp(10px, 1vw, 20px);
  font-family: "JetBrains Mono", ui-monospace, monospace;
  font-size: max(
    16px,
    min(clamp(20px, 4.5cqw, 40px), var(--row-height) * 0.34)
  );
  font-variant-numeric: tabular-nums;
  line-height: 1;
  white-space: nowrap;
}

/* Fresh (under five minutes): present but quiet — the number is still the news. */
.board-columns > section:last-child .board-orders .wait-fresh {
  color: rgb(255 244 231 / 0.78);
}

/* Aging (five to nine minutes): a filled warm chip — dark numerals on the
   amber ground, heavy weight. Fresh is bare text and stale is inverted to the
   lightest ground on the row, so all three tiers separate by fill as well as
   hue: weight and fill carry it, not colour alone (rule 24), and at 1080p the
   filled block reads heavier than fresh from across a hall. */
.board-columns > section:last-child .board-orders .wait-aging {
  padding: max(4px, calc(var(--row-height) * 0.06)) clamp(8px, 0.8vw, 14px);
  border-radius: 3px;
  color: #5a2c0e;
  background: #f7bd7f;
  font-weight: 700;
}

/* Stale (ten minutes and up): inverted chip — light ground, dark numerals,
   heavy weight. Weight and inversion carry it, not hue alone (rule 24), and an
   inverted block is the highest-contrast thing on the row at twenty feet. */
.board-columns > section:last-child .board-orders .wait-stale {
  padding: max(4px, calc(var(--row-height) * 0.06)) clamp(8px, 0.8vw, 14px);
  border-radius: 3px;
  color: #702e0d;
  background: #fffaf3;
  font-weight: 700;
}

.board-empty {
  margin: clamp(26px, 6vh, 72px) 0 0;
  color: #765845;
  font-size: clamp(24px, 2.1vw, 36px);
  line-height: 1.25;
}

/* Toast's information bar: status on the left, freshness on the right, one
   baseline, separated from the columns by a rule so it reads as chrome rather
   than as another order. */
.board-info {
  display: flex;
  flex-wrap: wrap;
  gap: 8px 24px;
  align-items: baseline;
  justify-content: space-between;
  /* The separation is mostly padding, so most of the bar's footprint falls inside
     its own `offsetHeight`, which is what the row budget in `board.js` measures.
     The remaining margin is read explicitly there. */
  padding-top: 16px;
  margin-top: 18px;
  border-top: 1px solid #d9c8b9;
}

.board-assignment {
  display: flex;
  gap: 10px 16px;
  align-items: baseline;
  margin: 0;
  color: #765845;
  font-size: clamp(16px, 1.15vw, 22px);
}

.board-assignment strong {
  overflow: hidden;
  max-width: 56vw;
  color: #332317;
  font-family: "JetBrains Mono", ui-monospace, monospace;
  text-overflow: ellipsis;
  text-transform: uppercase;
  white-space: nowrap;
}

.board-assignment #board-name:not(:empty)::before {
  content: "· ";
}

.board-status {
  margin: 0;
  color: #765845;
  font-size: clamp(16px, 1.3vw, 24px);
}

.board-freshness {
  margin: 0 0 14px;
  padding: 8px 14px;
  color: #7c3a13;
  border: 1px solid #efaa76;
  border-radius: 999px;
  background: #fff0df;
  font-family: "JetBrains Mono", ui-monospace, monospace;
  font-size: clamp(16px, 1.3vw, 24px);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

.board-freshness:empty {
  display: none;
}

.board-page.board-loading .board-assignment {
  visibility: hidden;
}

/*
 * Rule 25's message is the whole screen when it appears. An empty board reads as
 * "nothing is ready", which is a lie, so the correction has to be as legible as
 * the orders it replaces — at 24px on a wall-mounted 1080p display it is not, and
 * a Guest twenty feet away sees a blank board and walks to a counter.
 */
.board-page.board-outage .board-info {
  place-content: center;
  text-align: center;
}

.board-page.board-outage .board-assignment,
.board-page.board-no-open .board-assignment {
  display: none;
}

.board-page.board-outage .board-status,
.board-page.board-no-open .board-status {
  max-width: 24ch;
  color: #8a3211;
  font-size: clamp(24px, 3.4vw, 56px);
  line-height: 1.08;
}

.board-page.board-no-open .board-columns,
.board-page.board-outage .board-columns {
  display: none;
}

.board-page.board-no-open .board-main,
.board-page.board-outage .board-main {
  grid-template-rows: auto 1fr;
}

.board-page.board-no-open .board-info,
.board-page.board-outage .board-info {
  display: grid;
  place-content: center;
  min-height: 0;
  margin-top: 0;
  border-top: 0;
  text-align: center;
}

/* A board assignment remains the provenance after Close. This is a terminal
   public state, not an outage: identity stays visible, order lists are absent,
   and the irreversible result takes the screen at distance-reading scale. */
.board-page.board-closed .board-columns {
  display: none;
}

.board-page.board-closed .board-main {
  grid-template-rows: auto 1fr;
}

.board-page.board-closed .board-info {
  display: grid;
  place-content: center;
  gap: clamp(18px, 3vh, 36px);
  min-height: 0;
  margin-top: 0;
  text-align: center;
}

.board-page.board-closed .board-status {
  max-width: 22ch;
  color: #8a3211;
  font-size: clamp(48px, 5.5vw, 96px);
  line-height: 1.02;
  letter-spacing: -0.035em;
}

.board-page.board-closed .board-freshness {
  color: #765845;
  font-size: clamp(20px, 1.8vw, 32px);
  white-space: normal;
}

#event-list {
  display: grid;
  gap: 10px;
  margin: 0;
  padding: 0;
  list-style: none;
}

#event-list li {
  display: grid;
  grid-template-columns: 1fr auto;
  align-items: center;
  gap: 20px;
  min-height: 78px;
  padding: 18px 20px;
  border: 1px solid var(--border);
  background: rgb(255 255 255 / 0.72);
}

.event-name {
  display: block;
  margin-bottom: 6px;
  font-size: 17px;
  font-weight: 600;
}

.event-meta {
  color: var(--muted);
}

.event-state {
  padding: 8px 12px;
  color: var(--orange-text);
  background: #fff7ed;
  font-size: 12px;
}

/*
 * The one live region every Role Workspace announces through, and it must be
 * *gone* when it has nothing to say rather than merely mostly gone.
 *
 * A percentage translate resolves against the element's own height, so
 * `translateY(140%)` moved an empty region — 28px of padding, no text — by only
 * 39.2px, when leaving the screen costs its height plus its own 24px offset. It
 * sat 29px into the viewport as an unlabelled dark rectangle at the bottom
 * right, on all six surfaces. On a 390px phone showing four ready orders it
 * covered a Pickup Code, which is the single thing on that surface a Guest
 * cannot do without. Two earlier frames showed the rectangle and I read it as a
 * capture artefact; the Codex visual check named it, and `elementFromPoint`
 * settled it.
 *
 * So the hidden state is expressed in a unit that does not depend on how much
 * text the region happens to hold: `bottom` is the offset it must cross, and
 * `100%` its own height. `calc` keeps the two honest even if either changes, and
 * the region can carry one line or four without the geometry needing rechecking.
 *
 * `visibility` is what actually removes it from hit-testing — a fully
 * translated element is still a target, and this one overlaps real content. It
 * is deliberately not `display: none`: an assistive technology must keep
 * observing the node for `aria-live` to announce into it at all, and toggling
 * `display` would re-create rather than update it.
 */
.status {
  position: fixed;
  right: 24px;
  bottom: 24px;
  max-width: min(420px, calc(100% - 48px));
  padding: 14px 18px;
  color: white;
  background: var(--surface);
  box-shadow: 0 16px 35px -18px rgb(0 0 0 / 0.6);
  transform: translateY(calc(100% + 24px));
  visibility: hidden;
  transition:
    transform 180ms ease,
    visibility 180ms;
}

.status.visible {
  transform: translateY(0);
  visibility: visible;
}

.status.error {
  background: #9f1239;
}

/* Event Close has one persistent visual authority: `.event-lock`. The shared
   live region still announces the transition, but it must not remain as a
   second card or toast beside the terminal state after that card is visible.
   This is the visually-hidden utility (#163) restated with `!important`: it has
   to out-rank `.status.visible`'s toast presentation at equal-or-lower
   specificity, whatever state the shared live region is in. */
body:has(.event-lock:not([hidden])) [data-live-region] {
  position: fixed !important;
  width: 1px !important;
  height: 1px !important;
  padding: 0 !important;
  margin: -1px !important;
  overflow: hidden !important;
  clip-path: inset(50%) !important;
  white-space: nowrap !important;
  border: 0 !important;
  box-shadow: none !important;
  transform: none !important;
  visibility: visible !important;
}

/* Connectivity has already yielded to authoritative Closed state. Removing
   this redundant alert from layout also removes its alert semantics, leaving
   the clipped shared live region as the one announcement. */
body:has(.event-lock:not([hidden])) .connectivity-status {
  display: none !important;
}

/*
 * Desk feedback stays in the document flow. A fixed notice had only six pixels
 * of clearance above the greeting at 390px; one extra wrapped line on Linux
 * covered it. Flow layout makes every message reserve its actual height.
 */
.desk-page .status {
  position: static;
  max-width: 100%;
  margin: 0 0 16px;
  transform: none;
}

.desk-page .status.visible {
  transform: none;
}

.desk-page .status:empty {
  margin: 0;
  padding: 0;
  visibility: hidden;
}

.connectivity-status {
  max-width: 72rem;
  margin: 0 auto 1rem;
  padding: 0.9rem 1rem;
  border: 1px solid #c2410c;
  background: #fff7ed;
  color: #7c2d12;
  font-weight: 650;
}

@keyframes breathe {
  to {
    transform: rotate(4deg) scale(1.04);
  }
}

.registration {
  margin-top: 64px;
  padding-top: 38px;
  border-top: 1px solid var(--border);
}

.audit {
  margin-top: 64px;
  padding-top: 38px;
  border-top: 1px solid var(--border);
}

.audit-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 20px;
  margin-top: 24px;
}

.audit-grid > * {
  min-width: 0;
}

#event-close-form,
#report-access-form {
  margin-top: 24px;
}

#close-blockers:not(:empty) {
  padding: 12px;
  border-left: 5px solid var(--orange);
  background: #fff7ed;
}

.confirmation {
  border-left: 5px solid var(--orange);
  background: #fff7ed;
}

.report-scroll {
  margin-top: 20px;
  overflow: auto;
}

#report-table {
  width: 100%;
  border-collapse: collapse;
  font-size: 12px;
}

#report-table caption,
#report-table th,
#report-table td {
  padding: 10px;
  border: 1px solid var(--border);
  text-align: left;
}

#report-table caption {
  border-bottom: 0;
}

.registration-workspace {
  display: grid;
  grid-template-columns: 0.8fr 1.2fr;
  gap: 20px;
  margin-top: 24px;
}

.registration-tools {
  display: grid;
  gap: 20px;
}

.registration h3 {
  margin: 0 0 20px;
  font-size: 24px;
  font-weight: 500;
}

.access-qr {
  width: min(240px, 100%);
  padding: 10px;
  border: 1px solid var(--border);
  background: white;
}

.access-qr svg {
  display: block;
  width: 100%;
  height: auto;
}

.balance-line {
  margin: 20px 0 0;
  font-family: "JetBrains Mono", ui-monospace, monospace;
  font-size: 14px;
}

.balance-line output {
  font-size: 24px;
  font-weight: 700;
}

#recovery-form {
  margin-top: 28px;
  padding-top: 28px;
  border-top: 1px solid var(--border);
}

#recovery-form fieldset {
  display: grid;
  gap: 10px;
}

.hidden {
  display: none !important;
}

.onboarding-settings {
  display: grid;
  gap: 24px;
  margin-top: 72px;
}

.onboarding-settings details {
  padding-block: 18px;
  border-bottom: 1px solid var(--border);
}

.onboarding-settings summary {
  cursor: pointer;
  font-weight: 700;
}

.onboarding-settings li {
  margin-block: 10px;
  line-height: 1.55;
}

.onboarding-settings summary:focus-visible {
  outline: 3px solid rgb(234 88 12 / 0.3);
  outline-offset: 4px;
}

.guest-shell {
  max-width: 760px;
}

.guest-status,
.balance-panel,
.activity-panel {
  margin-top: 36px;
}

.credit-tools {
  display: grid;
  grid-template-columns: 0.85fr 1.15fr;
  gap: 20px;
  margin-top: 20px;
}

.receive-panel,
.transfer-panel {
  padding: clamp(24px, 4vw, 38px);
  border: 1px solid var(--border);
  background: rgb(255 255 255 / 0.8);
  backdrop-filter: blur(20px);
}

.recipient-code {
  display: block;
  margin: 8px 0 16px;
  overflow-wrap: anywhere;
  color: var(--orange-text);
  font-family: "JetBrains Mono", ui-monospace, monospace;
  font-weight: 700;
}

/*
 * A refused transfer field. It carries the same duty as `.handoff-error` — a
 * refusal written next to the field it was made in, staying there until the
 * Guest changes something — so it takes the same treatment rather than
 * inventing a second dialect for the same idea.
 */
.transfer-error {
  margin: 0;
  padding: 8px 10px;
  border-left: 3px solid #b91c1c;
  background: #fef2f2;
  color: #7f1d1d;
  font-weight: 600;
  overflow-wrap: anywhere;
}

#transfer-amount[aria-invalid="true"],
#transfer-recipient[aria-invalid="true"] {
  border-color: #b91c1c;
  outline: 2px solid #b91c1c;
  outline-offset: 1px;
}

.transfer-review {
  margin-top: 24px;
  padding: 20px;
  border-left: 4px solid var(--orange);
  background: #fff7ed;
}

.ordering {
  display: grid;
  grid-template-columns: minmax(0, 1.15fr) minmax(280px, 0.85fr);
  gap: 48px;
  margin-top: 64px;
}

.stall-list {
  display: grid;
  gap: 0;
  margin-top: 24px;
  border-top: 1px solid var(--border);
}

.stall-row {
  display: grid;
  grid-template-columns: 1fr auto;
  gap: 8px 24px;
  padding: 28px 0 28px 20px;
  border-bottom: 1px solid var(--border);
  border-left: 5px solid var(--orange);
}

.stall-row h3,
.stall-row p {
  margin: 0;
}

.capacity,
.orders-ahead {
  font-family: "JetBrains Mono", ui-monospace, monospace;
  font-size: 12px;
}

.orders-ahead {
  color: var(--muted);
}

.orders-ahead.busy {
  color: var(--rose);
  font-size: 14px;
  font-weight: 700;
}

.guest-menu {
  display: grid;
  grid-column: 1 / -1;
  gap: 10px;
  margin: 12px 0 0;
  padding: 0;
  list-style: none;
}

.guest-menu li {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
}

/*
 * Item rows read as small cards: the name is the scannable line, the price is
 * secondary state beneath it, and an "In Basket" count names what is already
 * chosen at the point of choice.
 */
.menu-item-label {
  display: grid;
  gap: 2px;
  min-width: 0;
}

.menu-item-name {
  font-weight: 650;
}

.menu-item-price {
  font-family: "JetBrains Mono", ui-monospace, monospace;
  font-size: 12px;
  color: var(--muted);
}

.menu-item-in-basket {
  justify-self: start;
  margin-top: 2px;
  padding: 1px 8px;
  border-radius: 999px;
  background: rgb(234 88 12 / 14%);
  color: var(--orange-text);
  font-family: "JetBrains Mono", ui-monospace, monospace;
  font-size: 11px;
  font-weight: 700;
}

.guest-menu button {
  /* WCAG 2.2 AA target size, on the control a Guest uses most, one-handed. */
  min-height: 44px;
  padding-inline: 18px;
}

/*
 * Rule 33, stated where it bites. The Basket panel carries the same rule in its own
 * words, but at 390px the two regions stack and the panel sits hundreds of pixels
 * below the Add controls this explains — so the reason has to travel to them. Prose
 * rather than a compact mono status label, because it names an escape and where to
 * find it: the mono voice on this surface is for state, not for instructions.
 */
.ordering-blocked {
  margin: 0;
  padding: 14px 16px;
  border-bottom: 1px solid var(--border);
  border-left: 5px solid var(--orange);
  background: rgb(253 186 116 / 22%);
  line-height: 1.5;
}

/* ------------------------------------------------------------------ *
 * Guest — Ready for Pickup and the Undo Window (#36)
 * ------------------------------------------------------------------ */

.ready-panel,
.undo-panel {
  padding: clamp(20px, 4vw, 32px);
  border: 1px solid var(--border);
  border-radius: 8px;
  background: rgb(255 255 255 / 70%);
}

@media (max-width: 480px) {
  .ready-panel {
    padding: 14px 12px;
  }
}

/* Let the Guest atmosphere show through a lighter directional wash. */
body:has(.guest-shell) {
  background: linear-gradient(
    100deg,
    rgb(255 255 255 / 0.88) 0 34%,
    transparent 72%
  );
}

.undo-panel {
  border-color: var(--orange);
}

.ready-list {
  display: grid;
  gap: 8px;
  margin: 10px 0 0;
  padding: 0;
  list-style: none;
}

/* Fixed columns keep every identifier pair on one line at phone width. */
.ready-row {
  padding: 12px 14px;
  border-left: 5px solid var(--orange);
  background: linear-gradient(
    100deg,
    rgb(253 186 116 / 18%),
    rgb(253 164 175 / 14%) 78%,
    rgb(255 247 237 / 90%)
  );
}

.ready-row strong {
  display: block;
}

.ready-total {
  color: var(--orange-text);
  font-family: "JetBrains Mono", ui-monospace, monospace;
  font-size: 15px;
  font-weight: 700;
  letter-spacing: 0;
  white-space: nowrap;
}

/*
 * The two numerals a Guest has to say out loud, at the scale of the thing they
 * are for. F3's Disney pickup screen gives the order number the whole display,
 * because it is read at a counter with somebody waiting — this surface had it at
 * 16px inside a sentence while the Credit balance below took 52px.
 *
 * Side by side rather than stacked: they are stated together, and the pair is
 * what identifies the Stall Order. Mono so the digits are unambiguous, and each
 * carries its own label so a three-digit code is never mistaken for an order
 * number. They wrap to one per line only when the row is too narrow to hold
 * both without shrinking the digits.
 */
.ready-stall {
  font-size: 16px;
  letter-spacing: -0.02em;
}

.ready-figures {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: 2px 16px;
  margin: 6px 0 0;
}

.ready-figure {
  display: grid;
  gap: 1px;
}

.ready-figure-label {
  color: var(--orange-text);
  font-family: "JetBrains Mono", ui-monospace, monospace;
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
}

/* Pickup identifiers outrank balance and greeting on phones. */
.ready-figure-value {
  font-family: "JetBrains Mono", ui-monospace, monospace;
  font-size: min(13vw, 44px);
  font-weight: 700;
  line-height: 1.04;
  letter-spacing: -0.03em;
  font-variant-numeric: tabular-nums;
}

.ready-instruction {
  display: block;
  margin: 6px 0 0;
  color: var(--muted);
  font-size: 13px;
  line-height: 1.4;
}

.ready-empty {
  color: var(--muted);
}

/*
 * The bounded period as a shape, filling toward the order being sent rather than
 * draining toward a loss. `--elapsed` is a 0–1 fraction set by `guest.js` each
 * second; the transition covers the gap between ticks so it reads as a sweep rather
 * than as a stutter.
 *
 * It carries no text and is `aria-hidden`, because the line beneath it says the same
 * thing in words — and this surface has exactly one live region, which speaks twice.
 * A second announcing element here would bury both.
 */
.undo-progress {
  height: 6px;
  margin: 14px 0 10px;
  border-radius: 3px;
  background: rgb(234 88 12 / 18%);
}

.undo-progress::before {
  display: block;
  width: calc(var(--elapsed, 0) * 100%);
  height: 100%;
  border-radius: inherit;
  background: var(--orange);
  content: "";
  transition: width 1s linear;
}

/* A Guest who has stopped watching should not find a bar frozen mid-sweep: once the
   countdown stops, it reads as full. `:has()` and not a sibling combinator, because
   the bar precedes the countdown in the DOM — the state lives downstream of the
   element it has to style. */
.undo-panel:has(.undo-countdown[data-expired="true"]) .undo-progress::before {
  width: 100%;
}

/* The sweep is decoration on top of a line of text that already says the same
   thing, so it is the first thing to go when motion is unwelcome. */
@media (prefers-reduced-motion: reduce) {
  .undo-progress::before {
    transition: none;
  }
}

/*
 * One timer node, updated in place. It is deliberately not a live region: the
 * workspace's single polite region announces at three seconds and at expiry,
 * so nobody is read a number once a second.
 */
.undo-countdown {
  margin: 8px 0 4px;
  font-family: "JetBrains Mono", ui-monospace, monospace;
  font-size: 1.1rem;
  color: var(--orange-text);
}

.undo-countdown[data-expired="true"] {
  color: var(--muted);
}

.undo-panel button {
  min-height: 44px;
  margin-top: 12px;
}

.basket-panel {
  align-self: start;
  padding: clamp(24px, 4vw, 40px);
  color: white;
  background: var(--surface);
}

/*
 * On `--surface` the accent inverts rather than darkens.
 *
 * `--orange-text` exists to pass AA on this product's *light* backgrounds, and
 * on the dark panels it does the opposite — `#c2410c` on `#191c21` is 3.30 to 1,
 * worse than the `#ea580c` it replaced. Contrast is a property of the pair, so a
 * single darker accent cannot serve both directions. Peach is the same ramp read
 * from the other end, at 10.13 to 1.
 *
 * Both dark panels are listed together so a third one cannot be added without
 * this decision being visible.
 */
.basket-panel .eyebrow,
.basket-panel a,
.balance-panel .eyebrow {
  color: var(--peach);
}

#basket-lines {
  display: grid;
  gap: 18px;
  margin-block: 28px;
  padding: 0;
  list-style: none;
}

.basket-line {
  display: grid;
  gap: 8px;
  padding-bottom: 18px;
  border-bottom: 1px solid rgb(255 255 255 / 0.2);
}

.basket-warning {
  color: #fed7aa;
  font-size: 13px;
  line-height: 1.5;
}

.remove-item {
  justify-self: start;
  min-height: 44px;
  /*
   * Padding buys the ~44px-wide touch target; the negative margin gives it
   * back so the quiet text link still sits flush where it did.
   */
  padding-inline: 12px;
  margin-inline: -12px;
  color: #fdba74;
  background: transparent;
  font-weight: 700;
  text-decoration: underline;
  text-underline-offset: 4px;
}

#basket-total {
  font-family: "JetBrains Mono", ui-monospace, monospace;
  font-weight: 700;
}

#basket-status {
  color: #fed7aa;
  line-height: 1.5;
}

.place-order {
  width: 100%;
  margin-top: 12px;
}

.order-panel {
  margin-top: 36px;
  padding: clamp(24px, 4vw, 40px);
  border: 1px solid var(--border);
  background: rgb(255 255 255 / 0.8);
  backdrop-filter: blur(20px);
}

#order-list {
  display: grid;
  gap: 16px;
}

.order-card {
  padding: 20px;
  border-left: 5px solid var(--orange);
  background: #fff7ed;
}

.order-card h3,
.order-card p {
  margin: 0 0 10px;
}

.order-card ul {
  padding-left: 20px;
}

/*
 * A retained Stall Order, held on the device through an outage.
 *
 * It gives up the orange left border and the warm fill a live `.order-card`
 * carries, because that accent is what live state looks like everywhere else on
 * this surface — and the one thing rule 31 cannot afford is a cached number
 * reading as a current one. Neutral border, no fill: present and legible, and
 * visibly not the same kind of fact as a live row.
 */
.retained-order {
  border-left: 5px solid var(--border);
  background: none;
}

.retained-note {
  margin: 0 0 4px;
  max-width: none;
}

/*
 * The number and the code in the mono face this surface already uses for a value
 * that gets read aloud — the Ready row's Pickup Code and the Recipient code both
 * do the same. A Guest stating this at a counter is transcribing it, so the
 * digits are set for reading rather than for prose.
 */
.retained-identity {
  margin: 0;
  font-family: "JetBrains Mono", ui-monospace, monospace;
  font-weight: 650;
  letter-spacing: 0.01em;
}

.balance-panel {
  padding: clamp(24px, 5vw, 52px);
  color: white;
  background: var(--surface);
}

#guest-balance {
  display: block;
  margin-block: 8px 18px;
  font-size: clamp(52px, 12vw, 96px);
  font-weight: 300;
  letter-spacing: -0.06em;
}

@media (max-width: 480px) {
  .guest-shell > h1 {
    font-size: min(9vw, 34px);
  }

  #guest-balance {
    font-size: min(8vw, 30px);
  }
}

.activity-panel ul {
  display: grid;
  gap: 10px;
  padding-left: 20px;
}

.error-text {
  color: #9f1239;
  font-weight: 600;
}

@media (max-width: 820px) {
  header,
  main {
    width: min(100% - 32px, 1280px);
  }

  main {
    padding-top: 42px;
  }

  .workspace,
  .service-grid,
  .registration-workspace,
  .audit-grid,
  .credit-tools,
  .ordering,
  .fulfillment-queues,
  .events {
    grid-template-columns: 1fr;
  }

  .workspace {
    margin-top: 42px;
  }

  .events {
    gap: 24px;
  }

  .menu-row,
  .ingredient-row,
  .stock-actions,
  .runner-alert,
  .handoff-form {
    grid-template-columns: 1fr;
  }

  .runner-alert input {
    grid-column: auto;
  }

  /*
   * The board is a wall-mounted landscape display, but it is a plain URL and
   * someone will open it on a phone — to check it, or because the TV failed. Two
   * narrow columns at 390px would leave neither legible, so they stack, and the
   * ready column keeps its larger type.
   */
  .board-main {
    padding-inline: 16px;
  }

  .board-columns {
    grid-template-columns: minmax(0, 1fr);
  }
}

/*
 * Action Feedback (ADR 0003 points 6, 7, 10, 11). A persistent record anchored
 * immediately after the immutable row it acted on. Never fixed, never
 * overlaid, never auto-dismissed — it is deliberately not a toast.
 */
.action-feedback {
  position: static;
  box-sizing: border-box;
  max-width: 100%;
  min-width: 0;
  overflow-wrap: anywhere;
  margin: 0.75rem 0 1rem;
  padding: 0.9rem 1rem;
  border: 1px solid var(--border);
  border-left: 4px solid var(--orange);
  border-start-start-radius: 0;
  border-end-end-radius: 0;
  background: #fffdfb;
  color: var(--surface);
  font-size: 0.9rem;
}

.action-feedback:focus-visible {
  outline: 3px solid var(--orange);
  outline-offset: 3px;
}

.action-feedback-heading {
  margin: 0 0 0.25rem;
  font-size: 0.95rem;
  font-weight: 650;
  letter-spacing: -0.01em;
}

.action-feedback-status {
  margin: 0 0 0.6rem;
  font-weight: 600;
}

.action-feedback-detail {
  display: grid;
  grid-template-columns: minmax(6rem, 12rem) minmax(0, 1fr);
  gap: 0.2rem 0.9rem;
  margin: 0;
  min-width: 0;
}

.action-feedback-detail dt {
  font-family: "JetBrains Mono", ui-monospace, monospace;
  font-size: 0.72rem;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--muted);
}

.action-feedback-detail dt,
.action-feedback-detail dd {
  min-width: 0;
  overflow-wrap: anywhere;
}

.action-feedback-detail dd {
  margin: 0;
}

.action-feedback-dismiss {
  min-height: 44px;
  min-width: 44px;
  margin-top: 0.8rem;
  padding: 0 1rem;
  border: 1px solid var(--surface);
  background: transparent;
  color: var(--surface);
  font: inherit;
}

.action-feedback-collapse > summary {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  min-height: 44px;
  margin: -0.9rem -1rem 0;
  padding: 0.45rem 0.75rem;
  cursor: pointer;
  font-weight: 600;
}

/* Discoverability follow-up to #185: a high-contrast labeled cue beside the
   chevron makes the collapsed record read as an expandable affordance (parity
   with Toast KDS's labeled utilities), while the full summary row stays the
   touch target and no extra button column is added. */
.action-feedback-collapse-cue {
  flex: none;
  padding: 0.15rem 0.5rem;
  border: 1px solid #9ca3af;
  background: #f3f4f6;
  color: #1f2937;
  font-family: "JetBrains Mono", ui-monospace, monospace;
  font-size: 0.68rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
}

/* Chevron, drawn so it can flip open without an icon dependency; #374151 on
   the card surface is ~10:1, well past AA for this non-text indicator. */
.action-feedback-collapse > summary::after {
  content: "";
  flex: none;
  width: 0.5rem;
  height: 0.5rem;
  border-right: 2px solid #374151;
  border-bottom: 2px solid #374151;
  transform: rotate(45deg);
  transition: transform 120ms ease;
}

.action-feedback-collapse[open] > summary::after {
  transform: rotate(225deg);
}

.action-feedback-collapse[open] > summary {
  margin-bottom: 0.6rem;
}

/* Collapsed by default (#185): the full ledger is one terse line until opened. */
.action-feedback[data-collapsed="true"] {
  padding-bottom: 0.9rem;
}
.action-feedback[data-collapsed="true"] .action-feedback-heading,
.action-feedback[data-collapsed="true"] .action-feedback-status,
.action-feedback[data-collapsed="true"] .action-feedback-detail {
  display: none;
}

.action-feedback-summary {
  margin: 0.75rem 0 1rem;
  border: 1px solid var(--border);
  background: #fffdfb;
}

.action-feedback-summary > summary {
  display: flex;
  align-items: center;
  min-height: 44px;
  padding: 0.45rem 0.75rem;
  cursor: pointer;
  font-family: "JetBrains Mono", ui-monospace, monospace;
  font-size: 0.72rem;
  letter-spacing: 0.04em;
}

.action-feedback-summary > .action-feedback {
  margin-inline: 0.75rem;
}

.action-feedback[data-state="pending"],
.action-feedback[data-state="checking"] {
  border-left-color: #b45309;
  background: #fffbeb;
}

.action-feedback[data-state="committed"] {
  border-left-color: #15803d;
  background: #f6fdf8;
}

/*
  A record whose origin row is no longer shown keeps its own state colour and
  reads as displaced rather than as a new kind of thing: same card, dashed edge,
  parked at the end of the workspace.
*/
.action-feedback[data-anchor="detached"] {
  border-left-style: dashed;
}

.action-feedback[data-state="validation"],
.action-feedback[data-state="failure"],
.action-feedback[data-state="not-submitted"],
.action-feedback[data-state="blocked"],
.action-feedback[data-state="offline"],
.action-feedback[data-state="closed-event"] {
  border-left-color: var(--rose);
  background: #fff5f7;
}

/* ------------------------------------------------------------------ *
 * Role Workspaces (#32 Event Admin, and the siblings that follow)
 * ------------------------------------------------------------------ */

.role-nav {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px 20px;
}

.role-nav .role {
  font-family: "JetBrains Mono", ui-monospace, monospace;
  font-size: 0.7rem;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--muted);
}

/* 44px targets apply to navigation as much as to buttons. */
.role-nav-link {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  padding-inline: 2px;
  color: inherit;
  text-decoration: none;
  border-bottom: 2px solid transparent;
}

.role-nav-link:hover {
  border-bottom-color: var(--peach);
}

.role-nav-link[aria-current="page"] {
  border-bottom-color: var(--orange);
}

.role-nav .text-button {
  min-height: 44px;
}

.role-sign-in {
  max-width: 32rem;
}

.closed-event-history {
  max-width: 32rem;
  margin-top: 24px;
}

.closed-event-history > summary {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  align-content: center;
  min-height: var(--v2-target);
  padding-inline: 2px;
  color: var(--orange-text);
  cursor: pointer;
  font-weight: 700;
  list-style-position: inside;
}

.closed-event-history > summary::after {
  content: "+";
  grid-column: 2;
  grid-row: 1 / span 2;
  align-self: center;
  padding-left: 12px;
  font-size: 1.35rem;
  font-weight: 400;
}

.closed-event-history[open] > summary::after {
  content: "−";
}

.closed-event-history :is(summary, input, button):focus-visible {
  outline: var(--v2-focus-ring);
  outline-offset: var(--v2-focus-offset);
}

.closed-event-history > summary small {
  grid-column: 1;
  margin: 3px 0 0;
  color: var(--muted);
  font-size: 0.78rem;
  font-weight: 400;
}

.closed-history-panel {
  margin-top: 12px;
  padding: clamp(22px, 5vw, 32px);
}

.closed-history-result {
  min-height: 0;
  margin: 18px 0 0;
  line-height: 1.5;
}

.closed-history-result:empty {
  display: none;
}

.closed-history-result[data-state="error"] {
  padding-left: 12px;
  border-left: 3px solid var(--rose);
  color: var(--v2-danger-text);
}

/*
 * Closed Event history keeps its visible result beside the controls that need
 * correction. The shared live region still announces the same sentence, but
 * this route scopes it to an assistive-only copy so it cannot become a second
 * fixed overlay above the history form. Any ordinary workspace announcement
 * removes this class and keeps the shared toast behavior unchanged. The how is
 * the visually-hidden utility at the top of this stylesheet (#163); this site's
 * selector joins it there.
 */

#closed-history-authentication-form {
  margin-top: 24px;
  padding-top: 24px;
  border-top: 1px solid var(--border);
}

#closed-history-events {
  display: grid;
  gap: 8px;
  margin-top: 10px;
}

.closed-history-choice {
  display: flex;
  align-items: center;
  gap: 10px;
  min-height: var(--v2-target);
  padding: 10px 12px;
  border: 1px solid var(--border);
  border-radius: var(--v2-radius);
  background: rgb(255 255 255 / 0.72);
}

.closed-history-choice input {
  width: 20px;
  min-height: 20px;
  margin: 0;
}

.overseer-workspace,
.overseer-settings {
  display: flex;
  flex-direction: column;
  gap: 44px;
  margin-top: 48px;
  min-width: 0;
}

.health-totals {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(9rem, 1fr));
  gap: 2px;
  margin: 18px 0 0;
  border: 1px solid var(--border);
  background: var(--border);
}

.health-totals > div {
  position: relative;
  padding: 18px 20px;
  background: #fffdfb;
}

.health-total-link {
  display: inline-flex;
  min-width: 44px;
  min-height: 44px;
  align-items: center;
}

.health-total-link::after {
  position: absolute;
  inset: 0;
  content: "";
}

.health-totals > div:has(.health-total-link:hover) {
  background: var(--v2-panel);
}

.health-totals > div:has(.health-total-link:focus-visible),
.overseer-workspace > section:focus-visible {
  outline: var(--v2-focus-ring);
  outline-offset: var(--v2-focus-offset);
}

.health-total-link:focus-visible {
  outline: 0;
}

.overseer-workspace > section {
  scroll-margin-top: 24px;
}

.health-totals dt {
  font-family: "JetBrains Mono", ui-monospace, monospace;
  font-size: 0.68rem;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--muted);
}

.health-totals dd {
  margin: 6px 0 0;
  font-size: 2.1rem;
  font-weight: 300;
  letter-spacing: -0.04em;
}

/*
 * O4: the comparison sits under the count it belongs to, at supporting weight,
 * so the absolute number stays what the eye lands on. It is a second `<dd>` for
 * the same `<dt>`, which is what a definition list is for — a share is another
 * statement about Received, not a separate metric.
 *
 * The `dd` rule above sets 2.1rem and -0.04em, so both are overridden here
 * rather than left to inherit a display size onto supporting text.
 */
.health-totals .total-share {
  margin: 4px 0 0;
  font-family: "JetBrains Mono", ui-monospace, monospace;
  font-size: 0.72rem;
  font-weight: 400;
  letter-spacing: 0;
  line-height: 1.4;
  color: var(--muted);
}

.health-basis {
  margin: 14px 0 0;
  font-size: 13px;
}

.exception-list,
.stall-health-list,
.credential-list,
.report-list,
.correction-list {
  margin: 18px 0 0;
  padding: 0;
  list-style: none;
  min-width: 0;
}

.exception,
.correction-row {
  padding: 16px 18px;
  border-left: 3px solid var(--orange);
  background: #fffdfb;
  margin-bottom: 12px;
  min-width: 0;
  overflow-wrap: anywhere;
}

.exception.empty {
  border-left-color: var(--border);
  color: var(--muted);
}

.exception-summary {
  margin: 0 0 4px;
  font-weight: 600;
}

.exception p:last-child,
.correction-row p:last-child {
  margin-bottom: 0;
}

.stall-health-row {
  padding: 20px 0;
  border-top: 1px solid var(--border);
  min-width: 0;
}

.stall-health-row h3 {
  margin: 0 0 8px;
}

.stall-health-row .queue-counts {
  margin: 10px 0 0;
}

/*
 * O4: this stall against the fair's own current median, directly under the
 * counts it is derived from.
 *
 * `data-load` tints the rule on the left, and that is all it does. The sentence
 * itself names the busiest and the quietest end, because colour is never the
 * sole carrier of meaning — remove every colour here and the row still says
 * which end of the fair it is on.
 */
.stall-health-row .stall-comparison {
  margin: 8px 0 0;
  padding-left: 10px;
  border-left: 3px solid var(--border);
  font-size: 13px;
}

.stall-comparison[data-load="high"] {
  border-left-color: var(--orange);
}

.stall-comparison[data-load="low"] {
  border-left-color: #9ca3af;
}

.stall-health-row .event-state[data-capacity-state="Open"] {
  background: #dcfce7;
}

/*
 * O6: the Pause form stacks, because rule 18's reason now carries a visible
 * label and a statement of what naming it does, and neither belongs squeezed
 * beside a button. A row was a flex line built for a field plus a button; the
 * requirement is a third thing and reads before the action, not beside it.
 */
.pause-exception {
  display: grid;
  gap: 12px;
  margin-top: 16px;
  min-width: 0;
}

.pause-exception label {
  min-width: 0;
}

.pause-exception input {
  width: 100%;
}

/* The shared `.hint` carries a 30px bottom margin; the grid gap owns spacing here. */
.pause-requirement {
  margin: 0;
  max-width: 46ch;
  font-size: 13px;
}

.pause-exception .pause-stall {
  justify-self: start;
}

/* #183: the row's collapsed Pause disclosure control. */
.pause-toggle {
  min-height: 48px;
  justify-self: start;
  margin-top: 16px;
  border: 1px solid var(--surface);
  background: transparent;
  color: var(--surface);
}

.pause-toggle:hover {
  background: var(--surface);
  color: white;
}

.pause-stall {
  min-height: 48px;
  border: 1px solid var(--surface);
  background: transparent;
  color: var(--surface);
}

.pause-stall:hover {
  background: var(--surface);
  color: white;
}

.settings-outbound {
  padding-top: 24px;
  border-top: 1px solid var(--border);
  color: var(--muted);
}

/*
 * A link that leaves this surface is a real target, so it is sized like one
 * rather than left as inline prose.
 */
.outbound-link {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  color: var(--orange-text);
}

.credential-row {
  padding-block: 20px;
  border-top: 1px solid var(--border);
  min-width: 0;
}

.credential-row h3 {
  margin: 0;
}

/*
 * The blast radius of a rotation, stated before the act. It carries the guarded
 * rose the Event Close panel uses, because it is the same kind of statement — an
 * irreversible consequence named in advance — but as an inline rule rather than
 * a whole guarded panel: rotating a credential is routine setup work an Overseer
 * is expected to do, not an act to be discouraged.
 *
 * The two spans stack so "what this ends" and "what it spares" read as separate
 * facts. Colour is never the only carrier: the rule, the indent, and the two
 * distinct sentences all survive a monochrome screen.
 */
.credential-radius {
  display: grid;
  gap: 4px;
  margin: 10px 0 0;
  padding-left: 14px;
  border-left: 2px solid var(--rose);
  font-size: 0.85rem;
  line-height: 1.5;
  min-width: 0;
  overflow-wrap: anywhere;
}

/*
 * What the rotation ends inherits the root text colour rather than the muted
 * one: it is the consequence, and it should not read as a footnote beside the
 * reassurance below it.
 */
.credential-spares {
  color: var(--muted);
}

.report-list li {
  padding-block: 4px;
}

.report-link {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  color: var(--orange-text);
}

/*
 * The label is the target for a checkbox, so it is the thing sized to 44px.
 */
.overseer-settings .choice {
  display: flex;
  align-items: center;
  gap: 10px;
  min-height: 44px;
}

.overseer-settings .board-link {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
}

.close-reconciliation {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  gap: 6px 20px;
  margin: 18px 0 0;
  min-width: 0;
}

.close-reconciliation dt {
  color: var(--muted);
  overflow-wrap: anywhere;
}

.close-reconciliation dd {
  margin: 0;
  font-family: "JetBrains Mono", ui-monospace, monospace;
  text-align: right;
}

/*
 * Event Close is visually guarded so it can never be mistaken for routine
 * setup, without relying on colour alone to say so.
 *
 * What survives a monochrome screen had to be checked rather than assumed, and
 * two of the five declarations below turned out to carry nothing. `.panel`
 * (line 145) sets no `border-radius`, so both `radius: 0` lines are no-ops
 * against it; the 1px border matches `.panel`'s own width and the background is
 * a near-white against `.panel`'s near-white. Strip the hue and the only thing
 * left distinguishing the most destructive act in the application from the
 * Ready Board name field above it was the word "Irreversible".
 *
 * So the weight is structural now: a 4px inline-start rule the panel above does
 * not have, and heavier padding at that edge. Both survive greyscale, both
 * survive a forced-colours mode that discards the background entirely, and the
 * rose is left doing what colour should do — reinforcing a distinction that is
 * already legible without it.
 */
.panel.guarded {
  border: 1px solid var(--rose);
  border-inline-start: 4px solid var(--rose);
  background: #fff5f7;
  padding: 26px;
  padding-inline-start: 22px;
}

/* Small rose text needs rose-800 on the guarded panel; `--rose` is 4.40:1. */
.guarded-eyebrow {
  color: #9f1239;
}

/* Opaque states avoid the user agent's 1.98:1 disabled composite. */
.danger {
  color: white;
  background: #9f1239;
}

.danger:disabled {
  color: var(--muted);
  background: var(--border);
  cursor: not-allowed;
}

.close-blockers-panel {
  margin: 24px 0;
  padding-top: 20px;
  border-top: 1px solid var(--rose);
}

.close-blockers-panel h3 {
  margin: 0 0 12px;
}

.close-blockers {
  display: grid;
  gap: 10px;
  margin: 0;
  padding-left: 20px;
}

/*
 * The blast radius, immediately above the confirmation (rule 22).
 *
 * It takes the same rose rule O7 gave `.credential-radius`, because it is the
 * same kind of statement — an irreversible consequence named in advance — and an
 * Overseer who has read one should recognise the other. What differs is weight:
 * this one is not a footnote, so it keeps root text colour and full size. Every
 * line here is a consequence; none of them is a reassurance.
 *
 * Nothing depends on the rose. The heading, the rule, the indent, and the list
 * markers all survive greyscale, and the sentences say in words what the colour
 * only emphasises.
 */
.close-radius-panel {
  margin: 24px 0;
  padding-top: 20px;
  border-top: 1px solid var(--rose);
}

.close-radius-panel h3 {
  margin: 0 0 12px;
}

.close-radius {
  display: grid;
  gap: 10px;
  margin: 0;
  padding-left: 20px;
  line-height: 1.5;
  min-width: 0;
  overflow-wrap: anywhere;
}

/*
 * The Event-level lock (#38).
 *
 * Rendered by the shared Role Workspace shell, so it looks the same on all six
 * Role Workspaces. It carries the same guarded rose the Event Close panel does
 * — a Closed Event is the same irreversible fact seen from the other side —
 * and it never relies on colour alone: it is a heading and a sentence.
 */
.event-lock {
  max-width: 72rem;
  margin: 0 auto 24px;
  padding: 20px clamp(20px, 3vw, 32px);
  border: 1px solid var(--rose);
  border-start-start-radius: 0;
  border-end-end-radius: 0;
  background: #fff5f7;
}

.event-lock-title {
  margin: 0;
  font-size: clamp(20px, 2.4vw, 28px);
}

.event-lock-detail {
  margin: 10px 0 0;
  max-width: 570px;
  line-height: 1.55;
}

/* ------------------------------------------------------------------ *
 * Registration Desk Role Workspace (#33), as a sequence (D2)
 *
 * One vertical lane, in the same rhythm as the Event Admin's. A desk
 * volunteer works down the screen in the order the queue moves — find, then
 * load, then hand over — so nothing here is laid out as a grid of equal cards
 * that would invite reading it in any other order.
 *
 * The sequence goes further than order: exactly one step is dominant, and the
 * other two collapse to what they resolved to. Rule 49 is the reason. This is
 * the one surface where a volunteer alternates between looking up at a person
 * and down at a phone, and a screen of four equal panels makes them re-find
 * their place in the task on every glance back down.
 * ------------------------------------------------------------------ */

/*
 * The masthead, sized for a surface worked standing up.
 *
 * `main` opens with 70px of padding and `.intro` puts a 48px display heading and
 * a lede under it, which is right on a page somebody reads and wrong on one they
 * *work*. Measured on a 390x844 phone it spent 517px — 61% of the viewport —
 * before the current step began, and left the step's own primary control at
 * 878px, below the fold. Rule 49 makes that the defining defect of this surface
 * rather than a matter of taste: the volunteer alternates between a person's
 * face and a phone screen, so anything they must scroll to reach costs them the
 * glance back up.
 *
 * The greeting stays, because arriving at a desk that says "Welcome each Guest"
 * is the tone this product wants and it is one line. It just stops being a
 * display heading.
 */
/* The eyebrow above the greeting was what kept it clear of the fixed sign-in
   notice; without it the two met at y149. This is that clearance, made its own
   rather than a side effect of a label. */
.desk-intro {
  padding-top: 12px;
  margin-bottom: 4px;
}

.desk-intro h1 {
  margin: 4px 0 0;
  font-size: 26px;
  font-weight: 400;
  letter-spacing: -0.03em;
}

.desk-workspace {
  display: flex;
  flex-direction: column;
  gap: 44px;
  margin-top: 28px;
  min-width: 0;
}

/*
 * The sequence is named for a screen reader and for anyone reading the page
 * cold, but the step headings carry the actual hierarchy — so this sits at the
 * scale of a label rather than competing with the step a volunteer is on.
 */
.sequence-heading {
  font-family: "JetBrains Mono", ui-monospace, monospace;
  font-size: 11px;
  font-weight: 600;
  color: var(--muted);
  letter-spacing: 0.06em;
  text-transform: uppercase;
}

/*
 * The identified Guest, held above the steps. It is deliberately compact: rule
 * 4 wants the immutable target visible wherever the volunteer acts, and rule 49
 * wants the current step to dominate — so this states who without becoming
 * something to read.
 */
.guest-strip {
  display: flex;
  flex-direction: column;
  gap: 12px;
  padding: 18px 20px;
  border: 1px solid var(--border);
  border-start-start-radius: 0;
  border-end-end-radius: 0;
  background: rgb(255 255 255 / 0.6);
  min-width: 0;
}

.desk-page .guest-strip:focus-visible {
  outline: 3px solid var(--surface);
  outline-offset: 4px;
}

/* The Desk uses the opaque V2 focus token. The shared legacy ring is
   translucent and falls below the visual distinction this busy workflow
   needs when an operator returns to the keyboard. */
.desk-page input:focus-visible,
.desk-page select:focus-visible,
.desk-page textarea:focus-visible,
.desk-page button:focus-visible,
.desk-page a:focus-visible {
  outline: var(--v2-focus-ring);
  outline-offset: var(--v2-focus-offset);
}

.strip-eyebrow {
  margin: 0;
  color: var(--orange-text);
  font-family: "JetBrains Mono", ui-monospace, monospace;
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
}

.strip-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 18px;
}

.desk-sequence {
  display: flex;
  flex-direction: column;
  gap: 28px;
  margin: 0;
  padding: 0;
  list-style: none;
  min-width: 0;
}

/*
 * A step is a step whether or not it is the current one; what changes is its
 * weight. `data-state` carries that, so no JavaScript sets an inline style and
 * the three states can be read off the DOM.
 */
.sequence-step {
  padding-top: 24px;
  border-top: 1px solid var(--border);
  min-width: 0;
}

/*
 * The current step. The orange left edge is the only place this surface spends
 * the accent on structure, and it is spent here because "which step am I on" is
 * the question the volunteer asks on every glance back down.
 */
.sequence-step[data-state="current"] {
  padding: 24px;
  border: 1px solid var(--border);
  border-inline-start: 3px solid var(--orange);
  background: #fffaf5;
}

/* Never colour alone: the index and the summary say the state in words. */
.sequence-step[data-state="waiting"] .step-index,
.sequence-step[data-state="waiting"] .step-name {
  color: var(--muted);
}

/*
 * Progress carried the smallest type on the surface at 11px, so a returning
 * glance could name the task but not place it in the three. 14px is the
 * arm-length floor; all three rise equally, so the current step's 28px heading
 * stays dominant against the collapsed steps' 20px.
 */
.step-index {
  margin: 0 0 6px;
  color: var(--muted);
  font-family: "JetBrains Mono", ui-monospace, monospace;
  font-size: 14px;
  font-weight: 600;
  letter-spacing: 0.05em;
  text-transform: uppercase;
}

.sequence-step[data-state="current"] .step-index {
  color: var(--orange-text);
}

.step-name {
  margin: 0;
}

/*
 * The current step's own heading is the largest thing on the screen — which this
 * comment claimed before it was true. `clamp(24px, 3vw, 32px)` resolves on its
 * *minimum* at every phone width, because 3vw of 390px is 11.7px, so the step a
 * volunteer is working measured 24px against a 48px page greeting. The scale said
 * the greeting mattered twice as much as the task.
 *
 * A fixed floor of 28px instead, and the `vw` term is what escalates on a
 * tablet rather than what decides the phone. With the masthead now at 26px this
 * heading is the largest thing on a 390px screen, as rule 49 asks and as the
 * surface already said of itself.
 *
 * A collapsed step keeps its heading at ordinary `h3` size, so the difference
 * between "here" and "already done" is legible without reading either.
 */
.sequence-step[data-state="current"] .step-name {
  font-size: clamp(28px, 3vw, 32px);
  font-weight: 400;
  letter-spacing: -0.04em;
}

.step-summary {
  margin: 8px 0 0;
  color: var(--muted);
  line-height: 1.55;
  overflow-wrap: anywhere;
}

.step-body {
  display: flex;
  flex-direction: column;
  gap: 24px;
  margin-top: 20px;
  min-width: 0;
}

.step-resume {
  margin-top: 6px;
}

/* The one line the control order cannot say by itself, after the lookup rather
   than in front of it. Zero margin: the shared `.hint` 30px is what let the
   branch read as a paragraph. */
.branch-guidance {
  margin: 0;
  color: var(--muted);
}

.desk-read-state {
  margin: 0;
  padding: 10px 12px;
  border-inline-start: 3px solid var(--orange);
  background: rgb(253 186 116 / 0.16);
  color: var(--surface);
}

.desk-read-state[data-state="failure"],
.desk-read-state[data-state="validation"] {
  border-inline-start-color: var(--rose);
  background: #fff5f7;
}

/*
 * The branch. It is inset under the lookup that failed, because a Guest nobody
 * holds is the other half of step one rather than a second way in — the whole
 * point of D3.
 */
.register-branch {
  padding-inline-start: 16px;
  border-inline-start: 3px solid var(--peach);
  min-width: 0;
}

.branch-reason {
  margin: 10px 0 20px;
  max-width: 570px;
  line-height: 1.55;
}

/*
 * The Guest record, opened from the strip. Shopify shows this as a sheet over a
 * persistent sale; at 390px there is no width for two regions at once, so ours
 * replaces the sequence and returns to it. That divergence is recorded in
 * `registration-desk.md`.
 */
.guest-record {
  display: flex;
  flex-direction: column;
  gap: 28px;
  padding: 24px;
  border: 1px solid var(--surface);
  min-width: 0;
}

.record-heading {
  margin: 0;
  font-size: clamp(24px, 3vw, 32px);
  font-weight: 400;
  letter-spacing: -0.04em;
}

.record-subject {
  margin: -20px 0 0;
  color: var(--muted);
  overflow-wrap: anywhere;
}

.desk-form {
  display: flex;
  flex-direction: column;
  gap: 14px;
  padding-top: 20px;
  border-top: 1px solid var(--border);
  min-width: 0;
}

.desk-form h3 {
  margin: 0;
}

/*
 * The review is the pause between typing a person in and committing them. It
 * is set apart so it reads as a distinct step rather than as more of the form
 * above it.
 */
.registration-review {
  padding: 24px;
  border: 1px solid var(--orange);
  background: #fffaf5;
  min-width: 0;
}

.registration-review h3 {
  margin: 0 0 14px;
}

.review-detail,
.guest-detail {
  display: grid;
  grid-template-columns: minmax(0, auto) minmax(0, 1fr);
  gap: 6px 20px;
  margin: 0;
  min-width: 0;
}

.review-detail > div,
.guest-detail > div {
  display: contents;
}

.review-detail dt,
.guest-detail dt {
  color: var(--muted);
  font-family: "JetBrains Mono", ui-monospace, monospace;
  font-size: 0.68rem;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  align-self: center;
}

.review-detail dd,
.guest-detail dd {
  margin: 0;
  overflow-wrap: anywhere;
}

.review-duplicate {
  margin: 16px 0 0;
  padding-inline-start: 14px;
  border-inline-start: 3px solid var(--rose);
  color: var(--surface);
}

/*
 * Three separate targets, stacked. They are never crowded onto one row,
 * because using an existing Guest and correcting a typo must not be adjacent
 * enough to hit by accident.
 */
.review-actions {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 12px;
  margin-top: 20px;
}

.use-existing {
  border: 1px solid var(--surface);
  background: transparent;
  color: var(--surface);
}

.use-existing:hover {
  background: var(--surface);
  color: white;
}

.credit-load,
.access-qr-panel,
.guest-recovery {
  min-width: 0;
}

/*
 * The balance beside the amount field (F5 store-credit frame 22: amount and
 * available balance explicit together). Smaller than the record's balance —
 * this is context for the number being typed, not the thing being disputed —
 * but tabular and mono-labelled like every other balance on this surface.
 */
.desk-page .fund-balance-line {
  display: flex;
  flex-direction: column;
  gap: 2px;
  margin: 0 0 12px;
}

/*
 * Round 2: the hint above the balance line kept its page-wide 30px bottom
 * margin on top of the step-body's 20px gap, so "Balance now" floated half a
 * line below the guidance that introduces it. The hint belongs to the same
 * breath as the balance — the fund body is one unit of meaning — so inside
 * the fund body the gap alone carries the rhythm.
 */
.desk-page #fund-body .hint {
  margin-bottom: 0;
}

.desk-page .fund-balance-label {
  color: var(--muted);
  font-family: "JetBrains Mono", ui-monospace, monospace;
  font-size: 0.68rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
}

.desk-page #fund-balance {
  font-size: clamp(22px, 3vw, 28px);
  font-weight: 400;
  letter-spacing: -0.03em;
  line-height: 1.1;
  font-variant-numeric: tabular-nums;
}

/* A recovered Credit request remains visibly locked while its committed
   result is checked. Dashed edges supply a non-colour unavailable cue, while
   opaque neutral fills keep the typed amount and action readable. */
.desk-page .credit-load input:disabled {
  opacity: 1;
  color: #374151;
  border-color: #6b7280;
  border-style: dashed;
  background: #e5e7eb;
  cursor: not-allowed;
}

.desk-page .credit-load button:disabled {
  opacity: 1;
  color: #4b5563;
  border: 2px dashed #6b7280;
  background: #d1d5db;
  box-shadow: inset 0 0 0 1px #9ca3af;
  cursor: not-allowed;
}

.guest-strip .guest-detail {
  gap: 4px 20px;
}

.access-details {
  margin-top: 20px;
}

.access-countdown {
  font-family: "JetBrains Mono", ui-monospace, monospace;
  font-size: 0.85rem;
  color: var(--muted);
}

/* Expiry is said in words, never by colour alone. */
.access-countdown[data-expired="true"] {
  color: var(--rose);
}

/*
 * A text button here is still a target a volunteer hits with a thumb while
 * holding a cash box, so it is sized like every other one.
 */
.desk-workspace .text-button {
  min-height: 44px;
}

.guest-recovery fieldset {
  margin: 0;
  padding: 0;
  border: 0;
}

.guest-recovery legend {
  padding: 0;
  color: var(--muted);
}

/* The label is the target for a radio, so the label is what is sized. */
.guest-recovery .choice {
  display: flex;
  align-items: center;
  gap: 10px;
  min-height: 44px;
}

@media (max-width: 640px) {
  .desk-page .guest-strip .guest-detail {
    grid-template-columns: minmax(0, 1fr);
    gap: 2px;
  }

  .desk-page .guest-strip .guest-detail dd {
    margin-bottom: 8px;
  }

  .desk-page .guest-strip .guest-detail > div:last-child dd {
    margin-bottom: 0;
  }

  .action-feedback-detail {
    grid-template-columns: 1fr;
    gap: 0 0;
  }

  .action-feedback-detail dd {
    margin-bottom: 0.45rem;
  }
}

/* ------------------------------------------------------------------ *
 * Stall Staff — My Stall (#34)
 * ------------------------------------------------------------------ */

.my-stall-workspace {
  display: flex;
  flex-direction: column;
  gap: 44px;
  margin-top: 48px;
  min-width: 0;
}

.stall-panel {
  display: flex;
  flex-direction: column;
  gap: 34px;
  min-width: 0;
}

.stall-form {
  display: flex;
  flex-direction: column;
  gap: 14px;
  min-width: 0;
}

.active-stall-picker h2,
.stall-panel h3 {
  margin: 0 0 12px;
}

/*
 * Each blocker names its own stall, so it is set apart rather than run
 * together: a volunteer reads one outstanding item at a time.
 */
.blocker-list {
  margin: 0 0 22px;
  padding: 0;
  list-style: none;
  min-width: 0;
}

.blocker {
  padding: 14px 18px;
  margin-bottom: 10px;
  border-left: 3px solid var(--orange);
  background: #fffdfb;
  min-width: 0;
  overflow-wrap: anywhere;
}

.blocker[data-blocker-code="readiness-unconfirmed"] {
  border-left-color: var(--surface);
}

/*
 * Enable Orders is visually its own decision, arriving in its own bordered
 * panel after readiness is confirmed. It is never adjacent to the readiness
 * control in a way that reads as one step with two buttons.
 */
.enable-orders-panel {
  margin-top: 24px;
  padding: 22px;
  border: 1px solid var(--orange);
  background: #fffaf5;
  min-width: 0;
}

.enable-orders-panel .hint {
  margin: 0 0 16px;
}

.capacity-panel .capacity-controls {
  margin-top: 16px;
}

.stall-queues {
  display: flex;
  flex-direction: column;
  gap: 20px;
  min-width: 0;
}

.stall-queues .queue-panel h4 {
  margin: 0 0 12px;
}

.stall-queues .queue-list,
.setup-list {
  margin: 0;
  padding: 0;
  list-style: none;
  min-width: 0;
}

.queue-row,
.setup-row {
  padding: 14px 0;
  border-top: 1px solid var(--border);
  min-width: 0;
  overflow-wrap: anywhere;
}

.menu-setup details,
.ingredient-setup details {
  margin-top: 16px;
}

.menu-setup summary,
.ingredient-setup summary {
  display: flex;
  align-items: center;
  width: fit-content;
  min-height: 44px;
  font-weight: 700;
  cursor: pointer;
}

.menu-setup details[open] summary,
.ingredient-setup details[open] summary {
  margin-bottom: 12px;
}

.menu-setup form,
.ingredient-setup form {
  max-width: 32rem;
  padding: 16px;
  border: 1px solid var(--border);
  background: rgb(255 255 255 / 0.72);
}

.menu-setup form > .hint,
.menu-setup label > .hint,
.ingredient-setup form > .hint,
.ingredient-setup label > .hint {
  margin: 0;
}

.menu-enabled-option {
  display: flex;
  align-items: center;
  gap: 10px;
  min-height: 44px;
}

.menu-enabled-option input {
  width: auto;
}

/*
 * A Recipe row is an Ingredient name and a number of units read together, so
 * the two stay on one line until the name needs its own. The field keeps a
 * full target either way: it is the thing being reached for one-handed, and a
 * blank one is a real answer — this Recipe does not use that Ingredient.
 */
.recipe-fieldset {
  /* fieldset is a row by default here; a Recipe reads down the page. */
  display: flex;
  flex-direction: column;
  gap: 10px;
  min-width: 0;
  margin: 0;
  padding: 12px;
  border: 1px solid var(--border);
}

.recipe-fieldset legend {
  padding: 0 6px;
  font-weight: 700;
}

.recipe-row {
  padding: 8px 0;
}

.recipe-label {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 8px 12px;
  min-height: 44px;
}

.recipe-units {
  width: 6rem;
  min-height: 44px;
}

/*
 * An ingredient card is read at arm's length in one hand, and the thing being
 * reached for is the field at the bottom of it. So the block above that field
 * is kept to what a volunteer has to know before typing — the name, the two
 * counts, the threshold, and any alert — and nothing in it is given room it
 * does not use. At 390×844 the padding, the gaps and the name all come down
 * together, because trimming any one of them alone just moves the field a few
 * pixels and leaves it below the fold.
 */
.ingredient-stock-card {
  display: grid;
  min-width: 0;
  gap: 10px;
  padding: clamp(14px, 3vw, 28px);
  border: 1px solid var(--border);
  border-left: 5px solid var(--surface);
  background: rgb(255 255 255 / 0.82);
}

.ingredient-stock-card[data-ingredient-status="Low Stock"] {
  border-left-color: var(--rose);
}

.ingredient-stock-card[data-ingredient-status="Not entered"] {
  border-left-color: var(--orange);
}

.ingredient-stock-heading {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: 8px 18px;
}

.ingredient-stock-name,
.ingredient-stock-card p {
  margin: 0;
}

.ingredient-stock-name {
  font-size: clamp(19px, 3vw, 30px);
  line-height: 1.2;
}

/*
 * The counts are the reading, not the caption. They were set smaller than the
 * ingredient's name, which put the largest type on the word a volunteer already
 * knows — they are standing in front of the potatoes — and the smallest on the
 * two numbers they came to the card to check. Available and Reserved lead now,
 * tabular so a column of ingredients lines up digit for digit, and the
 * threshold below stays a hint because it is the number that does not move.
 */
.ingredient-stock-card .stock-counts {
  font-size: clamp(15px, 2vw, 17px);
  font-variant-numeric: tabular-nums;
  letter-spacing: -0.01em;
}

.ingredient-threshold {
  font-size: 13px;
}

.ingredient-stock-status {
  padding: 5px 9px;
  border: 1px solid currentColor;
  border-radius: 999px;
  color: var(--muted);
  font-family: "JetBrains Mono", ui-monospace, monospace;
  font-size: 11px;
  font-weight: 700;
  text-transform: uppercase;
}

.ingredient-stock-card[data-ingredient-status="Low Stock"]
  .ingredient-stock-status {
  color: #9f1239;
}

.ingredient-stock-card[data-ingredient-status="Not entered"]
  .ingredient-stock-status {
  color: var(--orange-text);
}

/*
 * An active Restock Alert is the loudest thing on the card, and it is the only
 * thing on the card whose whole panel is tinted. The card's own left border is
 * left to say *which* state this ingredient is in; the alert says there is
 * something to do about it. Two rose left borders — the card's and the panel's —
 * read as one stripe with a gap in it at 390px, so the panel does not draw a
 * second one.
 */
.restock-alert {
  display: grid;
  gap: 8px;
  padding: 12px 14px;
  border: 1px solid #fecdd3;
  background: #fff1f2;
  color: #881337;
}

.restock-alert .eyebrow {
  margin: 0;
  font-weight: 700;
}

.restock-alert-text {
  font-weight: 500;
}

/*
 * Acknowledged is quieter and still present. It has to stay on the card: seen is
 * not fixed, and the alert clears only when Available rises above the threshold,
 * so a treatment that removed the panel would be telling a volunteer the
 * shortage was dealt with by looking at it.
 */
.restock-alert[data-acknowledged="true"] {
  border-color: #fed7aa;
  background: #fff7ed;
  color: #7c2d12;
}

.restock-alert .eyebrow,
.restock-alert .text-button {
  color: currentColor;
}

.restock-alert .text-button {
  justify-self: start;
  min-height: 44px;
  text-decoration: underline;
  text-underline-offset: 3px;
}

.ingredient-stock-card .stock-actions {
  grid-template-columns: repeat(2, minmax(0, 1fr));
  align-items: start;
}

/*
 * The two writes stay two forms, side by side where there is room. Adding units
 * and replacing the count are different acts with different consequences, and a
 * single form with a mode switch would be one control that does both.
 */
.stock-form {
  display: grid;
  min-width: 0;
  gap: 8px;
  padding: 12px;
  border: 1px solid var(--border);
  background: rgb(255 255 255 / 0.72);
}

.stock-form-title,
.stock-field-label {
  font-weight: 700;
}

.stock-field-label {
  display: grid;
  gap: 6px;
  min-width: 0;
}

.stock-form input,
.stock-form textarea {
  width: 100%;
  min-width: 0;
  box-sizing: border-box;
}

.stock-form button {
  justify-self: start;
  min-height: 44px;
}

@media (max-width: 820px) {
  .ingredient-stock-card .stock-actions {
    grid-template-columns: minmax(0, 1fr);
  }
}

/*
 * 390×844, one hand, and the measurement this section has to meet: the field and
 * the button that submits it are on screen together, without the volunteer
 * scrolling between typing a number and recording it.
 *
 * The savings come from the space around the reading, not from the reading. Every
 * guidance line, both counts, the threshold and the whole alert are still here
 * and still legible; what goes is the heading's gap, the card's padding, the gaps
 * between rows inside it, and the form's own padding. Nothing here reduces a tap
 * target below 44px, and the guidance keeps the body size the rest of the surface
 * uses — a stall in sun is the wrong place to save pixels on prose.
 */
@media (max-width: 480px) {
  .stall-setup-review:has(#ingredient-list) > .hint {
    margin: 0 0 12px;
  }

  #ingredient-list {
    display: grid;
    gap: 14px;
  }

  /* The section heading and its guidance are the last gap before the cards. */
  .stall-setup-review:has(#ingredient-list) h3 {
    margin: 0 0 8px;
  }

  #ingredient-list .ingredient-stock-card {
    gap: 8px;
    padding: 12px 14px;
  }

  /*
   * The status pill sits under the name rather than beside it. At 390px the two
   * of them competed for one line, and the name — the longest string on the card
   * — was the one that wrapped, putting a two-line ingredient name above a pill
   * that had space of its own all along.
   */
  .ingredient-stock-heading {
    gap: 4px 12px;
  }

  .ingredient-stock-card .stock-counts {
    font-size: 15px;
  }

  .ingredient-threshold {
    font-size: 12px;
  }

  /*
   * The hint above the button is what stood between the field and the submit.
   * It keeps its size and its wording and loses only the space around it.
   */
  .stock-form {
    gap: 6px;
    padding: 10px 12px;
  }

  .stock-form .stock-form-hint {
    line-height: 1.4;
  }

  .stock-form button {
    width: 100%;
    margin-top: 2px;
  }

  .restock-alert {
    padding: 10px 12px;
    gap: 6px;
  }
}

.queue-number {
  margin: 0 0 4px;
  font-weight: 600;
}

.queue-lines {
  margin: 0 0 4px;
}

.queue-empty {
  padding: 14px 0;
  border-top: 1px solid var(--border);
}

.stall-panel .event-state[data-capacity-state="Open"] {
  background: #dcfce7;
}

@media (min-width: 720px) {
  .stall-queues {
    display: grid;
    grid-template-columns: 1fr 1fr;
  }
}

/* ------------------------------------------------------------------ *
 * Stall Staff — All Stalls (#35)
 * ------------------------------------------------------------------ */

/*
 * The queue is the first operational task, so this surface spends as little
 * height as it can before reaching it: at 390x844 a Received Stall Order's
 * action has to be reachable without scrolling.
 */
.compact-intro {
  margin-bottom: 0;
}

.compact-intro h1 {
  margin-bottom: 0;
}

.all-stalls-workspace {
  display: flex;
  flex-direction: column;
  gap: 22px;
  margin-top: 20px;
  min-width: 0;
}

/*
 * Keep the one poll-safe live region with Stall Staff's work. A second region
 * would break ADR 0003 point 11; moving this one into either workspace would
 * let polling destroy an announcement mid-read.
 */
#status:has(~ #my-stall-workspace),
#status:has(~ #all-stalls-workspace) {
  position: sticky;
  z-index: 2;
  top: 12px;
  right: auto;
  bottom: auto;
  max-width: 100%;
  margin: 0 0 16px;
  transform: none;
  /*
   * Here the region is a *record*, not a toast, so it is visible whenever it
   * holds anything — including after the five-second `visible` class comes off,
   * which the shared rule uses to slide a toast away and this surface must
   * survive. Stall Staff reads an arrival while both hands are busy; an
   * announcement that withdrew itself on a timer would be gone by the time they
   * looked up. `:empty` below is what hides it when there is nothing to record.
   */
  visibility: visible;
  color: var(--text);
  background: #fffaf5;
  border: 1px solid var(--border);
  border-left: 3px solid var(--orange);
  box-shadow: none;
  opacity: 1;
  transition: none;
}

#status.error:has(~ #my-stall-workspace),
#status.error:has(~ #all-stalls-workspace) {
  color: #7f1d1d;
  background: #fef2f2;
  border-left-color: #b91c1c;
}

#status:empty:has(~ #my-stall-workspace),
#status:empty:has(~ #all-stalls-workspace) {
  margin-bottom: 0;
  padding: 0;
  border: 0;
  /* Nothing recorded, nothing shown — and nothing left to hit-test either. The
     node stays in the tree because `aria-live` announces into it. */
  visibility: hidden;
}

/*
 * Compact selection. One line and one control — an always-expanded selector
 * obscured the queue in the prototype audit, and the selector itself lives in
 * the dialog behind Change stalls.
 */
.monitoring {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 10px 16px;
  padding: 12px 18px;
  border: 1px solid var(--border);
  background: rgb(255 255 255 / 0.78);
  min-width: 0;
}

.monitoring-summary {
  margin: 0;
  min-width: 0;
  font-weight: 600;
  overflow-wrap: anywhere;
}

.monitoring button {
  min-height: 44px;
  padding: 0 18px;
  border: 1px solid var(--surface);
  background: rgb(255 255 255 / 0.9);
}

.setup-empty {
  padding: 22px;
  border: 1px solid var(--orange);
  background: #fffaf5;
  min-width: 0;
}

.setup-empty h3 {
  margin: 0 0 10px;
}

.setup-empty .hint {
  margin: 0;
}

.stall-dialog {
  width: min(30rem, calc(100vw - 32px));
  padding: clamp(20px, 4vw, 30px);
  border: 1px solid var(--border);
  background: #fffaf5;
  color: var(--text);
}

.stall-dialog::backdrop {
  background: rgb(17 24 39 / 0.45);
}

.stall-dialog h2 {
  margin: 0 0 10px;
}

.stall-choices {
  margin: 18px 0;
  padding: 0;
  border: 0;
}

.stall-choices legend {
  padding: 0;
  color: var(--muted);
}

/* 44px targets: the label is what the operator actually hits. */
.stall-choice {
  display: flex;
  align-items: center;
  gap: 12px;
  min-height: 44px;
  padding: 4px 0;
  border-bottom: 1px solid var(--border);
  cursor: pointer;
}

.stall-choice input {
  width: 22px;
  height: 22px;
  accent-color: var(--orange);
}

.stall-dialog-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 12px;
}

.stall-dialog-actions .clipped:not(.primary) {
  border: 1px solid var(--surface);
  background: transparent;
}

/*
 * One swimlane per selected stall. The lane is the action boundary, so it is
 * drawn as a boundary: its own bordered field, headed by its own stall's name.
 */
.swimlanes {
  display: flex;
  flex-direction: column;
  gap: 18px;
  min-width: 0;
}

.stall-lane {
  padding: 16px 18px;
  border: 1px solid var(--border);
  border-left: 5px solid var(--orange);
  background: rgb(255 255 255 / 0.85);
  min-width: 0;
}

.stall-lane-name {
  margin: 0;
  font-size: clamp(20px, 4vw, 24px);
  letter-spacing: -0.01em;
}

.stall-lane-counts {
  margin: 2px 0 12px;
  font-family: "JetBrains Mono", ui-monospace, monospace;
  font-size: 11px;
  color: var(--muted);
}

.lane-queue + .lane-queue {
  margin-top: 14px;
}

.lane-queue h3 {
  margin: 0 0 8px;
  font-size: 13px;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--muted);
}

.lane-list {
  display: grid;
  gap: 10px;
  margin: 0;
  padding: 0;
  list-style: none;
  min-width: 0;
}

.stall-order-card {
  padding: 12px 14px;
  border: 1px solid var(--border);
  background: white;
  min-width: 0;
  overflow-wrap: anywhere;
}

/*
 * The whole safety property in one line of markup: the stall's name and the
 * Stall Order number are one string, on the card, above the action. Two stalls
 * both holding Stall Order 42 are never told apart by number alone.
 */
.stall-order-title {
  margin: 0;
  font-size: 17px;
}

.stall-order-lines {
  margin: 4px 0 0;
}

.stall-order-actions {
  display: grid;
  gap: 8px;
  margin-top: 10px;
}

.stall-order-actions button {
  min-height: 44px;
  padding: 8px 14px;
  font-size: 14px;
  line-height: 1.25;
  text-align: left;
}

.ready-underway,
.lane-empty {
  margin: 0;
}

.duty-note {
  margin: 0;
}

.role-nav-link[aria-disabled="true"] {
  opacity: 0.5;
  cursor: not-allowed;
}

@media (min-width: 720px) {
  .stall-lane {
    padding: 22px 24px;
  }

  .lane-queue + .lane-queue {
    margin-top: 18px;
  }
}

@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    scroll-behavior: auto !important;
    animation: none !important;
    transition: none !important;
  }
}

/* ------------------------------------------------------------------ *
 * Stall Staff — safe handoff (#37)
 *
 * Shared by My Stall and All Stalls. Nothing here renders, masks, blurs, or
 * reveals an expected Pickup Code: the surface has never been sent one, so
 * there is no styling decision to get wrong.
 * ------------------------------------------------------------------ */

.handoff {
  min-width: 0;
}

.queue-row .handoff {
  margin-top: 10px;
}

.handoff-form {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: 8px;
  margin: 0;
}

.handoff-label {
  font-weight: 600;
  font-size: 14px;
}

.handoff-form input.stated-pickup-code {
  min-height: 44px;
  max-width: 9ch;
  padding: 0 12px;
  font-family: "JetBrains Mono", ui-monospace, monospace;
  font-size: 18px;
  letter-spacing: 0.25em;
}

.handoff-form input.stated-pickup-code[aria-invalid="true"] {
  border-color: #b91c1c;
  outline: 2px solid #b91c1c;
  outline-offset: 1px;
}

.handoff-hint,
.handoff-error {
  margin: 0;
}

.handoff-error {
  padding: 8px 10px;
  border-left: 3px solid #b91c1c;
  background: #fef2f2;
  color: #7f1d1d;
  font-weight: 600;
  overflow-wrap: anywhere;
}

.handoff-form button.confirm-handoff {
  min-height: 44px;
  justify-self: start;
  padding: 8px 18px;
  text-align: left;
}

/*
 * The Handoff Grace Period. It is loud because it is short and reversible:
 * ten seconds is exactly as long as an operator has to notice a mistake.
 */
.handoff-grace {
  display: grid;
  gap: 8px;
  padding: 12px;
  border: 1px solid var(--orange);
  background: #fff7ed;
  min-width: 0;
}

/* The one node that counts down, and the one node that replaces it. */
.handoff-countdown {
  margin: 0;
  font-family: "JetBrains Mono", ui-monospace, monospace;
  font-size: 13px;
  font-weight: 700;
  color: var(--orange-text);
  overflow-wrap: anywhere;
}

.handoff-countdown[data-expired] {
  color: var(--text);
}

/* The persistent caption naming the window's length. */
.handoff-window-hint {
  margin: 0;
  font-size: 12px;
  color: var(--text);
}

.handoff-grace button.undo-handoff {
  min-height: 44px;
  justify-self: start;
  padding: 8px 18px;
  border: 1px solid var(--surface);
  background: rgb(255 255 255 / 0.9);
  text-align: left;
}

.handed-off-row {
  padding: 10px 0;
  border-top: 1px solid var(--border);
  border-left: 3px solid #15803d;
  background: #f0fdf4;
  min-width: 0;
  overflow-wrap: anywhere;
  color: var(--muted);
}

.lane-handed-off .handed-off-row {
  padding: 8px 10px;
  border: 1px solid var(--border);
  background: rgb(255 255 255 / 0.7);
}

/*
 * Stall Staff fulfilment controls, shared by My Stall and All Stalls.
 *
 * These lived in a `<style>` block in each page, on the reasoning that they
 * belong to two surfaces rather than to every role. They never rendered: the
 * server sends `style-src 'self'` with no `unsafe-inline` (`src/server.js`), so
 * the browser refuses an inline block outright and both pages ran with this
 * file as their only stylesheet. The rules are here now because here is where a
 * rule takes effect.
 *
 * What they answer for is one measurement no other surface has to meet: a 44px
 * target held in one hand at 390×844, in noise, in sun.
 */
.fulfilment-actions {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  margin-top: 0.75rem;
  min-width: 0;
}

.fulfilment-control,
.fulfilment-confirm,
.fulfilment-abandon,
.exceptional-disclose {
  min-height: 44px;
  width: 100%;
  box-sizing: border-box;
  padding: 0.6rem 0.9rem;
  text-align: left;
  white-space: normal;
  overflow-wrap: anywhere;
  line-height: 1.35;
}

.fulfilment-control {
  background: #fffaf5;
  border: 1px solid #e5e7eb;
  color: #111827;
  font: inherit;
  cursor: pointer;
}

.fulfilment-control:hover:not(:disabled) {
  border-color: #fdba74;
}

.fulfilment-control:disabled {
  color: #4b5563;
  cursor: not-allowed;
  opacity: 0.55;
}

/*
 * Cancelling a line and sending food backwards are not the same weight as
 * marking one ready, and the surface says so before the confirmation does.
 */
.cancel-line,
.return-received {
  border-left: 3px solid #b91c1c;
}

.undo-ready {
  border-left: 3px solid #ea580c;
}

.fulfilment-panel {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  margin-top: 0.5rem;
  padding: 0.75rem;
  border: 1px solid #e5e7eb;
  border-radius: 8px;
  background: #fffaf5;
  min-width: 0;
}

.fulfilment-question,
.fulfilment-reason-label,
.exceptional-name-label,
.exceptional-contact-label {
  margin: 0;
  font-weight: 500;
  color: #111827;
  overflow-wrap: anywhere;
}

.fulfilment-reason,
.exceptional-name,
.exceptional-contact {
  width: 100%;
  box-sizing: border-box;
  min-height: 44px;
  padding: 0.6rem;
  border: 1px solid #e5e7eb;
  border-radius: 8px;
  font: inherit;
  color: #111827;
  background: #ffffff;
  resize: vertical;
}

.fulfilment-reason:focus-visible,
.exceptional-name:focus-visible,
.exceptional-contact:focus-visible {
  outline: 2px solid #ea580c;
  outline-offset: 2px;
}

.fulfilment-reason[aria-invalid="true"],
.exceptional-name[aria-invalid="true"],
.exceptional-contact[aria-invalid="true"] {
  border-color: #b91c1c;
}

.fulfilment-hint,
.exceptional-hint {
  margin: 0;
}

/*
 * A refusal is not a toast: it is written next to the field it was made in and
 * it stays there until the operator changes something. `.handoff-error` carries
 * the same duty and is styled above; it keeps the treatment it has been
 * rendering with rather than adopting this one.
 */
.fulfilment-error,
.exceptional-error {
  margin: 0;
  color: #b91c1c;
  overflow-wrap: anywhere;
}

.handoff-exceptional {
  margin-top: 0.75rem;
  padding-top: 0.75rem;
  border-top: 1px dashed #e5e7eb;
  min-width: 0;
}

.exceptional-disclose {
  background: transparent;
  border: 1px solid #e5e7eb;
  color: #4b5563;
  font: inherit;
  cursor: pointer;
}

.exceptional-disclose[aria-expanded="true"] {
  color: #111827;
  border-color: #fdba74;
}

.exceptional-form {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  margin-top: 0.5rem;
  min-width: 0;
}

/* Toast flags a reopened ticket at the top of the ticket. So does this. */
.recalled-flag {
  margin: 0 0 0.35rem;
  padding: 0.25rem 0.5rem;
  font-family: "JetBrains Mono", ui-monospace, monospace;
  font-size: 0.75rem;
  letter-spacing: 0.04em;
  color: #b91c1c;
  background: #fef2f2;
  border-left: 3px solid #b91c1c;
  overflow-wrap: anywhere;
}

/*
 * The arrival cue's visible half, for My Stall's `.queue-row` and All Stalls'
 * `.stall-order-card` alike. Position and colour rather than an announcement:
 * ADR 0003 point 11 allows one polite atomic live region per Role Workspace,
 * the announcement goes through the `#status` region each page already owns,
 * and this half is a treatment that says nothing to a screen reader at all.
 *
 * It lives here rather than in a per-page `<style>` block because the server
 * sends `style-src 'self'` with no `unsafe-inline`, so an inline block is
 * refused by the browser and the cue silently does not render. Last in the
 * file so the border wins against `.stall-order-card`'s own border shorthand
 * without needing a louder selector.
 */
[data-arrived] {
  border-left: 3px solid #ea580c;
  background: linear-gradient(90deg, #fff7ed, transparent 60%);
}

@media (prefers-reduced-motion: no-preference) {
  @keyframes stall-order-arrived {
    from {
      background-color: #fdba74;
    }
    to {
      background-color: transparent;
    }
  }

  [data-arrived] {
    animation: stall-order-arrived 1.2s ease-out 1;
  }
}

/* ------------------------------------------------------------------ *
 * Registration Desk — Credit history on the Guest record (D4)
 *
 * Shopify holds a balance and its movements together on the customer profile.
 * The transferable part is the pairing: a balance a volunteer can be challenged
 * on needs its movements adjacent, not on another screen.
 *
 * The balance is the largest thing here because it is what a Guest disputes. The
 * movements below it are compact and monospaced so two loads line up digit for
 * digit — a running balance that does not align is a column nobody can check at
 * arm's length (rule 49).
 * ------------------------------------------------------------------ */

.credit-history {
  display: flex;
  flex-direction: column;
  gap: 14px;
  padding-top: 20px;
  border-top: 1px solid var(--border);
  min-width: 0;
}

.credit-history h3 {
  margin: 0;
}

.record-balance-line {
  display: flex;
  flex-direction: column;
  gap: 4px;
  margin: 0;
}

.record-balance-label {
  color: var(--muted);
  font-family: "JetBrains Mono", ui-monospace, monospace;
  font-size: 0.68rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
}

.record-balance {
  font-size: clamp(28px, 4vw, 38px);
  font-weight: 400;
  letter-spacing: -0.04em;
  line-height: 1.1;
}

.credit-movements {
  display: flex;
  flex-direction: column;
  gap: 0;
  margin: 0;
  padding: 0;
  list-style: none;
  min-width: 0;
}

/*
 * One movement per row. The amount leads because a Guest disputing a balance
 * names an amount first; the balance it produced follows, so the column reads
 * as the arithmetic the volunteer is being asked to defend.
 */
.credit-movement {
  display: grid;
  grid-template-columns: minmax(0, auto) minmax(0, 1fr);
  gap: 2px 14px;
  padding: 12px 0;
  border-top: 1px solid var(--border);
  min-width: 0;
}

.credit-movement:first-child {
  border-top: 0;
}

.movement-amount {
  grid-column: 1;
  font-family: "JetBrains Mono", ui-monospace, monospace;
  font-size: 1rem;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  color: var(--orange-text);
}

.movement-kind {
  grid-column: 2;
}

.movement-balance {
  grid-column: 1 / -1;
  font-family: "JetBrains Mono", ui-monospace, monospace;
  font-size: 0.8rem;
  font-variant-numeric: tabular-nums;
  color: var(--muted);
}

.movement-time {
  grid-column: 1 / -1;
  font-family: "JetBrains Mono", ui-monospace, monospace;
  font-size: 0.72rem;
  color: var(--muted);
}

/*
 * The scope note. It is muted but never hidden: this desk cannot read a Guest's
 * spending, so a list that looked complete would be the misleading part.
 */
.credit-history-scope {
  padding-inline-start: 14px;
  border-inline-start: 3px solid var(--peach);
}

/* ------------------------------------------------------------------ *
 * Registration Desk — the review reads back every contact (D5)
 * ------------------------------------------------------------------ */

/*
 * A contact row is a `<div>` inside `.review-detail`, so it already inherits the
 * `display: contents` above that flattens it into the two-column grid. What it
 * adds is the value itself, set in mono: a volunteer reading a phone number or
 * an email back against the person in front of them is comparing characters, and
 * proportional digits are the wrong tool for that.
 */
.review-contact {
  font-family: "JetBrains Mono", ui-monospace, monospace;
  font-size: 0.86rem;
  font-variant-numeric: tabular-nums;
}

/* ==================================================================== *
 * V2 route sections (#89)
 *
 * This file is one stylesheet shared by every surface, and the V2 redesign
 * lands one route at a time. Each route owns exactly one section below, in
 * route order, and adds nothing outside it. Two builders working on
 * different routes therefore never append to the same place, and a rule
 * cannot leak: every selector in a section is scoped by that route's own id
 * or route-level class — `#access-route` and `.access-page` below — never by
 * a bare element and never by a class another surface also carries.
 *
 * The scoping is not tidiness. `.status` is shared by all six existing
 * surfaces, and `header`, `main`, `h1`, `h2`, `h3`, `form`, `label`,
 * `fieldset`, `legend`, `input`, `select`, `textarea` and `button` are all
 * styled bare at the top of this file. A new structural element therefore
 * arrives already styled by rules nobody wrote for it, and an unscoped rule
 * added here changes screens its author never opened. Check the bare-element
 * selectors before styling a new element, and override every property they
 * set rather than the one that looked wrong.
 *
 * A section stays empty apart from its marker until its route's ticket
 * lands. Do not move, reorder, merge, or rename the markers.
 * ==================================================================== */

/* ==== V2 route section: /access ==== */

/*
 * Access QR Redemption (#90).
 *
 * The reader is a Guest holding a phone at arm's length in a crowd, one
 * second after pointing a camera at a volunteer's screen. So the route is a
 * waypoint rather than a workspace: one centred card, one sentence, and no
 * controls at all. `main`'s 1280px column and its 70px/100px workspace
 * padding are both wrong here and are both overridden — an id beats the bare
 * `main` rule and the 820px media query that narrows it.
 */
.access-page {
  background: #fff8f6;
}

/*
 * The visible home link is the sole intentional composition divergence from
 * Stitch. Accepted #90 coverage requires one 44px keyboard-focusable target,
 * so only the workspace-sized header padding goes.
 */
.access-page header {
  padding-block: 20px 0;
}

#access-route {
  display: grid;
  align-content: center;
  justify-items: center;
  width: min(100% - (2 * var(--v2-gutter)), 460px);
  min-height: calc(100vh - 180px);
  margin-inline: auto;
  padding-block: 40px;
  text-align: center;
}

.access-card {
  display: grid;
  justify-items: center;
  width: 100%;
  min-width: 0;
}

/*
 * The state mark. It is `aria-hidden`, and every distinction it draws —
 * hue, glyph, and ring style — is already stated in words directly beneath
 * it, so nothing here is the only carrier of a meaning.
 */
.access-mark {
  display: grid;
  place-items: center;
  width: 96px;
  height: 96px;
  margin: 0 0 20px;
  border: 1px solid rgb(234 88 12 / 0.28);
  border-radius: 50%;
  background: radial-gradient(
    circle at 50% 50%,
    rgb(253 186 116 / 0.24),
    rgb(255 255 255 / 0.5)
  );
}

.access-mark-glyph {
  display: grid;
  place-items: center;
  width: 54px;
  height: 54px;
  border: 2px solid var(--orange);
  border-radius: 50%;
  color: var(--orange-text);
  font-size: 26px;
  line-height: 1;
}

/*
 * Loading is a ring with one open quadrant, turning. The global
 * `prefers-reduced-motion: reduce` block earlier in this file cancels every
 * animation with `!important`, so this stops there and the ring simply sits
 * with its gap — the state is still named in the heading either way.
 */
#access-route[data-state="loading"] .access-mark-glyph {
  border-top-color: transparent;
  animation: access-mark-turn 1.1s linear infinite;
}

@keyframes access-mark-turn {
  to {
    transform: rotate(360deg);
  }
}

#access-route[data-state="committed"] .access-mark-glyph::before {
  content: "✓";
}

#access-route[data-state="failure"] .access-mark-glyph::before,
#access-route[data-state="offline"] .access-mark-glyph::before {
  content: "!";
}

#access-route[data-state="failure"] .access-mark,
#access-route[data-state="offline"] .access-mark {
  border-color: rgb(159 18 57 / 0.28);
  background: radial-gradient(
    circle at 50% 50%,
    rgb(225 29 72 / 0.14),
    rgb(255 255 255 / 0.5)
  );
}

#access-route[data-state="failure"] .access-mark-glyph,
#access-route[data-state="offline"] .access-mark-glyph {
  border-color: var(--v2-danger-text);
  color: var(--v2-danger-text);
}

/* Offline is the one state the Guest can clear without walking anywhere, so
   its ring is drawn as provisional rather than settled. */
#access-route[data-state="offline"] .access-mark-glyph {
  border-style: dashed;
}

.access-live {
  display: grid;
  justify-items: center;
  width: 100%;
}

#access-heading {
  margin: 0;
  font-size: clamp(30px, 8.4vw, 40px);
  font-weight: 300;
  line-height: 1.3;
  letter-spacing: -0.045em;
}

#access-status {
  max-width: 34ch;
  margin: 14px auto 0;
  color: var(--muted);
  font-size: 16px;
  line-height: 1.6;
}

/* A refusal stays secondary to its heading, matching the canonical reading. */
#access-status.error-text {
  color: var(--muted);
  font-weight: 400;
}

.access-help {
  margin: 32px 0 0;
  color: var(--muted);
  font-size: 13px;
  line-height: 1.6;
}

/* A refusal carries exactly one instruction. Both sentences below already
   name the Registration Desk, so the standing note underneath would be a
   second, weaker copy of the thing the Guest has just been told to do. */
#access-route[data-state="failure"] .access-help,
#access-route[data-state="offline"] .access-help {
  display: none;
}

.access-page a:focus-visible {
  outline: var(--v2-focus-ring);
  outline-offset: var(--v2-focus-offset);
}

/* ==== V2 route section: /guest ==== */

.guest-page {
  container-type: inline-size;
  container-name: guest-page;
  background:
    linear-gradient(180deg, rgb(255 255 255 / 0.96), transparent 32%), #fffaf5;
}

.guest-page .atmosphere {
  inset: -28% -60% auto 58%;
  height: 52vh;
  opacity: 0.48;
}

.guest-page > header {
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  gap: 8px;
  width: min(100% - 24px, 680px);
  padding-block: 4px;
  border-bottom: 1px solid var(--v2-panel-border);
}

.guest-page .brand {
  grid-column: 2;
  justify-self: center;
  min-height: var(--v2-target);
  font-size: 14px;
  font-weight: 700;
  letter-spacing: -0.02em;
  text-transform: none;
}

.guest-page .role {
  font-size: 10px;
}

.guest-page .guest-shell {
  display: grid;
  gap: 10px;
  width: min(100% - 16px, 680px);
  max-width: 680px;
  padding-block: 8px 72px;
}

.guest-page .guest-route-state,
.guest-page .role-sign-in {
  width: min(calc(100% - 24px), 720px);
  margin: 18px auto;
  padding: 24px;
}

.guest-page .guest-route-state[data-state="loading"] {
  border-color: var(--v2-panel-border);
}

.guest-page .guest-route-state[data-state="failure"],
.guest-page .guest-route-state[data-state="expired-session"] {
  border-color: var(--v2-danger-text);
}

.guest-page .guest-shell > .eyebrow,
.guest-page .guest-greeting {
  margin: 0;
}

.guest-page .guest-credit-label {
  margin-top: 4px;
}

.guest-page .guest-greeting {
  font-size: 20px;
  font-weight: 400;
  line-height: 1.15;
  letter-spacing: -0.04em;
}

.guest-page .ready-panel,
.guest-page .undo-panel {
  padding: 12px;
  border: 0;
  border-radius: 0;
  clip-path: polygon(
    10px 0,
    100% 0,
    100% calc(100% - 10px),
    calc(100% - 10px) 100%,
    0 100%,
    0 10px
  );
}

.guest-page .ready-panel {
  padding: 11px 12px 12px;
  color: white;
  background: var(--orange-text);
}

.guest-page .ready-panel .eyebrow,
.guest-page .ready-total {
  color: white;
}

.guest-page .ready-panel h2 {
  margin: 0;
  font-size: 20px;
  font-weight: 600;
  line-height: 1.1;
  color: white;
}

.guest-page .ready-instruction,
.guest-page .ready-empty {
  color: white;
}

.guest-page .guest-session-actions {
  position: static;
  display: flex;
  align-items: center;
  gap: 6px;
  min-width: 0;
  margin-left: auto;
  grid-column: 3;
  justify-self: end;
}

.guest-page #guest-logout {
  min-height: var(--v2-target);
  padding-inline: 4px;
  color: var(--surface);
}

.guest-page .pickup-notifications {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  align-items: center;
  gap: 3px 10px;
  margin-top: 0;
  padding: 7px 0;
  border-bottom: 1px solid var(--v2-panel-border);
}

.guest-page .pickup-notifications .eyebrow,
.guest-page .pickup-notifications-detail {
  margin: 0;
  color: var(--muted);
}

.guest-page .pickup-notifications h3 {
  margin: 0;
  color: var(--surface);
  font-size: 13px;
  line-height: 1.25;
}

.guest-page .pickup-notifications-detail {
  max-width: 54ch;
  font-size: 11px;
  line-height: 1.3;
}

.guest-page #enable-pickup-notifications {
  grid-column: 2;
  grid-row: 1 / 4;
  width: fit-content;
  max-width: 100%;
  min-height: var(--v2-target);
  padding-inline: 14px;
  overflow-wrap: anywhere;
  color: white;
  border-color: var(--orange-text);
  background: var(--orange-text);
  font-size: 12px;
  line-height: 1.2;
}

.guest-page #enable-pickup-notifications:disabled {
  color: #4b5563;
  border-color: #d1d5db;
  background: #f3f4f6;
}

.guest-page .ready-row {
  padding: 9px 10px;
  border-left: 0;
  background: rgb(124 45 18 / 0.62);
  clip-path: none;
}

.guest-page .ready-row:focus-visible {
  outline: var(--v2-focus-ring);
  outline-offset: var(--v2-focus-offset);
}

.guest-page .ready-list {
  gap: 6px;
  margin-top: 8px;
}

.guest-page .ready-stall {
  color: white;
  font-size: 15px;
}

.guest-page .ready-figure-label {
  color: white;
  font-size: 12px;
}

.guest-page .ready-figure-value {
  color: white;
  font-size: min(10vw, 36px);
}

/*
 * Exactly one dominant Pickup Code per viewport. When a Stall Order is Ready,
 * its card in "Your Orders" below carries the code at 56px dark on green —
 * the one numeral a Guest reads from across a room. The Collect Now banner's
 * own copy of the same immutable code is demoted to counter scale so the two
 * never compete; it stays present (small, labelled, white) because the banner
 * is half of the two-part handoff and must remain self-sufficient if the
 * order card is scrolled away.
 */
.guest-page .ready-code .ready-figure-value {
  font-size: 14px;
  line-height: 1.2;
}

@media (max-width: 360px) {
  .guest-page .brand {
    font-size: 13px;
  }

  .guest-page .guest-session-actions {
    gap: 3px;
  }

  .guest-page .pickup-notifications {
    grid-template-columns: 1fr;
  }

  .guest-page #enable-pickup-notifications {
    grid-column: 1;
    grid-row: auto;
  }
}

@container guest-page (max-width: 360px) {
  .guest-page > header {
    display: flex;
    justify-content: space-between;
    gap: 4px;
  }
}

.guest-page .balance-panel {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(120px, 100%), 1fr));
  gap: 12px;
  margin-top: 0;
  padding: 12px 14px;
  color: var(--surface);
  border: 1px solid rgb(234 88 12 / 0.22);
  border-radius: var(--v2-radius);
  background: rgb(255 237 229 / 0.9);
}

.guest-page .balance-panel .eyebrow {
  margin: 0 0 4px;
  color: var(--orange-text);
  font-size: 10px;
}

.guest-page #guest-balance {
  margin: 0;
  color: var(--orange-text);
  font-size: 26px;
  font-weight: 650;
  letter-spacing: -0.04em;
}

.guest-page #guest-balance::after {
  content: " Credit";
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0;
}

.guest-page #event-disclosure {
  margin: 4px 0 0;
  color: var(--muted);
  font-size: 11px;
  line-height: 1.35;
}

.guest-page .credit-preview {
  min-width: 0;
  padding-left: 12px;
  border-left: 1px solid rgb(194 65 12 / 0.22);
}

.guest-page #credit-summary {
  display: grid;
  gap: 4px;
  margin: 0;
  padding: 0;
  list-style: none;
}

.guest-page #credit-summary li {
  display: flex;
  gap: 6px;
  justify-content: space-between;
  min-width: 0;
  font-size: 11px;
}

.guest-page #credit-summary span {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.guest-page #credit-summary strong {
  flex: 0 0 auto;
  color: var(--orange-text);
  font-family: "JetBrains Mono", ui-monospace, monospace;
  font-size: 10px;
}

.guest-page .ordering {
  grid-template-columns: minmax(0, 1fr);
  gap: 10px;
  margin-top: 8px;
}

.guest-page .ordering h2,
.guest-page .basket-panel h2,
.guest-page .order-panel h2 {
  font-size: 22px;
  font-weight: 600;
}

/*
 * Credit headroom on the header row, updated in place with every Add. The
 * short state uses the same danger treatment as the dock's insufficient
 * message, so one meaning keeps one color across the surface. The row shares
 * height with the eyebrow, so the readout costs no vertical space.
 */
.guest-page .ordering-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 8px;
}

.guest-page .ordering-credit {
  color: var(--orange-text);
  font-family: "JetBrains Mono", ui-monospace, monospace;
  font-size: 11px;
  font-weight: 700;
}

.guest-page .ordering-credit.short {
  color: var(--v2-danger-text);
}

.guest-page .stall-list {
  gap: 8px;
  margin-top: 10px;
  border-top: 0;
}

.guest-page .stall-row {
  grid-template-columns: repeat(auto-fit, minmax(min(120px, 100%), 1fr));
  gap: 4px 12px;
  padding: 11px 12px;
  border: 1px solid var(--v2-panel-border);
  border-left: 1px solid var(--v2-panel-border);
  border-radius: var(--v2-radius);
  background: var(--v2-panel);
}

.guest-page .stall-row h3 {
  font-size: 15px;
  font-weight: 650;
}

.guest-page .capacity {
  color: var(--orange-text);
  font-weight: 700;
}

.guest-page .orders-ahead {
  grid-column: 1 / -1;
  font-size: 11px;
}

.guest-page .orders-ahead.busy {
  color: var(--v2-danger-text);
  font-size: 12px;
}

.guest-page .guest-menu {
  gap: 6px;
  margin-top: 4px;
}

.guest-page .guest-menu li {
  min-height: var(--v2-target);
  padding-top: 6px;
  border-top: 1px solid var(--border);
  font-size: 13px;
}

.guest-page .guest-menu button {
  min-width: 64px;
  min-height: var(--v2-target);
  padding-inline: 12px;
}

.guest-page .basket-panel {
  margin-top: 2px;
  padding: 16px;
  border-radius: var(--v2-radius);
}

.guest-page #basket-lines {
  gap: 12px;
  margin-block: 14px;
}

.guest-page .basket-line {
  gap: 8px;
  padding-bottom: 12px;
}

.guest-page .basket-line-heading {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  gap: 12px;
  align-items: center;
}

.guest-page .remove-item {
  justify-self: end;
}

.guest-page .basket-line-edit {
  display: grid;
  gap: 4px;
}

.guest-page .basket-edit-label {
  color: #fed7aa;
  font-family: "JetBrains Mono", ui-monospace, monospace;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
}

.guest-page #basket-total,
.guest-page #basket-status {
  margin-block: 8px;
}

.guest-page .basket-checkout-dock {
  display: contents;
}

@media (max-width: 560px) {
  html:has(.guest-page) {
    /* Keep the final content row visibly clear of the inset checkout dock. */
    --guest-dock-clearance: 128px;

    scroll-padding-bottom: calc(
      var(--guest-dock-clearance) + env(safe-area-inset-bottom)
    );
  }

  /*
   * The insufficient-Credit notice adds a row to the dock, so it needs more
   * reserved space than the two-row dock to keep the stepper uncovered.
   */
  html:has(.basket-checkout-dock.short) {
    --guest-dock-clearance: 168px;
  }

  .guest-page .guest-shell {
    padding-bottom: calc(
      var(--guest-dock-clearance) + env(safe-area-inset-bottom)
    );
  }

  /*
   * A floating basket, not a banner (#180). The canonical bar is rounded and
   * inset with page ground visible either side, so the dock keeps gutters and
   * a radius instead of running edge to edge over the content it floats above.
   */
  .guest-page .basket-checkout-dock {
    position: fixed;
    z-index: 20;
    right: 10px;
    bottom: calc(10px + env(safe-area-inset-bottom));
    left: 10px;
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(138px, 1fr);
    gap: 4px 8px;
    align-items: center;
    /*
     * No explicit width here: `left`/`right` already stretch the dock between
     * its gutters, and a width would overflow the viewport by the combined
     * insets (the #180 review found the dock running 20px off the right edge
     * that way).
     */
    padding: 10px 14px;
    color: white;
    background: var(--orange-text);
    border-radius: 16px;
    box-shadow:
      0 -4px 16px rgb(17 24 39 / 0.18),
      0 4px 16px rgb(17 24 39 / 0.22);
  }

  .guest-page .basket-checkout-dock #basket-total,
  .guest-page .basket-checkout-dock #basket-status,
  .guest-page .basket-checkout-dock .place-order {
    margin: 0;
  }

  .guest-page .basket-checkout-dock #basket-total {
    color: white;
    font-size: 11px;
    line-height: 1.2;
    overflow-wrap: anywhere;
  }

  .guest-page .basket-checkout-dock #basket-status:not(:empty) {
    grid-column: 1 / -1;
    grid-row: 2;
    color: white;
    font-size: 11px;
    line-height: 1.3;
  }

  /*
   * The commit control reads as the primary action inside the bar: the same
   * near-black filled treatment every other committing control on this
   * surface carries, not a pale fill that reads as secondary (#180).
   */
  .guest-page .basket-checkout-dock .place-order {
    grid-column: 2;
    grid-row: 1;
    min-height: var(--v2-target);
    color: white;
    border-color: transparent;
    background: var(--surface);
  }
}

@media (max-width: 280px) {
  html:has(.guest-page) {
    --guest-dock-clearance: 184px;
  }

  html:has(.basket-checkout-dock.short) {
    --guest-dock-clearance: 232px;
  }

  .guest-page .basket-checkout-dock {
    grid-template-columns: minmax(0, 1fr);
  }

  .guest-page .basket-checkout-dock .place-order,
  .guest-page .basket-checkout-dock #basket-status:not(:empty) {
    grid-column: 1;
  }

  .guest-page .basket-checkout-dock .place-order {
    grid-row: 2;
  }

  .guest-page .basket-checkout-dock #basket-status:not(:empty) {
    grid-row: 3;
  }
}

.guest-page .basket-review {
  margin-top: 16px;
  padding: 16px;
  border: 1px solid var(--v2-panel-border);
  border-radius: var(--v2-radius);
  color: var(--surface);
  background: #fff7ed;
  box-shadow: inset 4px 0 0 var(--v2-accent);
}

.guest-page .basket-review .eyebrow {
  color: var(--orange-text);
}

.guest-page .basket-review h3 {
  margin: 4px 0 8px;
  font-size: 20px;
}

.guest-page .review-lines {
  display: grid;
  gap: 8px;
  margin: 14px 0;
  padding: 0;
  list-style: none;
}

.guest-page .review-lines li {
  padding-bottom: 8px;
  border-bottom: 1px solid var(--v2-panel-border);
  font-size: 13px;
}

.guest-page .review-total {
  margin: 12px 0;
  color: var(--orange-text);
  font-family: var(--mono);
  font-weight: 800;
}

.guest-page .review-actions {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 8px;
}

.guest-page .review-actions button {
  min-height: var(--v2-target);
  padding-inline: 10px;
}

@media (max-width: 360px) {
  .guest-page .review-actions {
    grid-template-columns: 1fr;
  }
}

.guest-page .order-panel,
.guest-page .credit-tools,
.guest-page .activity-panel {
  margin-top: 8px;
}

.guest-page .order-panel {
  padding: 16px;
}

.guest-page .order-card {
  padding: 14px;
}

/*
 * In Your Orders, each Stall Order is a small status board rather than a
 * sentence. The state leads, then the two changing/needed figures can be read
 * vertically without scanning punctuation. This mirrors F3's quiet wait-state
 * hierarchy while keeping Pickup Code prominent before the order is ready.
 */
.guest-page .order-progress-list {
  display: grid;
  gap: 10px;
  margin: 12px 0 0;
  padding: 0;
  list-style: none;
}

.guest-page .order-progress {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  gap: 7px 14px;
  padding: 12px;
  border: 1px solid var(--v2-panel-border);
  border-radius: 12px;
  background: rgb(255 255 255 / 0.03);
}

.guest-page .order-progress-heading {
  display: grid;
  gap: 2px;
}

.guest-page .stall-order-number,
.guest-page .order-progress-label {
  color: var(--muted);
  font-family: "JetBrains Mono", ui-monospace, monospace;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
}

.guest-page .order-progress-status {
  align-self: start;
  padding: 5px 8px;
  border-radius: 999px;
  color: var(--orange-text);
  background: rgb(245 158 11 / 0.12);
  font-size: 12px;
  white-space: nowrap;
}

.guest-page .order-progress-status.is-ready {
  color: #052e16;
  background: #86efac;
}

.guest-page .order-progress-status.is-complete {
  color: var(--muted);
  background: rgb(255 255 255 / 0.08);
}

.guest-page .order-progress-figures {
  display: grid;
  grid-column: 1 / -1;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 8px;
}

.guest-page .order-progress-figure {
  display: grid;
  gap: 1px;
  padding-top: 8px;
  border-top: 1px solid var(--v2-panel-border);
}

.guest-page .order-progress-value {
  font-family: "JetBrains Mono", ui-monospace, monospace;
  font-size: 24px;
  font-weight: 700;
  line-height: 1.1;
  font-variant-numeric: tabular-nums;
}

.guest-page .order-progress-cue {
  max-width: 19ch;
  margin-top: 4px;
  color: var(--muted);
  font-size: 12px;
  line-height: 1.35;
}

.guest-page .pickup-code-value {
  color: var(--orange-text);
  font-size: 30px;
}

/*
 * Ready is the one state a Guest acts on from across a room, so its card —
 * not just the badge — carries the green treatment and its Pickup Code is the
 * largest numeral on the surface. Disney's I'm-Here screen wins on exactly
 * this: one enormous pickup number. The code stays inside the Guest's own
 * workspace, so no privacy or recovery contract moves; the waiting cards keep
 * the quiet orange figure.
 */
.guest-page .order-progress.is-ready {
  border-color: #22c55e;
  background: rgb(34 197 94 / 0.14);
}

.guest-page .order-progress.is-ready .order-progress-status {
  color: #052e16;
  background: #86efac;
  font-size: 14px;
}

.guest-page .order-progress.is-ready .pickup-code-value {
  color: #052e16;
  background: #86efac;
  align-self: start;
  justify-self: start;
  padding: 2px 10px;
  border-radius: 10px;
  font-size: 56px;
}

.guest-page .credit-tools {
  grid-template-columns: minmax(0, 1fr);
  gap: 10px;
}

.guest-page .receive-panel,
.guest-page .transfer-panel,
.guest-page .activity-panel {
  min-width: 0;
  padding: 16px;
  border: 1px solid var(--v2-panel-border);
  border-radius: var(--v2-radius);
  background: var(--v2-panel);
  backdrop-filter: none;
}

.guest-page .receive-panel h2,
.guest-page .transfer-panel h2,
.guest-page .activity-panel h2 {
  margin: 4px 0 8px;
  font-size: 22px;
  font-weight: 600;
}

.guest-page .receive-panel {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(112px, 100%), 1fr));
  gap: 8px 14px;
  align-items: start;
}

.guest-page .receive-panel > .eyebrow,
.guest-page .receive-panel > h2,
.guest-page .receive-panel > .hint {
  grid-column: 1 / -1;
}

.guest-page .receive-panel .access-qr {
  grid-row: span 3;
  width: 100%;
  max-width: 160px;
  padding: 8px;
}

.guest-page .receive-panel .help,
.guest-page .receive-panel .recipient-code,
.guest-page .receive-panel .outbound-link {
  min-width: 0;
  margin: 0;
}

.guest-page .receive-panel .outbound-link {
  align-self: end;
  overflow-wrap: anywhere;
}

.guest-page .transfer-panel form,
.guest-page #dispute-form {
  gap: 12px;
  margin-top: 14px;
}

.guest-page .transfer-review {
  margin-top: 14px;
  padding: 14px;
  border: 1px solid rgb(234 88 12 / 0.3);
  border-left: 4px solid var(--orange);
  border-radius: var(--v2-radius);
}

.guest-page .transfer-review .form-actions {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, auto);
  gap: 8px;
}

.guest-page .transfer-review button,
.guest-page .transfer-panel form > button,
.guest-page #dispute-form > button {
  min-height: var(--v2-target);
}

.guest-page #dispute-entry-fieldset {
  display: grid;
  gap: 8px;
  min-width: 0;
}

.guest-page #credit-activity {
  display: grid;
  gap: 8px;
  margin: 0;
  padding: 0;
  list-style: none;
}

.guest-page .activity-entry {
  min-width: 0;
  padding: 10px;
  border: 1px solid var(--v2-panel-border);
  border-radius: var(--v2-radius);
  background: #fffaf5;
  font-size: 12px;
  line-height: 1.45;
}

.guest-page .activity-entry-summary {
  overflow-wrap: anywhere;
}

.guest-page .dispute-entry {
  grid-template-columns: var(--v2-target) minmax(0, 1fr);
  gap: 10px;
  align-items: center;
  margin: -10px;
  padding: 10px;
  cursor: pointer;
}

.guest-page .dispute-entry input[type="checkbox"] {
  width: var(--v2-target);
  min-width: var(--v2-target);
  height: var(--v2-target);
  min-height: var(--v2-target);
  margin: 0;
}

.guest-page .dispute-entry-copy {
  display: grid;
  gap: 3px;
  min-width: 0;
  font-weight: 400;
}

.guest-page .dispute-entry-copy strong {
  color: var(--surface);
  overflow-wrap: anywhere;
}

.guest-page .dispute-entry-copy span,
.guest-page .dispute-evidence-claim {
  color: var(--muted);
}

.guest-page .dispute-evidence-status {
  display: inline-flex;
  align-items: center;
  min-height: 28px;
  padding: 3px 8px;
  border: 1px solid rgb(234 88 12 / 0.34);
  border-radius: 999px;
  color: var(--orange-text);
  background: #ffedd5;
}

.guest-page .dispute-evidence-claim {
  margin: 8px 0 0;
  padding-top: 8px;
  border-top: 1px solid var(--v2-panel-border);
  overflow-wrap: anywhere;
}

.guest-page .dispute-details-label {
  margin: 4px 0 -6px;
}

.guest-page #dispute-issue-type,
.guest-page #dispute-form > button {
  min-height: var(--v2-target);
}

.guest-page #dispute-entry-fieldset[aria-invalid="true"],
.guest-page #dispute-issue-type[aria-invalid="true"],
.guest-page #dispute-claim[aria-invalid="true"] {
  border-color: #b91c1c;
  outline: 2px solid #b91c1c;
  outline-offset: 1px;
}

.guest-page .order-status-summaries {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin: 10px 0;
  padding: 0;
  list-style: none;
}

.guest-page .order-status-summary {
  padding: 6px 9px;
  border: 1px solid var(--v2-panel-border);
  border-radius: 999px;
  color: var(--orange-text);
  background: var(--v2-panel);
  font-size: 11px;
  font-weight: 700;
}

.guest-shell .basket-quantity {
  display: grid;
  grid-template-columns: var(--v2-target) minmax(88px, 1fr) var(--v2-target);
  align-items: center;
  width: min(100%, 220px);
  overflow: hidden;
  border: 1px solid rgb(255 255 255 / 0.28);
  border-radius: var(--v2-radius);
}

.guest-shell .quantity-control {
  display: grid;
  place-items: center;
  width: var(--v2-target);
  min-width: var(--v2-target);
  min-height: var(--v2-target);
  padding: 0;
  color: white;
  background: rgb(255 255 255 / 0.1);
  font-size: 22px;
}

.guest-shell .quantity-control:disabled {
  color: rgb(255 255 255 / 0.45);
  cursor: not-allowed;
}

.guest-shell .basket-quantity-value {
  min-width: 0;
  color: white;
  font-family: "JetBrains Mono", ui-monospace, monospace;
  font-size: 12px;
  font-variant-numeric: tabular-nums;
  text-align: center;
}

.guest-shell .remove-item {
  min-width: var(--v2-target);
  min-height: var(--v2-target);
}

.guest-shell #basket-total::after {
  content: " Credit";
}

.guest-shell button:focus-visible,
.guest-shell a:focus-visible,
.guest-shell input:focus-visible,
.guest-shell select:focus-visible,
.guest-shell textarea:focus-visible {
  outline: var(--v2-focus-ring);
  outline-offset: var(--v2-focus-offset);
}

/*
 * A recovered Guest mutation is more than a semantic lock. Native disabled
 * compositing made restored Transfer and Dispute controls look writable at
 * 390px, so the Guest surface uses an opaque neutral treatment plus a dashed
 * field edge. The shape change keeps the state legible without relying on hue.
 */
.guest-page .guest-shell input:disabled,
.guest-page .guest-shell select:disabled,
.guest-page .guest-shell textarea:disabled {
  opacity: 1;
  color: #374151;
  border-color: #6b7280;
  border-style: dashed;
  background: #e5e7eb;
  cursor: not-allowed;
}

.guest-page .guest-shell input[type="checkbox"]:disabled {
  outline: 2px solid #6b7280;
  outline-offset: 2px;
}

.guest-page .guest-shell button:disabled:not(.quantity-control) {
  opacity: 1;
  color: #4b5563;
  border-color: #9ca3af;
  background: #d1d5db;
  box-shadow: inset 0 0 0 2px #9ca3af;
  cursor: not-allowed;
}

/* Guest feedback is durable beside its form; this one live region announces
   in flow so it cannot cover the evidence at phone width. */
.guest-page #guest-status.status {
  position: static;
  right: auto;
  bottom: auto;
  max-width: none;
  min-height: 0;
  margin: 0;
  padding: 0;
  overflow: hidden;
  color: var(--surface);
  border: 0;
  background: transparent;
  box-shadow: none;
  transform: none;
  visibility: hidden;
  transition: none;
}

.guest-page #guest-status.status.visible {
  min-height: var(--v2-target);
  padding: 12px 14px;
  border: 1px solid var(--v2-panel-border);
  border-left: 4px solid var(--v2-accent);
  background: var(--v2-panel);
  visibility: visible;
}

.guest-page #guest-status.status.error {
  color: var(--v2-danger-text);
  border-color: var(--v2-danger-text);
  background: #fff7ed;
}

/* ==== V2 route section: /desk ==== */

/*
 * The desk surface uses the V2 presentation foundation. One vertical lane,
 * three states per step, exactly one dominant. All panels share the same
 * radius, border, and fill so the volunteer never has to relearn the surface.
 */
.desk-page header,
.desk-page main {
  width: min(100% - (2 * var(--v2-gutter)), 760px);
}

.desk-page header {
  flex-wrap: wrap;
  align-items: flex-start;
  gap: 16px;
  padding-block: 18px;
  border-bottom: 1px solid var(--v2-panel-border);
}

.desk-page .role-nav {
  min-width: 0;
  justify-content: flex-end;
}

.desk-page .role-nav .role {
  overflow-wrap: anywhere;
}

.desk-page .desk-intro {
  padding-top: 8px;
  margin-bottom: 4px;
}

.desk-page .desk-intro h1 {
  font-size: 26px;
  font-weight: 400;
  letter-spacing: -0.03em;
}

.desk-page .desk-workspace {
  gap: 36px;
  margin-top: 24px;
}

.desk-page .sequence-heading {
  font-size: 11px;
}

.desk-page .guest-strip {
  padding: 16px 16px;
  border: 1px solid var(--v2-panel-border);
  border-radius: var(--v2-radius);
  background: var(--v2-panel);
}

.desk-page .guest-strip:focus-visible {
  outline: var(--v2-focus-ring);
  outline-offset: var(--v2-focus-offset);
}

.desk-page .strip-actions {
  gap: 16px;
}

.desk-page .desk-sequence {
  gap: 24px;
}

.desk-page .sequence-step {
  padding-top: 20px;
  border-top: 1px solid var(--v2-panel-border);
  border-radius: var(--v2-radius);
}

.desk-page .sequence-step[data-state="current"] {
  padding: 20px;
  border: 1px solid var(--v2-panel-border);
  border-inline-start: 3px solid var(--orange);
  background: var(--v2-panel);
  border-radius: var(--v2-radius);
}

.desk-page .step-index {
  font-size: 14px;
}

.desk-page .sequence-step[data-state="current"] .step-name {
  font-size: clamp(28px, 3vw, 32px);
}

.desk-page .step-body {
  gap: 20px;
  margin-top: 16px;
}

.desk-page .branch-guidance {
  margin: 0;
}

.desk-page .desk-read-state {
  padding: 10px 12px;
  border-inline-start: 3px solid var(--orange);
  border-radius: 0 var(--v2-radius) var(--v2-radius) 0;
  background: rgb(253 186 116 / 0.16);
}

.desk-page .desk-read-state[data-state="failure"],
.desk-page .desk-read-state[data-state="validation"] {
  border-inline-start-color: var(--v2-danger-text);
  background: #fff5f7;
}

.desk-page .register-branch {
  padding-inline-start: 14px;
  border-inline-start: 3px solid var(--peach);
  border-radius: 0 var(--v2-radius) var(--v2-radius) 0;
}

.desk-page .guest-record {
  padding: 20px;
  border: 1px solid var(--v2-panel-border);
  border-radius: var(--v2-radius);
  background: var(--v2-panel);
}

.desk-page .desk-form {
  padding-top: 16px;
  border-top: 1px solid var(--v2-panel-border);
}

/*
 * The lookup as this surface's scan/search bar (F5 Shopify POS app frame 1:
 * the sale starts at one prominent search field, not a form to fill in). The
 * field is the largest input on the screen and carries the commit beside it at
 * desk width, so identification is type-then-Enter; below desk width it wraps
 * back to the stacked phone layout. The label stays above because "Registered
 * Contact" is what names the field, and the branch guidance still follows the
 * submit control.
 */
.desk-search .desk-search-label {
  font-family: "JetBrains Mono", ui-monospace, monospace;
  font-size: 0.68rem;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--muted);
}

.desk-search .desk-search-row {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
}

.desk-search .desk-search-row input {
  flex: 1 1 15rem;
  min-height: 48px;
  font-size: 1rem;
}

.desk-page .registration-review {
  padding: 20px;
  border: 1px solid var(--v2-panel-border);
  border-radius: var(--v2-radius);
  background: var(--v2-panel);
}

.desk-page .review-detail,
.desk-page .guest-detail {
  gap: 4px 16px;
}

.desk-page .review-detail dt,
.desk-page .guest-detail dt {
  font-size: 0.68rem;
}

.desk-page .review-duplicate {
  margin: 14px 0 0;
  padding-inline-start: 12px;
  border-inline-start: 3px solid var(--v2-danger-text);
}

/*
 * One dominant action per decision point (F5 Shopify POS app frame 5: the
 * return summary carries ONE dominant refund action). The desk's confirm step
 * holds three decisions — confirm the new Guest, use the existing Guest,
 * correct the contact (rule D5: three decisions, three controls, never
 * merged) — and Load Credit has its own commit. Hierarchy is carried by size,
 * fill, AND hue: the applicable terminal commit is the full-height control in
 * the one accent fill this page uses; correcting the contact keeps its
 * separate control but reads as an alternate, not a peer; and the step's own
 * non-committing primaries (`Find Guest`, `Review registration`) step down to
 * a neutral outlined weight, because a scroll showing two equal filled-dark
 * controls made a volunteer choose by reading rather than by looking. The
 * duplicate branch hides new-Guest confirmation, so its own applicable commit
 * (`Use existing Guest`) takes the same accent treatment — one dominant
 * control per view, never two at once.
 */
.desk-page .review-actions .primary,
.desk-page #credit-form button[type="submit"] {
  align-self: stretch;
  min-height: 56px;
  font-size: 1rem;
  font-weight: 600;
}

/*
 * The accent commit. `--orange-text` is the darkened ramp value this file
 * already trusts for contrast, so the accent is the same single hue the page
 * already speaks rather than a second brand colour — and white on `#c2410c`
 * clears AA comfortably at any of these sizes.
 */
.desk-page .review-actions #confirm-registration,
.desk-page .review-actions .use-existing.primary {
  border: 0;
  background: var(--orange-text);
  color: white;
}

.desk-page .review-actions #confirm-registration:hover,
.desk-page .review-actions .use-existing.primary:hover {
  background: #9a3412;
}

/*
 * Step-down weight for the primaries that do NOT commit anything: lookup and
 * review are reads, so they read as neutral outlined controls rather than as
 * peers of the accent commit further down the same scroll.
 */
.desk-page #guest-search button[type="submit"],
.desk-page #guest-register button[type="submit"] {
  border: 1px solid var(--muted);
  background: transparent;
  color: var(--surface);
}

.desk-page #guest-search button[type="submit"]:hover,
.desk-page #guest-register button[type="submit"]:hover {
  border-color: var(--surface);
}

.desk-page .credit-load,
.desk-page .access-qr-panel,
.desk-page .guest-recovery {
  min-width: 0;
}

.desk-page .credit-load fieldset,
.desk-page .access-qr-panel fieldset,
.desk-page .guest-recovery fieldset {
  margin: 0;
  padding: 20px;
  border: 1px solid var(--v2-panel-border);
  border-radius: var(--v2-radius);
  background: var(--v2-panel);
}

.desk-page .credit-load legend,
.desk-page .access-qr-panel legend,
.desk-page .guest-recovery legend {
  padding: 0;
  color: var(--muted);
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
}

.desk-page .credit-load .field,
.desk-page .access-qr-panel .field,
.desk-page .guest-recovery .field {
  display: flex;
  flex-direction: column;
  gap: 6px;
}

.desk-page .credit-load label,
.desk-page .access-qr-panel label,
.desk-page .guest-recovery label {
  font-size: 13px;
  font-weight: 500;
  color: var(--surface);
}

.desk-page .credit-load input,
.desk-page .access-qr-panel input,
.desk-page .guest-recovery input {
  min-height: var(--v2-target);
  padding: 10px 12px;
  border: 1px solid var(--v2-panel-border);
  border-radius: var(--v2-radius);
  background: white;
  font-size: 16px;
  color: var(--surface);
}

.desk-page .credit-load input:focus-visible,
.desk-page .access-qr-panel input:focus-visible,
.desk-page .guest-recovery input:focus-visible {
  outline: var(--v2-focus-ring);
  outline-offset: var(--v2-focus-offset);
}

.desk-page .credit-load input:disabled {
  opacity: 1;
  color: #374151;
  border-color: #6b7280;
  border-style: dashed;
  background: #e5e7eb;
  cursor: not-allowed;
}

.desk-page .credit-load button:disabled {
  opacity: 1;
  color: #4b5563;
  border: 2px dashed #6b7280;
  background: #d1d5db;
  box-shadow: inset 0 0 0 1px #9ca3af;
  cursor: not-allowed;
}

.desk-page .credit-load .actions,
.desk-page .access-qr-panel .actions,
.desk-page .guest-recovery .actions {
  display: flex;
  flex-wrap: wrap;
  gap: 12px;
  margin-top: 16px;
}

.desk-page .credit-load button,
.desk-page .access-qr-panel button,
.desk-page .guest-recovery button {
  min-height: var(--v2-target);
}

.desk-page .guest-recovery .choice {
  min-height: var(--v2-target);
}

.desk-page .guest-recovery input[type="radio"] {
  accent-color: var(--orange);
  width: 20px;
  height: 20px;
}

.desk-page .access-details {
  margin-top: 16px;
}

.desk-page .next-guest {
  display: block;
  margin-top: 12px;
  min-height: var(--v2-target);
}

.desk-page .access-countdown {
  font-size: 0.85rem;
}

.desk-page .access-countdown[data-expired="true"] {
  color: var(--v2-danger-text);
}

.desk-page .workspace .text-button {
  min-height: var(--v2-target);
}

.desk-page .action-feedback {
  border-radius: var(--v2-radius);
}

.desk-page .action-feedback[data-state="pending"] {
  border-color: var(--v2-panel-border);
  background: var(--v2-panel);
}

.desk-page .action-feedback[data-state="committed"] {
  border-color: var(--v2-panel-border);
  background: var(--v2-panel);
}

.desk-page .action-feedback[data-state="refused"] {
  border-color: var(--v2-danger-text);
  background: #fff5f7;
}

@media (max-width: 640px) {
  .desk-page header,
  .desk-page main {
    width: min(100% - (2 * var(--v2-gutter)), 760px);
  }

  .desk-page .desk-intro h1 {
    font-size: 24px;
  }

  .desk-page .sequence-step[data-state="current"] .step-name {
    font-size: 28px;
  }

  .desk-page .record-heading {
    font-size: 28px;
  }

  .desk-page .guest-strip .guest-detail {
    grid-template-columns: minmax(0, 1fr);
    gap: 2px;
  }

  .desk-page .guest-strip .guest-detail dd {
    margin-bottom: 8px;
  }

  .desk-page .guest-strip .guest-detail > div:last-child dd {
    margin-bottom: 0;
  }

  .action-feedback-detail {
    grid-template-columns: 1fr;
    gap: 0 0;
  }

  .action-feedback-detail dd {
    margin-bottom: 0.45rem;
  }
}

/* ==== V2 route section: /staff ==== */

/*
 * My Stall's V2 presentation is deliberately route-scoped. The role shell,
 * Action Feedback implementation and fulfilment behaviour stay shared; these
 * rules only arrange the one-stall readiness, capacity, menu and stock duty.
 */
.my-stall-page {
  background:
    linear-gradient(155deg, rgb(255 255 255 / 0.96) 0 46%, transparent 72%),
    radial-gradient(circle at 92% 8%, #fed7aa 0 5%, transparent 28%), #fffaf5;

  /*
   * Three tints, named once. They are route-scoped rather than added to
   * `:root` because only My Stall states things this way, and they are tokens
   * rather than repeated literals because `--tint` is used by four unrelated
   * rules — the ticket strip, the Handoff Grace Period, confirmed readiness,
   * and the medallion — which must stay the same colour as each other.
   *
   * `--tint-strong` is the Ready for Pickup strip: it is the one queue state
   * with a deadline attached, so it is the one that carries more weight. Both
   * tints keep `--surface` text past 12:1.
   */
  --stall-tint: #fdece2;
  --stall-tint-soft: #fff1e7;
  --stall-tint-strong: #fbc99e;
  /* The accent's own hover, one step darker on the same orange ramp. */
  --stall-accent-hover: #9a3412;
}

.my-stall-page header,
.my-stall-page main {
  width: min(100% - (2 * var(--v2-gutter)), 760px);
}

.my-stall-page main,
.all-stalls-page main {
  container-type: inline-size;
}

.my-stall-page header {
  align-items: flex-start;
  gap: 16px;
  padding-block: 18px;
  border-bottom: 1px solid var(--v2-panel-border);
}

.my-stall-page .role-nav {
  justify-content: flex-end;
  gap: 4px 14px;
}

.my-stall-page .role-nav .role {
  flex-basis: 100%;
  text-align: right;
}

.my-stall-page main {
  padding-block: 26px 80px;
}

.my-stall-page .intro {
  max-width: 620px;
}

.my-stall-page .intro h1 {
  margin: 8px 0 12px;
  font-size: clamp(42px, 11vw, 68px);
}

.my-stall-page[data-stall-selected] .intro {
  display: none;
}

.my-stall-page .my-stall-workspace {
  gap: 18px;
  margin-top: 18px;
}

/*
 * Guidance is kept and set quietly. Every sentence on this surface is load
 * bearing — what readiness does not do, what pausing does not stop, what a
 * Stock Correction does not touch — so density comes from typography rather
 * than from deleting the sentence a volunteer needs at 7pm.
 */
.my-stall-page .hint {
  margin: 8px 0 12px;
  max-width: 58ch;
  font-size: 13px;
  line-height: 1.5;
}

.my-stall-page #add-menu-item-effect,
.my-stall-page #ingredient-stock-guidance {
  font-style: italic;
}

.my-stall-page .my-stall-route-state,
.my-stall-page .active-stall-picker,
.my-stall-page .session-ended-notice,
.my-stall-page .event-lock {
  padding: 20px;
  border: 1px solid var(--v2-panel-border);
  border-left: 5px solid var(--orange-text);
  border-radius: var(--v2-radius);
  background: var(--v2-panel);
  box-shadow: 0 12px 30px rgb(120 53 15 / 0.08);
}

.my-stall-page .my-stall-route-state h2,
.my-stall-page .my-stall-route-state p,
.my-stall-page .session-ended-notice,
.my-stall-page .event-lock h2,
.my-stall-page .event-lock p {
  margin-block: 0 8px;
}

/*
 * The route states this surface can actually be in, each stated in its own
 * colour rather than all five arriving as the same orange panel: a refused
 * route and a route still loading are not the same news. `showRouteState` in
 * `my-stall.js` sets exactly these five kinds.
 */
.my-stall-page .my-stall-route-state[data-route-state="offline"],
.my-stall-page .my-stall-route-state[data-route-state="session-expired"],
.my-stall-page .my-stall-route-state[data-route-state="failure"] {
  border-left-color: var(--v2-danger-text);
  background: #fff5f7;
}

.my-stall-page .my-stall-route-state[data-route-state="failure"] h2,
.my-stall-page .my-stall-route-state[data-route-state="offline"] h2,
.my-stall-page .my-stall-route-state[data-route-state="session-expired"] h2 {
  color: var(--v2-danger-text);
}

/*
 * Loading and No Open Event are neither refusals nor news to act on, so they
 * are held in the neutral panel with a dashed edge: the surface is waiting or
 * has nothing to run, and neither is an error a volunteer caused.
 */
.my-stall-page .my-stall-route-state[data-route-state="loading"],
.my-stall-page .my-stall-route-state[data-route-state="no-open-event"] {
  border-left-color: var(--muted);
  border-left-style: dashed;
}

.my-stall-page .role-sign-in {
  border-radius: var(--v2-radius);
  background: var(--v2-panel);
}

.my-stall-page .active-stall-picker h2 {
  font-size: clamp(24px, 7vw, 34px);
  line-height: 1.08;
}

.my-stall-page .active-stall-picker .stall-form {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  align-items: end;
  gap: 10px;
}

.my-stall-page .active-stall-picker .stall-form label {
  grid-column: 1 / -1;
}

.my-stall-page .empty-state {
  margin: 14px 0 0;
  padding: 14px;
  border: 1px dashed var(--muted);
  border-radius: var(--v2-radius);
}

.my-stall-page .stall-panel {
  gap: 18px;
}

/*
 * The stall identity is the canonical screen's masthead, not a card floating
 * inside the page: a medallion, the stall's own name set in the accent, the
 * "Active stall" caption *under* the name, and Change as a quiet action at the
 * right. It is a rule below a title rather than a black slab, which is what
 * keeps the queue — the actual work — inside the first viewport.
 */
.my-stall-page .active-stall {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  align-items: center;
  gap: 4px 14px;
  padding: 0 0 14px;
  border-left: 0;
  border-bottom: 1px solid var(--v2-panel-border);
}

/*
 * Decorative. Generated content cannot be marked `aria-hidden`, so the glyph is
 * chosen to be a shape rather than a word: it adds nothing a reader would lose
 * and nothing a listener needs, and the stall's real name sits beside it.
 */
.my-stall-page .active-stall::before {
  content: "▤";
  display: grid;
  place-items: center;
  width: 46px;
  height: 46px;
  color: var(--orange-text);
  border-radius: 999px;
  background: var(--stall-tint-soft);
  font-size: 20px;
  line-height: 1;
}

.my-stall-page .active-stall-identity {
  display: flex;
  flex-direction: column;
  min-width: 0;
}

.my-stall-page .active-stall .eyebrow {
  order: 2;
  margin: 2px 0 0;
  color: var(--muted);
}

/*
 * Display type, so it takes `--orange` under the size split documented in
 * `:root` rather than the darkened text accent: 3.43:1 over the warm white,
 * against the 3:1 large-text threshold, at a floor of 28px bold.
 */
.my-stall-page #active-stall-heading {
  margin: 0;
  color: var(--orange);
  font-size: clamp(28px, 8vw, 42px);
  line-height: 1.02;
  overflow-wrap: anywhere;
}

.my-stall-page #change-stall {
  align-self: start;
  min-width: var(--v2-target);
  min-height: var(--v2-target);
  padding: 8px 0 8px 11px;
  color: var(--orange-text);
  border-color: transparent;
  background: transparent;
  font-size: 14px;
  font-weight: 700;
  text-align: right;
}

.my-stall-page #capacity-state {
  grid-column: 2 / -1;
  justify-self: start;
  min-height: 30px;
  padding: 6px 11px;
  color: var(--surface);
  border: 0;
  border-radius: 999px;
  background: #fed7aa;
  font-family: "JetBrains Mono", ui-monospace, monospace;
  font-size: 11px;
  font-weight: 800;
  text-transform: uppercase;
}

.my-stall-page #capacity-state::before {
  content: "● ";
}

.my-stall-page .active-stall .queue-counts {
  grid-column: 2 / -1;
  display: flex;
  flex-wrap: wrap;
  gap: 6px 14px;
  margin: 0;
  color: var(--muted);
  font-family: "JetBrains Mono", ui-monospace, monospace;
  font-size: 12px;
}

/*
 * Fulfillment is the task Stall Staff came here to do. Keep its two live
 * queues immediately below the immutable stall identity; readiness, Capacity
 * State, menu, and Ingredient Stock remain available below as setup context.
 * Toast's oldest-left rail becomes oldest-top on the phone rather than a
 * horizontal overflow trap.
 */
.my-stall-page .stall-queues {
  gap: 12px;
}

.my-stall-page .queue-heading-row,
.my-stall-page .queue-panel-heading {
  display: flex;
  align-items: end;
  justify-content: space-between;
  gap: 10px;
}

.my-stall-page .queue-heading-row .eyebrow,
.my-stall-page .queue-heading-row h3,
.my-stall-page .queue-panel-heading h4 {
  margin: 0;
}

.my-stall-page .queue-heading-row h3 {
  font-size: clamp(24px, 7vw, 32px);
  line-height: 1;
}

.my-stall-page .queue-order-note,
.my-stall-page .queue-priority {
  margin: 0;
  color: var(--muted);
  font-family: "JetBrains Mono", ui-monospace, monospace;
  font-size: 10px;
  font-weight: 800;
  line-height: 1.25;
  text-align: right;
  text-transform: uppercase;
}

.my-stall-page .queue-panel {
  padding: 14px;
  border-radius: var(--v2-radius);
  background: rgb(255 255 255 / 0.9);
  box-shadow: 0 8px 22px rgb(120 53 15 / 0.05);
}

.my-stall-page .queue-panel-heading {
  align-items: center;
  padding-bottom: 10px;
  border-bottom: 1px solid var(--v2-panel-border);
}

.my-stall-page .queue-panel-heading h4 {
  font-size: 18px;
}

.my-stall-page .queue-panel:has(#ready-orders) .queue-priority {
  color: var(--orange-text);
}

.my-stall-page .queue-list {
  display: grid;
  gap: 8px;
}

/*
 * A queue row is a ticket: a tinted strip states which Stall Order this is and
 * what state it is in, and everything actionable sits under it. The state word
 * is set in the strip rather than in the row text because `.queue-number`
 * carries the immutable target label — stall name and Stall Order number — and
 * nothing may be appended to it. The state word is its own `aria-hidden`
 * sibling: it restates the panel heading this row already sits under, so it is
 * shown and not spoken.
 */
.my-stall-page .queue-row {
  display: grid;
  gap: 5px;
  padding: 0 0 12px;
  overflow: hidden;
  border: 1px solid var(--v2-panel-border);
  border-left: 4px solid var(--surface);
  border-radius: 6px;
  background: white;
}

.my-stall-page .queue-row > :not(.queue-strip) {
  margin-inline: 12px;
}

.my-stall-page .queue-strip {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: 2px 10px;
  padding: 9px 12px;
  background: var(--stall-tint);
}

.my-stall-page .queue-state {
  margin: 0;
  color: var(--surface);
  font-family: "JetBrains Mono", ui-monospace, monospace;
  font-size: 10px;
  font-weight: 800;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

.my-stall-page #ready-orders .queue-row {
  border-left-color: var(--orange-text);
  background: #fffaf5;
}

.my-stall-page #ready-orders .queue-strip {
  background: var(--stall-tint-strong);
}

.my-stall-page .queue-number,
.my-stall-page .queue-lines {
  margin: 0;
}

.my-stall-page .queue-number {
  color: var(--surface);
  font-size: 15px;
  font-weight: 850;
  line-height: 1.25;
}

.my-stall-page .queue-lines {
  color: var(--surface);
  font-size: 13px;
  line-height: 1.35;
}

/*
 * The elapsed timer, where the Toast KDS reference puts its large elapsed
 * time: in the tinted strip, in distance-legible monospace. It escalates with
 * the wait — amber past three minutes, red past five — so a queue falling
 * behind announces itself without anybody comparing six numbers. The tiers are
 * set as `data-wait` on the row/card by both staff surfaces.
 */
.queue-meta {
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 1px;
  margin-left: auto;
}

.queue-timer,
.stall-order-timer {
  margin: 0;
  color: var(--surface);
  font-family: "JetBrains Mono", ui-monospace, monospace;
  font-size: 16px;
  font-variant-numeric: tabular-nums;
  font-weight: 800;
  line-height: 1.1;
}

[data-wait="warn"] .queue-timer,
.all-stalls-page [data-wait="warn"] .stall-order-timer {
  color: var(--orange-text);
}

[data-wait="late"] .queue-timer,
.all-stalls-page [data-wait="late"] .stall-order-timer {
  color: var(--v2-danger-text);
}

/*
 * Non-colour escalation cue: at red tier a literal "LATE" word sits beside the
 * timer, so the worst waits announce themselves as text too, not only as hue.
 * Compact by design — one small caps token in the tinted strip keeps the
 * collapsed-record density the round-2 review asked to preserve.
 */
.queue-late {
  padding: 1px 5px;
  border-radius: 4px;
  background: var(--v2-danger-text);
  color: #fff;
  font-size: 10px;
  font-weight: 800;
  letter-spacing: 0.08em;
}

/*
 * An empty queue is a state, not a gap: it keeps the height and the dashed
 * outline of the row that would be there, so a stall with nothing waiting reads
 * as caught up rather than as a panel that failed to render.
 */
.my-stall-page .queue-empty,
.my-stall-page .handed-off-empty {
  min-height: 44px;
  margin: 0;
  padding: 12px;
  border: 1px dashed var(--muted);
  border-radius: 6px;
  background: rgb(255 255 255 / 0.6);
}

/*
 * Validation. The shared boundary marks the field; on this route the marked
 * field also takes the refusal colour on its label, so what to fix is legible
 * without reading the record.
 */
.my-stall-page :is(input, select, textarea)[aria-invalid="true"] {
  border-color: var(--v2-danger-text);
  background: #fff5f7;
}

.my-stall-page label:has([aria-invalid="true"]) {
  color: var(--v2-danger-text);
  font-weight: 700;
}

.my-stall-page .fulfilment-actions {
  gap: 6px;
  margin-top: 6px;
}

.my-stall-page .fulfilment-actions:has(.mark-ready):has(.cancel-line) {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
}

/*
 * Every primary control on this route carries the accent rather than the
 * near-black of the shared shell — Sign in, Serve this stall, Confirm handoff,
 * Enable Orders, Add Menu Item, the stock controls, and Mark Ready below. It is
 * deliberately the whole set and not a chosen four: a volunteer learns "the
 * filled orange one commits" once, and a control that opted out would read as a
 * different kind of thing. `--orange-text` is the darkened accent, so white on
 * it stays past 4.5:1 whatever size the label is set at.
 */
.my-stall-page .primary {
  background: var(--orange-text);
}

.my-stall-page .primary:hover {
  background: var(--stall-accent-hover);
}

/*
 * Mark Ready is the one committing action on a Received Stall Order, so it is
 * the one filled control on the row and it takes the accent rather than the
 * near-black of a neutral surface.
 */
.my-stall-page
  .fulfilment-actions:has(.mark-ready):has(.cancel-line)
  .mark-ready {
  grid-column: 1 / -1;
  color: white;
  border-color: var(--orange-text);
  background: var(--orange-text);
  font-weight: 800;
}

.my-stall-page .fulfilment-control,
.my-stall-page .fulfilment-confirm,
.my-stall-page .fulfilment-abandon,
.my-stall-page .exceptional-disclose {
  padding: 8px 10px;
  font-size: 12px;
  line-height: 1.25;
}

.my-stall-page .queue-row .handoff {
  margin-top: 5px;
}

/*
 * The Handoff Grace Period reads as one settled strip — what was handed off on
 * the left, how long is left to undo it on the right — rather than as two
 * unrelated lines.
 */
.my-stall-page .handoff-grace {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 4px 10px;
  padding: 10px 12px;
  border-radius: 6px;
  background: var(--stall-tint);
}

.my-stall-page .handoff-countdown {
  order: 2;
  margin: 0;
  color: var(--orange-text);
  font-family: "JetBrains Mono", ui-monospace, monospace;
  font-size: 12px;
  font-variant-numeric: tabular-nums;
}

.my-stall-page .undo-handoff {
  min-height: var(--v2-target);
  padding: 6px 10px;
  font-size: 12px;
  line-height: 1.25;
  text-align: left;
}

/*
 * The stated Pickup Code and the control that submits it belong on one line,
 * with the guidance under them. Placement is explicit rather than automatic:
 * a refusal inserts a paragraph between the field and the hint, and auto-flow
 * would push the confirm control onto a row of its own the moment it did.
 */
.my-stall-page .handoff-form {
  grid-template-columns: minmax(0, 1fr) minmax(0, 45%);
  align-items: center;
  gap: 6px 8px;
}

.my-stall-page .handoff-label {
  grid-area: 1 / 1 / auto / -1;
}

.my-stall-page .handoff-form input.stated-pickup-code {
  grid-area: 2 / 1;
  width: 100%;
  max-width: none;
}

.my-stall-page .handoff-form button.confirm-handoff {
  grid-area: 2 / 2;
  justify-self: stretch;
  padding: 8px 12px;
  font-size: 12px;
}

/*
 * The guidance sits directly under the field, and a refusal takes the row above
 * it only once there is one. Reserving that row unconditionally would leave an
 * empty track whose two gaps still count, so the hint would sit lower while
 * nothing was wrong and the whole row would shift when something was.
 */
.my-stall-page .handoff-hint {
  grid-area: 3 / 1 / auto / -1;
}

.my-stall-page .handoff-form:has(.handoff-error) .handoff-hint {
  grid-row: 4;
}

.my-stall-page .handoff-error {
  grid-area: 3 / 1 / auto / -1;
}

.my-stall-page .handoff-exceptional {
  margin-top: 6px;
  padding-top: 6px;
}

.my-stall-page .handed-off-row {
  padding: 10px 12px;
  border: 1px solid var(--v2-panel-border);
  border-left: 4px solid #15803d;
  border-radius: 6px;
}

.my-stall-page .readiness,
.my-stall-page .capacity-panel,
.my-stall-page .stall-setup-review {
  min-width: 0;
  padding: 18px;
  border: 1px solid var(--v2-panel-border);
  border-radius: var(--v2-radius);
  background: var(--v2-panel);
  box-shadow: 0 10px 26px rgb(120 53 15 / 0.06);
}

.my-stall-page .readiness h3,
.my-stall-page .capacity-panel h3,
.my-stall-page .stall-setup-review h3 {
  margin: 0 0 12px;
  font-size: clamp(22px, 6vw, 30px);
}

/*
 * Confirmed readiness is a settled fact, so it is stated once as a tinted line
 * with a mark against it rather than as another paragraph of guidance.
 */
.my-stall-page #readiness-confirmed:not([hidden]) {
  display: flex;
  gap: 8px;
  margin: 0;
  padding: 12px 14px;
  color: var(--surface);
  border-radius: var(--v2-radius);
  background: var(--stall-tint);
}

.my-stall-page #readiness-confirmed::before {
  content: "✓";
  color: var(--orange-text);
  font-weight: 800;
}

.my-stall-page .blocker-list {
  display: grid;
  gap: 8px;
  margin-bottom: 16px;
}

.my-stall-page .blocker {
  margin: 0;
  padding: 12px 14px;
  border: 1px solid #fed7aa;
  border-left: 5px solid var(--orange-text);
  border-radius: var(--v2-radius);
  background: #fff7ed;
}

.my-stall-page .blocker::before {
  content: "Needs attention · ";
  font-weight: 800;
}

.my-stall-page .enable-orders-panel {
  margin-top: 16px;
  padding: 16px;
  border-width: 2px;
  border-radius: var(--v2-radius);
}

/*
 * Three peer choices reading as three choices: white cards on the page, and the
 * one in force filled with the accent. Colour alone never carries it — the
 * chosen control keeps `aria-pressed` and an underline as well.
 */
.my-stall-page .capacity-controls {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(76px, 100%), 1fr));
  gap: 8px;
  padding: 0;
  border: 0;
  background: transparent;
}

.my-stall-page .capacity-choice {
  width: 100%;
  min-width: 0;
  min-height: var(--v2-target);
  color: var(--surface);
  border: 1px solid var(--v2-panel-border);
  border-radius: var(--v2-radius);
  background: white;
  box-shadow: none;
  clip-path: none;
  font-weight: 700;
}

.my-stall-page .capacity-choice[aria-pressed="true"] {
  color: white;
  border-color: var(--orange-text);
  background: var(--orange-text);
  box-shadow: 0 2px 7px rgb(120 53 15 / 0.2);
  font-weight: 800;
  text-decoration: underline;
  text-decoration-thickness: 2px;
  text-underline-offset: 4px;
}

/*
 * A hairline-separated list, each item stating its own price and its own
 * availability at the right edge. Availability is a *state* here and not a
 * switch: on My Stall it is derived from what the item was enabled as, from
 * Ingredient Stock, and from Capacity State — so it is never dressed as a
 * control this surface does not have.
 */
.my-stall-page .menu-item-row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  gap: 5px 14px;
  padding: 14px 0;
}

/*
 * The separator belongs *between* rows, so it is stated as a preceded-by rule
 * rather than as a border on every row with the first one cancelled — which
 * would leave a stray rule under any placeholder the list grows later.
 */
.my-stall-page .menu-item-row + .menu-item-row {
  border-top: 1px solid var(--v2-panel-border);
}

.my-stall-page .menu-item-name {
  min-width: 0;
  font-size: 17px;
  font-weight: 800;
  overflow-wrap: anywhere;
}

.my-stall-page .menu-item-price {
  font-family: "JetBrains Mono", ui-monospace, monospace;
  font-size: 13px;
  font-variant-numeric: tabular-nums;
}

.my-stall-page .menu-item-availability {
  grid-column: 2;
  justify-self: end;
  padding: 4px 8px;
  color: var(--orange-text);
  border: 1px solid currentColor;
  border-radius: 999px;
  font-family: "JetBrains Mono", ui-monospace, monospace;
  font-size: 11px;
  font-weight: 800;
  text-transform: uppercase;
}

.my-stall-page #add-menu-item-effect,
.my-stall-page #add-ingredient-effect {
  margin: 12px 0 8px;
  padding-left: 12px;
  border-left: 3px solid var(--orange-text);
}

.my-stall-page .menu-setup details,
.my-stall-page .ingredient-setup details {
  margin-top: 8px;
}

.my-stall-page .menu-setup summary,
.my-stall-page .ingredient-setup summary {
  display: flex;
  justify-content: center;
  width: 100%;
  min-height: var(--v2-target);
  padding: 10px 12px;
  border: 1px dashed var(--surface);
  border-radius: var(--v2-radius);
  background: transparent;
}

.my-stall-page .menu-setup form,
.my-stall-page .ingredient-setup form {
  max-width: none;
  border-radius: var(--v2-radius);
}

.my-stall-page .ingredient-stock-card {
  border-radius: var(--v2-radius);
  box-shadow: 0 8px 20px rgb(17 24 39 / 0.06);
}

.my-stall-page .ingredient-stock-card[data-ingredient-status="Out of Stock"] {
  border-left-color: var(--rose);
}

.my-stall-page
  .ingredient-stock-card[data-ingredient-status="Out of Stock"]
  .ingredient-stock-status {
  color: var(--v2-danger-text);
}

.my-stall-page #status.status {
  position: static;
  max-width: none;
  min-height: 0;
  margin: 12px 0 0;
  padding: 0;
  overflow: hidden;
  color: var(--surface);
  border: 0;
  background: transparent;
  box-shadow: none;
  transform: none;
  visibility: hidden;
  transition: none;
}

.my-stall-page #status.status.visible {
  min-height: var(--v2-target);
  padding: 12px 14px;
  border: 1px solid var(--v2-panel-border);
  border-left: 4px solid var(--orange-text);
  border-radius: var(--v2-radius);
  background: var(--v2-panel);
  visibility: visible;
}

.my-stall-page #status.status.error {
  color: var(--v2-danger-text);
  border-left-color: var(--v2-danger-text);
}

/*
 * The Action Feedback record is a behavioural guarantee and stays whole —
 * every term of it, anchored to its own origin row, static rather than floated.
 * What parity corrects is its density: the record is the longest thing on this
 * surface and it was set at the same weight as the work it interrupts. So the
 * terms become a two-column list a volunteer can scan down, and the heading and
 * state line carry the record instead of its body.
 */
.my-stall-page .action-feedback {
  padding: 14px;
  border-radius: var(--v2-radius);
  font-size: 13px;
}

.my-stall-page .action-feedback-heading {
  margin-block: 0 4px;
  font-size: 15px;
}

.my-stall-page .action-feedback-status {
  margin-block: 0 8px;
  font-weight: 700;
}

.my-stall-page .action-feedback-detail {
  display: grid;
  grid-template-columns: 84px minmax(0, 1fr);
  gap: 2px 10px;
  margin: 0;
}

.my-stall-page .action-feedback-detail dt {
  margin: 0;
  font-size: 10px;
  line-height: 1.6;
}

.my-stall-page .action-feedback-detail dd {
  margin: 0;
  line-height: 1.4;
}

/*
 * Checking a result is the one state a volunteer waits inside, so it is the one
 * that keeps a live tint rather than the neutral card.
 */
.my-stall-page .action-feedback[data-state="checking"],
.my-stall-page .action-feedback[data-state="pending"] {
  background: #fffbeb;
}

.my-stall-page[data-mutation-state="pending"] .my-stall-workspace::before {
  content: "Saving this My Stall change… all actions are paused.";
  display: block;
  padding: 12px 14px;
  color: var(--surface);
  border: 2px dashed var(--muted);
  border-radius: var(--v2-radius);
  background: #f3f4f6;
  font-weight: 800;
}

.my-stall-page
  :is(
    a[href],
    button,
    input,
    select,
    textarea,
    summary,
    [tabindex]
  ):focus-visible {
  outline: var(--v2-focus-ring);
  outline-offset: var(--v2-focus-offset);
}

.my-stall-page :is(input, select, textarea):disabled {
  opacity: 1;
  color: #374151;
  border-color: #6b7280;
  border-style: dashed;
  background: #e5e7eb;
  cursor: not-allowed;
}

.my-stall-page button:disabled {
  opacity: 1;
  color: #4b5563;
  border: 2px dashed #6b7280;
  background: #d1d5db;
  box-shadow: inset 0 0 0 1px #9ca3af;
  cursor: not-allowed;
}

.my-stall-page .role-nav-link[aria-disabled="true"] {
  color: #4b5563;
  border-bottom-style: dashed;
  cursor: not-allowed;
}

@media (max-width: 560px) {
  .my-stall-page header {
    display: grid;
    grid-template-columns: auto minmax(0, 1fr);
  }

  .my-stall-page .brand {
    align-self: start;
  }

  .my-stall-page .role-nav {
    gap: 2px 12px;
  }

  .my-stall-page .active-stall-picker .stall-form {
    grid-template-columns: minmax(0, 1fr);
  }

  .my-stall-page .active-stall-picker .stall-form label,
  .my-stall-page .active-stall-picker .stall-form button {
    grid-column: 1;
    width: 100%;
  }

  .my-stall-page .active-stall {
    padding: 16px;
  }

  .my-stall-page #change-stall {
    max-width: 96px;
  }

  .my-stall-page .readiness,
  .my-stall-page .capacity-panel,
  .my-stall-page .stall-setup-review {
    padding: 16px;
  }
}

/*
 * Below a 360px line box — a small phone, or a 390px one at 200% zoom — the
 * masthead and the handoff row stop competing for one line and stack instead.
 * Nothing is hidden and nothing is clipped: the medallion goes, because it is
 * the one element on this surface that carries no information at all.
 *
 * These are container queries and not media queries on purpose. Page zoom
 * shrinks the space this content actually has without changing the viewport a
 * media query is asked about, so a media query answers a question nobody asked
 * at 200% and the masthead overflows sideways. `main` already declares
 * `container-type: inline-size`, so this asks the only question that matters:
 * how much room is there.
 */
@container (width <= 360px) {
  .my-stall-page .active-stall {
    grid-template-columns: minmax(0, 1fr);
  }

  .my-stall-page .active-stall::before {
    display: none;
  }

  .my-stall-page #capacity-state,
  .my-stall-page .active-stall .queue-counts,
  .my-stall-page #change-stall {
    grid-column: 1;
  }

  /*
   * Change stall drops under the name it changes rather than sharing a line
   * with it — at this width sharing leaves the stall's own name in a sliver.
   */
  .my-stall-page #change-stall {
    justify-self: start;
    padding-left: 0;
    text-align: left;
  }

  /*
   * The record's terms stack rather than keeping a fixed term column: at this
   * width the column leaves the value a few characters wide, and a request ID
   * set one letter per line is not a recoverable request ID.
   */
  .my-stall-page .action-feedback-detail {
    grid-template-columns: minmax(0, 1fr);
  }

  .my-stall-page .action-feedback-detail dt {
    margin-top: 6px;
  }

  .my-stall-page .handoff-form {
    grid-template-columns: minmax(0, 1fr);
  }

  .my-stall-page .handoff-form input.stated-pickup-code,
  .my-stall-page .handoff-form button.confirm-handoff {
    grid-column: 1;
  }

  .my-stall-page .handoff-form input.stated-pickup-code {
    grid-row: 2;
  }

  .my-stall-page .handoff-form button.confirm-handoff {
    grid-row: 3;
  }

  .my-stall-page .handoff-hint,
  .my-stall-page .handoff-error {
    grid-row: 4;
  }

  .my-stall-page .handoff-form:has(.handoff-error) .handoff-hint {
    grid-row: 5;
  }
}

/* ==== V2 route section: /staff/all-stalls ==== */

.all-stalls-page {
  background:
    linear-gradient(160deg, rgb(255 255 255 / 0.96) 0 48%, transparent 74%),
    radial-gradient(circle at 92% 8%, #fed7aa 0 5%, transparent 28%), #fffaf5;
}

.all-stalls-page header,
.all-stalls-page main {
  width: min(100% - (2 * var(--v2-gutter)), 820px);
}

.all-stalls-page header {
  align-items: flex-start;
  gap: 16px;
  padding-block: 18px;
  border-bottom: 1px solid var(--v2-panel-border);
}

.all-stalls-page .role-nav {
  justify-content: flex-end;
  gap: 4px 14px;
}

.all-stalls-page .role-nav .role {
  flex-basis: 100%;
  text-align: right;
}

.all-stalls-page main {
  padding-block: 24px 72px;
}

.all-stalls-page .compact-intro {
  max-width: 650px;
  margin-bottom: 16px;
}

.all-stalls-page .compact-intro h1 {
  margin: 8px 0 10px;
  font-size: clamp(40px, 11vw, 64px);
  line-height: 0.96;
}

.all-stalls-page .compact-intro p:last-child {
  max-width: 55ch;
  margin: 0;
  color: var(--muted);
}

.all-stalls-page[data-stalls-selected] .compact-intro {
  display: none;
}

.all-stalls-page .all-stalls-route-state,
.all-stalls-page .role-sign-in,
.all-stalls-page .session-ended-notice,
.all-stalls-page .event-lock {
  padding: 20px;
  border: 1px solid var(--v2-panel-border);
  border-left: 5px solid var(--orange-text);
  border-radius: var(--v2-radius);
  background: var(--v2-panel);
  box-shadow: 0 12px 30px rgb(120 53 15 / 0.08);
}

.all-stalls-page .all-stalls-route-state h2,
.all-stalls-page .all-stalls-route-state p,
.all-stalls-page .event-lock h2,
.all-stalls-page .event-lock p {
  margin-block: 0 8px;
}

/*
 * The five route states this surface can be in, each stated in its own colour
 * rather than all five arriving as the same orange panel — the same split My
 * Stall makes, because a runner moving between the two halves of one role must
 * not have to relearn what a refusal looks like.
 */
.all-stalls-page
  .all-stalls-route-state:is(
    [data-route-state="offline"],
    [data-route-state="session-expired"],
    [data-route-state="failure"]
  ) {
  border-left-color: var(--v2-danger-text);
  background: #fff5f7;
}

.all-stalls-page
  .all-stalls-route-state:is(
    [data-route-state="offline"],
    [data-route-state="session-expired"],
    [data-route-state="failure"]
  )
  h2 {
  color: var(--v2-danger-text);
}

/*
 * Loading and No Open Event are neither refusals nor news to act on, so they
 * are held in the neutral panel with a dashed edge: the surface is waiting or
 * has nothing to run, and neither is an error a runner caused.
 */
.all-stalls-page
  .all-stalls-route-state:is(
    [data-route-state="loading"],
    [data-route-state="no-open-event"]
  ) {
  border-left-color: var(--muted);
  border-left-style: dashed;
}

.all-stalls-page .all-stalls-workspace {
  gap: 14px;
  margin-top: 14px;
}

/*
 * Selection is the canonical screen's chip row: what this device serves, named
 * on the page itself, with the control that changes it sitting at the end of
 * the same row. It was a black slab, which cost the first viewport twice over —
 * once for the slab and once for the coverage strip below it — and the queue is
 * the first operational task. Nothing here is a container any more.
 */
.all-stalls-page .monitoring {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
  padding: 0 0 12px;
  color: var(--surface);
  border: 0;
  border-bottom: 1px solid var(--v2-panel-border);
  border-radius: 0;
  background: transparent;
  box-shadow: none;
}

/*
 * The written summary is the same fact the chips carry, and it is what the
 * announcement after Apply repeats. Set quietly, on its own line under them:
 * a device serving four stalls should be able to confirm the count without
 * counting chips.
 */
.all-stalls-page .monitoring-summary {
  flex-basis: 100%;
  order: 3;
  margin: 0;
  color: var(--muted);
  font-family: "JetBrains Mono", ui-monospace, monospace;
  font-size: 10px;
  font-weight: 800;
  letter-spacing: 0.06em;
  line-height: 1.3;
  text-transform: uppercase;
  overflow-wrap: anywhere;
}

/*
 * `+` is the canonical affordance and the label stays `Change stalls`: the
 * dialog it opens adds and removes in one applied change, so a control that
 * said only `Add` would misdescribe it.
 */
.all-stalls-page .monitoring button {
  order: 2;
  min-width: var(--v2-target);
  min-height: var(--v2-target);
  padding: 8px 14px;
  color: var(--orange-text);
  border: 1px solid var(--orange-text);
  border-radius: 999px;
  background: white;
  font-size: 13px;
  font-weight: 800;
}

.all-stalls-page .monitoring button::before {
  content: "+ ";
}

.all-stalls-page .coverage-overview {
  order: 1;
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin: 0;
  padding: 0;
  list-style: none;
}

/*
 * One chip per stall, filled in the accent: the row a runner reads to see what
 * this device is serving before reading any lane.
 */
.all-stalls-page .coverage-overview-item {
  display: flex;
  align-items: center;
  min-height: 38px;
  margin: 0;
  padding: 6px 14px;
  color: white;
  border: 0;
  border-radius: 999px;
  background: var(--orange-text);
}

.all-stalls-page .coverage-stall {
  font-size: 13px;
  font-weight: 800;
  line-height: 1.2;
}

/*
 * Spoken, not shown — the three things this route states for a reader and sets
 * elsewhere for a runner. Stated once, because they must stay the same kind of
 * hidden: `display: none` would take them out of the accessibility tree too,
 * which is the opposite of what each is for.
 *
 * - The chip's counts. Three full count phrases per chip turn a row of stalls
 *   into a stack of paragraphs, and the same three counts are already set,
 *   visibly, on the lane header the chip refers to.
 * - The separators either side. They keep the read-aloud string whole —
 *   `Chaat Corner · Received 0 · …` — while the parts around them are set
 *   separately.
 * - The written summary, once the chips are up. The chips are the same fact,
 *   set better; before there are chips it is the only one, so it stays visible.
 *
 * The how is the visually-hidden utility at the top of this stylesheet (#163);
 * these selectors join it there (the counts and separators are JS-built, so a
 * markup class is not available to them).
 */

/*
 * The work is titled once, above the lanes. The rail already says which half
 * of Stall Staff this is, so the title names the task rather than the route.
 */
.all-stalls-page .queue-heading {
  margin: 4px 0 0;
  font-size: clamp(28px, 8vw, 40px);
  line-height: 1;
}

.all-stalls-page:not([data-stalls-selected]) .queue-heading {
  display: none;
}

.all-stalls-page .setup-empty {
  padding: 18px;
  border: 1px dashed var(--muted);
  border-left: 5px solid var(--orange-text);
  border-radius: var(--v2-radius);
  background: var(--v2-panel);
}

.all-stalls-page .swimlanes {
  gap: 22px;
}

/*
 * A lane is a boundary, not a box within a box. It was a shadowed card holding
 * three more cards holding cards, which spent the whole viewport on edges: the
 * lane now states whose queue this is with a rule and a name, and the tickets
 * inside it get the room. The stall name is still on the lane, on every panel's
 * empty state, and on every ticket, action, confirmation and result within it.
 */
.all-stalls-page .stall-lane {
  display: grid;
  gap: 10px;
  padding: 0;
  border: 0;
  border-radius: 0;
  background: transparent;
  box-shadow: none;
}

.all-stalls-page .stall-lane-header {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 4px 10px;
  padding-bottom: 6px;
  border-bottom: 2px solid var(--stall-tint-strong);
}

/*
 * The stall's own name in the accent, at the size that separates one lane from
 * the next without becoming a second page title. Display type, so it takes
 * `--orange` under the size split documented in `:root`.
 */
.all-stalls-page .stall-lane-name {
  margin: 0;
  color: var(--orange);
  font-size: clamp(22px, 6.5vw, 30px);
  line-height: 1.04;
  overflow-wrap: anywhere;
}

.all-stalls-page .stall-lane-counts {
  margin: 0;
  padding: 4px 10px;
  color: var(--surface);
  border: 0;
  border-radius: 999px;
  background: var(--stall-tint);
  font-family: "JetBrains Mono", ui-monospace, monospace;
  font-size: 9px;
  font-weight: 800;
  letter-spacing: 0.05em;
  line-height: 1.2;
  text-transform: uppercase;
}

.all-stalls-page .restock-awareness-list {
  flex-basis: 100%;
  display: grid;
  gap: 6px;
  margin: 4px 0 0;
  padding: 0;
  list-style: none;
}

/*
 * The canonical alert card: an eyebrow over the sentence, a warning shape at
 * the left, in the refusal-adjacent tint that says a runner is needed without
 * saying it in colour alone — the state word is written out either way.
 */
.all-stalls-page .restock-awareness {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  align-items: baseline;
  gap: 1px 9px;
  margin: 0;
  padding: 10px 12px;
  color: #7c2d12;
  border: 0;
  border-radius: 8px;
  background: #fde8e8;
}

/* Decorative: a shape rather than a word, beside a sentence that says it. */
.all-stalls-page .restock-awareness::before {
  content: "⚠";
  grid-row: 1 / span 2;
  align-self: center;
  color: var(--v2-danger-text);
  font-size: 15px;
  line-height: 1;
}

.all-stalls-page .restock-awareness-eyebrow {
  font-family: "JetBrains Mono", ui-monospace, monospace;
  font-size: 9px;
  font-weight: 800;
  letter-spacing: 0.1em;
  line-height: 1.3;
  text-transform: uppercase;
}

.all-stalls-page .restock-awareness-ingredient,
.all-stalls-page .restock-awareness-state {
  grid-column: 2;
  font-size: 12px;
  font-weight: 700;
  line-height: 1.35;
}

.all-stalls-page .restock-awareness-ingredient {
  font-weight: 800;
}

.all-stalls-page .restock-awareness-state {
  color: #7c2d12;
  font-size: 11px;
  font-weight: 700;
}

/*
 * An acknowledged Alert is still active — it is quieter, never gone, and never
 * quieter by colour alone: the row says "Seen — still active" in words.
 */
.all-stalls-page .restock-awareness[data-acknowledged="true"] {
  color: var(--muted);
  border: 1px dashed var(--muted);
  background: #f9fafb;
}

.all-stalls-page .restock-awareness[data-acknowledged="true"]::before {
  color: var(--muted);
}

.all-stalls-page
  .restock-awareness[data-acknowledged="true"]
  .restock-awareness-state {
  color: var(--muted);
}

/*
 * The three panels stay three panels — Handed Off is final and carries no
 * forward action, and folding it in invites a backwards action on a settled
 * Stall Order — but they stop being boxes. A letterspaced label over its
 * tickets is enough to divide them inside a lane that is already bounded.
 */
.all-stalls-page .lane-queue {
  min-width: 0;
  padding: 0;
  border: 0;
  border-radius: 0;
  background: transparent;
}

.all-stalls-page .lane-queue + .lane-queue {
  margin-top: 0;
}

.all-stalls-page .lane-queue h4 {
  margin-bottom: 6px;
  color: var(--muted);
  font-family: "JetBrains Mono", ui-monospace, monospace;
  font-size: 10px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
}

.all-stalls-page .lane-queue:has(.lane-ready) h4 {
  color: var(--orange-text);
}

.all-stalls-page .lane-list {
  gap: 8px;
}

/*
 * A ticket, set exactly as My Stall sets one: a tinted strip states which Stall
 * Order this is and which queue it is sitting in, and everything actionable
 * sits under it.
 */
.all-stalls-page .stall-order-card {
  display: grid;
  gap: 4px;
  padding: 0 0 11px;
  overflow: hidden;
  border: 1px solid var(--v2-panel-border);
  border-left: 4px solid var(--surface);
  border-radius: 8px;
  background: white;
}

.all-stalls-page .stall-order-card > :not(.stall-order-strip) {
  margin-inline: 11px;
}

.all-stalls-page .stall-order-strip {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: 2px 10px;
  padding: 8px 11px;
  background: var(--stall-tint);
}

.all-stalls-page .stall-order-state {
  margin: 0;
  color: var(--surface);
  font-family: "JetBrains Mono", ui-monospace, monospace;
  font-size: 9px;
  font-weight: 800;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

.all-stalls-page .lane-ready .stall-order-strip {
  background: var(--stall-tint-strong);
}

.my-stall-page [data-arrived],
.all-stalls-page [data-arrived] {
  border-left: 3px solid #ea580c;
  background: linear-gradient(90deg, #fff7ed, transparent 60%);
}

.all-stalls-page .lane-ready .stall-order-card {
  border-left-color: var(--orange-text);
  background: #fffaf5;
}

.all-stalls-page .new-order-cue {
  justify-self: start;
  margin: 9px 0 0;
  padding: 3px 7px;
  color: white;
  border-radius: 999px;
  background: var(--orange-text);
  font-family: "JetBrains Mono", ui-monospace, monospace;
  font-size: 9px;
  font-weight: 850;
  letter-spacing: 0.06em;
  line-height: 1.35;
  text-transform: uppercase;
}

.all-stalls-page .stall-order-title,
.all-stalls-page .stall-order-lines {
  margin: 0;
}

.all-stalls-page .stall-order-title {
  color: var(--surface);
  font-size: 14px;
  font-weight: 850;
  line-height: 1.25;
  overflow-wrap: anywhere;
}

/*
 * One row per Order Line, divided rather than boxed, so a four-line ticket
 * reads as four things to make instead of one sentence to parse.
 */
.all-stalls-page .stall-order-lines {
  display: grid;
  padding: 0;
  color: var(--surface);
  list-style: none;
}

.all-stalls-page .stall-order-line {
  padding: 5px 0;
  font-size: 13px;
  line-height: 1.3;
}

.all-stalls-page .stall-order-line + .stall-order-line {
  border-top: 1px solid var(--v2-panel-border);
}

/*
 * An empty queue collapses to its heading and count (ease-of-use pass,
 * finding 4): no dashed sentence box, because the lane header already states
 * the counts and a panel announcing nothing costs the same height as work.
 * The heading of an empty panel is quiet even where a live queue would carry
 * the Ready accent, so an empty lane reads as caught up, never as unlabelled.
 */
.all-stalls-page .lane-list[data-empty] {
  min-height: 0;
}

.all-stalls-page .lane-queue:has(.lane-list[data-empty]) h4 {
  color: var(--muted);
}

.all-stalls-page .stall-order-actions,
.all-stalls-page .fulfilment-actions {
  gap: 6px;
  margin-top: 5px;
}

.all-stalls-page .fulfilment-actions:has(.mark-ready):has(.cancel-line) {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
}

/*
 * Every primary control on this route carries the accent, as on My Stall and
 * for the same reason: a runner learns "the filled orange one commits" once,
 * across both halves of Stall Staff, and a control that opted out would read as
 * a different kind of thing. `--orange-text` is the darkened accent, so white
 * on it stays past 4.5:1 whatever size the label is set at.
 */
.all-stalls-page .primary {
  background: var(--orange-text);
}

.all-stalls-page .primary:hover {
  background: var(--stall-accent-hover);
}

.all-stalls-page
  .fulfilment-actions:has(.mark-ready):has(.cancel-line)
  .mark-ready,
.all-stalls-page .handoff-form button.confirm-handoff {
  color: white;
  border-color: var(--orange-text);
  background: var(--orange-text);
  font-weight: 800;
}

.all-stalls-page
  .fulfilment-actions:has(.mark-ready):has(.cancel-line)
  .mark-ready {
  grid-column: 1 / -1;
}

.all-stalls-page
  :is(
    .fulfilment-actions .mark-ready,
    .handoff-form button.confirm-handoff
  ):hover:not(:disabled) {
  background: var(--stall-accent-hover);
}

.all-stalls-page .fulfilment-control,
.all-stalls-page .fulfilment-confirm,
.all-stalls-page .fulfilment-abandon,
.all-stalls-page .exceptional-disclose,
.all-stalls-page .handoff-form button.confirm-handoff {
  padding: 8px 10px;
  font-size: 12px;
  line-height: 1.25;
}

.all-stalls-page .handoff-form {
  grid-template-columns: 8ch minmax(0, 1fr);
  gap: 6px 8px;
}

.all-stalls-page .handoff-label,
.all-stalls-page .handoff-hint,
.all-stalls-page .handoff-error {
  grid-column: 1 / -1;
}

.all-stalls-page .handoff-form input.stated-pickup-code {
  width: 100%;
  max-width: none;
}

.all-stalls-page .handoff-form button.confirm-handoff {
  width: 100%;
  justify-self: stretch;
}

.all-stalls-page .handoff-exceptional {
  margin-top: 6px;
  padding-top: 6px;
}

.all-stalls-page .duty-note {
  padding: 12px 14px;
  border-left: 3px solid var(--orange-text);
  background: #fff7ed;
}

.all-stalls-page #status.status {
  position: static;
  max-width: none;
  min-height: 0;
  margin: 10px 0 0;
  padding: 0;
  overflow: hidden;
  color: var(--surface);
  border: 0;
  background: transparent;
  box-shadow: none;
  transform: none;
  visibility: hidden;
  transition: none;
}

.all-stalls-page #status.status.visible,
.all-stalls-page #status.status:not(:empty) {
  min-height: var(--v2-target);
  padding: 12px 14px;
  border: 1px solid var(--v2-panel-border);
  border-left: 4px solid var(--orange-text);
  border-radius: var(--v2-radius);
  background: var(--v2-panel);
  visibility: visible;
}

.all-stalls-page #status.status.error {
  color: var(--v2-danger-text);
  border-left-color: var(--v2-danger-text);
}

.all-stalls-page .action-feedback {
  padding: 12px;
  border-radius: var(--v2-radius);
}

.all-stalls-page .action-feedback-heading,
.all-stalls-page .action-feedback-status {
  margin-block: 0 6px;
}

.all-stalls-page .action-feedback-detail {
  display: grid;
  grid-template-columns: 72px minmax(0, 1fr);
  gap: 3px 8px;
  margin: 8px 0;
  font-size: 11px;
  line-height: 1.3;
}

.all-stalls-page .action-feedback-detail dt,
.all-stalls-page .action-feedback-detail dd {
  margin: 0;
  overflow-wrap: anywhere;
}

.all-stalls-page[data-mutation-state="pending"] .all-stalls-workspace::before {
  content: "Checking this All Stalls change… selection, navigation, and queues are paused in this tab.";
  display: block;
  padding: 12px 14px;
  color: var(--surface);
  border: 2px dashed var(--muted);
  border-radius: var(--v2-radius);
  background: #f3f4f6;
  font-weight: 800;
}

.all-stalls-page
  :is(
    a[href],
    button,
    input,
    select,
    textarea,
    summary,
    [tabindex]
  ):focus-visible {
  outline: var(--v2-focus-ring);
  outline-offset: var(--v2-focus-offset);
}

.all-stalls-page :is(input, select, textarea):disabled {
  opacity: 1;
  color: #374151;
  border-color: #6b7280;
  border-style: dashed;
  background: #e5e7eb;
  cursor: not-allowed;
}

.all-stalls-page button:disabled {
  opacity: 1;
  color: #4b5563;
  border: 2px dashed #6b7280;
  background: #d1d5db;
  box-shadow: inset 0 0 0 1px #9ca3af;
  cursor: not-allowed;
}

.all-stalls-page .role-nav-link[aria-disabled="true"] {
  color: #4b5563;
  border-bottom-style: dashed;
  cursor: not-allowed;
}

.all-stalls-page .stall-dialog {
  max-height: calc(100dvh - 32px);
  overflow-y: auto;
  border-radius: var(--v2-radius);
}

@media (max-width: 560px) {
  .all-stalls-page header {
    display: grid;
    grid-template-columns: auto minmax(0, 1fr);
    gap: 10px;
    padding-block: 12px;
  }

  .all-stalls-page .brand {
    align-self: start;
  }

  .all-stalls-page .role-nav {
    gap: 2px 12px;
  }

  .all-stalls-page main {
    padding-block: 14px 52px;
  }
}

/*
 * The narrow fallbacks for the content itself are container queries, not media
 * queries, for the reason recorded on My Stall: page zoom shrinks the room this
 * content has without changing the viewport a media query asks about, so at
 * 200% a media query answers a question nobody asked and the chips, tickets and
 * handoff row overflow sideways. `.all-stalls-page main` is the container.
 */
@container (width <= 360px) {
  .all-stalls-page .all-stalls-workspace {
    gap: 10px;
    margin-top: 10px;
  }

  .all-stalls-page .monitoring {
    gap: 6px;
    padding-bottom: 10px;
  }

  .all-stalls-page .swimlanes {
    gap: 18px;
  }

  .all-stalls-page .stall-lane {
    gap: 9px;
  }

  .all-stalls-page .fulfilment-actions:has(.mark-ready):has(.cancel-line),
  .all-stalls-page .handoff-form {
    grid-template-columns: minmax(0, 1fr);
  }

  .all-stalls-page
    .fulfilment-actions:has(.mark-ready):has(.cancel-line)
    .mark-ready {
    grid-column: auto;
  }

  .all-stalls-page .fulfilment-control,
  .all-stalls-page .fulfilment-confirm,
  .all-stalls-page .fulfilment-abandon,
  .all-stalls-page .exceptional-disclose,
  .all-stalls-page .handoff-form button.confirm-handoff {
    width: 100%;
  }

  .all-stalls-page .handoff-form input.stated-pickup-code {
    width: 100%;
    max-width: none;
  }

  .all-stalls-page[data-mutation-state="pending"]
    .all-stalls-workspace::before {
    content: "Checking result… this tab's selection, navigation, and queues are paused.";
    padding: 9px 11px;
    font-size: 12px;
  }
}

@container (max-width: 220px) {
  .my-stall-page #status:has(~ #my-stall-workspace),
  .all-stalls-page #status:has(~ #all-stalls-workspace) {
    position: static;
  }

  /*
   * Past this the chip row cannot stay a row. Each stall takes a line of its
   * own rather than a name breaking mid-word inside a pill.
   */
  .all-stalls-page .coverage-overview-item,
  .all-stalls-page .monitoring button {
    flex-basis: 100%;
    width: 100%;
  }
}

/* ==== V2 route section: /overseer ==== */

/*
 * Operations is a glanceable phone lane: exceptions first, two dominant live
 * counts, then the complete Capacity State denominator and compact stall rows.
 * Every selector is route-scoped so Settings keeps its administrative rhythm.
 */
.operations-page header,
.operations-page main {
  width: min(calc(100% - 32px), 720px);
  max-width: 720px;
}

.operations-page header {
  gap: 16px;
  padding-block: 14px;
  border-bottom: 1px solid var(--v2-panel-border);
}

.operations-page .brand {
  flex: 0 0 auto;
  font-size: 15px;
}

.operations-page .role-nav {
  justify-content: flex-end;
  gap: 4px 12px;
}

.operations-page .role-nav-link,
.operations-page .role-nav .text-button {
  min-height: var(--v2-target);
  font-size: 13px;
}

.operations-page
  :is(input, select, textarea, button, a[href], summary):focus-visible {
  outline: var(--v2-focus-ring);
  outline-offset: var(--v2-focus-offset);
}

.operations-page .clipped:focus-visible {
  box-shadow: inset 0 0 0 3px var(--orange-text);
}

.operations-page main {
  padding-block: 18px 64px;
}

.operations-page .intro {
  max-width: 590px;
}

.operations-page .intro h1 {
  margin: 8px 0;
  font-size: clamp(2.25rem, 11vw, 4rem);
  line-height: 0.94;
}

.operations-page .intro > p:last-child {
  margin: 0;
  font-size: 14px;
  line-height: 1.45;
}

.operations-route-state {
  margin-top: 16px;
  padding: 16px;
  border: 1px solid var(--v2-panel-border);
  border-left: 4px solid var(--orange-text);
  border-radius: var(--v2-radius);
  background: var(--v2-panel);
}

.operations-route-state h2,
.operations-route-state p {
  margin: 0;
}

.operations-route-state h2 {
  margin-top: 4px;
  font-size: 1.1rem;
}

.operations-route-state p:last-child {
  margin-top: 5px;
  color: var(--muted);
  font-size: 13px;
}

.operations-route-state[data-route-state="offline"],
.operations-route-state[data-route-state="session-expired"] {
  border-left-color: var(--v2-danger-text);
}

.operations-page .connectivity-status {
  margin-top: 12px;
}

.operations-page .role-sign-in :disabled {
  cursor: not-allowed;
  opacity: 0.48;
}

.operations-page #status.status {
  position: static;
  max-width: none;
  min-height: 0;
  margin: 12px 0 0;
  padding: 0;
  overflow: hidden;
  color: var(--surface);
  border: 0;
  background: transparent;
  box-shadow: none;
  transform: none;
  visibility: hidden;
  transition: none;
}

.operations-page #status.status.visible,
.operations-page #status.status:not(:empty) {
  min-height: var(--v2-target);
  padding: 11px 13px;
  border: 1px solid var(--v2-panel-border);
  border-left: 4px solid var(--orange-text);
  border-radius: var(--v2-radius);
  background: var(--v2-panel);
  visibility: visible;
}

.operations-page #status.status.error {
  color: var(--v2-danger-text);
  border-left-color: var(--v2-danger-text);
}

/* Routine announcements remain available to assistive technology without
 * displacing live metrics. Action Feedback is the durable visible record.
 * The how is the visually-hidden utility at the top of this stylesheet (#163);
 * these selectors join it there. */

.operations-page .overseer-workspace {
  gap: 28px;
  margin-top: 18px;
}

.operations-page .operations-offline > * {
  display: none;
}

.operations-page .operations-section-heading .eyebrow,
.operations-page .operations-section-heading h2 {
  margin: 0;
}

.operations-page .operations-section-heading h2 {
  margin-top: 3px;
  font-size: 1.35rem;
  letter-spacing: -0.025em;
}

.operations-page .exception-list,
.operations-page .stall-health-list {
  margin-top: 10px;
}

.operations-page .exception {
  margin-bottom: 8px;
  padding: 12px 14px;
  border: 1px solid var(--v2-panel-border);
  border-left: 4px solid var(--orange-text);
  border-radius: var(--v2-radius);
  background: var(--v2-panel);
}

.operations-page .exception.empty {
  border-left-color: #15803d;
  color: var(--surface);
}

.operations-page .exception-summary {
  margin-bottom: 2px;
  font-size: 14px;
}

.operations-page .exception .hint {
  margin: 0;
  font-size: 12px;
  line-height: 1.4;
}

.operations-page .health-totals {
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 1px;
  margin-top: 10px;
  border-radius: var(--v2-radius);
  overflow: hidden;
}

/*
 * Six cells on a 4-column grid: the two primary totals each span a half row,
 * and the four supporting totals — Open, Busy, Paused, and the exception
 * count — fill the second row at equal weight. Nothing depends on viewport
 * width to reach a total.
 */
.operations-page .health-totals > div {
  grid-column: span 1;
  padding: 11px 10px;
}

.operations-page .health-totals > .primary-total {
  grid-column: span 2;
  padding: 15px 14px;
}

.operations-page .health-totals dt {
  font-size: 0.61rem;
}

.operations-page .health-totals dd {
  margin-top: 3px;
  font-size: 1.25rem;
  font-weight: 650;
}

.operations-page .health-totals .primary-total dd:not(.total-share) {
  font-size: 2.8rem;
  /*
   * Canonical weights the primary numeral like a figurehead, not a whisper:
   * the 320 inherited from the pre-parity strip read as secondary next to the
   * Stitch screen's solid display figures, so it takes the same 650 the
   * supporting totals already carry.
   */
  font-weight: 650;
  line-height: 1;
}

/*
 * O1: the two primary totals are not interchangeable, and the canonical screen
 * says so with fill. Received stays a light warm tint with its numeral in the
 * darkened accent; Ready for Pickup inverts to a solid accent fill with light
 * text, because it is the number an operator walks toward. Both route-local via
 * the `.primary-total` cells' own position — no shared status selector moves.
 */
.operations-page .health-totals > .primary-total:first-child {
  background: rgb(253 218 180 / 0.4);
}

.operations-page
  .health-totals
  > .primary-total:first-child
  dd:not(.total-share) {
  color: var(--orange-text);
}

.operations-page .health-totals > .primary-total:nth-child(2) {
  background: var(--orange-text);
}

.operations-page .health-totals > .primary-total:nth-child(2) dt {
  color: #fffaf5;
}

.operations-page .health-totals > .primary-total:nth-child(2) dd {
  color: #fffaf5;
}

.operations-page .health-totals .total-share {
  font-size: 0.62rem;
  line-height: 1.3;
}

.operations-page .health-basis,
.operations-page .stall-health > .hint {
  margin: 8px 0 0;
  font-size: 12px;
  line-height: 1.45;
}

.operations-page .stall-health-row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  gap: 5px 12px;
  padding: 13px 0;
}

.operations-page .stall-health-row h3,
.operations-page .stall-health-row .queue-counts,
.operations-page .stall-health-row .stall-comparison {
  margin: 0;
}

.operations-page .stall-health-row .event-state {
  align-self: start;
  min-height: 30px;
  padding: 6px 10px;
  font-size: 11px;
}

.operations-page .stall-health-row .queue-counts,
.operations-page .stall-health-row .stall-comparison,
.operations-page .pause-exception,
.operations-page .paused-note {
  grid-column: 1 / -1;
}

.operations-page .stall-health-row .queue-counts {
  font-size: 13px;
}

.operations-page .stall-health-row .stall-comparison {
  padding-left: 8px;
  font-size: 11px;
  line-height: 1.4;
}

.operations-page .pause-exception {
  gap: 7px;
  margin-top: 6px;
}

.operations-page .pause-label {
  font-size: 13px;
}

.operations-page .pause-exception input {
  min-height: var(--v2-target);
  margin-top: 5px;
}

.operations-page .pause-requirement {
  font-size: 11px;
  line-height: 1.4;
}

.operations-page .pause-stall {
  min-height: var(--v2-target);
  padding-inline: 16px;
  font-size: 13px;
}

.operations-page .paused-note {
  margin: 0;
  font-size: 12px;
}

.operations-page .settings-outbound {
  margin: 0;
  padding-top: 16px;
  border-top: 1px solid var(--v2-panel-border);
  color: var(--muted);
  font-size: 13px;
}

.operations-page .settings-outbound a {
  display: inline-flex;
  align-items: center;
  min-height: var(--v2-target);
}

@media (max-width: 560px) {
  .operations-page header {
    align-items: start;
  }

  .operations-page .role-nav .role {
    /* A width-scoped restatement of the visually-hidden utility (#163): a
       markup class cannot be toggled by viewport, so the recipe is repeated
       here with the same `clip-path` spelling. */
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
    border: 0;
  }

  .operations-page .role-nav {
    gap: 3px 10px;
  }

  .operations-page .role-nav-link,
  .operations-page .role-nav .text-button {
    font-size: 12px;
  }
}

/* ==== V2 route section: /overseer/settings ==== */

/*
 * Settings is the quiet administrative counterpart to Operations: the same
 * warm paper and clipped geometry, with denser evidence modules and no live
 * health dashboard treatment.
 */
.settings-page header,
.settings-page main {
  width: min(calc(100% - 32px), 720px);
  max-width: 720px;
}

.settings-page header {
  flex-wrap: wrap;
  padding-block: 20px;
}

.settings-page main {
  container-type: inline-size;
  padding-bottom: 72px;
}

.settings-page .intro {
  padding-block: 30px 14px;
}

.settings-page .intro h1 {
  max-width: none;
  margin-bottom: 10px;
  font-size: clamp(2.2rem, 11vw, 3.7rem);
  line-height: 0.94;
}

.settings-page .intro > p:last-child {
  max-width: 58ch;
  margin-bottom: 0;
}

.settings-page .settings-route-state {
  margin-top: 20px;
  padding: 24px;
  border: 1px solid var(--border);
  border-inline-start: 4px solid var(--orange);
  background: rgb(255 253 251 / 0.9);
  clip-path: polygon(0 0, calc(100% - 18px) 0, 100% 18px, 100% 100%, 0 100%);
}

.settings-page .settings-route-state h2,
.settings-page .settings-route-state p {
  margin-bottom: 8px;
}

.settings-page .settings-route-state p:last-child {
  margin-bottom: 0;
  color: var(--muted);
}

.settings-page .session-ended-notice {
  margin: 0 0 16px;
  padding: 12px 14px;
  border-inline-start: 3px solid var(--rose);
  background: #fff5f7;
}

.settings-page .overseer-settings {
  gap: 12px;
  margin-top: 20px;
}

.settings-page .settings-module {
  position: relative;
  min-width: 0;
  padding: clamp(15px, 4vw, 21px);
  border-color: #ded7ce;
  background: rgb(255 253 251 / 0.94);
  clip-path: polygon(0 0, calc(100% - 18px) 0, 100% 18px, 100% 100%, 0 100%);
}

.settings-page .settings-module::before {
  content: "";
  position: absolute;
  inset: 0 auto 0 0;
  width: 3px;
  background: var(--orange);
}

.settings-page .settings-module h2,
.settings-page .settings-module h3,
.settings-page .settings-module p {
  overflow-wrap: anywhere;
}

.settings-page .settings-module > h2,
.settings-page .module-heading h2 {
  margin: 0;
  font-size: clamp(1.4rem, 6vw, 1.8rem);
}

/*
  Round-2 F6: each module's explanatory preamble sits collapsed behind a
  disclosure so the controls lead and the prose is on demand (ease-of-use
  finding 3 at section level). The wording itself is unchanged — the specs
  still pin it.
*/
.settings-page .module-preamble {
  margin: 7px 0 0;
}

.settings-page .module-preamble > summary {
  display: flex;
  align-items: center;
  min-height: 32px;
  width: fit-content;
  padding: 0 10px;
  border: 1px solid var(--border);
  background: #f7f2eb;
  color: var(--muted);
  font-family: "JetBrains Mono", ui-monospace, monospace;
  font-size: 0.68rem;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  cursor: pointer;
  list-style: none;
}

.settings-page .module-preamble > summary::-webkit-details-marker {
  display: none;
}
/* #F6: a collapsed/expanded disclosure needs a visible state cue, not just a
   colour shift. The native <details>/<summary> control already exposes expanded
   state to accessibility APIs, so no redundant aria-expanded is added. */
.settings-page .module-preamble > summary::after {
  content: "\2304"; /* ⌄ collapsed */
  margin-left: 8px;
  font-size: 0.85rem;
  line-height: 1;
  color: inherit;
}
.settings-page .module-preamble[open] > summary::after {
  content: "\2303"; /* ⌃ expanded */
}

.settings-page .module-preamble[open] > summary {
  color: var(--orange-text);
}

.settings-page .settings-module .module-preamble .hint,
.settings-page .module-heading + .module-preamble .hint {
  margin: 8px 0 0;
  max-width: 60ch;
  font-size: 0.84rem;
  line-height: 1.45;
}

.settings-page .module-heading {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 16px;
}

.settings-page .module-heading .eyebrow {
  margin-bottom: 5px;
}

.settings-page .settings-state {
  display: inline-flex;
  align-items: center;
  min-height: 30px;
  padding: 5px 10px;
  border: 1px solid var(--border);
  background: #f7f2eb;
  color: var(--muted);
  font-family: "JetBrains Mono", ui-monospace, monospace;
  font-size: 0.68rem;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  white-space: nowrap;
}

.settings-page .credential-list,
.settings-page .report-list,
.settings-page .correction-list,
.settings-page .dispute-list {
  margin-top: 16px;
}

.settings-page .credential-row {
  padding: 0;
  border-top: 0;
}

.settings-page .credential-details {
  border-top: 1px solid var(--border);
}

.settings-page .credential-details > summary {
  display: flex;
  /*
   * No wrap. With `flex-wrap: wrap` a long "Last rotated …" date pushed the
   * "+" disclosure onto an orphaned line of its own (visible on the
   * Registration Desk row at 390px). Both text spans shrink instead
   * (`min-width: 0` below), so the date wraps inside its own box and the
   * toggle keeps its place at the row's right edge on every line count.
   */
  flex-wrap: nowrap;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  min-height: 44px;
  padding: 7px 0;
  cursor: pointer;
  list-style: none;
}

.settings-page .credential-role,
.settings-page .credential-details .credential-rotated {
  min-width: 0;
  overflow-wrap: anywhere;
}

.settings-page .credential-details > summary::-webkit-details-marker {
  display: none;
}

.settings-page .credential-details > summary::after {
  content: "+";
  flex: 0 0 auto;
  color: var(--orange-text);
  font-size: 1.25rem;
}

.settings-page .credential-details[open] > summary::after {
  content: "−";
}

.settings-page .credential-details .credential-rotated {
  margin: 0 0 0 auto;
  font-size: 0.72rem;
  text-align: right;
  /* Round-3 F6: `overflow-wrap: anywhere` let "…10:24 AM" break inside the
     time, orphaning "AM" on its own line in a narrow credential row — the
     same class of wrap defect the "+" toggle had. The timestamp is short
     enough to hold one line, so soft wraps are switched off for it. */
  white-space: nowrap;
}

.settings-page .credential-details .credential-form {
  padding: 2px 0 12px;
}

.settings-page .credential-form {
  display: grid;
  gap: 10px;
}

.settings-page .credential-form .hint,
.settings-page .credential-radius {
  margin: 0;
}

.settings-page .credential-form button,
.settings-page .boards-module button,
.settings-page .dispute-form button {
  width: 100%;
  min-height: 44px;
}

.settings-page .board-list {
  display: grid;
  gap: 12px;
  margin-top: 16px;
}

.settings-page .board-row {
  min-width: 0;
  margin: 0;
  padding: 13px 14px;
  border: 1px solid var(--border);
  background: #fffdfb;
}

.settings-page .board-row legend {
  padding-inline: 6px;
  font-family: "JetBrains Mono", ui-monospace, monospace;
  font-size: 0.72rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
}

.settings-page .board-row .stall-choices {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 4px 10px;
  margin-top: 10px;
}

.settings-page .board-link {
  margin-top: 8px;
  color: var(--orange-text);
}

.settings-page .form-actions {
  margin-top: 14px;
}

.settings-page .report-list {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: 0;
}

.settings-page .report-list li {
  min-width: 0;
  padding: 0;
}

.settings-page .report-link {
  width: 100%;
  min-height: 44px;
  padding: 9px 2px;
  border: 0;
  border-block-start: 1px solid var(--border);
  background: transparent;
  display: flex;
  align-items: center;
  font-size: 0.78rem;
  line-height: 1.3;
  text-decoration: none;
}

.settings-page .report-list li:last-child .report-link {
  border-block-end: 1px solid var(--border);
}

.settings-page .report-link::after {
  content: "↓";
  margin-left: auto;
  padding-left: 8px;
  font-size: 1rem;
}

.settings-page .correction-list,
.settings-page .dispute-list {
  max-height: 390px;
  overflow: auto;
  overscroll-behavior: contain;
  scrollbar-gutter: stable;
}

.settings-page .correction-row {
  margin: 0;
  padding: 10px 12px;
  border-block-start: 1px solid var(--border);
  border-inline-start: 3px solid var(--orange);
  background: #fffdfb;
}

.settings-page .correction-summary,
.settings-page .correction-row .hint {
  margin: 0;
}

.settings-page .correction-summary {
  font-size: 0.87rem;
  font-weight: 650;
}

.settings-page .correction-row .hint {
  margin-top: 4px;
  font-size: 0.76rem;
}

.settings-page .reconciliation-grid {
  display: grid;
  gap: 22px;
  margin-top: 20px;
}

.settings-page .reconciliation-grid > .reconciliation-details {
  padding: 16px;
  border: 1px solid var(--border);
  background: #fffdfb;
}

.settings-page .reconciliation-details > summary {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  min-height: 44px;
  cursor: pointer;
  list-style: none;
}

.settings-page .reconciliation-details > summary::-webkit-details-marker {
  display: none;
}

.settings-page .reconciliation-details > summary span {
  color: var(--orange-text);
  font-family: "JetBrains Mono", ui-monospace, monospace;
  font-size: 0.68rem;
  letter-spacing: 0.05em;
  text-transform: uppercase;
}

.settings-page .reconciliation-grid h3 {
  margin: 0;
  font-size: 1.08rem;
}

.settings-page .reconciliation-grid .hint {
  margin: 8px 0 14px;
  font-size: 0.76rem;
}

.settings-page .settings-reconciliation {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  gap: 6px 14px;
  margin: 0;
  font-size: 0.82rem;
}

.settings-page .settings-reconciliation dt {
  color: var(--muted);
}

.settings-page .settings-reconciliation dd {
  margin: 0;
  font-family: "JetBrains Mono", ui-monospace, monospace;
  font-size: 0.76rem;
  text-align: right;
}

.settings-page .tender-review-fields {
  display: grid;
  gap: 10px;
  margin: 14px 0;
}

.settings-page .tender-review-fields label {
  margin: 0;
}

.settings-page .reconciliation-reviewed {
  min-height: 44px;
  margin-top: 14px;
  padding: 10px 12px;
  border-inline-start: 3px solid var(--orange);
  background: #fffaf5;
  font-size: 0.8rem;
  line-height: 1.4;
}

.settings-page .event-close-flow > summary[aria-disabled="true"] {
  cursor: not-allowed;
  background: rgb(255 255 255 / 0.5);
}

.settings-page #event-close-review-state {
  color: #9f1239;
  font-family: "JetBrains Mono", ui-monospace, monospace;
  font-size: 0.68rem;
  letter-spacing: 0.05em;
  text-transform: uppercase;
}

.settings-page
  .event-close-flow
  > summary[aria-disabled="false"]
  #event-close-review-state {
  color: var(--orange-text);
}

.settings-page .dispute-row {
  padding: 16px;
  border-block-start: 1px solid var(--border);
  background: #fffdfb;
}

.settings-page .dispute-row:focus-visible {
  outline: var(--v2-focus-ring);
  outline-offset: -3px;
}

.settings-page
  :is(input, select, textarea, button, a[href], summary):focus-visible {
  outline: var(--v2-focus-ring);
  outline-offset: var(--v2-focus-offset);
}

.settings-page .dispute-heading {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
}

.settings-page .dispute-heading h3,
.settings-page .dispute-claim,
.settings-page .dispute-guest,
.settings-page .dispute-resolution-reason {
  margin: 0;
}

.settings-page .dispute-heading h3 {
  flex: 1;
  min-width: 0;
}

.settings-page .dispute-status {
  flex: 0 1 62%;
  max-width: 62%;
  padding: 4px 8px;
  border: 1px solid var(--border);
  background: #f7f2eb;
  font-family: "JetBrains Mono", ui-monospace, monospace;
  font-size: 0.66rem;
  line-height: 1.35;
  text-align: right;
}

.settings-page .dispute-claim {
  margin-top: 8px;
  font-weight: 600;
}

.settings-page .dispute-guest {
  margin-top: 4px;
  font-size: 0.74rem;
}

.settings-page .dispute-links {
  margin: 10px 0 0;
  padding-left: 18px;
  color: var(--muted);
  font-family: "JetBrains Mono", ui-monospace, monospace;
  font-size: 0.7rem;
}

.settings-page .dispute-form {
  display: grid;
  gap: 10px;
  margin-top: 14px;
  padding-top: 14px;
  border-top: 1px solid var(--border);
}

.settings-page .dispute-form textarea {
  min-height: 78px;
  resize: vertical;
}

.settings-page .dispute-resolution-reason {
  margin-top: 12px;
  padding: 10px 12px;
  border-inline-start: 3px solid var(--orange);
  background: #f7f2eb;
}

.settings-page .event-close-flow {
  overflow: hidden;
  border: 1px solid var(--rose);
  background: #fff5f7;
  clip-path: polygon(0 0, calc(100% - 18px) 0, 100% 18px, 100% 100%, 0 100%);
}

.settings-page .event-close-flow > summary {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 18px;
  min-height: 76px;
  padding: 18px 20px;
  cursor: pointer;
  list-style: none;
}

.settings-page .event-close-flow > summary::-webkit-details-marker {
  display: none;
}

.settings-page .event-close-flow > summary > span:first-child {
  display: grid;
  gap: 4px;
}

.settings-page .event-close-flow summary small {
  color: #9f1239;
  font-family: "JetBrains Mono", ui-monospace, monospace;
  font-size: 0.65rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
}

.settings-page .event-close-flow summary strong {
  font-size: 1.15rem;
}

.settings-page .event-close-flow summary span span {
  color: var(--muted);
  font-size: 0.78rem;
  line-height: 1.4;
}

.settings-page .event-close-flow[open] > summary > span:last-child {
  transform: rotate(90deg);
}

.settings-page .event-close-flow .panel.guarded {
  border: 0;
  border-top: 1px solid var(--rose);
  border-inline-start: 4px solid var(--rose);
}

.settings-page .settings-offline .credential-list,
.settings-page .settings-offline .board-list,
.settings-page .settings-offline .report-list,
.settings-page .settings-offline .correction-list,
.settings-page .settings-offline .reconciliation-grid,
.settings-page .settings-offline .dispute-list,
.settings-page .settings-offline .event-close-flow {
  visibility: hidden;
}

@media (min-width: 620px) {
  .settings-page .credential-form {
    grid-template-columns: minmax(0, 1fr) auto;
    align-items: end;
  }

  .settings-page .credential-form > :not(label):not(button) {
    grid-column: 1 / -1;
  }

  .settings-page .credential-form button {
    width: auto;
  }

  .settings-page .reconciliation-grid {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

@media (max-width: 430px) {
  .settings-page header,
  .settings-page main {
    width: min(calc(100% - 24px), 720px);
  }

  .settings-page header {
    align-items: flex-start;
    gap: 10px;
  }

  .settings-page .role-nav {
    min-width: 0;
    justify-content: flex-end;
    gap: 8px;
  }

  .settings-page .role-nav .role {
    flex-basis: 100%;
    text-align: right;
  }

  .settings-page .panel.guarded {
    padding: 20px 16px;
  }
}

@container (max-width: 220px) {
  .settings-page #status.status {
    position: static;
    right: auto;
    bottom: auto;
    max-width: 100%;
    margin: 0 0 16px;
    transform: none;
  }

  .settings-page #status.status:not(.visible) {
    margin: 0;
    padding: 0;
  }
}

/* ==== V2 route section: /board ==== */
