/* Dinner By Derek — customer app. No hex literals: see theme.css. */

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

/* The browser hides [hidden] with `display: none` from its own stylesheet, and
   any author rule setting display beats it — so every component with a display
   of its own quietly stopped being hideable. The order bar was the visible
   one: it carried `hidden` at zero items and showed "0 items $0.00" anyway.
   One rule here rather than a :not([hidden]) on each, so the next component
   with a display does not have to remember. */
[hidden] { display: none !important; }

html { -webkit-text-size-adjust: 100%; }

body {
  margin: 0;
  background: var(--dbd-surface);
  color: var(--dbd-ink);
  font-family: var(--dbd-body);
  font-size: var(--dbd-step-0);
  line-height: 1.55;
  /* Parchment behind the safe areas so a notch never shows white */
  padding-top: env(safe-area-inset-top);
  padding-bottom: env(safe-area-inset-bottom);
}

h1, h2, h3, h4 {
  font-family: var(--dbd-display);
  color: var(--dbd-heading);
  line-height: 1.15;
  letter-spacing: -0.01em;
  margin: 0 0 var(--dbd-sp-3);
}
h1 { font-size: var(--dbd-step-4); }
h2 { font-size: var(--dbd-step-3); }
h3 { font-size: var(--dbd-step-2); }

a { color: var(--dbd-olive-deep); }

:focus-visible {
  outline: 3px solid var(--dbd-tan-deep);
  outline-offset: 2px;
  border-radius: var(--dbd-radius-sm);
}

.wrap { max-width: 720px; margin: 0 auto; padding: 0 var(--dbd-sp-4); }

/* --- Header / footer ---------------------------------------------------- */
/* The header is the repository's banner (docs/banner.svg, drawn by
   readme-art.js) rendered in CSS: olive band, a warm bloom behind the mark,
   a tan hairline top and bottom, and the lockup centred over a spaced tagline.
   The two are meant to be recognisably one thing, so if you move one, move the
   other — the banner's geometry is in scripts/readme-art.js. The motion is
   carried across too, at half scale: a shine along each hairline and steam
   off the mark. What is not carried across is the banner's second line of
   copy, because a README is looked at once and a header is passed every time
   somebody orders. */
.site-header {
  position: relative;
  overflow: hidden;
  background: var(--dbd-olive);
  color: var(--dbd-parchment);
  padding: var(--dbd-sp-5) 0;
  border-bottom: 3px solid var(--dbd-umber);
  text-align: center;
}
/* The bloom behind the mark — the oven door opening, roughly. */
.site-header::before {
  content: '';
  position: absolute;
  left: 50%; top: 50%;
  width: min(660px, 96%); aspect-ratio: 2.2;
  transform: translate(-50%, -60%);
  background: radial-gradient(ellipse at center,
    var(--dbd-bloom) 0%, transparent 70%);
  pointer-events: none;
}
/* The pair of hairlines, inset from the band's edges as they are in the SVG. */
.site-header::after {
  content: '';
  position: absolute;
  inset: var(--dbd-sp-3) 0;
  border-top: 3px solid var(--dbd-rule-tan-dark);
  border-bottom: 3px solid var(--dbd-rule-tan-dark);
  pointer-events: none;
}
/* --- The two moving parts ---
   In the banner these are SMIL; here they are CSS animations, so the
   prefers-reduced-motion rule further down already switches them off. Both
   rest in their invisible state — the shine parked off the left edge, the
   steam at zero opacity — so switching them off leaves nothing frozen on
   screen. Both paint above the hairlines and under the lockup, which is the
   order the banner draws them in. */
.site-shine {
  position: absolute;
  top: var(--dbd-sp-3); bottom: var(--dbd-sp-3);
  left: 0; width: 40%;
  z-index: 1;
  pointer-events: none;
  transform: translateX(-100%);
  background-image:
    linear-gradient(90deg, transparent, var(--dbd-tan-lift), transparent),
    linear-gradient(90deg, transparent, var(--dbd-tan-lift), transparent);
  background-size: 100% 3px;
  background-position: left top, left bottom;
  background-repeat: no-repeat;
  opacity: 0.95;
  animation: dbd-shine 7s linear infinite;
}
/* 250% of a 40%-wide box is one header width, so this crosses edge to edge. */
@keyframes dbd-shine {
  from { transform: translateX(-100%); }
  to   { transform: translateX(250%); }
}

/* 120x48 user units drawn at 120x48 CSS pixels, so the px in the keyframes
   below are the same units as the path data. */
.site-steam {
  position: absolute;
  top: 0; left: 50%; margin-left: -60px;
  z-index: 1;
  pointer-events: none;
  /* The band clips at its own top edge, and a bright stroke cut off mid-curve
     reads as a bug rather than as steam. Fading the top third dissipates the
     wisp before the edge can cut it. */
  -webkit-mask-image: linear-gradient(to bottom, transparent 0, #000 45%);
          mask-image: linear-gradient(to bottom, transparent 0, #000 45%);
}
.site-steam path {
  fill: none;
  stroke: var(--dbd-tan-lift);
  stroke-width: 1.75;
  stroke-linecap: round;
  opacity: 0;
  transform-box: view-box;
  --drift: -9px;
  animation: dbd-steam 5.5s linear infinite;
}
.site-steam path:nth-child(2) { --drift:  4px; animation-duration: 6.2s; animation-delay: 1.4s; }
.site-steam path:nth-child(3) { --drift: 10px; animation-duration: 5.9s; animation-delay: 2.9s; }
/* Rises, drifts sideways, fades out, repeats — the banner's wisp timings. */
@keyframes dbd-steam {
  0%   { opacity: 0;    transform: translate(0, 13px); }
  25%  { opacity: 0.68; }
  50%  { transform: translate(calc(var(--drift) * 0.4), 0); }
  60%  { opacity: 0.68; }
  100% { opacity: 0;    transform: translate(var(--drift), -15px); }
}
.site-header .wrap { position: relative; z-index: 1; }
.site-header a { color: var(--dbd-parchment); text-decoration: none; }
.site-lockup {
  display: inline-flex; flex-direction: column; align-items: center;
  gap: var(--dbd-sp-2);
}
.brandmark {
  width: 84px; height: 84px; border-radius: 50%;
  object-fit: contain; flex: none;
}
.site-title {
  font-family: var(--dbd-display);
  font-size: var(--dbd-step-3);
  color: var(--dbd-parchment);
  margin: 0; line-height: 1.1;
}
.site-tag {
  display: block;
  margin: var(--dbd-sp-2) 0 0;
  font-size: var(--dbd-step--1);
  color: var(--dbd-tan-lift);
  letter-spacing: 0.08em; text-transform: uppercase;
}
.site-footer {
  background: var(--dbd-olive);
  color: var(--dbd-parchment);
  margin-top: var(--dbd-sp-7);
  padding: var(--dbd-sp-5) 0;
  font-size: var(--dbd-step--1);
}
.site-footer a { color: var(--dbd-parchment); }

/* --- Cards -------------------------------------------------------------- */
.card {
  background: var(--dbd-surface-raised);
  border: 1px solid var(--dbd-rule-tan);
  border-radius: var(--dbd-radius);
  padding: var(--dbd-sp-4);
  margin-bottom: var(--dbd-sp-4);
}
.card--sunk { background: var(--dbd-surface-sunk); border-color: var(--dbd-rule-olive); }
/* Something happened that you should know about, but nothing is on fire and
   nothing needs doing this minute. Same card, one edge marked. */
.card--warn { border-left: 4px solid var(--dbd-warn); }

/* --- Buttons ------------------------------------------------------------ */
.btn {
  display: inline-flex; align-items: center; justify-content: center;
  min-height: var(--dbd-tap); padding: var(--dbd-sp-3) var(--dbd-sp-5);
  font: inherit; font-weight: 600;
  border-radius: var(--dbd-radius-sm);
  border: 1px solid transparent;
  cursor: pointer; text-decoration: none;
  transition: background-color 140ms ease, border-color 140ms ease;
}
.btn--primary { background: var(--dbd-tan); color: var(--dbd-espresso); }
.btn--primary:hover, .btn--primary:focus-visible { background: var(--dbd-ochre); }
.btn--secondary {
  background: var(--dbd-parchment); color: var(--dbd-umber);
  border-color: var(--dbd-olive);
}
.btn--secondary:hover { background: var(--dbd-surface-sunk); }
.btn--block { width: 100%; }
.btn[disabled] { opacity: 0.5; cursor: not-allowed; }

@media (prefers-reduced-motion: reduce) {
  * { transition: none !important; animation: none !important; }
}

/* --- Badges / chips ----------------------------------------------------- */
.badge {
  display: inline-block;
  background: var(--dbd-tan);
  color: var(--dbd-umber-deep);       /* 4.52:1 — see CONTRAST.md */
  font-size: var(--dbd-step--1); font-weight: 700;
  letter-spacing: 0.03em;
  padding: var(--dbd-sp-1) var(--dbd-sp-3);
  border-radius: var(--dbd-radius-pill);
  text-shadow: 0 1px 0 var(--dbd-rule-tan);   /* debossed */
}
.chip {
  display: inline-block;
  background: var(--dbd-surface-sunk);
  border: 1px solid var(--dbd-rule-olive);
  color: var(--dbd-ink);
  font-size: var(--dbd-step--1);
  padding: 2px var(--dbd-sp-3);
  border-radius: var(--dbd-radius-pill);
  margin: 0 var(--dbd-sp-1) var(--dbd-sp-1) 0;
}
.chip--allergen { border-color: var(--dbd-rule-tan); color: var(--dbd-tan-deep); font-weight: 600; }
/* A chip that is itself a one-button form. */
.chip--rm { display: inline-block; }
.chip--rm button {
  background: none; border: 0; padding: 0 0 0 var(--dbd-sp-1);
  color: var(--dbd-ink-muted); font: inherit; cursor: pointer;
}
.chip--rm button:hover { color: var(--dbd-danger); }
.chip--soldout { background: var(--dbd-umber); color: var(--dbd-parchment); border-color: var(--dbd-umber); }

/* --- Day selector ------------------------------------------------------- */
.daylist { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--dbd-sp-3); }
.daycard {
  display: block; text-decoration: none; color: inherit;
  background: var(--dbd-surface-raised);
  border: 1px solid var(--dbd-rule-tan);
  border-left: 5px solid var(--dbd-olive);
  border-radius: var(--dbd-radius);
  padding: var(--dbd-sp-4);
}
.daycard:hover { border-left-color: var(--dbd-tan); background: var(--dbd-cream-hi); }
.daycard__date {
  font-size: var(--dbd-step--1); text-transform: uppercase;
  letter-spacing: 0.09em; color: var(--dbd-ink-muted); font-weight: 700;
}
.daycard__dish { font-family: var(--dbd-display); font-size: var(--dbd-step-2); color: var(--dbd-heading); }
/* The second main on a day that has one. A step down from the featured dish,
   because it is the alternative rather than the headline — but in the heading
   face and the heading colour, because it is a main and not a footnote. */
.daycard__alt {
  font-family: var(--dbd-display); font-size: var(--dbd-step-0);
  color: var(--dbd-heading); margin-top: var(--dbd-sp-1);
}
.daycard__alt span {
  font-family: var(--dbd-body); font-size: var(--dbd-step--1);
  text-transform: uppercase; letter-spacing: 0.09em; font-weight: 700;
  color: var(--dbd-ink-muted); margin-right: var(--dbd-sp-2);
}
.daycard__meta { font-size: var(--dbd-step--1); color: var(--dbd-ink-muted); }

/* A day the kitchen is shut. Rendered as a plain block, never a link: there
   is nothing behind it to order, and a card that looks tappable and isn't is
   worse than one that plainly says so. */
.daycard--shut { border-left-color: var(--dbd-umber-soft); background: var(--dbd-parchment-2); }
.daycard--shut:hover { border-left-color: var(--dbd-umber-soft); background: var(--dbd-parchment-2); }

.state { font-weight: 700; font-size: var(--dbd-step--1); }
.state--open { color: var(--dbd-ok); }
/* Closing tonight and the late-request window are both amber: something can
   still be done, with a deadline on it. Everything past that is danger. */
.state--closing, .state--late { color: var(--dbd-warn); }
.state--closed, .state--past, .state--shut { color: var(--dbd-danger); }

/* --- Featured dish ------------------------------------------------------ */
.featured {
  background: var(--dbd-surface-raised);
  border: 1px solid var(--dbd-rule-tan);
  border-top: 6px solid var(--dbd-tan);
  border-radius: var(--dbd-radius);
  overflow: hidden;
  margin-bottom: var(--dbd-sp-6);
}
.featured__photo { width: 100%; aspect-ratio: 4 / 3; object-fit: cover; display: block; background: var(--dbd-surface-sunk); }
.featured__body { padding: var(--dbd-sp-5); }
.featured__eyebrow {
  font-size: var(--dbd-step--1); text-transform: uppercase; letter-spacing: 0.12em;
  color: var(--dbd-tan-deep); font-weight: 700; margin-bottom: var(--dbd-sp-2);
}
.featured__name { font-size: var(--dbd-step-4); margin-bottom: var(--dbd-sp-3); }

/* Other Options: a clearly secondary tier, still legible and complete */
.section-rule {
  display: flex; align-items: center; gap: var(--dbd-sp-3);
  margin: var(--dbd-sp-6) 0 var(--dbd-sp-4);
}
.section-rule::after { content: ''; flex: 1; height: 1px; background: var(--dbd-rule-tan); }
.section-rule h2 { margin: 0; font-size: var(--dbd-step-2); }

.subhead {
  font-family: var(--dbd-display); font-size: var(--dbd-step-1);
  color: var(--dbd-olive-deep); margin: var(--dbd-sp-5) 0 var(--dbd-sp-3);
  padding-bottom: var(--dbd-sp-1); border-bottom: 1px solid var(--dbd-rule-olive);
}
.optioncard {
  display: flex; gap: var(--dbd-sp-3);
  background: var(--dbd-surface-raised);
  border: 1px solid var(--dbd-rule-olive);
  border-radius: var(--dbd-radius-sm);
  padding: var(--dbd-sp-3);
  margin-bottom: var(--dbd-sp-3);
}
.optioncard__photo { width: 76px; height: 76px; object-fit: cover; border-radius: var(--dbd-radius-sm); flex: none; }

/* --- Tap a dish photo for the big one ----------------------------------- */
/* The button carries none of its own look — it exists to be a button, and the
   image inside it keeps the size and shape it already had. line-height:0 so
   the inline image contributes no descender gap under it. */
.photozoom {
  padding: 0; border: 0; background: none; font: inherit; color: inherit;
  line-height: 0; cursor: zoom-in; -webkit-appearance: none; appearance: none;
}
.photozoom:focus-visible { outline: 3px solid var(--dbd-olive); outline-offset: 2px; }
/* The featured photo is full-bleed across the card; the option card's is a
   fixed thumbnail in a flex row. The button has to be told which it is, or it
   shrink-wraps the first and stretches the second. */
.photozoom--featured__photo { display: block; width: 100%; }
/* align-self, or the flex row stretches the button to the full card height and
   the tappable area runs on well below the picture — 76px of photo with 62px
   of nothing under it, all of it opening the lightbox. */
.photozoom--optioncard__photo { flex: none; align-self: flex-start; }

/* --- A dish photographed at both sizes ----------------------------------
   Two pictures, each captioned with the size it is, above the price rows.
   Only drawn when the second photo exists and both sizes are for sale, so a
   dish with one picture is laid out exactly as it was. */
.photopair { display: grid; gap: var(--dbd-sp-2); }
/* Side by side under the headline dish, which is full-bleed at the top of the
   card and has the width to halve. */
.photopair--featured { grid-template-columns: 1fr 1fr; }
.photopair--featured .featured__photo { aspect-ratio: 1 / 1; }
/* Stacked in an option card, which is a row -- photo beside text. Two of them
   side by side would take 160px off a 375px phone and leave the description
   in a column three words wide. */
.photopair--option { grid-template-columns: 1fr; flex: none; align-self: flex-start; }
.photopair__item { margin: 0; }
.photopair__cap {
  font-size: var(--dbd-step--1); color: var(--dbd-tan-deep);
  text-align: center; padding-top: var(--dbd-sp-1);
}

/* The box takes the shape of the picture rather than the picture being letter-
   boxed into a fixed box: Derek shoots on a phone, so most of these are taller
   than they are wide, and a portrait photo in a 4:3 frame is mostly empty
   background. width:max-content lets the dialog shrink to the image, with a
   floor so the caption under a very narrow one still has a line to sit on. */
.lightbox {
  border: 0; padding: 0;
  width: max-content; min-width: min(280px, 88vw); max-width: min(92vw, 1100px);
  max-height: 90vh;
  background: var(--dbd-surface-raised); color: var(--dbd-ink);
  border-radius: var(--dbd-radius); overflow: auto;
}
.lightbox::backdrop { background: var(--dbd-scrim); }
.lightbox__img {
  display: block; width: auto; max-width: 100%; max-height: 74vh;
  margin: 0 auto; background: var(--dbd-surface-sunk);
}
.lightbox__body { padding: var(--dbd-sp-4) var(--dbd-sp-5) var(--dbd-sp-5); }
.lightbox__name { font-size: var(--dbd-step-2); margin-bottom: var(--dbd-sp-2); }
.lightbox__desc { margin: 0; }
.lightbox__close { position: absolute; top: var(--dbd-sp-3); right: var(--dbd-sp-3); margin: 0; }
.lightbox__close .btn { min-width: 44px; padding: var(--dbd-sp-2); }
.optioncard__name { font-family: var(--dbd-display); font-size: var(--dbd-step-1); color: var(--dbd-heading); }
.optioncard__desc { font-size: var(--dbd-step--1); }

/* --- Variant rows / quantity ------------------------------------------- */
/* Three columns, not space-between. With space-between the price sat wherever
   the label left it, so "Meal for one" pushed its price 15px right of the one
   above it and the column of figures read as a mistake. The stepper column is
   held at a fixed width and the price is right-aligned against it, so every
   price in a card ends on the same pixel whatever its label says, whatever it
   costs, and whether the row is orderable or sold out. */
:root { --dbd-qty-col: calc(var(--dbd-tap) * 2 + 2ch + var(--dbd-sp-2) * 2); }

.variant {
  display: grid;
  grid-template-columns: 1fr auto var(--dbd-qty-col);
  align-items: center;
  gap: var(--dbd-sp-3); padding: var(--dbd-sp-2) 0;
  border-top: 1px solid var(--dbd-rule-olive);
}
.variant__label { font-size: var(--dbd-step--1); }
.variant__price { font-weight: 700; font-variant-numeric: tabular-nums; text-align: right; }
.variant .chip--soldout { justify-self: end; }
.qty { display: flex; align-items: center; justify-content: flex-end; gap: var(--dbd-sp-2); }
.qty button {
  width: var(--dbd-tap); height: var(--dbd-tap);
  font-size: var(--dbd-step-2); font-family: var(--dbd-body); line-height: 1;
  background: var(--dbd-parchment); color: var(--dbd-umber);
  border: 1px solid var(--dbd-olive); border-radius: var(--dbd-radius-sm);
  cursor: pointer;
}
.qty button:not(:disabled):not([aria-disabled]):hover { background: var(--dbd-ochre); }
/* A + that cannot add has to look like one. These are marked on any day whose
   ordering has closed, and with no style of their own they sat at full contrast
   with a pointer cursor over them — tapping a dish on today's page did nothing
   at all and looked like it should have. The banner above says ordering has
   closed; these now agree with it.

   Opacity alone did not carry it. At 0.5 on parchment the buttons still read as
   live on a phone, and cursor:not-allowed says nothing to a thumb — which is
   exactly how a closed Monday got reported as a broken app. The border and the
   glyph now go muted too, so the control looks spent rather than merely pale. */
.qty button:disabled,
.qty button[aria-disabled="true"] {
  opacity: 0.55; cursor: not-allowed;
  background: var(--dbd-surface-sunk);
  border-color: var(--dbd-rule-tan);
  color: var(--dbd-ink-muted);
}
/* Said where the tap happened, because the banner that says it properly is off
   the top of the screen by the time anyone is reaching for a +. */
/* --- What happens after the order is placed ------------------------------ */
/* Body size, not the muted small print the reference used to be set in: on an
   order with no email address this is the only copy that survives closing the
   page, and the people ordering are not helped by small grey type. */
.confirm__sent { font-size: var(--dbd-step-0); }
.confirm__norecord { font-size: var(--dbd-step-0); }
/* Big enough to copy onto paper from a phone held at arm's length. */
.confirm__ref {
  display: block; margin: var(--dbd-sp-3) 0;
  font-family: var(--dbd-display); font-size: var(--dbd-step-4);
  font-variant-numeric: tabular-nums; letter-spacing: 0.06em;
  color: var(--dbd-danger);
}

/* The one action left after ordering. Sized like an instruction someone is
   meant to follow while holding a banking app in the other hand. */
.etdo__h { font-size: var(--dbd-step-2); margin: 0 0 var(--dbd-sp-3); }
.etdo__step { font-size: var(--dbd-step-1); margin: 0; }
.etdo__why { font-size: var(--dbd-step-0); margin: 0; }

/* The loud telling, after two taps. Deliberately not styled like the quiet
   notices further up the page — those are what went unread. Full-width on a
   phone, a warm-dark heading rule across the top so it reads as a stop rather
   than as another card, and the way out as the primary button. */
.shutbox {
  border: 0; padding: 0;
  width: min(92vw, 460px); max-height: 90vh;
  background: var(--dbd-surface-raised); color: var(--dbd-ink);
  border-top: 6px solid var(--dbd-warn);
  border-radius: var(--dbd-radius); overflow: auto;
  padding: var(--dbd-sp-5);
}
.shutbox::backdrop { background: var(--dbd-scrim); }
.shutbox__title { font-size: var(--dbd-step-3); margin: 0 0 var(--dbd-sp-3); }
/* danger, not warn: warn is tan-deep at 5.14:1 and danger is umber at 11.23:1
   on the same ground. Both pass, and for a reader who is not being helped by
   sharp eyes the darker one is not a nicety. The alarm is carried by the rule
   across the top, where colour costs nothing to read. */
.shutbox__why {
  font-weight: 700; color: var(--dbd-danger);
  font-size: var(--dbd-step-2); margin: 0 0 var(--dbd-sp-3);
}
.shutbox p { font-size: var(--dbd-step-1); }
.shutbox__actions {
  display: flex; flex-wrap: wrap; gap: var(--dbd-sp-3);
  margin-top: var(--dbd-sp-5);
}
.shutbox__actions .btn { flex: 1 1 auto; text-align: center; }

.shutnote {
  /* .variant is a three-column grid, so the note has to be told to span it or
     it lands in the first column and squeezes the price and the stepper. */
  grid-column: 1 / -1;
  margin: var(--dbd-sp-2) 0 0;
  /* Body size, not the small print. This was a step below the surrounding text,
     which is the wrong way round for the one line on the row explaining why the
     button did nothing — and doubly so for readers who are not helped by sharp
     eyes. Umber rather than tan for the same reason: 11.23:1 against 5.14:1. */
  font-size: var(--dbd-step-0); font-weight: 700;
  color: var(--dbd-danger);
}
.qty output { min-width: 2ch; text-align: center; font-weight: 700; font-variant-numeric: tabular-nums; }

/* --- Forms -------------------------------------------------------------- */
label { display: block; font-weight: 600; font-size: var(--dbd-step--1); margin-bottom: var(--dbd-sp-1); }
input[type=text], input[type=tel], input[type=email], input[type=number],
input[type=date], input[type=time], select, textarea {
  width: 100%; min-height: var(--dbd-tap);
  padding: var(--dbd-sp-2) var(--dbd-sp-3);
  font: inherit; color: var(--dbd-ink);
  background: var(--dbd-cream-hi);
  border: 1px solid var(--dbd-olive);
  border-radius: var(--dbd-radius-sm);
  margin-bottom: var(--dbd-sp-3);
}
textarea { min-height: 5rem; resize: vertical; }
fieldset { border: 1px solid var(--dbd-rule-olive); border-radius: var(--dbd-radius-sm); padding: var(--dbd-sp-3); margin: 0 0 var(--dbd-sp-4); }
legend { font-weight: 700; font-size: var(--dbd-step--1); padding: 0 var(--dbd-sp-2); }

.radio-row { display: flex; gap: var(--dbd-sp-3); }
.radio-card {
  flex: 1; display: flex; align-items: center; justify-content: center;
  min-height: var(--dbd-tap); gap: var(--dbd-sp-2);
  border: 1px solid var(--dbd-olive); border-radius: var(--dbd-radius-sm);
  padding: var(--dbd-sp-3); cursor: pointer; font-weight: 600;
  background: var(--dbd-parchment);
}
.radio-card input { margin: 0; }
.radio-card:has(input:checked) { background: var(--dbd-tan); color: var(--dbd-espresso); border-color: var(--dbd-umber); }

/* --- Fields that need fixing --------------------------------------------
   Colour alone never carries this: the box is doubled in weight and sunk a
   shade, and the reason is spelled out in words directly under it. */
/* Written out per element: the base rule for inputs is `input[type=text]`,
   which outranks a bare attribute selector and kept the border olive. */
input[aria-invalid="true"], select[aria-invalid="true"], textarea[aria-invalid="true"] {
  border-color: var(--dbd-danger);
  box-shadow: inset 0 0 0 1px var(--dbd-danger);
  background: var(--dbd-parchment-2);
}
.field-err {
  margin: calc(-1 * var(--dbd-sp-2)) 0 var(--dbd-sp-3);
  color: var(--dbd-danger);
  font-size: var(--dbd-step--1);
  font-weight: 600;
}
.field-err::before { content: "⚠︎"; margin-right: var(--dbd-sp-1); }
#formerror ul { margin: var(--dbd-sp-2) 0 0; padding-left: var(--dbd-sp-5); }
#formerror li { margin-bottom: var(--dbd-sp-1); }

/* --- Notices ------------------------------------------------------------ */
.notice {
  border-left: 4px solid var(--dbd-tan);
  background: var(--dbd-surface-sunk);
  padding: var(--dbd-sp-3) var(--dbd-sp-4);
  border-radius: 0 var(--dbd-radius-sm) var(--dbd-radius-sm) 0;
  font-size: var(--dbd-step--1);
  margin-bottom: var(--dbd-sp-4);
}
.notice--strong { border-left-color: var(--dbd-umber); background: var(--dbd-parchment-2); }
.notice--late { border-left-color: var(--dbd-umber); }
.notice strong { color: var(--dbd-heading); }

/* --- Order bar ---------------------------------------------------------- */
.orderbar {
  position: sticky; bottom: 0; z-index: 20;
  background: var(--dbd-olive); color: var(--dbd-parchment);
  border-top: 3px solid var(--dbd-umber);
  padding: var(--dbd-sp-3) var(--dbd-sp-4);
  padding-bottom: calc(var(--dbd-sp-3) + env(safe-area-inset-bottom));
  display: flex; align-items: center; justify-content: space-between; gap: var(--dbd-sp-3);
  /* It is a <button> now: undo the parts of that which would fight the bar. */
  width: 100%; border-left: 0; border-right: 0; border-bottom: 0;
  font: inherit; font-size: var(--dbd-step-0); text-align: left;
  cursor: pointer; -webkit-appearance: none; appearance: none;
}
.orderbar:hover { background: var(--dbd-olive-deep); }
.orderbar:focus-visible { outline: 3px solid var(--dbd-parchment); outline-offset: -6px; }
.orderbar__right { display: flex; align-items: center; gap: var(--dbd-sp-3); }
/* Says what the tap does. Underlined rather than coloured, because the bar is
   already a solid colour and a second one on top of it reads as a warning. */
.orderbar__cue { text-decoration: underline; text-underline-offset: 3px; opacity: 0.9; }
.orderbar__total { font-weight: 700; font-variant-numeric: tabular-nums; }

/* --- The order, as a list ------------------------------------------------ */
.cartline { display: flex; justify-content: space-between; gap: var(--dbd-sp-3); padding: var(--dbd-sp-2) 0; }
.cartline + .cartline { border-top: 1px solid var(--dbd-rule-tan); }
.cartline__qty { font-variant-numeric: tabular-nums; }
.cartline__sum { font-variant-numeric: tabular-nums; white-space: nowrap; }

.totals { border-top: 1px solid var(--dbd-rule-tan); padding-top: var(--dbd-sp-3); }
.totals__row { display: flex; justify-content: space-between; padding: var(--dbd-sp-1) 0; }
.totals__row--grand { font-weight: 700; font-size: var(--dbd-step-1); border-top: 1px solid var(--dbd-rule-tan); margin-top: var(--dbd-sp-2); padding-top: var(--dbd-sp-2); }

/* --- Install prompts ---------------------------------------------------- */
.installbar {
  background: var(--dbd-tan); color: var(--dbd-espresso);
  padding: var(--dbd-sp-3) var(--dbd-sp-4);
  display: flex; align-items: center; gap: var(--dbd-sp-3); justify-content: space-between;
  font-size: var(--dbd-step--1); font-weight: 600;
}
.installbar button { min-height: 36px; }
@media (display-mode: standalone) { .installbar { display: none !important; } }

.visually-hidden {
  position: absolute; width: 1px; height: 1px; overflow: hidden;
  clip: rect(0 0 0 0); white-space: nowrap;
}

/* --- Review before submitting ------------------------------------------- */
/* Sized to a phone first: full width, capped on a laptop, and scrollable in
   the middle so the confirm button never falls off the bottom of a long order. */
.review {
  width: min(560px, calc(100vw - var(--dbd-sp-4) * 2));
  border: none; border-radius: var(--dbd-radius);
  background: var(--dbd-surface); color: var(--dbd-ink);
  padding: var(--dbd-sp-5);
  box-shadow: 0 18px 50px var(--dbd-shadow);
  max-height: min(90vh, 900px);
}
.review::backdrop { background: var(--dbd-scrim); }
.review h2 { margin-top: 0; }
.review__close { position: absolute; top: var(--dbd-sp-3); right: var(--dbd-sp-3); margin: 0; }
.review__close .btn { min-width: 44px; padding: var(--dbd-sp-2); }
.review__section { margin-top: var(--dbd-sp-4); }
.review__section h3 {
  font-family: var(--dbd-body); font-size: var(--dbd-step--1);
  text-transform: uppercase; letter-spacing: 0.07em;
  color: var(--dbd-tan-deep); margin: 0 0 var(--dbd-sp-2);
}
.review__actions {
  display: flex; gap: var(--dbd-sp-3); margin-top: var(--dbd-sp-5);
  position: sticky; bottom: 0; background: var(--dbd-surface);
  padding-top: var(--dbd-sp-3);
}
.review__actions .btn { flex: 1 1 auto; }
.review__err { color: var(--dbd-danger); font-weight: 600; }
