@import url("fonts.css");

/* ---------------------------------------------------------------------------
   Madras Catering & Co. — brand tokens
   Palette from docs/branding/MadrasCatering_BrandBook (Primary Palette, p.17).
   Typefaces substitute open-source equivalents for the commercial brand fonts:
   Fraunces for Awesome Serif, Hanken Grotesk for Glonimove. Piazzolla is the
   brand's own body face and is already open source. All three self-hosted —
   see fonts.css.

   Direction: "Peacock" — a dark forest ground with cream plate surfaces
   (cards, panels, the size-picker dialog), colour-blocked section bands, and
   arch-niche photo placeholders standing in for the photography this menu
   doesn't have yet (see MenuItem.placeholder_tint). Full rationale and
   variants explored in docs/mockups/redesign/.
--------------------------------------------------------------------------- */

:root {
  --brick: #be1e2d;
  --brick-dark: #790f1c;
  --mulberry: #510f26;
  --mulberry-deep: #3d0b1c;
  --wine: #9a264f;
  --teal: #48897d;
  --sage: #a1c2b5;
  --forest: #1d342c;
  --forest-deep: #152720;
  --almond: #e2cfb3;
  --cream: #fff7e5;
  --blush: #f5bdc4;
  --rust: #ca581b;
  --ink: #19131a;

  /* page ground is dark; cards/panels/dialogs are light "plates" on top of it */
  --page: var(--forest);
  --surface: #fffaf0;
  --chrome: rgba(21, 39, 32, 0.92);
  --line: rgba(29, 52, 44, 0.14);
  --line-on-dark: rgba(226, 207, 179, 0.18);
  --muted: rgba(25, 19, 26, 0.6);
  --muted-on-dark: rgba(255, 247, 229, 0.72);

  --serif: "Fraunces", Georgia, "Times New Roman", serif;
  --sans: "Hanken Grotesk", ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif;
  --body: "Piazzolla", Georgia, serif;

  /* The pinned chrome, top to bottom: the 72px site header, then the menu
     toolbar. Each layer parks below the sum of the ones above it, so anything
     else that sticks (the filter sidebar) clears the whole stack. --toolbar-h
     is the toolbar's *condensed* height — the state it is in whenever the
     page is scrolled far enough for the offset to matter. */
  --chrome-h: 72px;
  --toolbar-h: 60px;

  --radius: 12px;
  --radius-lg: 18px;
  --shadow: 0 1px 2px rgba(0, 0, 0, 0.14), 0 8px 24px rgba(0, 0, 0, 0.16);
  --shadow-lg: 0 4px 10px rgba(0, 0, 0, 0.2), 0 24px 60px rgba(0, 0, 0, 0.32);
  --wrap: 1180px;
  --ease: cubic-bezier(0.2, 0.7, 0.3, 1);
}

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

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

body {
  margin: 0;
  background: var(--page);
  color: var(--cream);
  font-family: var(--body);
  font-size: 16px;
  line-height: 1.6;
  font-variant-numeric: tabular-nums;
  -webkit-font-smoothing: antialiased;
}

/* Ambient ground: a faint mulberry bloom top-of-page and a barely-there
   quatrefoil lattice (brand book p.27), both fixed so they read as room
   lighting rather than page decoration that scrolls away. */
body::before {
  content: "";
  position: fixed;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  background: radial-gradient(1000px 560px at 50% -10%, rgba(81, 15, 38, 0.5), transparent 68%);
}
body::after {
  content: "";
  position: fixed;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  opacity: 0.045;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='72' height='72' viewBox='0 0 72 72'%3E%3Cg fill='none' stroke='%23e2cfb3' stroke-width='1.1'%3E%3Ccircle cx='0' cy='36' r='24'/%3E%3Ccircle cx='72' cy='36' r='24'/%3E%3Ccircle cx='36' cy='0' r='24'/%3E%3Ccircle cx='36' cy='72' r='24'/%3E%3Ccircle cx='36' cy='36' r='24'/%3E%3C/g%3E%3C/svg%3E");
}
body > * { position: relative; z-index: 1; }

::selection { background: var(--teal); color: var(--cream); }

a { color: var(--blush); text-decoration-thickness: 1px; text-underline-offset: 2px; }
a:hover { color: var(--cream); }
/* Links inside a light plate (cards, panels, dialog) need dark-ground colours.
   :not(.btn) matters: without it this rule's higher specificity (.panel a)
   beats .btn--primary/.btn--ghost's own single-class colour, so a button like
   "Continue to pickup details" — brick background, .btn--primary sets its
   text to white — would get its text silently repainted brick-on-brick. */
.panel a:not(.btn), .confirm-card a:not(.btn), .pcard a:not(.btn), .picker a:not(.btn) { color: var(--brick); }
.panel a:not(.btn):hover, .confirm-card a:not(.btn):hover, .pcard a:not(.btn):hover, .picker a:not(.btn):hover { color: var(--brick-dark); }

h1, h2, h3, h4 {
  font-family: var(--serif);
  font-weight: 600;
  line-height: 1.12;
  margin: 0 0 0.4em;
  color: var(--cream);
  letter-spacing: -0.01em;
}
/* headings inside a light plate default back to forest */
.panel h1, .panel h2, .panel h3, .panel h4,
.confirm-card h1, .confirm-card h2, .confirm-card h3, .confirm-card h4,
.pcard__name, .picker__name { color: var(--forest); }

h1 { font-size: clamp(2.1rem, 5.4vw, 3.4rem); }
h2 { font-size: clamp(1.5rem, 3vw, 2rem); }
h3 { font-size: 1.15rem; }

.wrap { max-width: var(--wrap); margin: 0 auto; padding: 0 20px; }
.stack > * + * { margin-top: var(--gap, 16px); }
.muted { color: var(--muted-on-dark); }
.panel .muted, .confirm-card .muted { color: var(--muted); }
.small { font-size: 0.875rem; }
.tiny { font-size: 0.78rem; }
.eyebrow {
  font-family: var(--sans);
  font-size: 0.68rem;
  font-weight: 700;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--sage);
}
.panel .eyebrow, .confirm-card .eyebrow { color: var(--wine); }

/* focus floor — visible on both light plates and the dark ground */
a:focus-visible, button:focus-visible, input:focus-visible, textarea:focus-visible {
  outline: 2px solid var(--teal);
  outline-offset: 2px;
  border-radius: 2px;
}

/* --- header --------------------------------------------------------------- */

.site-header {
  position: sticky;
  top: 0;
  z-index: 40;
  background: var(--chrome);
  backdrop-filter: blur(14px);
  border-bottom: 1px solid var(--line-on-dark);
}

.site-header__inner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 20px;
  min-height: 72px;
}

.brand { display: flex; align-items: center; gap: 12px; text-decoration: none; }
.brand img { height: 48px; width: auto; display: block; }
.brand__text { font-family: var(--serif); font-size: 1.1rem; color: var(--cream); font-weight: 600; }

.site-header__actions {
  display: flex;
  align-items: center;
  gap: 18px;
}

@media (max-width: 480px) {
  .site-header__actions { gap: 10px; }
  .cart-link { padding: 10px 14px; }
}

.cart-link {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  font-family: var(--sans);
  font-weight: 700;
  font-size: 0.85rem;
  letter-spacing: 0.02em;
  padding: 10px 20px;
  border-radius: 999px;
  background: var(--brick);
  color: var(--cream);
  text-decoration: none;
  transition: background 0.15s var(--ease);
}
.cart-link:hover { background: var(--wine); color: var(--cream); }
.cart-link__icon {
  width: 19px;
  height: 19px;
  flex: none;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.8;
  stroke-linecap: round;
  stroke-linejoin: round;
}
.cart-link__count {
  background: rgba(0, 0, 0, 0.28);
  color: #fff;
  border-radius: 999px;
  min-width: 22px;
  height: 22px;
  display: inline-grid;
  place-items: center;
  font-size: 0.75rem;
  font-weight: 700;
  padding: 0 6px;
}
.cart-link__count.is-empty { display: none; }

/* --- hero ------------------------------------------------------------------
   Deep mulberry gradient with faint ogee-arch silhouettes — the brand book's
   Mughal-arch illustration grammar (pp.24–29), otherwise unused elsewhere on
   the site. */

.hero {
  position: relative;
  overflow: hidden;
  background: linear-gradient(152deg, var(--mulberry-deep) 0%, var(--mulberry) 46%, var(--wine) 100%);
  color: var(--cream);
  padding: 32px 0 30px;
  text-align: center;
}
.hero__arches {
  position: absolute;
  inset: 0;
  opacity: 0.14;
  pointer-events: none;
  display: flex;
  justify-content: center;
  gap: 2%;
}
.hero__arches i {
  display: block;
  width: 23%;
  background: var(--blush);
  clip-path: url(#ogee-niche);
  transform: translateY(26%);
}
.hero__arches i:nth-child(2) { transform: translateY(9%); }
.hero__inner { position: relative; max-width: 78ch; margin: 0 auto; }
.hero .eyebrow { color: var(--blush); }
.hero h1 { color: var(--cream); margin: 8px 0 0; font-size: clamp(1.4rem, 3vw, 2rem); }
.hero h1 em {
  font-style: italic;
  font-weight: 400;
  color: var(--blush);
}
.hero p { max-width: 68ch; color: rgba(255, 247, 229, 0.88); margin: 14px auto 0; font-size: 0.9rem; line-height: 1.5; }
.hero__facts {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 8px 26px;
  margin-top: 16px;
  font-family: var(--sans);
  font-size: 0.8rem;
  font-weight: 600;
  color: var(--cream);
}
.hero__facts span { display: inline-flex; align-items: center; gap: 7px; }
/* Stands in for the header's old "Track my existing order" button, which sat
   at every viewport whether or not there was an order to track. Anchored to
   pickup hours instead — the moment someone's actually deciding whether
   they've missed the window is the moment this is worth surfacing. */
.hero__track { margin-top: 10px; font-size: 0.8rem; color: rgba(255, 247, 229, 0.75); }
.hero__track a { font-weight: 700; }

/* --- layout ----------------------------------------------------------------- */

.layout {
  display: grid;
  grid-template-columns: 264px minmax(0, 1fr);
  gap: 40px;
  align-items: start;
  /* Longhand on purpose: this element is always .wrap too (see menu.html),
     and a `padding: 36px 0 80px` shorthand here would zero out .wrap's own
     `0 20px` — same specificity, later in the cascade, wins outright — and
     strand every card flush against the viewport edge on a phone. */
  padding-top: 36px;
  padding-bottom: 80px;
}
@media (max-width: 900px) {
  .layout { grid-template-columns: 1fr; gap: 22px; }
}

/* Sticks below the whole chrome stack. Capped and scrollable in its own right:
   a sticky box taller than the space it has pins its top and strands its bottom
   until you reach the page footer, and the tag lists can get there on a short
   viewport. When it fits, the overflow is inert and no scrollbar shows. */
.sidebar {
  position: sticky;
  top: calc(var(--chrome-h) + var(--toolbar-h) + 20px);
  max-height: calc(100dvh - var(--chrome-h) - var(--toolbar-h) - 40px);
  overflow-y: auto;
  overscroll-behavior: contain;
  /* overflow clips the panel's drop shadow flat — inset the scroll box by the
     shadow's spread and pull it back out so the alignment is unchanged */
  padding: 10px;
  margin: -10px;
  scrollbar-width: thin;
  scrollbar-color: rgba(226, 207, 179, 0.35) transparent;
}
/* --- filters drawer ----------------------------------------------------------
   #filters-drawer is the .sidebar above and only the .sidebar above: on a
   wide viewport every rule below is inert (header/footer/backdrop stay
   display:none, the trigger button stays hidden) and it's the plain sticky
   panel from the block above. Under 900px — the same width .layout stacks
   at — it becomes a fixed off-canvas panel that slides in from the right,
   the way a filter drawer works in most retail apps, instead of dumping
   three always-open groups straight into the page flow above the results.
   #filters-trigger/#filters-close/#filters-backdrop are wired up in
   menu.html's script block; nothing here needs JS to render correctly. ---- */

.filters-trigger {
  display: none;
  align-items: center;
  gap: 7px;
  padding: 11px 18px;
  border-radius: 999px;
  border: 1px solid var(--line-on-dark);
  background: rgba(21, 39, 32, 0.55);
  color: var(--cream);
  font-family: var(--sans);
  font-weight: 700;
  font-size: 0.8rem;
  letter-spacing: 0.02em;
  cursor: pointer;
  white-space: nowrap;
  transition: background 0.15s var(--ease), border-color 0.15s var(--ease);
}
.filters-trigger:hover { background: rgba(255, 247, 229, 0.1); }
.filters-trigger__dot { width: 7px; height: 7px; border-radius: 50%; background: var(--blush); }

.filters-drawer__header,
.filters-drawer__footer { display: none; }

.filters-backdrop {
  display: none;
  position: fixed;
  inset: 0;
  z-index: 69;
  background: rgba(10, 8, 12, 0.55);
  opacity: 0;
  transition: opacity 0.2s var(--ease);
}
.filters-backdrop.is-open { opacity: 1; }

/* Set on <body> while the drawer is open. The picker dialog doesn't need this
   — showModal() gets background-scroll locking for free from the browser —
   but this is a plain fixed-position panel, not a top-layer modal, so it has
   to be done by hand. */
body.no-scroll { overflow: hidden; }

@media (max-width: 900px) {
  .filters-trigger { display: inline-flex; }
  .filters-backdrop { display: block; }
  /* Only the open backdrop should ever intercept a tap — closed, it must not
     sit invisibly over the results and swallow clicks meant for them. */
  .filters-backdrop:not(.is-open) { pointer-events: none; }

  .sidebar {
    position: fixed;
    inset: 0 0 0 auto;
    z-index: 70;
    width: min(86vw, 380px);
    max-height: none;
    margin: 0;
    padding: 0;
    background: var(--surface);
    box-shadow: var(--shadow-lg);
    display: flex;
    flex-direction: column;
    overflow: hidden;
    transform: translateX(100%);
    /* visibility, not display or aria-hidden: this same node is the plain,
       always-visible desktop sidebar above 900px, so anything that hides it
       has to live entirely inside this media query. visibility:hidden pulls
       the closed drawer out of both the tab order and the accessibility
       tree (unlike the transform alone, which leaves an off-screen panel
       full of focusable checkboxes) — the delay holds it visible through the
       close transition so the slide-out isn't cut off mid-animation. */
    visibility: hidden;
    transition: transform 0.28s var(--ease), visibility 0s linear 0.28s;
  }
  .sidebar.is-open {
    transform: translateX(0);
    visibility: visible;
    transition: transform 0.28s var(--ease);
  }

  .sidebar .panel {
    flex: 1 1 auto;
    min-height: 0;
    overflow-y: auto;
    overscroll-behavior: contain;
    border-radius: 0;
    box-shadow: none;
  }

  .filters-drawer__header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    flex: 0 0 auto;
    padding: 16px 18px;
    border-bottom: 1px solid var(--line);
  }
  .filters-drawer__header h2 {
    margin: 0;
    font-family: var(--serif);
    font-size: 1.2rem;
    color: var(--forest);
  }
  .filters-drawer__close {
    width: 34px;
    height: 34px;
    flex: none;
    border-radius: 50%;
    border: none;
    background: rgba(25, 19, 26, 0.06);
    color: var(--forest);
    font-size: 1.3rem;
    line-height: 1;
    cursor: pointer;
    transition: background 0.15s var(--ease);
  }
  .filters-drawer__close:hover { background: rgba(25, 19, 26, 0.12); }

  .filters-drawer__footer {
    display: block;
    flex: 0 0 auto;
    padding: 14px 18px calc(14px + env(safe-area-inset-bottom));
    border-top: 1px solid var(--line);
    background: var(--surface);
  }
}

/* --- menu toolbar (search + view toggle) -------------------------------------
   A full-width bar parked under the site header. It carries its own opaque
   chrome because the results scroll beneath it, and it condenses once pinned
   (.is-stuck, set by the sentinel observer in menu.html): the captions collapse
   away and the row tightens, so on a phone it costs one slim row instead of a
   two-line block. Everything that moves is transitioned, so it reads as the bar
   folding up rather than the page jumping.
--------------------------------------------------------------------------- */

.menu-toolbar {
  position: sticky;
  top: var(--chrome-h);
  z-index: 39;
  background: var(--chrome);
  backdrop-filter: blur(14px);
  border-bottom: 1px solid var(--line-on-dark);
  padding: 16px 0;
  transition: padding 0.2s var(--ease);
}
.menu-toolbar__inner {
  display: flex;
  align-items: flex-end;
  gap: 14px 24px;
}
.menu-toolbar__field { flex: 1 1 auto; min-width: 0; }
.menu-toolbar__field--view,
.menu-toolbar__field--filters { flex: 0 0 auto; }
.menu-toolbar__label {
  display: block;
  margin: 0 0 8px;
  font-family: var(--sans);
  font-size: 0.68rem;
  font-weight: 700;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--sage);
  /* collapsed rather than display:none, so aria-labelledby still resolves */
  overflow: hidden;
  max-height: 20px;
  opacity: 1;
  transition: max-height 0.2s var(--ease), opacity 0.15s var(--ease),
              margin-bottom 0.2s var(--ease);
}

.menu-toolbar .search-input {
  transition: border-color 0.15s var(--ease), box-shadow 0.15s var(--ease),
              padding 0.2s var(--ease), font-size 0.2s var(--ease);
}

.menu-toolbar.is-stuck { padding: 9px 0; }
.menu-toolbar.is-stuck .menu-toolbar__label {
  max-height: 0;
  opacity: 0;
  margin-bottom: 0;
}
.menu-toolbar.is-stuck .search-input { padding: 8px 13px; font-size: 0.9rem; }
.menu-toolbar.is-stuck .viewtoggle__opt > span { padding: 7px 14px; }

@media (max-width: 640px) {
  /* Expanded, there is no room to sit the two side by side without squeezing
     search to a couple of words — so let it take the row and drop the toggle
     beneath. The extra height only exists at the top of the page. */
  .menu-toolbar__inner { flex-wrap: wrap; gap: 10px 12px; justify-content: space-between; }
  .menu-toolbar__field { flex: 1 1 100%; }
  .menu-toolbar__field--view,
  .menu-toolbar__field--filters { flex: 0 0 auto; }
  /* Search alone fills the first wrapped line, so space-between above has
     nothing to split there — it only ever acts on the second line, pinning
     Filters to the left edge and the view toggle to the right, level with
     the search box's own edges instead of the two sitting bunched together
     on the left. */

  /* Pinned, it folds back to a single row: the toggle drops to its glyphs and
     search takes everything left over. The inputs carry their own aria-labels,
     so the pair still announces as "Card view" / "Table view". */
  .menu-toolbar.is-stuck .menu-toolbar__inner { flex-wrap: nowrap; }
  .menu-toolbar.is-stuck .menu-toolbar__field { flex: 1 1 auto; }
  .menu-toolbar.is-stuck .viewtoggle__text { display: none; }
  .menu-toolbar.is-stuck .viewtoggle__opt > span { padding: 7px 12px; }
}

/* --- view toggle ----------------------------------------------------------- */

.viewtoggle {
  display: inline-flex;
  gap: 3px;
  padding: 3px;
  border: 1px solid var(--line-on-dark);
  border-radius: 999px;
  background: rgba(21, 39, 32, 0.55);
}
.viewtoggle__opt { display: inline-flex; margin: 0; }
.viewtoggle__icon { font-style: normal; }
/* Kept in the DOM and focusable (opacity, not display:none) so the pair still
   behaves as a keyboard-navigable radio group; the <span> is the visible pill. */
.viewtoggle__opt input {
  position: absolute;
  opacity: 0;
  width: 0;
  height: 0;
  margin: 0;
  pointer-events: none;
}
/* Direct child throughout: the pill wraps an icon and a label span, and a
   descendant selector here would paint those children directly — pinning the
   label to the unchecked colour so it never inherits the checked pill's. */
.viewtoggle__opt > span {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 7px;
  padding: 9px 18px;
  border-radius: 999px;
  font-family: var(--sans);
  font-size: 0.8rem;
  font-weight: 700;
  letter-spacing: 0.02em;
  color: var(--muted-on-dark);
  cursor: pointer;
  white-space: nowrap;
  transition: background 0.15s var(--ease), color 0.15s var(--ease),
              padding 0.2s var(--ease);
}
.viewtoggle__opt:hover > span { color: var(--cream); }
.viewtoggle__opt input:checked + span { background: var(--cream); color: var(--forest); }
.viewtoggle__opt input:focus-visible + span { outline: 2px solid var(--teal); outline-offset: 2px; }

/* --- filters -----------------------------------------------------------------
   The filter panel, size-picker dialog and every card are the same visual
   object: a cream "plate" resting on the dark ground. ----------------------- */

.panel {
  background: var(--surface);
  border-radius: var(--radius-lg);
  padding: 20px;
  box-shadow: var(--shadow);
  color: var(--ink);
}

.filter-group + .filter-group { margin-top: 22px; padding-top: 20px; border-top: 1px solid var(--line); }
.filter-group__title {
  font-family: var(--sans);
  font-size: 0.68rem;
  font-weight: 700;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--wine);
  margin: 0 0 12px;
}

/* Each .filter-group is a <details> with the title as its <summary>, always
   rendered open — so it looks exactly like the old plain <div>/<p> markup
   until someone actually interacts with it. Strip the native disclosure
   triangle and swap in a chevron that matches the site's chrome. The toggle
   works at every width (a keyboard user tabbing to <summary> can always
   activate it, so gating that off for desktop-only would either fight
   keyboard access or need JS); it earns its keep most inside the mobile
   drawer above, where all three groups plus a header and footer share one
   fixed-height panel and folding a couple away is the difference between
   "Hide items containing" being one tap down and being off the bottom. */
.filter-group > summary.filter-group__title {
  list-style: none;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
}
.filter-group > summary.filter-group__title::marker,
.filter-group > summary.filter-group__title::-webkit-details-marker { display: none; }
.filter-group > summary.filter-group__title::after {
  content: "";
  flex: 0 0 auto;
  width: 8px;
  height: 8px;
  border-right: 1.5px solid currentColor;
  border-bottom: 1.5px solid currentColor;
  transform: rotate(-135deg);
  transition: transform 0.2s var(--ease);
}
.filter-group:not([open]) > summary.filter-group__title::after { transform: rotate(45deg); }
.filter-group:not([open]) > summary.filter-group__title { margin-bottom: 0; }

.check {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 6px 0;
  font-family: var(--sans);
  font-size: 0.92rem;
  cursor: pointer;
  color: var(--ink);
}
.check input { accent-color: var(--teal); width: 16px; height: 16px; cursor: pointer; }
.check:hover { color: var(--teal); }

.search-input,
.field-input {
  width: 100%;
  font-family: var(--sans);
  font-size: 0.95rem;
  padding: 11px 14px;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: #fff;
  color: var(--ink);
  transition: border-color 0.15s var(--ease), box-shadow 0.15s var(--ease);
}
.search-input:focus,
.field-input:focus {
  outline: none;
  border-color: var(--teal);
  box-shadow: 0 0 0 3px rgba(72, 137, 125, 0.18);
}
textarea.field-input { resize: vertical; min-height: 96px; font-family: var(--body); }

/* --- buttons ------------------------------------------------------------------ */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  font-family: var(--sans);
  font-weight: 700;
  font-size: 0.86rem;
  letter-spacing: 0.02em;
  padding: 11px 20px;
  border-radius: 999px;
  border: 1px solid transparent;
  cursor: pointer;
  text-decoration: none;
  transition: background 0.15s var(--ease), border-color 0.15s var(--ease), color 0.15s var(--ease);
}
.btn--primary { background: var(--brick); color: #fff; }
.btn--primary:hover { background: var(--wine); color: #fff; }
.btn--dark { background: var(--forest); color: var(--cream); }
.btn--dark:hover { background: var(--teal); color: var(--cream); }
.btn--ghost { background: transparent; border-color: var(--forest); color: var(--forest); border-radius: var(--radius); }
.btn--ghost:hover { border-color: var(--teal); color: var(--teal); }
.btn--block { width: 100%; }
.btn--lg { padding: 15px 28px; font-size: 0.95rem; }
.btn--link { background: none; border: none; padding: 0; color: var(--muted); font-size: 0.82rem; cursor: pointer; font-family: var(--sans); }
.btn--link:hover { color: var(--brick); text-decoration: underline; }

/* --- menu sections --------------------------------------------------------------
   Each section gets a colour-blocked band — the brand book's own graphic
   grammar (pp.24–25) — rotating through four accents via {% cycle %} in
   _menu_results.html so a 178-item menu reads as rooms, not one long list. -- */

.results-meta {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 16px;
  flex-wrap: wrap;
  margin-bottom: 20px;
  font-family: var(--sans);
  font-size: 0.86rem;
  color: var(--muted-on-dark);
}
/* The count and pricing note are a nice-to-have on a wide screen, but on a
   phone they're the first thing above the results and just read as more
   noise crowding the top of the list — drop the line rather than let it
   wrap into two more lines of small type before anything else shows up. */
@media (max-width: 640px) {
  .results-meta { display: none; }
}

/* Explains why a fuzzy or synonym hit appeared; sits under the result count. */
.results-note {
  margin: -12px 0 20px;
  font-family: var(--sans);
  font-size: 0.82rem;
  font-style: italic;
  color: var(--muted-on-dark);
}

.section-block + .section-block { margin-top: 46px; }

.band {
  position: relative;
  overflow: hidden;
  border-radius: var(--radius-lg);
  padding: 22px 26px;
  margin-bottom: 22px;
  display: flex;
  align-items: flex-end;
  gap: 18px;
  flex-wrap: wrap;
  color: var(--cream);
}
.band::after {
  content: "";
  position: absolute;
  inset: 0;
  opacity: 0.13;
  pointer-events: none;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='34' height='34' viewBox='0 0 34 34'%3E%3Cg fill='none' stroke='%23fff7e5' stroke-width='1'%3E%3Cpath d='M17 0 L34 17 L17 34 L0 17Z'/%3E%3Cpath d='M17 8.5 L25.5 17 L17 25.5 L8.5 17Z'/%3E%3C/g%3E%3C/svg%3E");
}
.band > * { position: relative; z-index: 1; }
.band h2 { margin: 0; font-size: clamp(1.5rem, 3.2vw, 1.9rem); color: var(--cream); }
.band p { margin: 4px 0 0; font-family: var(--sans); font-size: 0.85rem; color: rgba(255, 247, 229, 0.82); max-width: 52ch; }
.band__count {
  margin-left: auto;
  font-family: var(--sans);
  font-size: 0.66rem;
  font-weight: 700;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: rgba(255, 247, 229, 0.66);
  white-space: nowrap;
}
.band--wine { background: var(--wine); }
.band--teal { background: var(--teal); }
.band--brick { background: var(--brick-dark); }
.band--rust { background: var(--rust); }

/* Cuisine divider inside a section (Section.group_by_cuisine). A rule with a
   label sitting in it — uppercase sans, a step up from .band__count but well
   under the band's own serif h2, so it divides the section without competing
   to be its heading. The short leading rule is what stops the label floating
   loose off the left edge. */
.subhead {
  display: flex;
  align-items: center;
  gap: 12px;
  margin: 0 0 15px;
  font-family: var(--sans);
  font-size: 0.8rem;
  font-weight: 700;
  letter-spacing: 0.15em;
  text-transform: uppercase;
  color: var(--muted-on-dark);
}
.subhead::before,
.subhead::after {
  content: "";
  height: 1px;
  background: var(--line-on-dark);
}
.subhead::before { width: 26px; flex: none; }
.subhead::after { flex: 1; }
/* Every divider after the first follows a grid of cards, and needs more air
   above it than below it — otherwise it reads as belonging to the block it
   just closed rather than the one it opens. */
.item-grid + .subhead { margin-top: 34px; }

.item-card__note {
  font-family: var(--sans);
  font-size: 0.78rem;
  color: #8f3d13;
  background: rgba(202, 88, 27, 0.1);
  padding: 5px 10px;
  border-radius: 7px;
  display: inline-block;
}

.tag-row { display: flex; flex-wrap: wrap; gap: 6px; }
.tag {
  font-family: var(--sans);
  font-size: 0.66rem;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  padding: 4px 9px;
  border-radius: 4px;
  border: 1px solid transparent;
  white-space: nowrap;
}
.tag--cuisine { background: rgba(29, 52, 44, 0.08); color: var(--forest); }
.tag--diet { background: rgba(29, 52, 44, 0.08); color: var(--forest); }
.tag--diet-nonveg { background: rgba(190, 30, 45, 0.09); color: var(--brick-dark); }
.tag--allergen { background: rgba(154, 38, 79, 0.09); color: var(--wine); }

/* --- photo cards -----------------------------------------------------------------
   The arch niche is this menu's whole visual identity until real photography
   exists. tint-0..4 (item.placeholder_tint, stable per item) rotate through
   five brand colours so a grid of many cards doesn't read as one flat block;
   the isotype watermark sits inside an ogee-clipped niche, and a real photo
   (once uploaded) replaces the whole background with object-fit: cover. ---- */

.item-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(230px, 1fr));
  gap: 18px;
}

.pcard {
  background: var(--surface);
  border-radius: var(--radius-lg);
  overflow: hidden;
  transition: box-shadow 0.16s var(--ease), transform 0.16s var(--ease);
}
.pcard:hover { box-shadow: var(--shadow-lg); transform: translateY(-3px); }

/* The whole card is the control — one target, easy to hit on a phone. */
.pcard { display: flex; }
.pcard__hit {
  display: flex;
  flex-direction: column;
  width: 100%;
  padding: 0;
  border: none;
  background: none;
  cursor: pointer;
  text-align: left;
  font: inherit;
  color: inherit;
}
.pcard__hit:focus-visible { outline: 2px solid var(--teal); outline-offset: -2px; }

.pcard__photo {
  position: relative;
  width: 100%;
  height: 128px;
  flex: none;
  display: grid;
  place-items: center;
  overflow: hidden;
}
.pcard__photo img { width: 100%; height: 100%; object-fit: cover; display: block; }

/* Real photo: no tint, no niche, no watermark — just the picture. ":has(> img)"
   (direct child only) is deliberate: the isotype watermark is also an <img>,
   but nested inside .pcard__niche, so a plain ":has(img)" would match every
   card — even placeholders — and always suppress the tint. */
.pcard__photo:has(> img) { background: none; }
.pcard__photo:has(> img) .pcard__niche { display: none; }
.pcard__photo:has(> img)::before { display: none; }

/* Hide photo section entirely when there's no image to avoid blank space. */
.pcard__photo:not(:has(> img)) { display: none; }

.pcard__niche {
  position: relative;
  z-index: 1;
  width: 64px;
  height: 88px;
  clip-path: url(#ogee-niche);
  background: rgba(255, 247, 229, 0.19);
  display: grid;
  place-items: center;
}
.pcard__niche img { width: 34%; height: auto; object-fit: contain; opacity: 0.85; }

.tint-0 { background: var(--mulberry); }
.tint-1 { background: var(--teal); }
.tint-2 { background: var(--brick); }
.tint-3 { background: var(--rust); }
.tint-4 { background: var(--wine); }
.pcard__photo::before {
  content: "";
  position: absolute;
  inset: 0;
  opacity: 0.22;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='28' height='28' viewBox='0 0 28 28'%3E%3Cg fill='none' stroke='%23fff7e5' stroke-width='1'%3E%3Ccircle cx='0' cy='14' r='9'/%3E%3Ccircle cx='28' cy='14' r='9'/%3E%3Ccircle cx='14' cy='0' r='9'/%3E%3Ccircle cx='14' cy='28' r='9'/%3E%3Ccircle cx='14' cy='14' r='9'/%3E%3C/g%3E%3C/svg%3E");
}

.pcard__body {
  padding: 13px 15px 15px;
  display: flex;
  flex-direction: column;
  gap: 5px;
  flex: 1;
}
.pcard__name {
  font-family: var(--serif);
  font-size: 1rem;
  font-weight: 600;
  color: var(--forest);
  line-height: 1.25;
  display: flex;
  align-items: baseline;
  gap: 7px;
}
.pcard__meta {
  font-family: var(--sans);
  font-size: 0.75rem;
  color: var(--muted);
  min-height: 1.1em;
}
/* auto, not a fixed gap — keeps the price row on a common baseline when a
   neighbouring card's title wraps to two lines. */
.pcard__foot {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  margin-top: auto;
  padding-top: 12px;
  border-top: 1px solid var(--line);
}
.pcard__price { font-family: var(--sans); font-size: 1rem; font-weight: 700; color: var(--forest); }
.pcard__price small { font-weight: 500; font-size: 0.72rem; color: var(--muted); }
.pcard__add {
  font-family: var(--sans);
  font-size: 0.78rem;
  font-weight: 700;
  letter-spacing: 0.03em;
  padding: 8px 16px;
  border-radius: 999px;
  border: 1px solid var(--forest);
  color: var(--forest);
  background: transparent;
  transition: background 0.15s var(--ease), color 0.15s var(--ease), border-color 0.15s var(--ease);
}
.pcard:hover .pcard__add { background: var(--brick); color: var(--cream); border-color: var(--brick); }

/* --- table view -------------------------------------------------------------------
   The photo grid's counterpart, chosen from the toolbar toggle. Same cream
   plate as a card, but every variant price is on the page at once — the point
   of the view is comparing sizes across a section without opening anything.
   Under 760px a four-column table stops being readable, so the same markup
   re-flows into stacked blocks (thead hidden, cells become rows). ---------- */

.table-wrap {
  background: var(--surface);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow);
  color: var(--ink);
  overflow: hidden;
}

.menu-table {
  width: 100%;
  border-collapse: collapse;
  font-family: var(--sans);
}
.menu-table thead th {
  text-align: left;
  padding: 12px 18px;
  background: #fdf6e7;
  border-bottom: 1px solid var(--line);
  font-size: 0.66rem;
  font-weight: 700;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--wine);
  white-space: nowrap;
}
/* The sizes column carries the widest content — "Medium Tray · serves 20–25"
   plus a price — so it gets the room; the tag column is narrow because tags
   wrap onto their own lines happily. */
.menu-table__c-item { width: 33%; }
.menu-table__c-about { width: 17%; }
.menu-table__c-sizes { width: 34%; }
.menu-table__c-act { width: 1%; }

.menu-table tbody tr + tr { border-top: 1px solid var(--line); }
.menu-table tbody tr:not(.menu-table__group):hover { background: rgba(72, 137, 125, 0.07); }

/* The table's answer to .subhead: one <tbody> per cuisine, opened by a row
   spanning all four columns. tr + tr already draws the line between rows, but
   not across a tbody boundary — so this row carries its own top border and the
   section stays visually continuous through the split. */
.menu-table tbody .menu-table__group th {
  padding: 16px 18px 8px;
  background: #fdf6e7;
  border-top: 1px solid var(--line);
  border-bottom: 1px solid var(--line);
  text-align: left;
  font-size: 0.76rem;
  font-weight: 700;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--wine);
}
.menu-table tbody:first-of-type .menu-table__group th { border-top: 0; }
.menu-table tbody th,
.menu-table tbody td {
  padding: 14px 18px;
  vertical-align: top;
  text-align: left;
  font-weight: 400;
}

.menu-table__name {
  font-family: var(--serif);
  font-size: 1rem;
  font-weight: 600;
  color: var(--forest);
  line-height: 1.25;
  display: flex;
  align-items: baseline;
  gap: 7px;
}
.menu-table__desc {
  display: block;
  margin-top: 4px;
  font-size: 0.78rem;
  line-height: 1.45;
  color: var(--muted);
  max-width: 48ch;
}

/* one line per variant: label (with the serving or minimum caveat) left,
   price right, so the prices form a scannable column of their own */
.msize {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 14px;
  font-size: 0.84rem;
  color: var(--ink);
}
.msize + .msize { margin-top: 4px; }
/* nowrap keeps "serves 20–25" together — if the column ever gets tight the
   whole phrase drops to the next line instead of splitting across the dash */
.msize__label small { color: var(--muted); font-size: 0.72rem; white-space: nowrap; }
.msize__label small::before { content: "· "; }
.msize__price { font-weight: 700; color: var(--forest); white-space: nowrap; }
.msize__price small { font-weight: 500; font-size: 0.72rem; color: var(--muted); }

.menu-table__act { text-align: right; }
.menu-table__act .btn { padding: 9px 18px; }

@media (max-width: 760px) {
  .menu-table thead { display: none; }
  .menu-table,
  .menu-table tbody,
  .menu-table tr,
  .menu-table tbody th,
  .menu-table tbody td { display: block; width: auto; }
  .menu-table tbody tr { padding: 15px 16px; }
  .menu-table tbody th,
  .menu-table tbody td { padding: 0; }
  .menu-table tbody th + td,
  .menu-table tbody td + td { margin-top: 10px; }
  /* The stacked layout gives every row its own padding box; the group heading
     is a label over the blocks that follow, not one of them. */
  .menu-table tbody tr.menu-table__group { padding: 0; }
  .menu-table tbody .menu-table__group th { padding: 14px 16px 10px; }
  /* qualified with tbody td so it outranks the padding reset above */
  .menu-table tbody td.menu-table__sizes {
    border-top: 1px dashed var(--line);
    padding-top: 10px;
  }
  .menu-table__act { text-align: left; }
  .menu-table__act .btn { width: 100%; }
}

/* --- veg / non-veg mark ----------------------------------------------------------- */

.vdot {
  width: 11px;
  height: 11px;
  border-radius: 2px;
  border: 1.5px solid;
  display: inline-grid;
  place-items: center;
  flex: none;
  align-self: center;
}
.vdot::after { content: ""; display: block; }
.vdot--veg { border-color: #3f7d33; }
.vdot--veg::after { width: 5px; height: 5px; border-radius: 50%; background: #3f7d33; }
.vdot--non { border-color: var(--brick); }
.vdot--non::after {
  width: 0; height: 0;
  border-left: 3px solid transparent;
  border-right: 3px solid transparent;
  border-bottom: 5px solid var(--brick);
}

/* --- size picker dialog -----------------------------------------------------------
   Comes off the card and into a dialog: with three tray prices per card the
   grid read as a wall of numbers, none of them comparable while scrolling.
   One honest price on the card ("$70 and up"), then this dialog gets the
   room for serving sizes, per-item notes and the allergen line. Adding
   swaps a size row's Add button into a stepper in place and the dialog
   deliberately stays open — see the comment in base.html. ------------------ */

#picker {
  /* Pin to the viewport ourselves. Without this the UA stylesheet decides, and
     WebKit still uses position:absolute for dialogs — which anchors to the top
     of the *document*, so on a scrolled page the picker opened off-screen above
     you and focus-into-view yanked the whole page to the top. inset:0 + margin
     auto then centres it in the viewport, i.e. right where you were looking. */
  position: fixed;
  inset: 0;
  margin: auto;
  border: none;
  padding: 0;
  border-radius: var(--radius-lg);
  max-width: 480px;
  width: calc(100vw - 32px);
  height: fit-content;
  max-height: 88vh;
  max-height: 88svh; /* svh = small viewport height: excludes mobile browser chrome */
  background: var(--surface);
  box-shadow: 0 24px 70px rgba(0, 0, 0, 0.5);
  overflow: hidden;
  overscroll-behavior: contain;
}
/* [open] guard: a bare `display:flex` here would out-specify the UA's
   `dialog:not([open]) { display: none }` and leave the picker on screen. */
#picker[open] { display: flex; }
#picker::backdrop { background: rgba(10, 8, 12, 0.68); backdrop-filter: blur(3px); }

/* Column layout so the photo stays fixed and only .picker__body scrolls,
   however tall the dialog ends up. min-height:0 lets the body actually shrink. */
.picker { position: relative; display: flex; flex-direction: column; min-height: 0; width: 100%; }
.picker__close {
  position: absolute;
  top: 12px;
  right: 12px;
  z-index: 3;
  width: 34px;
  height: 34px;
  border-radius: 50%;
  border: none;
  background: rgba(25, 19, 26, 0.5);
  color: var(--cream);
  font-size: 1.3rem;
  line-height: 1;
  cursor: pointer;
  backdrop-filter: blur(4px);
  transition: background 0.15s var(--ease);
}
.picker__close:hover { background: rgba(25, 19, 26, 0.82); }

.picker__photo {
  position: relative;
  width: 100%;
  height: 140px;
  flex: none;
  display: grid;
  place-items: center;
  overflow: hidden;
}
.picker__photo img { width: 100%; height: 100%; object-fit: cover; display: block; }
/* :has(> img) — direct child only; see the .pcard__photo comment above. */
.picker__photo:has(> img) { background: none; }
.picker__photo:has(> img) .picker__niche { display: none; }
.picker__photo:has(> img)::before { display: none; }
.picker__photo::before {
  content: "";
  position: absolute;
  inset: 0;
  opacity: 0.22;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='28' height='28' viewBox='0 0 28 28'%3E%3Cg fill='none' stroke='%23fff7e5' stroke-width='1'%3E%3Ccircle cx='0' cy='14' r='9'/%3E%3Ccircle cx='28' cy='14' r='9'/%3E%3Ccircle cx='14' cy='0' r='9'/%3E%3Ccircle cx='14' cy='28' r='9'/%3E%3Ccircle cx='14' cy='14' r='9'/%3E%3C/g%3E%3C/svg%3E");
}
.picker__niche {
  position: relative;
  z-index: 1;
  width: 70px;
  height: 96px;
  clip-path: url(#ogee-niche);
  background: rgba(255, 247, 229, 0.19);
  display: grid;
  place-items: center;
}
.picker__niche img { width: 36%; height: auto; object-fit: contain; opacity: 0.85; }

.picker__body {
  padding: 18px 22px 22px;
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  /* Stops a flick past the end of the size list from scrolling the menu behind. */
  overscroll-behavior: contain;
}
.picker__name {
  font-family: var(--serif);
  font-size: 1.5rem;
  font-weight: 600;
  color: var(--forest);
  margin: 4px 0 0;
  display: flex;
  align-items: baseline;
  gap: 9px;
}
.picker__desc { margin: 8px 0 12px; font-size: 0.92rem; color: var(--muted); }

.picker__sizes { display: flex; flex-direction: column; gap: 8px; margin-top: 6px; }
.psize {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto auto;
  align-items: center;
  gap: 10px;
  padding: 11px 13px;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: #fdf6e7;
  transition: border-color 0.14s var(--ease), background 0.14s var(--ease);
}
.psize:hover { border-color: var(--sage); }
.psize:has(.qty-stepper) { border-color: var(--teal); background: rgba(72, 137, 125, 0.08); }
.psize__info { min-width: 0; }
.psize__label { font-family: var(--sans); font-size: 0.9rem; font-weight: 700; color: var(--forest); display: block; }
.psize__meta { font-family: var(--sans); font-size: 0.72rem; color: var(--muted); display: block; margin-top: 1px; }
.psize__price { font-family: var(--sans); font-weight: 700; font-size: 0.98rem; color: var(--forest); white-space: nowrap; }
.psize__price small { font-weight: 500; font-size: 0.7rem; color: var(--muted); }
.psize .btn { padding: 9px 16px; }

@media (max-width: 460px) {
  .psize { grid-template-columns: 1fr auto; row-gap: 8px; }
  .psize__info { grid-column: 1 / -1; }
}

/* On a phone the picker becomes a bottom sheet: it meets the thumb instead of
   the eye, and the size rows land in the reachable half of the screen. */
@media (max-width: 560px) {
  #picker {
    inset: auto 0 0 0;
    margin: 0;
    width: 100%;
    max-width: none;
    max-height: 90vh;
    max-height: 90svh;
    border-radius: var(--radius-lg) var(--radius-lg) 0 0;
  }
  .picker__photo { height: 110px; }
  /* Clear the home-indicator bar so the last size row is never half-covered. */
  .picker__body { padding-bottom: calc(22px + env(safe-area-inset-bottom)); }
}

/* --- quantity stepper (cart + picker) ----------------------------------------------- */

.qty-stepper {
  display: inline-flex;
  align-items: center;
  gap: 2px;
  border: 1px solid var(--line);
  border-radius: 999px;
  background: #fff;
  padding: 3px;
}
.qty-stepper form { display: contents; }
.qty-stepper__btn {
  width: 28px;
  height: 28px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: none;
  border-radius: 50%;
  background: transparent;
  color: var(--forest);
  font-size: 1rem;
  line-height: 1;
  cursor: pointer;
  transition: background 0.14s var(--ease), color 0.14s var(--ease);
}
.qty-stepper__btn:hover:not(:disabled) { background: var(--sage); color: var(--forest); }
.qty-stepper__btn:disabled { color: var(--line); cursor: not-allowed; }
.qty-stepper__value {
  min-width: 34px;
  text-align: center;
  font-family: var(--sans);
  font-size: 0.86rem;
  font-weight: 700;
  color: var(--ink);
}

/* --- cart / checkout --------------------------------------------------------------- */

.cart-layout {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 360px;
  gap: 34px;
  align-items: start;
  /* Longhand on purpose — see the identical note on .layout above. This
     element is always .wrap too (cart.html, checkout.html), and the
     shorthand form here zeroed .wrap's horizontal padding on both pages. */
  padding-top: 36px;
  padding-bottom: 80px;
}
@media (max-width: 900px) { .cart-layout { grid-template-columns: 1fr; } }

.cart-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  flex-wrap: wrap;
}
.cart-header h1 { margin: 0; }

.cart-row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  gap: 14px;
  padding: 18px 0;
  border-bottom: 1px solid var(--line);
}
.cart-row:last-of-type { border-bottom: none; }
.cart-row__name { font-family: var(--serif); font-size: 1rem; font-weight: 600; color: var(--forest); }
.cart-row__meta { font-family: var(--sans); font-size: 0.82rem; color: var(--muted); }
.cart-row__controls { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; margin-top: 10px; }
.cart-row__total { font-family: var(--sans); font-weight: 700; text-align: right; white-space: nowrap; color: var(--forest); }

.summary { position: sticky; top: 96px; }
@media (max-width: 900px) { .summary { position: static; } }

.summary__line {
  display: flex;
  justify-content: space-between;
  gap: 16px;
  font-family: var(--sans);
  font-size: 0.92rem;
  padding: 7px 0;
  color: var(--ink);
}
.summary__line--total {
  border-top: 1px solid var(--line);
  margin-top: 8px;
  padding-top: 14px;
  font-size: 1.12rem;
  font-weight: 700;
  color: var(--forest);
}

/* --- pickup vs delivery ----------------------------------------------------------------- */

.fulfil { margin-top: 16px; }
.fulfil__title {
  margin: 0 0 14px;
  font-size: 1.05rem;
}

/* Two equal cards side by side, stacking under the cart's own breakpoint.
   Each is a <form> wrapping one full-bleed button, so the grid has to be told
   to stretch the form and the form to stretch the button — otherwise the
   shorter card's button floats at the top of its cell. */
.fulfil__choices {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 12px;
}
.fulfil__choices > form { display: flex; }
@media (max-width: 560px) { .fulfil__choices { grid-template-columns: 1fr; } }

.fulfil__option {
  flex: 1;
  display: flex;
  flex-direction: column;
  gap: 4px;
  width: 100%;
  text-align: left;
  padding: 14px 16px;
  border: 1.5px solid var(--line);
  border-radius: var(--radius);
  background: #fff;
  font-family: var(--sans);
  cursor: pointer;
  transition: border-color 0.12s var(--ease), background 0.12s var(--ease),
              color 0.12s var(--ease);
}
.fulfil__option:hover:not(.is-selected) { border-color: var(--teal); }
.fulfil__option.is-selected {
  background: var(--forest);
  border-color: var(--forest);
  color: var(--cream);
}
.fulfil__option-name { font-weight: 700; font-size: 0.95rem; color: var(--forest); }
.fulfil__option-note { font-size: 0.78rem; color: var(--muted); line-height: 1.35; }
.fulfil__option-price {
  margin-top: 4px;
  font-size: 0.8rem;
  font-weight: 700;
  color: var(--brick);
}
.fulfil__option.is-selected .fulfil__option-name,
.fulfil__option.is-selected .fulfil__option-price { color: var(--cream); }
.fulfil__option.is-selected .fulfil__option-note { color: rgba(255, 247, 229, 0.75); }

.fulfil__caveat { margin: 16px 0 0; }
.fulfil__address { margin-top: 16px; }
.fulfil__address-actions { display: flex; align-items: center; gap: 12px; }

/* hx-indicator: HTMX sets .htmx-request on this element for the duration of
   the request, so the label is hidden until a lookup is actually in flight. */
.fulfil__checking {
  font-family: var(--sans);
  font-size: 0.82rem;
  color: var(--muted);
  opacity: 0;
  transition: opacity 0.12s var(--ease);
}
.fulfil__checking.htmx-request { opacity: 1; }

.fulfil__rates { margin-top: 14px; font-family: var(--sans); }
.fulfil__rates summary {
  font-size: 0.82rem;
  font-weight: 600;
  color: var(--muted);
  cursor: pointer;
}
.fulfil__rates summary:hover { color: var(--brick); }
.fulfil__rate-list { list-style: none; margin: 10px 0 0; padding: 0; }
.fulfil__rate-list li {
  display: flex;
  justify-content: space-between;
  gap: 16px;
  padding: 6px 8px;
  border-radius: 6px;
  font-size: 0.84rem;
  color: var(--ink);
}
.fulfil__rate-list li.is-current {
  background: rgba(72, 137, 125, 0.12);
  font-weight: 700;
  color: #2f5c53;
}

/* --- the delivery quote ------------------------------------------------------------------ */

.quote {
  margin-top: 16px;
  padding: 14px 16px;
  border-radius: var(--radius);
  border: 1px solid;
  font-family: var(--sans);
}
/* Priced reads as settled, open as still-in-progress — the same green/amber
   split the notices use, so the two never look like different systems. */
.quote--priced { background: rgba(72, 137, 125, 0.1); border-color: rgba(72, 137, 125, 0.28); }
.quote--open { background: rgba(202, 88, 27, 0.09); border-color: rgba(202, 88, 27, 0.26); }

.quote__head {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 14px;
  flex-wrap: wrap;
}
.quote__address { font-weight: 700; font-size: 0.9rem; color: var(--forest); }
.quote__miles { font-size: 0.8rem; color: var(--muted); white-space: nowrap; }
.quote__matched {
  margin: 6px 0 0;
  font-size: 0.78rem;
  color: var(--muted);
}
.quote__fee {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 14px;
  margin-top: 10px;
  padding-top: 10px;
  border-top: 1px solid var(--line);
  font-size: 0.88rem;
  color: var(--ink);
}
.quote__fee strong { font-size: 1.15rem; color: var(--forest); }
.quote__detail {
  margin: 10px 0 0;
  font-size: 0.8rem;
  line-height: 1.45;
  color: var(--muted);
}

/* --- forms --------------------------------------------------------------------------- */

.field { margin-bottom: 18px; }
.field label {
  display: block;
  font-family: var(--sans);
  font-size: 0.82rem;
  font-weight: 700;
  letter-spacing: 0.02em;
  color: var(--forest);
  margin-bottom: 6px;
}
.field__help { font-family: var(--sans); font-size: 0.78rem; color: var(--muted); margin-top: 5px; }
.field__error { font-family: var(--sans); font-size: 0.8rem; color: var(--brick); margin-top: 5px; }

/* A field the server rejected, marked on the control itself and not only in
   the line of type beneath it. One red sentence under one of six boxes, on a
   page the customer has already filled in once and scrolled past, is not a
   thing anyone finds — the box goes red, its label goes with it, and the
   summary at the top of the form (see checkout.html) names and links it. */
.field.is-error label,
.field.is-error .field__legend { color: var(--brick); }
.field.is-error .field-input { border-color: var(--brick); }
.field.is-error .field-input:focus {
  border-color: var(--brick);
  box-shadow: 0 0 0 3px rgba(190, 30, 45, 0.16);
}
/* The date and time live in the calendar and the time rail whenever script is
   running; their inputs are hidden by then, so the two boxes carry the mark. */
.field.is-error .pcal,
.field.is-error .ptimes { border-color: rgba(190, 30, 45, 0.45); }

.form-errors { margin-bottom: 18px; }
.form-errors:focus-visible { outline: 2px solid var(--brick); outline-offset: 2px; }
.form-errors ul { margin: 8px 0 0; padding-left: 18px; }
.form-errors li + li { margin-top: 4px; }
.form-errors a { color: inherit; }
.field-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 0 16px; }
@media (max-width: 640px) { .field-grid { grid-template-columns: 1fr; } }

/* --- pickup date & time picker ------------------------------------------------
   Two matching boxes side by side: a month grid on the left, the day's pickup
   slots on the right. Both are enhancements over the native date input and
   <select>, which stay in the DOM and carry the value — see checkout.html.

   The two columns are one grid row, so the rail inherits the calendar's height
   and scrolls inside it. That only holds because the calendar's height is
   constant: fixed row heights, and always six week rows regardless of month. */

/* The UA's [hidden] rule loses to .field-grid/.pickup setting display, which
   would leave both interfaces on screen at once. */
#pickup-picker [hidden] { display: none; }

.field__legend {
  display: block;
  font-family: var(--sans);
  font-size: 0.78rem;
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--wine);
  margin-bottom: 10px;
}

/* Even halves, not calendar-plus-a-rail. A 1fr calendar next to a 196px rail
   made the column ~494px wide, which stretched each day cell to 63×44 — wide
   flat rectangles that stopped reading as a calendar at all. At half the panel
   the seven columns land near 40px, so the cells come out square.

   Sized against the form panel rather than the viewport, because the panel is
   not monotonic in it: .cart-layout keeps the 360px summary beside the form
   until 900px, so at a 901px viewport the picker gets 427px — less than the
   819px it gets at 899px, where the summary has dropped below. A viewport
   query that fits one end squeezes the other; at 901px two-up put the day
   cells at 20px wide. Stacked is the floor, two-up the enhancement. */
#pickup-picker { container-type: inline-size; }

.pickup {
  display: grid;
  grid-template-columns: 1fr;
  gap: 18px;
  align-items: stretch;
  /* Stacked in a wide panel the calendar would span it and go back to wide
     flat cells — the very thing two-up was narrowed to avoid. Capped, the
     seven columns stay near square and the box keeps the form's left edge. */
  max-width: 380px;
}

/* ~660px is where two boxes still leave the seven day columns near 37px. */
@container (min-width: 660px) {
  .pickup {
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
    max-width: none;
  }
}

/* One shared box treatment so the pair reads as two halves of one control
   rather than a calendar with a list parked beside it. */
.pcal,
.ptimes {
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: #fff;
  padding: 14px;
}

/* Both headers are the same object too: same height, same rule under them, so
   the calendar grid and the first row of times start on one line. */
.pcal__head,
.ptimes__head {
  display: flex;
  align-items: center;
  min-height: 32px;
  padding-bottom: 12px;
  margin-bottom: 12px;
  border-bottom: 1px solid var(--line);
}

.pcal__head { justify-content: space-between; gap: 8px; }
.pcal__month {
  flex: 1;
  text-align: center;
  font-family: var(--serif);
  font-size: 1.05rem;
  font-weight: 600;
  color: var(--ink);
}
.pcal__nav {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: none;
  width: 30px;
  height: 30px;
  padding: 0;
  border: 1px solid var(--line);
  border-radius: 999px;
  background: transparent;
  color: var(--forest);
  cursor: pointer;
  transition: border-color 0.15s var(--ease), color 0.15s var(--ease), background 0.15s var(--ease);
}
.pcal__nav svg { width: 15px; height: 15px; }
.pcal__nav:hover:not(:disabled) {
  border-color: var(--teal);
  color: var(--teal);
  background: rgba(72, 137, 125, 0.08);
}
.pcal__nav:disabled { color: var(--line); cursor: not-allowed; }

.pcal__dow,
.pcal__grid {
  display: grid;
  grid-template-columns: repeat(7, minmax(0, 1fr));
  gap: 5px;
}
.pcal__dow {
  font-family: var(--sans);
  font-size: 0.64rem;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--muted);
  text-align: center;
  margin-bottom: 6px;
}
/* Fixed row height × six rows = a calendar that never changes height. 40px is
   also what the column width works out to, which is what keeps cells square. */
.pcal__grid { grid-auto-rows: 40px; }

.pcal__day {
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  border: 1px solid transparent;
  border-radius: 9px;
  background: transparent;
  font-family: var(--sans);
  font-size: 0.9rem;
  font-weight: 600;
  color: var(--ink);
  cursor: pointer;
  transition: background 0.12s var(--ease), color 0.12s var(--ease), border-color 0.12s var(--ease);
}
.pcal__day:hover:not(:disabled):not(.is-selected) {
  background: rgba(72, 137, 125, 0.12);
  color: var(--teal);
}
.pcal__day:focus-visible,
.ptime:focus-visible {
  outline: none;
  border-color: var(--teal);
  box-shadow: 0 0 0 3px rgba(72, 137, 125, 0.22);
}
.pcal__day:disabled {
  color: rgba(25, 19, 26, 0.24);
  font-weight: 500;
  cursor: not-allowed;
}
.pcal__day.is-selected {
  background: var(--forest);
  border-color: var(--forest);
  color: var(--cream);
}

.ptimes-col { min-height: 0; }

.ptimes {
  display: flex;
  flex-direction: column;
  min-height: 0;
}

/* Side by side, the times box must take its height FROM the calendar, not
   contribute to it — a day with 17 slots would otherwise stretch the row and
   stop it scrolling. Absolute positioning removes it from row sizing, so the
   row is the calendar's height exactly, and inset:0 stretches it back over.
   Stacked there is no calendar beside it to measure against, so it stays in
   flow and the scroller is capped instead. */
@container (min-width: 660px) {
  .ptimes-col { position: relative; }
  .ptimes { position: absolute; inset: 0; }
}
.ptimes__head {
  flex: none;
  font-family: var(--sans);
  font-size: 0.72rem;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--wine);
}
.ptimes__scroll {
  position: relative;   /* offsetTop of a bubble is measured against this */
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  overscroll-behavior: contain;
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(88px, 1fr));
  align-content: start;
  gap: 8px;
  padding-right: 4px;
  max-height: 216px;
}
@container (min-width: 660px) {
  .ptimes__scroll { max-height: none; }
}
.ptimes__scroll::-webkit-scrollbar { width: 6px; }
.ptimes__scroll::-webkit-scrollbar-thumb { background: var(--line); border-radius: 999px; }
/* With no day chosen the box is otherwise a tall empty rectangle with one line
   of text stuck at the top — centre the prompt in the space it actually has.
   Only two-up: stacked, the box is short and centring gains nothing. */
@container (min-width: 660px) {
  .ptimes__scroll:has(.ptimes__empty) { align-content: center; }
}
.ptimes__empty {
  grid-column: 1 / -1;
  margin: 0;
  padding: 0 6px;
  font-family: var(--sans);
  font-size: 0.82rem;
  line-height: 1.5;
  text-align: center;
  text-wrap: balance;
  color: var(--muted);
}

.ptime {
  padding: 10px 6px;
  border: 1px solid var(--line);
  border-radius: 999px;
  background: #fff;
  font-family: var(--sans);
  font-size: 0.78rem;
  font-weight: 600;
  white-space: nowrap;
  color: var(--ink);
  cursor: pointer;
  transition: background 0.12s var(--ease), color 0.12s var(--ease), border-color 0.12s var(--ease);
}
.ptime:hover:not(:disabled):not(.is-selected) { border-color: var(--teal); color: var(--teal); }
.ptime:disabled {
  color: rgba(25, 19, 26, 0.26);
  border-color: rgba(29, 52, 44, 0.07);
  font-weight: 500;
  cursor: not-allowed;
}
.ptime.is-selected {
  background: var(--forest);
  border-color: var(--forest);
  color: var(--cream);
}

.honeypot { position: absolute; left: -9999px; width: 1px; height: 1px; overflow: hidden; }

/* visible to assistive tech only — used for the table's action-column header */
.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* --- notices ---------------------------------------------------------------------------- */

.notice {
  border-radius: var(--radius);
  padding: 13px 16px;
  font-family: var(--sans);
  font-size: 0.89rem;
  border: 1px solid;
  margin-bottom: 12px;
}
.notice--info { background: rgba(72, 137, 125, 0.1); border-color: rgba(72, 137, 125, 0.24); color: #2f5c53; }
.notice--success { background: rgba(72, 137, 125, 0.12); border-color: rgba(72, 137, 125, 0.3); color: #2f5c53; }
.notice--error { background: rgba(190, 30, 45, 0.08); border-color: rgba(190, 30, 45, 0.24); color: var(--brick-dark); }
.notice--warning { background: rgba(202, 88, 27, 0.1); border-color: rgba(202, 88, 27, 0.26); color: #8f3d13; }

.messages { position: fixed; top: 88px; right: 20px; z-index: 60; width: min(360px, calc(100vw - 40px)); }
.messages .notice { box-shadow: var(--shadow-lg); background: var(--surface); }

/* --- empty state ------------------------------------------------------------------------ */

.empty {
  text-align: center;
  padding: 64px 20px;
  background: var(--surface);
  border: 1px dashed var(--line);
  border-radius: var(--radius-lg);
  color: var(--ink);
}
.empty h3 { color: var(--forest); }
.empty p { color: var(--muted); margin: 0 0 20px; }

/* --- confirmation ------------------------------------------------------------------------ */

.confirm-card {
  background: var(--surface);
  border-radius: var(--radius-lg);
  padding: 34px;
  box-shadow: var(--shadow-lg);
  color: var(--ink);
}
.order-number {
  font-family: var(--sans);
  font-size: 1.6rem;
  font-weight: 700;
  letter-spacing: 0.04em;
  color: var(--brick);
}
.status-pill {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  font-family: var(--sans);
  font-size: 0.78rem;
  font-weight: 700;
  letter-spacing: 0.02em;
  padding: 6px 13px;
  border-radius: 999px;
  background: rgba(202, 88, 27, 0.12);
  color: #8f3d13;
}
/* Tones come from Order.status_summary() so the storefront and the model agree
   on what each status looks like. Bare .status-pill is the "wait" tone. */
.status-pill--go { background: rgba(72, 137, 125, 0.14); color: #2f5c53; }
.status-pill--done { background: rgba(29, 52, 44, 0.1); color: var(--forest); }
.status-pill--stop { background: rgba(190, 30, 45, 0.1); color: var(--brick-dark); }

/* --- order status timeline ----------------------------------------------------------------
   Three milestones on a vertical rail: request received → confirmed → ready.
   The connector below a step is only "travelled" (teal) when the step after it
   has also been reached — which is why .is-current, always the *last* completed
   step, keeps a grey line running down to whatever is still pending. */

.timeline { list-style: none; margin: 0; padding: 0; }
.timeline__step { position: relative; padding: 0 0 26px 34px; }
.timeline__step:last-child { padding-bottom: 0; }

/* the rail */
.timeline__step::after {
  content: "";
  position: absolute;
  left: 7px;
  top: 22px;
  bottom: 2px;
  width: 2px;
  background: var(--line);
}
.timeline__step:last-child::after { display: none; }
.timeline__step.is-done:not(.is-current)::after { background: var(--teal); }

.timeline__dot {
  position: absolute;
  left: 0;
  top: 3px;
  width: 16px;
  height: 16px;
  border-radius: 50%;
  background: var(--surface);
  border: 2px solid var(--line);
  box-sizing: border-box;
}
.timeline__step.is-done .timeline__dot {
  background: var(--teal);
  border-color: var(--teal);
}
/* tick, drawn as two borders on a rotated box */
.timeline__step.is-done .timeline__dot::after {
  content: "";
  position: absolute;
  left: 4px;
  top: 1px;
  width: 3px;
  height: 6px;
  border: solid var(--cream);
  border-width: 0 2px 2px 0;
  transform: rotate(45deg);
}
.timeline__step.is-current .timeline__dot {
  background: var(--brick);
  border-color: var(--brick);
  box-shadow: 0 0 0 4px rgba(190, 30, 45, 0.16);
}

.timeline__label {
  margin: 0;
  font-family: var(--sans);
  font-size: 0.95rem;
  font-weight: 700;
  color: var(--muted);
}
.timeline__step.is-done .timeline__label { color: var(--forest); }

.timeline__now {
  display: inline-block;
  margin-left: 8px;
  padding: 2px 8px;
  border-radius: 999px;
  background: rgba(190, 30, 45, 0.1);
  color: var(--brick-dark);
  font-size: 0.68rem;
  font-weight: 700;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  vertical-align: 1px;
}

.timeline__at {
  margin: 3px 0 0;
  font-family: var(--sans);
  font-size: 0.82rem;
  color: var(--muted);
}

/* A cancelled order still shows how far it got, but nothing is "in progress" —
   so no rail reads as travelled and the whole thing sits back behind the
   notice explaining what happened. */
.timeline--stopped { opacity: 0.72; }
.timeline--stopped .timeline__step::after { background: var(--line); }

/* Explainer on the lookup form's empty state — same rail, no state. */
.timeline--legend .timeline__label { color: var(--forest); }

/* --- order updates -----------------------------------------------------------------------
   One-way notes posted from the order admin, newest first. Styled apart from
   the timeline rail — this is a person talking, not a milestone. */

.order-updates { list-style: none; margin: 0 0 28px; padding: 0; display: flex; flex-direction: column; gap: 10px; }
.order-updates__item {
  background: var(--cream);
  border-left: 3px solid var(--teal);
  border-radius: 0 var(--radius) var(--radius) 0;
  padding: 12px 16px;
}
.order-updates__body {
  margin: 0;
  font-family: var(--sans);
  font-size: 0.92rem;
  color: var(--ink);
}
.order-updates__at {
  margin: 6px 0 0;
  font-family: var(--sans);
  font-size: 0.74rem;
  color: var(--muted);
}

/* --- footer ------------------------------------------------------------------------------ */

.site-footer {
  background: var(--forest-deep);
  color: rgba(255, 247, 229, 0.68);
  padding: 40px 0;
  font-family: var(--sans);
  font-size: 0.86rem;
  border-top: 1px solid var(--line-on-dark);
}
.site-footer a { color: var(--almond); }
.site-footer__inner { display: flex; justify-content: space-between; gap: 24px; flex-wrap: wrap; }

.htmx-request .htmx-indicator { opacity: 1; }
.htmx-indicator { opacity: 0; transition: opacity 0.2s ease; }

/* --- responsive -------------------------------------------------------------------------- */

@media (max-width: 820px) {
  .hero { padding: 26px 0 24px; }
  .item-grid { grid-template-columns: repeat(auto-fill, minmax(180px, 1fr)); gap: 13px; }
  .pcard__photo { height: 106px; }
  .pcard__niche { width: 54px; height: 74px; }
  .band { padding: 17px 20px; margin-bottom: 18px; }
  .band__count { margin-left: 0; }
}
@media (max-width: 520px) {
  .item-grid { grid-template-columns: 1fr 1fr; gap: 11px; }
  .pcard__body { padding: 11px 12px 12px; }
  .pcard__foot { flex-direction: column; align-items: stretch; gap: 8px; }
  .pcard__add { text-align: center; }
}

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