/* The shared visual system — decision 10, held to decision 13's floor.
 *
 * An editorial noticeboard: a church sign-up sheet, not a corkboard simulation
 * and not a delivery app. Three things carry that read, and nothing else has to:
 * a system serif on the event title, the dish names and the one line that
 * confirms the order, hairline rules instead of cards, and one tilted snapshot
 * per row. There is no paper texture and no simulated board — both were tried
 * and rejected as twee at phone width.
 *
 * Linked by every generated order page AND by the builder, which gets the tokens
 * and the shared dish card and nothing else; its own chrome is builder.css.
 *
 * No Google Fonts, no webfont, no self-hosted font: system stacks only. That
 * removes the last third-party runtime dependency from a static site.
 *
 * Every colour pair below was computed, not eyeballed. All text clears 4.5:1 on
 * its own ground with no large-text exemption claimed anywhere.
 */

:root {
  /* Decision 10's palette, measured on the #f7f4ee ground:
   *   ink       13.9:1    secondary  5.7:1    accent  5.6:1  (6.1:1 on white)
   * There is deliberately NO muted token. The incumbent's #9b8c84 measures
   * 2.7:1 and there is no text tier it can legally serve, so it is gone rather
   * than demoted — a token that exists gets used. */
  --ground: #f7f4ee;
  --ink: #2c2420;
  --secondary: #6b5e56;
  --accent: #8f5322;
  --accent-deep: #7a4519; /* pressed / in-flight submit — 7.8:1 with white */
  --rule: #e0d8ca; /* hairline between rows; decorative, never a boundary */
  --frame: #fff; /* the polaroid frame and every control face */
  --frame-hover: #fbf8f3; /* that face under the cursor — a hint of the ground */

  /* What sits behind a photo: the letterbox under an image still painting, and
   * the empty frame in the builder. Secondary ink measures 5.10:1 on it, which
   * is this page's TIGHTEST measured pair — so nothing quieter than --secondary
   * may ever be set on this ground. */
  --photo-ground: #efe7db;

  /* The one colour the palette did not carry and the controls cannot do
   * without: a real edge on the steppers and the text fields. WCAG 1.4.11 asks
   * 3:1 for the visual boundary of a control, and --rule is 1.3:1 — fine as
   * decoration between rows, invisible as an affordance. 3.4:1 on the ground,
   * 3.7:1 on white. NEVER used for text; it would not clear 4.5:1 and this is
   * not the dropped muted token coming back through a side door. */
  --control-edge: #8f8279;

  --serif: ui-serif, Georgia, 'Iowan Old Style', 'Palatino Linotype', serif;
  --sans: system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;

  /* Decision 13's touch floor, named once so it cannot drift per control. The
   * prototype's 38px steppers were superseded by this. */
  --tap: 44px;
}

*,
*::before,
*::after {
  box-sizing: border-box;
}

/* Elements ship with `hidden` and are revealed by script (#closed-notice,
 * .dish-subtotal, #form-problem). A layout rule below would otherwise out-rank
 * the UA's hidden styling and reveal them at load. */
[hidden] {
  display: none !important;
}

body {
  margin: 0;
  background: var(--ground);
  color: var(--ink);
  font: 1rem/1.55 var(--sans);
  -webkit-text-size-adjust: 100%;
}

img {
  max-width: 100%;
}

/* One authored ring on every interactive control. The incumbent authored one
 * for text inputs and left the page's primary control on the bare UA outline
 * (decision 13). The offset puts the ring on the ground, where the accent
 * measures 5.6:1 — well past the 3:1 a focus indicator needs. */
:focus-visible {
  outline: 3px solid var(--accent);
  outline-offset: 2px;
}

/* The sticky bar can otherwise cover whatever the keyboard just moved to. */
html {
  scroll-padding-top: 4rem;
}

/* ── the running total ───────────────────────────────────────────────────── */

/* Decision 14: the total rides at the TOP, sticky and never fixed, because the
 * 44px steppers have already claimed the thumb zone. A tally, not a cart — no
 * badge, no count chip, no floating action button.
 *
 * This element is the page's single aria-live region, and it ships rendered and
 * empty rather than hidden. The empty state is therefore a GEOMETRIC collapse:
 * height, padding and overflow only. Anything that removes it from the
 * accessibility tree drops the first announcement — the one that matters — and
 * tests/run.sh fails the suite over it. See src/pages.mjs for the contract.
 */
.total-bar {
  position: sticky;
  top: 0;
  z-index: 5;
  background: var(--ground);
  border-bottom: 1px solid var(--rule);
  padding: 0.75rem 1.25rem;
}

.total-bar.is-empty {
  height: 0;
  padding-top: 0;
  padding-bottom: 0;
  border-bottom-width: 0;
  overflow: hidden;
}

.total-bar-text {
  margin: 0;
  max-width: 34rem;
  margin-inline: auto;
  text-align: center;
  font-size: 1rem;
  font-variant-numeric: tabular-nums;
  color: var(--ink);
}

/* ── the sheet ───────────────────────────────────────────────────────────── */

.board {
  max-width: 34rem;
  margin-inline: auto;
  padding: 1.5rem 1.25rem 3rem;
}

.board-head {
  text-align: center;
  padding-bottom: 1.25rem;
  margin-bottom: 1.5rem;
  border-bottom: 1px solid var(--rule);
}

/* The serif is the single biggest reason the page stops reading as a delivery
 * app. It is spent here and on the dish names, and nowhere else. */
.event-title {
  margin: 0;
  font-family: var(--serif);
  font-size: 1.75rem;
  font-weight: 600;
  line-height: 1.2;
  letter-spacing: -0.01em;
}

.event-when {
  margin: 0.5rem 0 0;
  color: var(--secondary);
}

.instruction {
  margin: 0.875rem 0 0;
  font-style: italic;
  color: var(--secondary);
}

/* ── the dish row ────────────────────────────────────────────────────────── */

/* Ruled rows, not cards: no background, no border box, no drop shadow. The row
 * is a line on a sheet of paper, and the sheet is the page. */
.dish {
  display: flex;
  align-items: flex-start;
  gap: 0.875rem;
  padding: 1.125rem 0 1.125rem 0.8125rem;
  border-left: 3px solid transparent;
  border-bottom: 1px solid var(--rule);
}

.dish:last-of-type {
  border-bottom: none;
}

/* The index reuses the ruled row as a link — decision 16: no new visual
 * vocabulary. Stripping the UA anchor dress (underline, link blue) is what
 * keeps a row the same row whether it is an <article> (a dish) or an <a>
 * (a gathering). */
a.dish {
  text-decoration: none;
  color: inherit;
}

/* Selection is a 3px accent rule down the left edge — and it is redundant
 * decoration, not the signal: the stepper already shows the quantity as a
 * numeral (decision 13).
 *
 * `[data-qty]` is load-bearing. card.mjs emits the attribute in member mode
 * only, so a bare `:not([data-qty="0"])` would match every card in the builder,
 * where the attribute is absent — and light the whole menu up. */
.dish[data-qty]:not([data-qty='0']) {
  border-left-color: var(--accent);
}

/* The one physical gesture that survives the dial-back, and what stops the
 * photography reading as commercial food shots. Even padding on all four sides:
 * a real polaroid's weighted chin was tried and reads as dead space at this
 * size. `flex-start` keeps the frame at its natural height at the top of the
 * row rather than stretching to it; the fixed ratio is what makes the left edge
 * a straight column down the page. */
.polaroid {
  flex: 0 0 88px;
  align-self: flex-start;
  padding: 6px;
  background: var(--frame);
  border: 1px solid var(--rule);
  box-shadow: 0 1px 4px rgba(60, 45, 30, 0.13);
  transform: rotate(-2.4deg);
  /* Declared on the base rule, not inside `.is-sending`, so the fade is
   * symmetric: a transition that only exists while the class is on eases in and
   * then snaps back the instant it is removed, which is exactly the moment the
   * failure path lands on. */
  transition: opacity 0.2s ease;
}

.dish:nth-of-type(even) .polaroid {
  transform: rotate(2.4deg);
}

/* The frame is `flex: 0 0` on purpose — a polaroid that shrinks by a few pixels
 * per row would lose the straight left column that holds the sheet together.
 * But 88px holds the row to about ten pixels of headroom at the 320px reflow
 * floor, and ten pixels is not a margin, it is luck: one longer stepper glyph
 * or one browser rounding the 44px targets up and the row overflows. So the
 * frame steps down once, below the narrowest phone anyone actually carries, and
 * the column stays straight on either side of the step. */
@media (max-width: 22.5rem) {
  .polaroid {
    flex-basis: 76px;
  }
}

.polaroid-img {
  display: block;
  width: 100%;
  aspect-ratio: 5 / 4;
  object-fit: cover;
  background: var(--photo-ground);
}

.dish-body {
  flex: 1;
  min-width: 0;
}

.dish-name {
  margin: 0;
  font-family: var(--serif);
  font-size: 1.1875rem;
  font-weight: 600;
  line-height: 1.3;
  color: var(--ink);
}

.dish-desc {
  margin: 0.375rem 0 0;
  font-size: 1rem;
  line-height: 1.5;
  color: var(--secondary);
}

/* At 375px the polaroid and the row's own indent leave roughly 217px of text
 * column, and a price plus a three-cell 44px stepper needs about 225px. Rather
 * than let that wrap raggedly — some rows on one line, some on two, depending
 * on how long the price is — the narrow layout stacks deliberately, and the row
 * becomes a single line only once there is room for one. */
.dish-foot {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 0.625rem;
  margin-top: 0.75rem;
}

@media (min-width: 30rem) {
  .dish-foot {
    flex-direction: row;
    align-items: center;
    justify-content: space-between;
    gap: 0.75rem;
  }
}

/* Price is NEVER a pill. Plain text at body weight in secondary ink — the
 * accent is not spent on prices, because a price-forward card implies a
 * checkout that is never coming. */
.dish-cost {
  margin: 0;
  font-size: 1rem;
  color: var(--secondary);
  font-variant-numeric: tabular-nums;
}

/* Revealed by order.mjs once there is a quantity — and carrying NO rule of its
 * own, deliberately. Decision 10 wants the WHOLE price string, "$8.00 each ·
 * $16.00", as plain text at body weight in secondary ink, which is exactly what
 * .dish-cost above already sets. Giving the line total ink at 600 would make it
 * the one thing on the page that reads price-forward, and price-forward is the
 * e-commerce checkout this design spent a ticket walking away from: the money
 * is settled in person, so the number is a note, not a call to action.
 *
 * The separator and the space around it belong to the text ticket 3 writes —
 * `' · ' + formatCurrency(line)` — not to a margin or a ::before here. Text
 * rather than a margin because it also collapses correctly when the line wraps
 * between "each" and the total. */

/* ── the stepper ─────────────────────────────────────────────────────────── */

/* 44x44, the AAA target size, because older members ordering on their own
 * phones is the confirmed premise of the whole product. The three cells share
 * one continuous edge. */
.stepper {
  display: flex;
  align-items: center;
}

.step {
  min-width: var(--tap);
  min-height: var(--tap);
  padding: 0 0.5rem;
  background: var(--frame);
  color: var(--accent);
  border: 1px solid var(--control-edge);
  border-radius: 0;
  font: 600 1.25rem/1 var(--sans);
  cursor: pointer;
  /* Same reason as the polaroid's: the in-flight drain below has to ease out as
   * well as in, so the transition lives on the base rule. */
  transition:
    background-color 0.2s ease,
    color 0.2s ease;
}

.stepper .step:first-child {
  border-radius: 3px 0 0 3px;
}

.stepper .step:last-child {
  border-radius: 0 3px 3px 0;
}

.step:hover {
  background: var(--frame-hover);
}

.qty {
  width: 3.25rem;
  min-height: var(--tap);
  margin-inline: -1px; /* one shared edge between the three cells */
  padding: 0 0.25rem;
  background: var(--frame);
  color: var(--ink);
  border: 1px solid var(--control-edge);
  border-radius: 0;
  font: 600 1rem/1 var(--sans);
  font-variant-numeric: tabular-nums;
  text-align: center;
  appearance: textfield;
  -moz-appearance: textfield;
}

/* The native spinners would eat a third of a cell sized for a thumb. */
.qty::-webkit-outer-spin-button,
.qty::-webkit-inner-spin-button {
  -webkit-appearance: none;
  margin: 0;
}

.qty:focus-visible {
  position: relative; /* the ring is drawn over the neighbouring buttons */
}

/* ── the tally ───────────────────────────────────────────────────────────── */

/* The review before committing. It stays even though the sticky bar carries the
 * same number (decision 14) — and it updates silently, because the bar is the
 * page's only live region. */
.tally {
  border-top: 2px solid var(--ink);
  margin-top: 1.75rem;
  padding-top: 1rem;
}

.tally-head {
  margin: 0 0 0.5rem;
  font-size: 0.875rem; /* 14px, the absolute floor, and only ever for a label */
  font-weight: 600;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--ink);
}

/* order.mjs owns the line markup and this file cannot name a class that has not
 * been written yet, so the two-column line keys off the container's children. */
.tally-lines > * {
  display: flex;
  justify-content: space-between;
  gap: 1rem;
  margin: 0;
  padding: 0.1875rem 0;
  color: var(--secondary);
  font-variant-numeric: tabular-nums;
}

.tally-sum {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 1rem;
  margin: 0.75rem 0 0;
  padding-top: 0.75rem;
  border-top: 1px solid var(--rule);
}

/* "To bring on the day" — never "Total", never "Subtotal". The copy is
 * load-bearing (decision 10) and the type stays quiet under it. */
.tally-label {
  color: var(--ink);
}

.tally-total {
  font-size: 1.5rem;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  letter-spacing: -0.01em;
}

.tally-note {
  margin: 0.625rem 0 0;
  color: var(--secondary);
}

/* ── who is putting their name down ──────────────────────────────────────── */

.who {
  margin-top: 1.75rem;
}

.field {
  margin: 0 0 1rem;
}

/* A placeholder is not a label (decision 13): every field keeps a visible,
 * persistent one at body size. */
.field label {
  display: block;
  margin-bottom: 0.375rem;
  font-weight: 600;
  color: var(--ink);
}

.field input {
  width: 100%;
  min-height: var(--tap);
  padding: 0.625rem 0.75rem;
  background: var(--frame);
  color: var(--ink);
  border: 1px solid var(--control-edge);
  border-radius: 3px;
  font: 1rem/1.4 var(--sans);
}

/* Placeholders are examples, never labels, and they hold 4.5:1 if used at all —
 * the incumbent's measured 1.91:1. */
::placeholder {
  color: var(--secondary);
  opacity: 1;
}

/* ── putting your name down ──────────────────────────────────────────────── */

.go {
  display: block;
  width: 100%;
  min-height: 52px;
  margin-top: 1.25rem;
  padding: 0.875rem 1rem;
  background: var(--accent);
  color: var(--frame);
  border: none;
  border-radius: 3px;
  font: 600 1.0625rem/1.3 var(--sans);
  cursor: pointer;
}

.go:hover {
  background: var(--accent-deep);
}

/* Ticket 6 disables this only while a submission is in flight; decision 07 says
 * it is never disabled to signal an unmet condition. Either way it DARKENS
 * rather than fades: the incumbent's disabled state measured 1.90:1, and a
 * control a member is waiting on is the worst place to lose the label. */
.go:disabled {
  background: var(--accent-deep);
  cursor: progress;
}

/* The accent ring would vanish against the accent face, so this one control
 * takes an ink ring. */
.go:focus-visible {
  outline-color: var(--ink);
}

/* What is missing, written by order.mjs and read out by the focus move onto the
 * field it describes — deliberately not a live region, since the total bar is
 * the only one. Marked by an accent rule rather than a red that the palette
 * does not have and does not need. */
.form-problem {
  margin: 0.75rem 0 0;
  padding-left: 0.75rem;
  /* impeccable-disable-next-line side-tab: decision 10's notice vocabulary — the palette has no red, an accent rule marks what needs attention (classified by ticket 12) */
  border-left: 3px solid var(--accent);
  color: var(--ink);
}

/* ── while it is in flight ───────────────────────────────────────────────── */

/* Spec 07 measured the round trip at 2679 ms, so this is not a flash between
 * two states — it is roughly three seconds of screen time on a phone, and it
 * has to look deliberate rather than hung. Ticket 6 adds `is-sending` to
 * #order-form for the duration and removes it again; the label swap to "Putting
 * your name down…" and the `disabled` are ticket 6's, and `.go:disabled` above
 * already darkens rather than fades so the label survives the wait.
 *
 * "The order list dims but stays visible" is served here by the FURNITURE going
 * quiet, and never by an opacity over text. Opacity cannot be cancelled by a
 * child, so it may only be set on a subtree that carries no words at all, and
 * on this page that is the photographs and nothing else: decorative imagery
 * holds no ratio, while every text pair here was measured against 4.5:1 with no
 * headroom to spend (secondary ink at 0.9 over the ground is already 4.57:1).
 *
 * That rules out the whole stepper, not just the row. `.qty` is a text input
 * INSIDE `.stepper`, so an opacity there takes the number the member is waiting
 * to see recorded from 15.2:1 down to 2.7:1 for the ~2.7s. Nor is there an
 * exemption to lean on: the only inertness this file can apply is
 * `pointer-events`, which a keyboard ignores, so the controls stay focusable
 * and operable and 1.4.3 keeps applying to them in full. Real operability is
 * ticket 6's, and this file may not assume markup ticket 6 has not promised.
 *
 * So the two buttons go quiet by DRAINING rather than fading: the accent face
 * washes out to the paper and the glyph drops to secondary ink. Measured on the
 * ground, that glyph is 5.69:1 against the 6.1:1 of the accent it replaces —
 * both clear the floor — and the 1px --control-edge still holds 3.39:1 on the
 * ground, so the 44px target keeps its 1.4.11 boundary while it waits. `.qty`
 * is left alone entirely: full ink on the full white frame, 15.2:1, which also
 * leaves it the one bright cell among three and so reads as the thing being
 * sent rather than the thing switched off. */
.is-sending {
  cursor: progress;
}

.is-sending .polaroid {
  opacity: 0.45;
}

/* Later than `.step:hover` at equal specificity, so it wins for the duration. */
.is-sending .step {
  background: var(--ground);
  color: var(--secondary);
}

/* The pointer half of "inert", and only that half: ticket 6 still owns real
 * operability, because pointer-events does not stop a keyboard and a quantity
 * changed after the payload was built would be recorded wrong. */
.is-sending .stepper {
  pointer-events: none;
}

/* ── the terminal moment ─────────────────────────────────────────────────── */

/* Ticket 6 owns src/order.mjs and writes every byte of the markup below; this
 * file owns every rule for it. Neither ticket can reach the other's file, so
 * the class names ARE the contract and they are spelled out here in full. As
 * with .tally-lines, the itemised lines are styled by position rather than by
 * class, so ticket 6 can emit <li>, <p> or <div> and get the two-column line
 * either way — it only has to put two children in each one.
 *
 * On success, replacing #order-form in place (spec 07, no navigation):
 *
 *   <section class="confirmed" id="confirmed" tabindex="-1">
 *     <h2 class="confirmed-head">
 *       <span class="confirmed-mark" aria-hidden="true">&check;</span>
 *       You&rsquo;re down for Sunday, 18 August
 *     </h2>
 *     <ul class="confirmed-lines">
 *       <li><span>Southern Fried Chicken &times; 2</span><span>$16.00</span></li>
 *       <li><span>Buttermilk Cornbread &times; 1</span><span>$2.50</span></li>
 *     </ul>
 *     <p class="confirmed-sum">
 *       <b class="confirmed-total">$18.50</b> to bring on the day
 *     </p>
 *     <p class="confirmed-echo">Under: Chris Chandra<br>
 *       We&rsquo;ll reach you on 0412 345 678</p>
 *     <p class="confirmed-amend">Need to change it? Have a word with Chris
 *       &mdash; 0412 000 000</p>
 *   </section>
 *
 * The tabindex is there so ticket 6 can move focus to the block it just swapped
 * in; without it the focus falls to <body> and the terminal moment is silent.
 * There is no order reference in that markup and none may be added — a
 * reference is the single artifact that turns a sign-up into a checkout.
 */
.confirmed {
  margin-top: 2rem;
}

/* The serif reaches one line further than the event title and the dish names,
 * and this is the line. It is the title restated as a fact — "You're down for"
 * the same date the header names — so it wears the type of the sentence it
 * answers; set in the sans it would read as a toast dropped onto the sheet
 * rather than as the sheet's own last word. Nothing else here takes the serif. */
.confirmed-head {
  margin: 0;
  font-family: var(--serif);
  font-size: 1.5rem;
  font-weight: 600;
  line-height: 1.3;
  letter-spacing: -0.01em;
  color: var(--ink);
}

/* The one tick on the page. It is decoration over a sentence that already says
 * the thing, hence aria-hidden in the markup above. */
.confirmed-mark {
  color: var(--accent);
  margin-right: 0.125rem;
}

/* Ruled lines, like the dishes they reflect — the confirmation is the same
 * sheet, not a receipt printed on top of it. The rule under the last line is
 * kept rather than dropped: here it is the ledger line above the total. */
.confirmed-lines {
  margin: 1.5rem 0 0;
  padding: 0;
  list-style: none;
  border-top: 1px solid var(--rule);
}

.confirmed-lines > * {
  display: flex;
  justify-content: space-between;
  gap: 1rem;
  margin: 0;
  padding: 0.625rem 0;
  border-bottom: 1px solid var(--rule);
  color: var(--ink);
  font-variant-numeric: tabular-nums;
}

/* "$18.50 to bring on the day" — the framing decision 10 authored, with the
 * amount leading and the sentence carrying it. Never "Total", never "Paid". */
.confirmed-sum {
  margin: 1.125rem 0 0;
  color: var(--ink);
}

.confirmed-total {
  font-size: 1.5rem;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  letter-spacing: -0.01em;
}

/* The name and the phone read back. Validation catches the shape of a number
 * and can never catch a mistyped middle digit; this echo is the only thing that
 * can, so it is set at full ink and given room to be re-read, not tucked away
 * as fine print. */
.confirmed-echo {
  margin: 1.25rem 0 0;
  color: var(--ink);
  line-height: 1.6;
}

/* Amendment is out-of-band by design — this line is the entire mechanism, so it
 * is quiet but it is never optional. */
.confirmed-amend {
  margin: 1.25rem 0 0;
  padding-top: 1.25rem;
  border-top: 1px solid var(--rule);
  color: var(--secondary);
}

/* ── and when it does not go through ─────────────────────────────────────── */

/* Ticket 6's markup, appended near the submit button with the form left
 * standing — the order is never cleared on failure, which is what makes the
 * copy "your order is still here" true rather than reassuring:
 *
 *   <section class="trouble" id="trouble" tabindex="-1">
 *     <h2 class="trouble-head">That didn&rsquo;t go through.</h2>
 *     <p class="trouble-note">Your order is still here &mdash; nothing&rsquo;s lost.</p>
 *     <div class="trouble-actions">
 *       <button class="go" id="retry" type="button">Try again</button>
 *       <a class="trouble-alt" href="https://wa.me/&hellip;">or send it to Chris on WhatsApp &rarr;</a>
 *     </div>
 *   </section>
 *
 * It wears the notice language of .closed-notice rather than a red panel: the
 * palette has no red, retry is free because the submission id makes it
 * idempotent, and nothing here is an error the member caused.
 */
.trouble {
  margin-top: 1.25rem;
  padding: 1.125rem 1.25rem;
  background: var(--frame);
  border: 1px solid var(--rule);
  /* impeccable-disable-next-line side-tab: the same notice vocabulary as .form-problem, deliberately (classified by ticket 12) */
  border-left: 3px solid var(--accent);
  color: var(--ink);
}

.trouble-head {
  margin: 0;
  font-size: 1.125rem;
  font-weight: 600;
  line-height: 1.3;
  color: var(--ink);
}

.trouble-note {
  margin: 0.375rem 0 0;
  color: var(--ink);
}

/* Stacked at phone width, because a full-width primary control is the whole
 * point of it and the two actions are not peers. */
.trouble-actions {
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
  margin-top: 1rem;
}

/* Retry is the same action as before, so it is the same control: .go, ring and
 * all. Only the stacking margin it carries as the form's last element goes. */
.trouble-actions .go {
  margin-top: 0;
}

/* The fallback leaves the page, so it stays a link and is never dressed as a
 * second button. Accent on the frame measures 6.1:1, and the tap floor is met
 * with height rather than with a box. */
.trouble-alt {
  display: inline-flex;
  align-items: center;
  min-height: var(--tap);
  color: var(--accent);
  text-underline-offset: 3px;
}

@media (min-width: 30rem) {
  .trouble-actions {
    flex-direction: row;
    align-items: center;
    gap: 1.25rem;
  }

  .trouble-actions .go {
    width: auto;
  }
}

/* ── ordering has closed ─────────────────────────────────────────────────── */

/* Filled and revealed by order.mjs once the deadline has passed. The menu stays
 * and reads as a notice board still worth looking at; only the controls go. */
.closed-notice {
  margin-bottom: 1.5rem;
  padding: 1rem 1.125rem;
  background: var(--frame);
  border: 1px solid var(--rule);
  /* impeccable-disable-next-line side-tab: the same notice vocabulary as .form-problem, deliberately (classified by ticket 12) */
  border-left: 3px solid var(--accent);
  color: var(--ink);
}

/* The two in-sentence links — the closed notice's and the confirmation's
 * "have a word with Chris" — stay prose rather than buttons, but each is the
 * only control its state leaves a member, so the 44px tap floor still applies:
 * ticket 12 measured the bare line at 35×21. Padding pulled back by an equal
 * negative margin grows the touch target past 44px on both axes without moving
 * a glyph, and everything the larger target overlaps is plain prose, so it
 * competes with nothing. (.trouble-alt already meets the floor with height.) */
.closed-notice a,
.confirmed-amend a {
  padding: 0.75rem 0.5rem;
  margin: -0.75rem -0.5rem;
}

/* ── the same card, being edited ─────────────────────────────────────────── */

/* The shared card does not fork for the builder: the row, the rules, the tilt
 * and the type are byte-identical, and only the controls swap. The tilt stays
 * in edit mode on purpose — a straightened card would misrepresent what gets
 * published. Everything below is the swapped control wearing the type it
 * replaced. The builder holds AA contrast but not the 44px sizing: one known
 * person, on a desktop (decision 13). */
.name-input,
.desc-input,
.price-input {
  background: var(--frame);
  color: var(--ink);
  border: 1px solid var(--control-edge);
  border-radius: 3px;
  padding: 0.375rem 0.5rem;
  font-size: 1rem;
}

.name-input {
  width: 100%;
}

.desc-input {
  width: 100%;
  resize: vertical;
  line-height: 1.5;
}

.price-input {
  width: 5.5rem;
  font-variant-numeric: tabular-nums;
}

/* The one place a dish legitimately has no photo yet: the moment before the
 * organiser attaches one. Same frame, same tilt, now a click target. */
.polaroid-target {
  display: block;
  width: 88px;
  cursor: pointer;
}

.polaroid-empty {
  display: grid;
  place-items: center;
  aspect-ratio: 5 / 4;
  background: var(--photo-ground);
  color: var(--secondary);
  font: 0.875rem/1.3 var(--sans);
  text-align: center;
  padding: 0 0.25rem;
}

.card-actions {
  display: flex;
  align-items: center;
  gap: 0.375rem;
}

.card-actions .step {
  border-radius: 3px;
}

.step-remove {
  padding: 0 0.75rem;
  font-size: 1rem;
}

/* ── more room ───────────────────────────────────────────────────────────── */

@media (min-width: 40rem) {
  .board {
    padding: 2.25rem 1.75rem 4rem;
  }

  .event-title {
    font-size: 2.125rem;
  }

  .dish {
    gap: 1.125rem;
    padding-left: 1rem;
  }

  .polaroid {
    flex-basis: 104px;
  }

  .polaroid-target {
    width: 104px;
  }
}

/* ── forced colors (Windows High Contrast) ───────────────────────────────── */

/* The check carried over from ticket 2's review: `forced-colors: active`
 * overrides every background-color and color above, so the in-flight drain on
 * the steppers has zero effect there — and that is acceptable, because the
 * drain was never the signal's only carrier. While a submission is in flight,
 * ticket 6 sets `disabled` on every control, and forced-colors renders
 * disabled controls in GrayText natively — a stronger cue there than the drain
 * is here — on top of the polaroid's opacity (which forced-colors leaves
 * alone) and the submit label swap to "Putting your name down…". Greyscale and
 * colour-vision deficiency lean on the same two non-colour cues; the drain
 * itself is near-isoluminant (5.69:1 vs 6.13:1 against their grounds) and is
 * treated as invisible in both audits. So there are deliberately NO
 * forced-colors rules for the in-flight state.
 *
 * What does need one is the submit button: the one control drawn with
 * `border: none`, carried entirely by its accent face — and forced-colors
 * strips that face, leaving the page's primary control as bare text on canvas
 * with no boundary at all. One border restores it; the system palette colours
 * it. (Everything else — steppers, fields, notices, the polaroid — already
 * has a real border that forced-colors recolours and keeps.) */
@media (forced-colors: active) {
  .go {
    border: 1px solid ButtonText;
  }
}

/* ── on paper ────────────────────────────────────────────────────────────── */

/* A congregation prints things. The menu taped to a noticeboard, and the
 * confirmation folded into a pocket so the right cash arrives on Sunday, are
 * both plausible enough to be worth six rules. Everything that only works with
 * a finger goes; everything that says what was ordered stays.
 *
 * The total bar collapses GEOMETRICALLY here too, for the same reason it does
 * in .is-empty and by the same means. `display:none` would be harmless on paper
 * — there is no accessibility tree in a printer — but the rule that keeps this
 * element out of display:none is worth more as an absolute than as a rule with
 * one exception, and tests/run.sh enforces it as one. The number is not lost
 * either way: the tally carries it, and the tally prints. */
@media print {
  .total-bar {
    position: static;
    height: 0;
    padding-top: 0;
    padding-bottom: 0;
    border-bottom-width: 0;
    overflow: hidden;
  }

  .stepper,
  .go,
  .form-problem,
  .trouble-actions {
    display: none;
  }

  body {
    background: #fff;
  }

  /* Ink and rules survive a printer; a 13%-alpha shadow becomes a grey smudge
   * and the tilt stays, because the tilt is the world and costs nothing. */
  .polaroid {
    box-shadow: none;
  }

  .dish,
  .confirmed {
    break-inside: avoid;
  }
}
