: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;
}

* {
  box-sizing: border-box;
}

body {
  min-width: 320px;
  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;
  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);
}

.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);
}

[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-age {
  color: var(--muted);
  font-family: "JetBrains Mono", ui-monospace, monospace;
  font-size: 11px;
}

.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 {
  color: white;
  background:
    radial-gradient(circle at 80% 20%, rgb(234 88 12 / 0.35), transparent 38%),
    var(--surface);
}

/*
 * 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%;
  min-height: 100vh;
  padding: 32px clamp(24px, 3vw, 56px);
}

/* 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(12px, 1.6vw, 28px);
  align-items: baseline;
  justify-content: flex-start;
  padding-block: 0;
}

.board-main h1 {
  margin: 0;
  font-size: clamp(22px, 2.2vw, 34px);
}

/* 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;
  color: var(--peach);
}

.board-stalls {
  overflow: hidden;
  margin: 0;
  color: var(--peach);
  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(24px, 3vw, 56px);
  align-content: start;
  min-height: 0;
  margin-top: clamp(20px, 2.5vh, 36px);
}

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

.board-columns > section > h2 {
  margin: 0 0 16px;
  font-size: clamp(22px, 2.4vw, 38px);
}

/* 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 {
  font-size: clamp(28px, 3.2vw, 52px);
}

/*
 * 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. */
  gap: min(max(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(8px, calc(var(--row-height) * 0.07)), 40px);
  padding-left: clamp(12px, 1.2vw, 20px);
  border-left: 4px solid var(--peach);
  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: var(--peach);
  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: 1px solid rgb(255 255 255 / 0.25);
  /* 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;
}

/*
 * 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);
}

/* Names are set at roughly half the number: legible to a Guest who has walked up,
   subordinate to the number a volunteer calls across the hall. `0.4` is what binds on
   a full page, so only a sparse board grows. */
.board-orders small {
  overflow: hidden;
  color: var(--peach);
  /* `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(15px, min(clamp(18px, 10cqw, 64px), var(--row-height) * 0.4));
  line-height: 1.2;
  text-align: right;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* 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: 20px;
  margin-top: 20px;
  border-top: 1px solid rgb(255 255 255 / 0.25);
}

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

.board-freshness {
  margin: 0;
  color: #fed7aa;
  font-family: "JetBrains Mono", ui-monospace, monospace;
  font-size: clamp(16px, 1.3vw, 24px);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

/*
 * 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-status {
  font-size: clamp(24px, 3.4vw, 56px);
}

#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;
}

/*
 * 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;
}

.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: 40px;
  padding: 0;
  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-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 Overseer, 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;
}

.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 {
  padding: 18px 20px;
  background: #fffdfb;
}

.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;
}

.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 Overseer'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;
}

.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);
}

/*
 * 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;
}

.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) {
  .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;
}

/*
 * 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-age {
  margin: 0;
  color: var(--surface);
  font-size: 13px;
  font-weight: 700;
}

.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-age {
  margin: 2px 0 0;
  font-family: "JetBrains Mono", ui-monospace, monospace;
  font-size: 13px;
  font-weight: 700;
  color: var(--surface);
}

.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);
}

.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;
}
