/* ==========================================================================
   Wole — design system root
   Loaded on every page. Page-specific rules live in their own file.

   The system is built on one artifact: the kitchen ticket. It is the thing
   this platform actually moves — customer to rail to chef to oven to box to
   door — so it is also the visual motif, and monospace is the data face
   because order numbers, timers and prices are printed matter.
   ========================================================================== */

:root {
  /* Ink and paper. Char is warm near-black (burnt crust), not neutral grey. */
  --ink:        #1a1614;
  --ink-soft:   #6b625c;
  --ink-faint:  #9c938b;
  --paper:      #f7f4ef;   /* semolina */
  --surface:    #ffffff;
  --line:       #e5dfd5;
  --line-soft:  #f0ebe3;

  /* Heat. --brand is overridden per tenant from the database. */
  --brand:      #d62828;
  --brand-ink:  #ffffff;
  --ember:      #ff7a1a;   /* oven glow — motion and heat states only */
  --basil:      #2f6d4f;   /* ready, success, delivered */
  --night:      #16110f;   /* kitchen at night — chrome, builder stage, KDS */
  --night-soft: #241c18;

  --radius:     10px;
  --radius-lg:  18px;

  /* A type scale with real jumps, not a gentle ramp. */
  --step--1:  0.8125rem;
  --step-0:   1rem;
  --step-1:   1.1875rem;
  --step-2:   1.5rem;
  --step-3:   2.125rem;
  --step-4:   3rem;
  --step-5:   clamp(2.75rem, 7vw, 5rem);

  --sans: "Helvetica Neue", Helvetica, Arial, ui-sans-serif, system-ui, sans-serif;
  --mono: ui-monospace, "SF Mono", "Cascadia Mono", Consolas, "Liberation Mono", Menlo, monospace;

  --shadow-card: 0 1px 2px rgb(26 22 20 / 6%), 0 8px 24px -12px rgb(26 22 20 / 18%);
  --shadow-lift: 0 2px 4px rgb(26 22 20 / 8%), 0 18px 40px -18px rgb(26 22 20 / 28%);

  /* --- Direction B ------------------------------------------------------- --

     The customer-facing look, chosen 2026-08-11: true white ground, layered
     soft shadow, larger radii, and the search as one floating pill.

     These are additions rather than edits. Slice N builds the two new public
     pages on them; slice R moves the storefront, the checkout, the tracker and
     the review page over. Redefining --paper here instead would have restyled
     the kitchen board and five admin screens in a slice that was supposed to
     touch neither.

     **The kitchen-ticket motif stops being the brand.** The palette above is
     named semolina, ember and basil because the design system was built on one
     artifact — a pizza order. That does not describe an all-in-one till for a
     sushi restaurant. Mono keeps the jobs it earns (money, order numbers,
     timers) and stops being the voice of the product, which is why there is no
     --mono in any token below.
     ----------------------------------------------------------------------- */

  --b-ground:   #ffffff;   /* the page itself */
  --b-raised:   #ffffff;   /* a card sitting on it, separated by shadow not fill */
  --b-sunken:   #f7f6f4;   /* a well: map frame, inactive tab, empty state */
  --b-line:     #eceae6;   /* the hairline that survives on white */
  --b-line-str: #ddd9d2;   /* a border that has to be seen */

  --b-radius:    14px;
  --b-radius-lg: 20px;
  --b-radius-pill: 999px;

  /*
    Two shadows, both layered. The tight one grounds the element; the wide,
    heavily negative-spread one is the light. A single blurred shadow is the
    thing that makes a white card look like a 2013 bootstrap panel.
  */
  --b-shadow:      0 1px 2px rgb(26 22 20 / 5%), 0 14px 34px -18px rgb(26 22 20 / 30%);
  --b-shadow-lift: 0 2px 6px rgb(26 22 20 / 7%), 0 26px 56px -22px rgb(26 22 20 / 34%);
}

/* --- Reset ---------------------------------------------------------------- */

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

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

body {
  margin: 0;
  background: var(--paper);
  color: var(--ink);
  font-family: var(--sans);
  font-size: var(--step-0);
  line-height: 1.55;
  -webkit-font-smoothing: antialiased;
}

img, svg { max-width: 100%; display: block; }
button, input, select, textarea { font: inherit; color: inherit; }

h1, h2, h3, h4, p, ul, ol, figure { margin: 0; }
ul, ol { padding: 0; list-style: none; }

a { color: inherit; text-decoration-thickness: 1px; text-underline-offset: 3px; }

:focus-visible {
  outline: 2.5px solid var(--ember);
  outline-offset: 2px;
  border-radius: 3px;
}

/* --- Type ----------------------------------------------------------------- */

.display {
  font-size: var(--step-5);
  font-weight: 800;
  line-height: 0.94;
  letter-spacing: -0.035em;
  text-wrap: balance;
}

h1 { font-size: var(--step-4); font-weight: 800; letter-spacing: -0.03em; line-height: 1.05; }
h2 { font-size: var(--step-3); font-weight: 700; letter-spacing: -0.02em; line-height: 1.12; }
h3 { font-size: var(--step-1); font-weight: 700; letter-spacing: -0.01em; }

/* The data face. Order numbers, money, timers, codes — printed matter. */
.mono,
.money,
.order-no,
.timer {
  font-family: var(--mono);
  font-variant-numeric: tabular-nums;
  letter-spacing: -0.02em;
}

.order-no { font-weight: 700; }

/* An eyebrow marks a section's role. It is a label, not decoration. */
.eyebrow {
  font-family: var(--mono);
  font-size: var(--step--1);
  text-transform: uppercase;
  letter-spacing: 0.14em;
  color: var(--ink-faint);
}

.lede { font-size: var(--step-1); color: var(--ink-soft); max-width: 58ch; }
.muted { color: var(--ink-soft); }

/* --- The impersonation banner --------------------------------------------- --

   A superadmin is wearing somebody else's identity. This is a full-width bar
   above everything, on every page, because the failure it prevents is an admin
   forgetting — reading a shop's numbers as the platform's, or making a change
   they think they are making as themselves. A toast disappears and a corner
   badge is furniture; a bar that pushes the whole page down is neither.

   Amber rather than red. Red is this product's brand colour and also its error
   colour, and the state is neither of those: nothing has gone wrong, but
   something is true that must not be forgotten.
   -------------------------------------------------------------------------- */

.impbar {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  padding: 0.6rem 1.25rem;
  background: #2b2009;
  color: #ffd88a;
  font-size: var(--step--1);
  border-bottom: 2px solid var(--ember);
}

.impbar__dot {
  width: 0.55rem;
  height: 0.55rem;
  border-radius: 50%;
  background: var(--ember);
  flex: none;
  animation: impbar-blink 2s ease-in-out infinite;
}

@keyframes impbar-blink { 50% { opacity: 0.3; } }

@media (prefers-reduced-motion: reduce) {
  .impbar__dot { animation: none; }
}

.impbar__text { flex: 1; min-width: 0; }
.impbar__shop { color: #fff; font-weight: 650; }

.impbar__exit {
  flex: none;
  border: 1px solid currentColor;
  background: none;
  color: inherit;
  border-radius: var(--b-radius-pill);
  padding: 0.35rem 0.9rem;
  font-size: var(--step--1);
  font-weight: 650;
  cursor: pointer;
}

.impbar__exit:hover { background: rgb(255 216 138 / 14%); }
.impbar__exit:disabled { opacity: 0.5; cursor: default; }

/* --- The shop card (direction B) ------------------------------------------ --

   One restaurant, as the finder shows it and as a city page shows it. It lives
   here rather than in finder.css because three pages render it, and a shared
   component in a page stylesheet is the mistake that left the kitchen board
   unstyled once — see the note at the top of assets/css.

   The whole card is one link. A card with a link *inside* it gives a thumb a
   small target inside a large clickable-looking area, which is the most
   irritating twenty pixels on any directory.
   -------------------------------------------------------------------------- */

.shoplist { display: flex; flex-direction: column; gap: 0.75rem; }

.shopcard {
  display: flex;
  align-items: center;
  gap: 1rem;
  padding: 0.9rem 1.1rem;
  background: var(--b-raised);
  border: 1px solid var(--b-line);
  border-radius: var(--b-radius);
  box-shadow: var(--b-shadow);
  text-decoration: none;
  transition: box-shadow 0.16s ease, transform 0.16s ease, border-color 0.16s ease;
}

.shopcard:hover {
  box-shadow: var(--b-shadow-lift);
  border-color: var(--b-line-str);
  transform: translateY(-1px);
}

/* A closed shop is listed on purpose — a missing row reads as "not on Wole",
   which is a different and wrong message. It is quietened, not hidden. */
.shopcard--shut { background: var(--b-sunken); box-shadow: none; }
.shopcard--shut .shopcard__name { color: var(--ink-soft); }

.shopcard__main { flex: 1; min-width: 0; }

.shopcard__name {
  font-size: var(--step-1);
  font-weight: 650;
  letter-spacing: -0.015em;
  margin-bottom: 0.2rem;
}

.shopcard__meta {
  font-size: var(--step--1);
  color: var(--ink-soft);
  display: flex;
  flex-wrap: wrap;
  gap: 0.15rem 0.5rem;
}

/* Separators are drawn, not typed. A literal "·" between spans becomes a
   stranded bullet the moment one of them is absent. */
.shopcard__meta > * + *::before { content: '·'; margin-right: 0.5rem; color: var(--ink-faint); }

.shopcard__tags {
  font-size: var(--step--1);
  color: var(--ink-faint);
  margin-top: 0.2rem;
}

.shopcard__aside { text-align: right; flex-shrink: 0; }

.shopcard__state {
  display: inline-block;
  font-size: var(--step--1);
  font-weight: 650;
  padding: 0.3rem 0.65rem;
  border-radius: var(--b-radius-pill);
  background: #f0f6f2;
  color: var(--basil);
  white-space: nowrap;
}

.shopcard__state--shut { background: #f1efec; color: var(--ink-soft); }
.shopcard__fee { font-size: var(--step--1); color: var(--ink-soft); margin-top: 0.35rem; }

@media (max-width: 33rem) {
  .shopcard { align-items: flex-start; }
  .shopcard__aside { text-align: left; }

  /*
    The separators go. A drawn "·" belongs between two items on one line, and on
    a narrow card the meta wraps — which strands a bullet at the start of the
    second line, looking like a bullet list that lost its first item. The gap
    alone separates them once they are stacked.
  */
  .shopcard__meta { gap: 0.1rem 0.65rem; }
  .shopcard__meta > * + *::before { content: none; }
}

/* --- Layout --------------------------------------------------------------- */

.page {
  max-width: 1180px;
  margin-inline: auto;
  padding: 2rem 1.25rem 5rem;
}

.page--wide { max-width: 1440px; }
.page--narrow { max-width: 640px; }

.stack > * + * { margin-top: 1rem; }
.stack-lg > * + * { margin-top: 2.5rem; }

/* --- Site chrome ---------------------------------------------------------- */

.site-nav {
  display: flex;
  align-items: center;
  gap: 1.25rem;
  padding: 0.875rem 1.25rem;
  background: var(--night);
  color: var(--paper);
}

.site-nav__brand {
  font-weight: 800;
  letter-spacing: -0.03em;
  font-size: var(--step-1);
  text-decoration: none;
  display: inline-flex;
  align-items: center;
  gap: 0.55rem;
}

/* A filled dot in the tenant's colour: the whole nav mark, nothing more. */
.site-nav__dot {
  width: 0.7rem;
  height: 0.7rem;
  border-radius: 50%;
  background: var(--brand);
  flex: none;
}

/* --- The hamburger --------------------------------------------------------- */

/*
  One menu, every role, every width (slice U, 2026-08-11).

  The owner's nav was ten links across the top of every screen, permanently,
  most of which are opened once a month. A visible bar for the short public
  navs and a panel for the long private ones would have been two components,
  and the one that is not looked at is the one that rots.

  It is a `<details>` element. The browser owns the open state, which is what
  makes it work with JavaScript off and what keeps it out of the `hidden`-versus-
  `display` trap this project has paid for twice. `nav.js` only adds manners.
*/
.navmenu {
  margin-left: auto;
  position: relative;
}

.navmenu__toggle {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0.4rem;
  width: 2.5rem;
  height: 2.5rem;
  border-radius: var(--radius);
  cursor: pointer;
  position: relative;

  /* The default disclosure triangle, in both engines that draw one. */
  list-style: none;
}

.navmenu__toggle::-webkit-details-marker { display: none; }

.navmenu__toggle:hover { background: rgb(247 244 239 / 10%); }
.navmenu__toggle:focus-visible { outline: 2px solid var(--brand); outline-offset: 2px; }

/*
  Three bars from one element: the middle is the box, the other two are its
  pseudo-elements. Drawn rather than an inline SVG, so the colour follows the
  chrome — the light chrome remaps it below without touching this markup.
*/
.navmenu__bars,
.navmenu__bars::before,
.navmenu__bars::after {
  display: block;
  width: 1.15rem;
  height: 2px;
  border-radius: 1px;
  background: currentcolor;
  transition: transform 0.16s ease, opacity 0.12s ease;
}

.navmenu__bars { position: relative; color: rgb(247 244 239 / 88%); }

.navmenu__bars::before,
.navmenu__bars::after {
  content: "";
  position: absolute;
  left: 0;
}

.navmenu__bars::before { top: -0.36rem; }
.navmenu__bars::after  { top: 0.36rem; }

/* Open, the bars become a cross. The middle bar goes rather than rotating with
   them, because two lines crossing at the centre is a cleaner X than three. */
.navmenu[open] .navmenu__bars { background: transparent; }
.navmenu[open] .navmenu__bars::before { transform: translateY(0.36rem) rotate(45deg); }
.navmenu[open] .navmenu__bars::after  { transform: translateY(-0.36rem) rotate(-45deg); }

/* A count that must be visible while the panel is shut — otherwise the one
   thing that needs answering today is behind a tap nobody takes. */
.navmenu__badge {
  position: absolute;
  top: -0.15rem;
  right: -0.15rem;
  min-width: 1.05rem;
  height: 1.05rem;
  padding: 0 0.25rem;
  border-radius: 999px;
  background: var(--brand);
  color: #fff;
  font-size: 0.65rem;
  font-weight: 700;
  line-height: 1.05rem;
  text-align: center;
}

.navmenu__panel {
  position: absolute;
  right: 0;
  top: calc(100% + 0.5rem);
  z-index: 60;
  min-width: 13rem;
  max-height: calc(100vh - 5rem);
  overflow-y: auto;
  margin: 0;
  padding: 0.4rem;
  list-style: none;
  border-radius: var(--radius);
  background: var(--night);
  border: 1px solid rgb(247 244 239 / 14%);
  box-shadow: 0 18px 40px rgb(0 0 0 / 35%);
}

.navmenu__panel a {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.75rem;
  padding: 0.55rem 0.75rem;
  border-radius: calc(var(--radius) - 2px);
  color: rgb(247 244 239 / 82%);
  text-decoration: none;
  font-size: var(--step--1);
  font-weight: 600;
  white-space: nowrap;
}

.navmenu__panel a:hover { background: rgb(247 244 239 / 10%); color: var(--paper); }

.navmenu__panel a[aria-current="page"] {
  color: var(--paper);
  background: rgb(247 244 239 / 7%);
}

.navmenu__rule {
  height: 1px;
  margin: 0.35rem 0.25rem;
  background: rgb(247 244 239 / 14%);
}

.navmenu__count {
  min-width: 1.25rem;
  padding: 0 0.35rem;
  border-radius: 999px;
  background: var(--brand);
  color: #fff;
  font-size: 0.7rem;
  font-weight: 700;
  text-align: center;
}

/*
  On a phone the panel is the width of the screen rather than a dropdown pinned
  to a corner. A 13rem menu hanging off the right edge of a 360px screen is a
  column of text with nothing beside it.
*/
@media (max-width: 599px) {
  .navmenu__panel {
    position: fixed;
    left: 0.5rem;
    right: 0.5rem;
    top: 3.75rem;
    min-width: 0;
  }
}

/*
  The light chrome, for the public pages built in direction B.

  Opted into per page with `$pageChrome = 'light'` in header.php, so the working
  screens — the kitchen board, the driver queue, every admin page — keep the
  dark bar that separates a tool from the room it is used in. A page that says
  nothing is unchanged.
*/
/*
  Direction B reaches the whole customer surface by REMAPPING the old token
  names, not by editing four page stylesheets.

  `storefront.css`, `checkout.css`, `tracker.css` and `account.css` between them
  reference `--paper`, `--surface`, `--line`, `--radius` and the two shadows a
  dozen times. Rewriting each of those by hand would be a dozen chances to miss
  one, and would leave the two systems interleaved in files that are hard to
  read afterwards. Pointing the old names at the new values inside this scope
  moves every one of them at once, and moves them back the moment a page stops
  opting in.

  **`--night`, `--brand` and `--ember` are deliberately untouched.** The
  tracker's scenes and the builder's stage are dark on purpose — they are a
  photograph's backdrop, not chrome — and the tenant's brand colour is data.
*/
.chrome-light {
  --paper:   var(--b-ground);
  --surface: var(--b-raised);
  --line:      var(--b-line);
  --line-soft: var(--b-line);

  --radius:    var(--b-radius);
  --radius-lg: var(--b-radius-lg);

  --shadow-card: var(--b-shadow);
  --shadow-lift: var(--b-shadow-lift);

  background: var(--b-ground);
}

.chrome-light .site-nav {
  background: var(--b-ground);
  color: var(--ink);
  border-bottom: 1px solid var(--b-line);
}

/*
  The hamburger on a light page.

  The bars follow `currentcolor`, so only the toggle's colour has to move — but
  the panel does not follow the chrome. It stays dark on purpose: a menu that
  floats over the page it belongs to needs to read as a layer above it, and a
  white panel on a white ground is a border doing all the work.
*/
.chrome-light .navmenu__bars { color: var(--ink); }
.chrome-light .navmenu__toggle:hover { background: rgb(22 17 15 / 7%); }

.chrome-light .site-footer {
  background: var(--b-ground);
  border-top: 1px solid var(--b-line);
}

.site-footer {
  padding: 2.5rem 1.25rem;
  border-top: 1px solid var(--line);
  color: var(--ink-soft);
  font-size: var(--step--1);
}

/* --- Buttons -------------------------------------------------------------- */

.btn {
  --btn-bg: var(--surface);
  --btn-fg: var(--ink);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.5rem;
  padding: 0.7rem 1.15rem;
  min-height: 2.75rem;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--btn-bg);
  color: var(--btn-fg);
  font-weight: 650;
  font-size: var(--step-0);
  letter-spacing: -0.01em;
  /* A button that happens to be an <a> must not wear the global link
     underline — it reads as a link inside a button otherwise. */
  text-decoration: none;
  cursor: pointer;
  transition: transform 120ms ease, box-shadow 160ms ease, background-color 160ms ease;
}

.btn:hover { box-shadow: var(--shadow-card); }
.btn:active { transform: translateY(1px); }

.btn[disabled],
.btn[aria-disabled="true"] {
  opacity: 0.45;
  cursor: not-allowed;
  box-shadow: none;
  transform: none;
}

.btn--primary {
  --btn-bg: var(--brand);
  --btn-fg: var(--brand-ink);
  border-color: transparent;
}

.btn--ghost { --btn-bg: transparent; }

/*
  The action on a menu card. Outlined at rest, because a grid of eighteen cards
  painted in brand red is eighteen equally loud primaries and no hierarchy at
  all. It fills the moment it is the thing being pointed at — and the whole card
  is a target, so this is the affordance rather than the only way in.
*/
.btn--card {
  --btn-bg: transparent;
  --btn-fg: var(--brand);
  border-color: currentColor;
  font-weight: 700;
}

.btn--card:hover,
.btn--card:focus-visible,
.dish:hover .btn--card {
  --btn-bg: var(--brand);
  --btn-fg: var(--brand-ink);
  border-color: transparent;
}

.btn--card[disabled] {
  --btn-fg: var(--ink-faint);
  border-color: var(--line);
  cursor: not-allowed;
}

.btn--lg { padding: 0.95rem 1.6rem; min-height: 3.25rem; font-size: var(--step-1); }
.btn--block { width: 100%; }

/* --- Forms ---------------------------------------------------------------- */

.field { display: block; }

.field__label {
  display: block;
  font-size: var(--step--1);
  font-weight: 650;
  margin-bottom: 0.35rem;
}

.field__hint { font-size: var(--step--1); color: var(--ink-soft); margin-top: 0.3rem; }

/*
  A set of tick-boxes that reads as one answer — the store's cuisines today,
  and whatever the setup wizard asks next. Named `pick` rather than `chip`
  because `.chip` is already the status pill, and two components sharing a class
  is how one of them silently inherits the other's colour.

  The native checkbox stays in the DOM and stays focusable; it is the thing
  screen readers and the keyboard operate. Only its appearance is replaced.
*/
.pickset { display: flex; flex-wrap: wrap; gap: 0.5rem; margin-top: 0.5rem; }

.pick {
  display: inline-flex;
  align-items: center;
  gap: 0.45rem;
  padding: 0.45rem 0.8rem;
  border: 1px solid var(--line);
  border-radius: 999px;
  background: var(--surface);
  font-size: var(--step--1);
  cursor: pointer;
  user-select: none;
  transition: border-color 0.12s ease, background 0.12s ease;
}

.pick input { margin: 0; accent-color: var(--brand); }
.pick:hover { border-color: var(--ink-faint); }
.pick:has(input:checked) { border-color: var(--ink); background: var(--paper); font-weight: 650; }
.pick:has(input:focus-visible) { outline: 2px solid var(--brand); outline-offset: 2px; }

/*
  A consent tick-box: one checkbox and a sentence that wraps.

  Deliberately NOT `.pick`. That one is a pill in a `.pickset` — a short label
  on one line, sized to its text, meant to sit in a row of siblings. A consent
  line is a full-width paragraph with a control at its start, and forcing it
  through the pill would put a contract inside a rounded chip.

  It lives here rather than in `legal.css` or `checkout.css` because both of
  those pages use it, and this project has paid three times for a shared
  component filed under one of its consumers — `.cartbar` sat in `checkout.css`
  and the storefront's cart went unstyled for months.
*/
.consent {
  display: flex;
  align-items: flex-start;
  gap: 0.7rem;
  padding: 0.85rem 1rem;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--paper);
  font-size: var(--step--1);
  line-height: 1.55;
  cursor: pointer;
}

/* The control must not shrink when the sentence beside it wraps to three lines. */
.consent input {
  margin: 0;
  flex: none;
  width: 1.05rem;
  height: 1.05rem;
  margin-top: 0.15rem;
  accent-color: var(--brand);
}

.consent:has(input:focus-visible) { outline: 2px solid var(--brand); outline-offset: 2px; }
.consent:has(input:checked) { border-color: var(--ink); }
.consent a { color: inherit; text-underline-offset: 2px; }

/* An unanswered required consent, marked by the page that needs it. */
.consent.is-missing { border-color: #d1503a; background: #ffe9e2; }

.input,
.select,
.textarea {
  width: 100%;
  padding: 0.7rem 0.85rem;
  min-height: 2.75rem;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--surface);
  transition: border-color 140ms ease, box-shadow 140ms ease;
}

.input:focus,
.select:focus,
.textarea:focus {
  border-color: var(--ink);
  outline: none;
  box-shadow: 0 0 0 3px rgb(255 122 26 / 22%);
}

.textarea { min-height: 6rem; resize: vertical; }

/* --- Cards and the ticket motif ------------------------------------------- */

.card {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  padding: 1.25rem;
}

/*
  The ticket: the platform's signature object. A docket with a torn bottom
  edge, used wherever an order is represented as a physical thing — the
  kitchen board, the tracker's first scene, receipts, order history.
  The tear is a mask so it works on any background.
*/
.ticket {
  position: relative;
  background: var(--surface);
  border: 1px solid var(--line);
  border-bottom: none;
  border-radius: var(--radius) var(--radius) 0 0;
  padding: 1rem 1.1rem 1.35rem;
  box-shadow: var(--shadow-card);
}

.ticket::after {
  content: "";
  position: absolute;
  inset-inline: -1px;
  bottom: -9px;
  height: 10px;
  background: var(--surface);
  border-inline: 1px solid var(--line);
  /* Scalloped tear — a repeating notch along the bottom edge. */
  mask-image: radial-gradient(9px 10px at 9px -1px, transparent 0 9px, #000 9.5px);
  mask-size: 18px 10px;
  mask-repeat: repeat-x;
}

.ticket__head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 0.75rem;
  padding-bottom: 0.6rem;
  margin-bottom: 0.7rem;
  border-bottom: 1px dashed var(--line);
}

.ticket__no { font-size: var(--step-2); }

.ticket__lines { font-size: var(--step--1); }
.ticket__lines li + li { margin-top: 0.45rem; }

/* Modifiers read the way a cook reads them: additions and removals, plainly. */
.mod { font-family: var(--mono); font-size: var(--step--1); display: block; padding-left: 0.9rem; }
.mod--add { color: var(--basil); }
.mod--remove { color: var(--brand); text-transform: uppercase; }

/* --- Store switcher ------------------------------------------------------- */
/* Shared: the storefront, the kitchen board and the owner pages all pick a store. */

.storepick { display: flex; gap: 0.4rem; flex-wrap: wrap; }

.storepick__opt {
  padding: 0.4rem 0.85rem;
  border: 1px solid var(--line);
  border-radius: 999px;
  background: var(--surface);
  font-size: var(--step--1);
  font-weight: 650;
  text-decoration: none;
  white-space: nowrap;
}

.storepick__opt.is-active {
  background: var(--ink);
  border-color: var(--ink);
  color: var(--paper);
}

/* --- Status chips --------------------------------------------------------- */

.chip {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  padding: 0.25rem 0.6rem;
  border-radius: 999px;
  background: var(--line-soft);
  color: var(--ink-soft);
  font-size: var(--step--1);
  font-weight: 650;
  white-space: nowrap;
}

.chip::before {
  content: "";
  width: 0.45rem;
  height: 0.45rem;
  border-radius: 50%;
  background: currentcolor;
  flex: none;
}

.chip--waiting { background: #fff2e0; color: #a2540c; }
.chip--cooking { background: #ffe9e2; color: #b2350f; }
.chip--ready   { background: #e4f2ea; color: var(--basil); }
.chip--moving  { background: #e6eefb; color: #1d4f9c; }
.chip--done    { background: var(--line-soft); color: var(--ink-soft); }
.chip--dead    { background: var(--line-soft); color: var(--ink-faint); }

/* --- Banners -------------------------------------------------------------- */

.banner {
  padding: 0.85rem 1.1rem;
  border-radius: var(--radius);
  border: 1px solid var(--line);
  background: var(--surface);
  font-size: var(--step--1);
}

.banner--closed { background: #fff2e0; border-color: #f2d5ac; color: #7d400a; }
/*
  Amber, the same as `--closed`, and a separate name on purpose. They mean
  different things — one is "this shop is shut", the other is "something here
  needs your attention" — and a state that borrows another state's class is a
  state that silently changes when that one is restyled.
*/
.banner--warn   { background: #fff2e0; border-color: #f2d5ac; color: #7d400a; }
.banner--error  { background: #ffe9e2; border-color: #f3c3b5; color: #8f2a0c; }
.banner--ok     { background: #e4f2ea; border-color: #b9dcc8; color: #1d5138; }

/*
  A banner that belongs to the page shell rather than to a form. It sits under
  the nav on every page the owner opens, so it carries the page's own gutter
  instead of inheriting a card's.
*/
/*
  A banner belonging to the page shell rather than to a form: it sits between
  the nav and whatever page follows, so it lines up with `.page`'s own gutter
  (1180px wide inside 1.25rem of padding) instead of running edge to edge.
*/
.site-banner {
  width: min(1180px, 100% - 2.5rem);
  margin: 1rem auto 0;
}

/* --- Utilities ------------------------------------------------------------ */

.row { display: flex; gap: 0.75rem; align-items: center; flex-wrap: wrap; }
.row--between { justify-content: space-between; }
.row--center { justify-content: center; }
.push { margin-left: auto; }

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

/* --- Money fields ---------------------------------------------------------
   Prices are typed in euros. The symbol sits in the field rather than in the
   label, so the owner can see they are entering 13,90 and not 1390 while their
   eyes are on the box they are typing into.
   -------------------------------------------------------------------------- */

.field--money { position: relative; }

.input--money {
  padding-right: 2.1rem;
  font-family: var(--mono);
  text-align: right;
}

.field--money::after {
  content: "€";
  position: absolute;
  right: 0.8rem;
  bottom: 0.72rem;
  color: var(--ink-faint);
  font-family: var(--mono);
  pointer-events: none;
}

/* --- Catalogue list -------------------------------------------------------
   The owner's menu and ingredient pages. One line per thing, everything else
   behind a modal: a pizzeria has thirty toppings and a dozen pizzas, and
   showing every field of every one of them at once turns a two-second price
   change into a scavenger hunt.
   -------------------------------------------------------------------------- */

.catalist {
  list-style: none;
  margin: 0;
  padding: 0;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--surface);
  overflow: hidden;
}

.catalist__row {
  display: flex;
  align-items: center;
  gap: 0.9rem;
  padding: 0.6rem 0.85rem;
  border-top: 1px solid var(--line-soft);
}

.catalist__row:first-child { border-top: 0; }
.catalist__row:hover { background: var(--paper); }

/* A thing that is off tonight reads as off at a glance, without hiding it. */
.catalist__row.is-off { background: repeating-linear-gradient(
  135deg, transparent, transparent 7px, var(--line-soft) 7px, var(--line-soft) 14px); }
.catalist__row.is-off .catalist__name { color: var(--ink-soft); text-decoration: line-through; }

.catalist__art {
  flex: 0 0 auto;
  width: 2.5rem; height: 2.5rem;
  border-radius: 50%;
  background: var(--paper) center/cover no-repeat;
  border: 1px solid var(--line);
}

.catalist__name { font-weight: 600; }
.catalist__meta { flex: 1 1 auto; min-width: 0; }

.catalist__sub {
  display: block;
  font-size: var(--step--1);
  color: var(--ink-soft);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.catalist__price {
  font-family: var(--mono);
  font-size: var(--step--1);
  white-space: nowrap;
}

.catalist__actions { display: flex; gap: 0.35rem; flex: 0 0 auto; align-items: center; }

/* Secondary controls in a dense list. A row is scanned, not read, so the
   buttons must not outweigh the name and the price they sit beside. */
.btn--sm {
  padding: 0.3rem 0.7rem;
  min-height: 2rem;
  font-size: var(--step--1);
}

/* The availability toggle shows the current state; pressing it changes that
   state. Green for on sale, hatched grey for off — legible at a glance down a
   column of thirty toppings. */
.btn--toggle[aria-pressed="true"] {
  --btn-bg: #e4f2ea;
  color: var(--basil);
  border-color: #bfe0cd;
}

.btn--toggle[aria-pressed="false"] {
  --btn-bg: var(--line-soft);
  color: var(--ink-soft);
}

/* A category heading names the group; its kind is a footnote about pricing. */
.catsection__kind {
  font-family: var(--sans);
  font-size: var(--step--1);
  font-weight: 400;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--ink-faint);
  margin-left: 0.6rem;
  vertical-align: middle;
}

.catalist__empty { padding: 1.1rem 0.85rem; color: var(--ink-soft); }

.chip--off { background: var(--line-soft); color: var(--ink-soft); }
.chip--off::before { background: var(--ink-faint); }

/* --- Modal ----------------------------------------------------------------
   A native <dialog>: the browser already provides Esc, the focus trap and the
   inert background, and every one of those is a thing a hand-rolled overlay
   gets subtly wrong.
   -------------------------------------------------------------------------- */

.modal {
  width: min(46rem, calc(100vw - 2rem));
  max-height: calc(100vh - 4rem);
  padding: 0;
  border: 0;
  border-radius: var(--radius-lg);
  background: var(--surface);
  color: var(--ink);
  box-shadow: var(--shadow-lift);
}

.modal::backdrop { background: rgb(26 22 20 / 45%); }

.modal__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  padding: 1.1rem 1.3rem;
  border-bottom: 1px solid var(--line);
  position: sticky;
  top: 0;
  background: var(--surface);
  z-index: 1;
}

.modal__title { margin: 0; font-size: var(--step-1); }
.modal__body { padding: 1.3rem; overflow-y: auto; }
.modal__body > * + * { margin-top: 1.1rem; }

.modal__foot {
  display: flex;
  gap: 0.5rem;
  flex-wrap: wrap;
  justify-content: flex-end;
  padding: 1rem 1.3rem;
  border-top: 1px solid var(--line);
}

/* --- Standard topping library --------------------------------------------- */

.libpick {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(11rem, 1fr));
  gap: 0.4rem 1rem;
}

.libpick__have { color: var(--ink-faint); }

@media (max-width: 640px) {
  .catalist__row { flex-wrap: wrap; }
  .catalist__meta { flex-basis: 100%; order: 2; }
  .modal { width: 100vw; max-width: 100vw; height: 100vh; max-height: 100vh; border-radius: 0; }
}

/* --- Delivery bands -------------------------------------------------------
   Shared by the storefront and checkout, so it lives here rather than in a page
   sheet — a shared component parked in storefront.css once left the kitchen
   board unstyled, because that page does not load it.                         */

.bandview {
  margin: 2rem 0;
  display: grid;
  gap: 0.75rem;
}

.bandview__title {
  font-size: var(--step-1, 1.1rem);
  margin: 0;
}

.bandview__table {
  width: 100%;
  border-collapse: collapse;
}

.bandview__row > * {
  padding: 0.45rem 0.5rem;
  border-bottom: 1px solid var(--line-soft);
  text-align: left;
  font-weight: 400;
}

.bandview__row .money { text-align: right; white-space: nowrap; }

.bandview__free {
  color: var(--ink-soft);
  font-size: var(--step--1);
  text-align: right;
}

/* The band the customer's own address fell into. */
.bandview__row.is-here > * {
  background: var(--line-soft);
  font-weight: 650;
}

.bandview__note,
.bandview__you {
  margin: 0;
  font-size: var(--step--1);
  color: var(--ink-soft);
}

.bandview__you {
  color: var(--ink);
  font-weight: 650;
}

/* --- Leaflet containment ---------------------------------------------------
   Leaflet numbers its own furniture in the hundreds — panes at 400, controls at
   800, the corner containers at 1000 — and `.leaflet-container` is positioned
   but sets no z-index, so it opens no stacking context and every one of those
   numbers competes in the page root. Against the builder's 50 the map wins, and
   a customer composing a pizza gets the delivery map drawn across the dialog.

   Fixing this by out-bidding Leaflet is a race nobody wins: the next component
   would have to clear 1000 too. Giving the map its own stacking context settles
   it once — everything Leaflet draws is then ordered *within* the map and the
   whole map sits at 0, under every layer of ours.

   `isolation: isolate` is what creates the context; the z-index is there so the
   rule still reads as deliberate to someone who removes the isolation.         */

.leaflet-container {
  position: relative;
  z-index: 0;
  isolation: isolate;
}

/* --- Category nav ----------------------------------------------------------
   Sticky, and horizontally scrollable rather than wrapping: a shop with twelve
   categories must not push the first dish below the fold. In app.css because
   the owner's menu editor is the obvious second home for it.
   -------------------------------------------------------------------------- */

.catnav {
  position: sticky;
  top: 0;
  z-index: 30;
  display: flex;
  gap: 0.5rem;
  margin: 0 -1.25rem 2rem;
  padding: 0.75rem 1.25rem;
  overflow-x: auto;
  scrollbar-width: none;
  background: color-mix(in srgb, var(--paper) 88%, transparent);
  backdrop-filter: blur(8px);
  border-bottom: 1px solid var(--line);
}

.catnav::-webkit-scrollbar { display: none; }

.catnav__chip {
  flex: 0 0 auto;
  padding: 0.4rem 0.9rem;
  border: 1px solid var(--line);
  border-radius: 999px;
  background: var(--surface);
  color: var(--ink-soft);
  font-size: var(--step--1);
  font-weight: 650;
  text-decoration: none;
  white-space: nowrap;
  transition: background-color 140ms ease, color 140ms ease, border-color 140ms ease;
}

.catnav__chip:hover { color: var(--ink); border-color: var(--ink-faint); }

.catnav__chip.is-current {
  background: var(--ink);
  border-color: var(--ink);
  color: var(--paper);
}

/* A jump must not land the heading underneath the bar that did the jumping. */
.menu-group { scroll-margin-top: 4.5rem; }

/* --- Cart pill -------------------------------------------------------------
   Shared by the storefront and, on a phone, the checkout — which is why it is
   here and not in either page's file. It lived in checkout.css until
   2026-08-10, so the one page that needed it most never loaded it and the
   storefront's cart was an unstyled row of text.
   -------------------------------------------------------------------------- */

.cartbar {
  position: fixed;
  left: 50%;
  bottom: 1.1rem;
  z-index: 40;
  display: flex;
  align-items: center;
  gap: 0.75rem;
  max-width: calc(100vw - 2rem);
  padding: 0.6rem 0.6rem 0.6rem 1rem;
  border-radius: 999px;
  background: var(--night);
  color: var(--paper);
  box-shadow: var(--shadow-lift);
  transform: translateX(-50%);
  animation: cartbar-in 260ms cubic-bezier(.2, .9, .3, 1) both;
}

.cartbar[hidden] { display: none; }

@keyframes cartbar-in {
  from { opacity: 0; transform: translate(-50%, 18px); }
  to   { opacity: 1; transform: translate(-50%, 0); }
}

.cartbar__count {
  display: grid;
  place-items: center;
  min-width: 1.6rem;
  height: 1.6rem;
  padding-inline: 0.35rem;
  border-radius: 999px;
  background: var(--brand);
  color: var(--brand-ink);
  font-weight: 700;
  font-size: var(--step--1);
}

.cartbar__label { font-size: var(--step--1); }

/* The running total. Display only — cart_quote() decides what is owed. */
.cartbar__total {
  font-family: var(--mono);
  font-variant-numeric: tabular-nums;
  font-weight: 700;
  letter-spacing: -0.02em;
  /* "10,90 €" broke across two lines inside the pill without this. */
  white-space: nowrap;
}

/* How far the basket is from the shop's minimum. Sits beside the total rather
   than replacing it: the customer needs both numbers to know what to add. */
.cartbar__short {
  font-size: var(--step--1);
  color: var(--ember);
  white-space: nowrap;
}

.cartbar__short[hidden] { display: none; }

.cartbar.is-bumped { animation: cartbar-bump 340ms cubic-bezier(.34, 1.4, .64, 1); }

@keyframes cartbar-bump {
  0%   { transform: translate(-50%, 0) scale(1); }
  40%  { transform: translate(-50%, -5px) scale(1.04); }
  100% { transform: translate(-50%, 0) scale(1); }
}

/* The shortfall is the first thing to go when the pill runs out of room. */
@media (max-width: 420px) {
  .cartbar__short { display: none; }
}

/* --- Phone verification (slice P) ----------------------------------------- */

/*
  One panel, two callers. At the checkout it sits inside the ticket below Place
  order and appears only once the server has said a code is needed, so it reads
  as the last thing between a composed order and the kitchen. On `register.php`
  it is the whole step, and `.verify--page` drops the rule that separated it.

  It lives here rather than in `checkout.css` because `register.php` loads no
  page stylesheet — the shared-component rule, paid for once already when a rule
  left in `storefront.css` unstyled the kitchen board.
*/
.verify {
  display: flex;
  flex-direction: column;
  gap: .6rem;
  margin-top: .9rem;
  padding-top: .9rem;
  border-top: 1px solid var(--line);
}

/*
  A class that sets `display` beats the `hidden` attribute. The slot picker cost
  this project that bug on 2026-08-09 and the till's pay-on-delivery button on
  2026-08-11; this rule is why it does not cost it a third time.
*/
.verify[hidden] { display: none; }

/* Standing on its own: nothing above it to be separated from. */
.verify--page {
  margin-top: 0;
  padding-top: 0;
  border-top: 0;
}

.verify__title {
  margin: 0;
  font-size: 1rem;
}

.verify__lede,
.verify__msg,
.verify__why {
  margin: 0;
  font-size: .85rem;
}

.verify__msg:empty { display: none; }

.verify__msg { color: var(--ember); }

/* Six digits, spaced out and large: this is read off a phone screen and typed
   with a thumb, often while standing up. */
.verify__code {
  font-size: 1.5rem;
  letter-spacing: .35em;
  text-align: center;
}

/* --- Motion --------------------------------------------------------------- */

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}
