/* ==========================================================================
   Sunday Table
   Tokens and components. Tailwind handles layout utilities in the markup.
   Palette verified by render test: green carries the page, cream carries the
   cards, terracotta only ever sits on cream.
   ========================================================================== */

:root {
  /* Ground. The page is warm paper, the way Graza is. Cream is the surface,
     not a trim. */
  --ground:    #F7F2E8;  /* cream page, straight off the logo */
  --ground-2:  #F0E8D8;  /* a half step deeper, for alternating sections */
  --surface:   #EFE4D2;  /* cards sit deeper than the page, as Levain does */

  /* Ink. Green does the work of near black. It is type, not background. */
  --ink:       #143B24;
  --ink-muted: #5A6E5E;

  /* Blocks and accents */
  --green:      #12572F;  /* buttons, and one full bleed block */
  --green-deep: #0B3A20;  /* footer */
  --butter:     #F2C94C;  /* the loud block, and highlights */
  --terracotta: #9C3A2D;  /* brand accent. Prices, marks, small emphasis. */
  --terracotta-dark: #7E2C22;
  --terracotta-soft: #B4553F;  /* secondary button on hover */

  --r-card: 14px;
  --r-pill: 999px;
  --r-btn: 12px;   /* buttons and controls: a rounded box, not a pill */
  --line: 1.5px;
  --slip-h: 64px;
}

* { box-sizing: border-box; }

/* The hidden attribute has to beat a component's own display. Any class that
   sets display - .btn is inline-flex - otherwise wins over it, which left a
   dead "Send" button drawn on the empty order bar, linking to nothing. */
[hidden] { display: none !important; }

/* Read aloud, never drawn. Used where a control is shown as a glyph and the
   sentence around it still has to make sense without the picture. */
.sr-only {
  position: absolute; width: 1px; height: 1px;
  margin: -1px; padding: 0; border: 0;
  clip-path: inset(50%); overflow: hidden; white-space: nowrap;
}

/* 68px of header plus air. Every in-page link - the nav, the menu buttons, the
   category panels - lands under a sticky header without it. */
html { -webkit-text-size-adjust: 100%; scroll-padding-top: 84px; }

/* Every in-page jump - the nav, the menu buttons, the category panels - glides
   rather than cuts. Guarded, because for anyone who has asked their system for
   less motion a long smooth scroll is the worst kind. */
@media (prefers-reduced-motion: no-preference) {
  html { scroll-behavior: smooth; }
}

body {
  margin: 0;
  /* Green, not cream: the footer is fixed behind the page, so anything visible
     past the end of the document, including an overscroll bounce, should read
     as footer rather than as a white gap. The page itself carries its own
     cream background on .page-body. */
  background: var(--green);
  color: var(--ink);
  font-family: Archivo, system-ui, -apple-system, sans-serif;
  font-size: 17px;
  line-height: 1.55;
  /* No blanket bottom padding: only pages with the sticky order bar need room
     for it, and order.js sets that to the bar's real height. */
}

h1, h2, h3, .display {
  font-family: 'Bricolage Grotesque', Archivo, system-ui, sans-serif;
  font-weight: 700;
  line-height: 0.98;
  letter-spacing: -0.02em;
  margin: 0;
}

p { margin: 0; }
img { display: block; max-width: 100%; }
a { color: inherit; }

.wrap { width: 100%; max-width: 1400px; margin-inline: auto; padding-inline: 16px; }
@media (min-width: 1024px) { .wrap { padding-inline: 40px; } }

/* --- Surfaces ----------------------------------------------------------- */
.sec-paper   { background: var(--ground);     color: var(--ink); }
.sec-paper-2 { background: var(--ground-2);   color: var(--ink); }
.sec-butter  { background: var(--butter);     color: var(--ink); }
.sec-green   { background: var(--green);      color: var(--ground); }
.sec-deep    { background: var(--green-deep); color: var(--ground); }

/* --- Type --------------------------------------------------------------- */
.t-display { font-size: clamp(2.5rem, 7.6vw, 4.6rem); }
.t-h2      { font-size: clamp(2rem, 7vw, 3.25rem); }
.t-h3      { font-size: clamp(1.3rem, 4vw, 1.6rem); }
.t-body    { font-size: 1.0625rem; }
.t-small   { font-size: 0.9375rem; }
.t-label {
  font-size: 0.75rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  font-weight: 600;
}
.measure { max-width: 58ch; }
.muted { color: var(--ink-muted); }
.muted-on-green { color: #CFE0D4; }

/* --- Controls ----------------------------------------------------------- */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  min-height: 48px;
  padding: 0 24px;
  border-radius: var(--r-btn);
  border: none;
  font: inherit;
  font-weight: 700;
  font-size: 1rem;
  white-space: nowrap;
  cursor: pointer;
  text-decoration: none;
  transition: background-color 120ms ease, transform 120ms ease;
}
/* Buttons are printed objects: a solid block sitting on a hard shadow with no
   blur. Hover presses it halfway down, active presses it flat. That reads as
   something physical, and unlike an outline it works identically on a flat
   colour and over a photograph.

   The fill lives in ::before because a pseudo element with a negative z-index
   paints above its own element's background, so anything drawn behind the
   button would otherwise tint the fill. */
.btn {
  position: relative;
  z-index: 0;
  background: transparent;
  box-shadow: 4px 4px 0 var(--btn-shade, transparent);
  transition: transform 140ms cubic-bezier(.2, .8, .3, 1),
              box-shadow 140ms cubic-bezier(.2, .8, .3, 1),
              background-color 140ms ease,
              color 140ms ease;
}
.btn::before {
  content: '';
  position: absolute;
  inset: 0;
  z-index: -1;
  border-radius: inherit;
  background: var(--btn-fill, transparent);
  transition: background-color 140ms ease;
}
.btn:hover  { transform: translate(2px, 2px); box-shadow: 2px 2px 0 var(--btn-shade, transparent); }
.btn:active { transform: translate(4px, 4px); box-shadow: 0 0 0 var(--btn-shade, transparent); }

@media (prefers-reduced-motion: reduce) {
  .btn { transition: none; }
  .btn:hover, .btn:active { transform: none; }
}

/* --- Button variants ----------------------------------------------------
   Each carries its own fill and its own shadow, a darker tone of that fill.
   ------------------------------------------------------------------------ */

/* On the paper page, green is the primary action. */
.btn-primary { --btn-fill: var(--green); --btn-shade: var(--green-deep); color: var(--ground); }

/* Inside the green blocks and over the hero photograph, butter is primary. */
.btn-onGreen { --btn-fill: var(--butter); --btn-shade: #C39A2E; color: var(--ink); }
.btn-onCream { --btn-fill: var(--green);  --btn-shade: var(--green-deep); color: var(--ground); }

/* WhatsApp is where an order actually goes, so the button that sends it wears
   WhatsApp's green rather than the brand's. It is a handoff signal, not a
   brand moment - and it has to read as a different kind of thing from every
   other green on the page. */
.btn-wa { --btn-fill: #1FA855; --btn-shade: #14733A; color: #fff; gap: 8px; }

/* The secondary action is a stroke at rest and fills terracotta on hover, so
   the two never compete for the same treatment. */
.btn-ghost {
  --btn-fill: transparent;
  color: var(--ink);
  box-shadow: inset 0 0 0 2px rgba(20, 59, 36, 0.45), 4px 4px 0 rgba(20, 59, 36, 0.2);
}
.btn-ghost:hover {
  --btn-fill: var(--terracotta);
  color: var(--ground);
  box-shadow: inset 0 0 0 2px var(--terracotta), 2px 2px 0 rgba(156, 58, 45, 0.35);
}
.btn-ghost:active {
  box-shadow: inset 0 0 0 2px var(--terracotta), 0 0 0 rgba(156, 58, 45, 0.35);
}

/* Over a photograph the stroke is cream and the shadow is the photo's own dark. */
.btn-ghost.on-dark {
  color: var(--ground);
  box-shadow: inset 0 0 0 2px rgba(247, 242, 232, 0.7), 4px 4px 0 rgba(26, 16, 8, 0.35);
}
.btn-ghost.on-dark:hover {
  --btn-fill: var(--terracotta);
  color: var(--ground);
  box-shadow: inset 0 0 0 2px rgba(247, 242, 232, 0.9), 2px 2px 0 rgba(26, 16, 8, 0.4);
}
.btn-ghost.on-dark:active {
  box-shadow: inset 0 0 0 2px rgba(247, 242, 232, 0.9), 0 0 0 rgba(26, 16, 8, 0.4);
}

:where(a, button, summary, input, select, textarea):focus-visible {
  outline: 3px solid var(--terracotta);
  outline-offset: 2px;
  border-radius: 4px;
}

/* --- Product card ------------------------------------------------------- */
.card {
  background: var(--surface);
  box-shadow: inset 0 0 0 var(--line) rgba(20, 59, 36, 0.14);
  color: var(--ink);
  border-radius: var(--r-card);
  overflow: hidden;
  display: flex;
  flex-direction: column;
}
.card-media { aspect-ratio: 4 / 5; width: 100%; object-fit: cover; }
.card-body { padding: 18px; display: flex; flex-direction: column; gap: 8px; flex: 1; }
.card-price { color: var(--terracotta); font-weight: 700; font-size: 1.25rem; }

/* --- Quantity stepper --------------------------------------------------- */
.stepper {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  background: var(--ground);
  border-radius: var(--r-btn);
  box-shadow: inset 0 0 0 var(--line) rgba(20, 59, 36, 0.22);
}
.stepper button {
  width: 44px; height: 44px;
  border: none; background: transparent; cursor: pointer;
  font-size: 1.35rem; line-height: 1; color: var(--ink);
  border-radius: var(--r-btn);
}
.stepper output { min-width: 28px; text-align: center; font-weight: 700; }

/* --- Order slip --------------------------------------------------------- */
.slip {
  position: fixed;
  left: 0; right: 0; bottom: 0;
  z-index: 40;
  background: var(--surface);
  color: var(--ink);
  border-top: var(--line) solid rgba(20, 59, 36, 0.28);
  max-height: 72vh;
  display: flex;
  flex-direction: column;
}
.slip-head {
  display: flex; align-items: center; justify-content: space-between;
  gap: 12px; padding: 10px 16px; min-height: var(--slip-h);
}

.slip-line {
  display: grid;
  grid-template-columns: auto 1fr auto;
  gap: 12px; align-items: center;
  padding: 10px 0;
  border-top: var(--line) solid rgba(20, 59, 36, 0.12);
}
.slip-foot { padding: 12px 16px calc(12px + env(safe-area-inset-bottom)); }
.slip[hidden] { display: none; }

/* --- Sections ----------------------------------------------------------- */
.section { padding-block: clamp(56px, 11vw, 112px); }
.section-tight { padding-block: clamp(32px, 6vw, 56px); }

.rule { height: var(--line); background: currentColor; opacity: 0.18; border: 0; margin: 0; }

/* --- Horizontal scroll rail (build a box) ------------------------------- */
.rail {
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: 78%;
  gap: 14px;
  overflow-x: auto;
  scroll-snap-type: x mandatory;
  padding-bottom: 8px;
  scrollbar-width: thin;
}
.rail > * { scroll-snap-align: start; }
@media (min-width: 700px)  { .rail { grid-auto-columns: 40%; } }
@media (min-width: 1024px) { .rail { grid-auto-columns: 26%; } }

/* --- Steps stack -------------------------------------------------------- */
.step { display: grid; gap: 6px; padding-block: 24px; border-top: var(--line) solid rgba(20, 59, 36, 0.18); }
@media (min-width: 768px) {
  .step { grid-template-columns: 220px 1fr; gap: 40px; align-items: baseline; }
}

/* --- Accordion ---------------------------------------------------------- */
details.qa { border-top: var(--line) solid rgba(20, 59, 36, 0.18); }
details.qa summary {
  cursor: pointer; list-style: none;
  padding: 18px 0; font-weight: 700;
  display: flex; justify-content: space-between; gap: 16px; align-items: center;
}
details.qa summary::-webkit-details-marker { display: none; }
details.qa summary::after { content: '+'; font-size: 1.5rem; line-height: 1; color: var(--terracotta); }
details.qa[open] summary::after { content: '\2212'; }
details.qa .qa-body { padding-bottom: 18px; }

/* --- Drop status band --------------------------------------------------- */
/* The live marker on the drop status line. It pulses because the state it
   reports is live, not because motion looks nice. */
.asterisk { flex: none; width: 30px; height: 30px; color: var(--terracotta); transform-origin: 50% 50%; }
.asterisk { animation: asterisk-pulse 2.6s ease-in-out infinite; }
@keyframes asterisk-pulse {
  0%, 100% { transform: scale(1) rotate(0deg);      opacity: 1; }
  50%      { transform: scale(1.18) rotate(30deg);  opacity: .72; }
}
@media (prefers-reduced-motion: reduce) { .asterisk { animation: none; } }

/* --- Motion ------------------------------------------------------------- */
/* Motion is reserved for order feedback. A scroll reveal on a product grid
   communicates nothing, so there is none. */
.slip-line { animation: slipIn 180ms ease both; }
@keyframes slipIn { from { opacity: 0; transform: translateY(6px); } to { opacity: 1; transform: none; } }
/* The line just added or changed glows for a moment. */
/* The glow is its own layer, wider than the line and clear of the dividers,
   so it never sits tight against the text or the rules above and below. */
.slip-line { position: relative; isolation: isolate; }
.slip-line.is-fresh::before {
  content: ""; position: absolute; inset: 4px -10px; z-index: -1;
  border-radius: 10px; background: rgba(242, 201, 76, 0.28);
  opacity: 0; animation: slipFresh 1.2s ease;
}
@keyframes slipFresh { from { opacity: 1; } to { opacity: 0; } }
@media (prefers-reduced-motion: reduce) { .slip-line.is-fresh::before { animation: none; } }
/* "Box of 6", small under the product name. */
.slip-note { display: block; font-size: 0.75rem; font-weight: 500; opacity: 0.72; margin-top: 1px; }

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.001ms !important;
    transition-duration: 0.001ms !important;
  }
  .reveal { opacity: 1; transform: none; }
}

/* A short terracotta rule under headings on cream grounds. It is the brand
   colour doing structural work rather than sitting in one price label. */
.sec-paper .t-h2::after, .sec-paper-2 .t-h2::after, .shop-head .t-h2::after {
  content: '';
  display: block;
  width: 62px;
  height: 4px;
  border-radius: 4px;
  background: var(--terracotta);
  margin-top: 18px;
}

/* --- Oversized wordmark moment ------------------------------------------ */
.bigmark {
  font-family: 'Bricolage Grotesque', Archivo, sans-serif;
  font-weight: 800;
  font-size: clamp(3.4rem, 15.5vw, 13rem);
  line-height: 0.82;
  letter-spacing: -0.035em;
  color: var(--green);
  text-align: center;
  margin: 0;
}

/* --- Illustration slots -------------------------------------------------
   Every drawing on this page is a flat single colour hand drawn mark in the
   Graza manner. None exist yet. Each slot below is labelled with the prompt
   that produces it, listed in IMAGE-PROMPTS.md.
   ------------------------------------------------------------------------ */
/* Most of the drawings arrived with their cream paper baked in rather than as
   transparency, so they only disappear on a section that is exactly that
   colour. Every section carrying a drawing uses --ground for that reason.
   Multiply is not the answer here: it darkens even when the colours match. */
/* darken, not multiply: where the drawing's paper and the section are within a
   value or two of each other it keeps the lighter of the two untouched, so no
   box shows, while the dark green line still prints. Multiply darkened even
   matching colours, which is what left a visible tile behind. */
.draw {
  display: block;
  width: 100%;
  height: auto;
  mix-blend-mode: darken;
}
/* draw-8 is the inverse of the rest: cream lines on a green ground, sitting on
   a green section. darken would swap the cream for green and erase it, so the
   green sections take the opposite blend. */
.sec-green .draw { mix-blend-mode: lighten; }
.draw-sq { max-width: 220px; }

/* --- Ordering steps ----------------------------------------------------- */
.steps { display: grid; gap: clamp(20px, 3vw, 34px); }
@media (min-width: 640px)  { .steps { grid-template-columns: repeat(2, 1fr); } }
@media (min-width: 1000px) { .steps { grid-template-columns: repeat(4, 1fr); } }

/* A fixed art row so all four titles sit on the same line no matter what
   proportions each drawing came back at. */
.step-item { display: grid; grid-template-rows: 160px auto 1fr; gap: 6px; align-content: start; }
.step-item .draw {
  height: 100%;
  width: auto;
  max-width: 190px;
  object-fit: contain;
  object-position: left bottom;
}
.step-item h3 { font-size: clamp(1.25rem, 2.4vw, 1.5rem); }
.step-item p { color: var(--ink-muted); font-size: 0.9375rem; max-width: 24ch; }

/* --- Fact block --------------------------------------------------------- */
.fact {
  border: var(--line) solid var(--terracotta);
  color: var(--terracotta);
  border-radius: 999px;
  padding: 10px 22px;
  display: inline-block;
  font-family: 'Bricolage Grotesque', Archivo, sans-serif;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  font-size: 0.85rem;
}

/* --- Questions, Levain style bordered rows ------------------------------ */
.qa-card { background: var(--surface); border-radius: var(--r-card); padding: 4px 20px; }
.qa-card details.qa:first-child { border-top: 0; }

/* --- Site header --------------------------------------------------------
   Solid from the first pixel. A transparent sticky header over a light page
   leaves the links floating on whatever scrolls under them.
   ------------------------------------------------------------------------ */

/* Pages that open on a full bleed hero let the image run under the header until
   the reader scrolls. Everything in it turns cream so it stays legible on the
   photograph, and the scrim behind the hero text keeps the contrast honest. */

/* The sentinel is a 1px marker at the very top of the document. When it leaves
   the viewport the header has started overlapping content, so it turns solid. */

/* Mobile menu, built on details so it needs no javascript. */

/* --- Background hero ----------------------------------------------------
   Full bleed photograph with the words sitting in the lower left. The scrim
   is a gradient behind the text only, so the food is never globally dimmed.
   ------------------------------------------------------------------------ */
/* The hero fills the first screen exactly. */

/* Taller than the hero so it can drift upward on scroll without exposing the
   bottom edge. With no javascript it simply sits still, cropped as before. */

/* The copy is capped, never the container: the container has to keep the same
   left edge as the header and every section below it. */
/* Wide enough for each sentence of the headline to hold one line. The cap was
   46ch of body text, which is far narrower than the display size needs, so
   both sentences were breaking mid phrase. The subtext keeps its own measure. */

/* --- Field block --------------------------------------------------------
   A photograph of the product repeated edge to edge, with the line drawing of
   that same product laid over it. The drawing is one of ours, recoloured to
   cream in the browser, so it stays sharp and stays editable.
   ------------------------------------------------------------------------ */

/* Same arithmetic as .bleed-img: flush to the top, 124% tall, all 24% of the
   overhang below against a drift of 22% of the frame. */

/* --- Big image blocks ---------------------------------------------------- */
/* The frame crops, the picture inside is taller than it. Without that
   headroom the parallax lifts the image and leaves a strip of background
   showing along the bottom of the section. */

/* The image sits flush with the top of the frame, so every bit of its extra
   height is headroom below, exactly where the upward drift needs it. Centring
   it meant half the overhang sat above the frame doing nothing, and the height
   had to go to 160% to compensate, which crops the picture 1.6x and reads as a
   zoom. At 124% with no top offset the drift is 22% of the frame and the crop
   is barely noticeable. */

/* ========================================================================
   SHOP PAGE
   ======================================================================== */

/* --- Page head ---------------------------------------------------------- */

/* --- Filter pills ------------------------------------------------------- */

/* --- Product card ------------------------------------------------------- */

/* Round add control, Levain style, sitting on the image. */

/* Quantity control replaces the round button once something is in the order. */

/* --- Sticky order bar --------------------------------------------------- */

/* --- Shop notes accordion ----------------------------------------------- */

/* --- Site header --------------------------------------------------------
   Solid from the first pixel. A transparent sticky header over a light page
   leaves the links floating on whatever scrolls under them.
   ------------------------------------------------------------------------ */
.site-head {
  position: sticky; top: 0; z-index: 50;
  background: var(--ground);
  border-bottom: var(--line) solid rgba(20, 59, 36, 0.16);
  transition: background-color 200ms ease, border-color 200ms ease, color 200ms ease;
}

/* Pages that open on a full bleed hero let the image run under the header until
   the reader scrolls. Everything in it turns cream so it stays legible on the
   photograph, and the scrim behind the hero text keeps the contrast honest. */
.site-head.over-hero:not(.is-stuck) {
  background: transparent;
  border-bottom-color: transparent;
  color: var(--ground);
}
.site-head.over-hero:not(.is-stuck) .nav-desktop a,
.site-head.over-hero:not(.is-stuck) .brand-name { color: var(--ground); }
.site-head.over-hero:not(.is-stuck) .btn-primary { --btn-fill: var(--butter); --btn-shade: #C39A2E; color: var(--ink); }
.site-head.over-hero:not(.is-stuck) .nav-mobile > summary { box-shadow: inset 0 0 0 var(--line) rgba(247,242,232,.65); }
.site-head.over-hero:not(.is-stuck) .bars i { background: var(--ground); }

/* The sentinel is a 1px marker at the very top of the document. When it leaves
   the viewport the header has started overlapping content, so it turns solid. */
.head-sentinel { position: absolute; top: 0; left: 0; width: 1px; height: 1px; }
.site-head-in { display: flex; align-items: center; justify-content: space-between; gap: 16px; height: 68px; }
.brand { display: flex; align-items: center; gap: 10px; text-decoration: none; }
.brand img { width: 44px; height: 44px; }
.brand-name { font-size: 1.15rem; white-space: nowrap; }
@media (max-width: 460px) { .brand-name { display: none; } }
.head-right { display: flex; align-items: center; gap: 10px; }
.btn-sm { min-height: 42px; padding: 0 18px; font-size: 0.9375rem; }

.nav-desktop { display: none; }
.nav-desktop ul { display: flex; gap: 26px; list-style: none; margin: 0; padding: 0; }
.nav-desktop a { text-decoration: none; font-weight: 500; }
@media (min-width: 900px) { .nav-desktop { display: block; } }

/* Mobile menu, built on details so it needs no javascript. */
.nav-mobile { position: relative; }
@media (min-width: 900px) { .nav-mobile { display: none; } }
.nav-mobile > summary {
  list-style: none; cursor: pointer;
  width: 44px; height: 44px;
  display: grid; place-items: center;
  border-radius: var(--r-btn);
  box-shadow: inset 0 0 0 var(--line) rgba(20, 59, 36, 0.35);
}
.nav-mobile > summary::-webkit-details-marker { display: none; }
.bars { display: grid; gap: 4px; }
.bars i { display: block; width: 18px; height: 2px; background: var(--ink); border-radius: 2px; }
.nav-mobile[open] > summary .bars i:first-child { transform: translateY(6px) rotate(45deg); }
.nav-mobile[open] > summary .bars i:nth-child(2) { opacity: 0; }
.nav-mobile[open] > summary .bars i:last-child { transform: translateY(-6px) rotate(-45deg); }
.bars i { transition: transform 140ms ease, opacity 140ms ease; }
.nav-mobile > ul {
  position: absolute; right: 0; top: calc(100% + 10px);
  background: var(--surface);
  border-radius: var(--r-card);
  box-shadow: inset 0 0 0 var(--line) rgba(20, 59, 36, 0.2);
  list-style: none; margin: 0; padding: 8px;
  display: grid; gap: 2px; min-width: 200px;
}
.nav-mobile > ul a { display: block; padding: 12px 14px; text-decoration: none; font-weight: 600; border-radius: 10px; }

/* --- Background hero ----------------------------------------------------
   Full bleed photograph with the words sitting in the lower left. The scrim
   is a gradient behind the text only, so the food is never globally dimmed.
   ------------------------------------------------------------------------ */
/* The hero fills the first screen exactly. */
.hero-block {
  min-height: 100svh;
  display: flex;
  flex-direction: column;
  margin-top: -68px;      /* the header sits over the photograph */
}
.hero {
  position: relative;
  flex: 1 1 auto;
  min-height: 0;
  padding-top: 68px;
  display: flex;
  align-items: flex-end;
  overflow: hidden;
}
/* Taller than the hero so it can drift upward on scroll without exposing the
   bottom edge. With no javascript it simply sits still, cropped as before. */
.hero-media {
  position: absolute;
  top: 0; left: 0;
  width: 100%;
  height: 122%;
  object-fit: cover;
  object-position: center 42%;
  z-index: 0;
  will-change: transform;
}
.hero::after {
  content: ''; position: absolute; inset: 0; z-index: 1;
  background: linear-gradient(to top, rgba(26,16,8,.86) 0%, rgba(26,16,8,.5) 30%, rgba(26,16,8,0) 58%);
}
.hero-body { position: relative; z-index: 2; width: 100%; padding-block: clamp(30px, 6vw, 64px); color: var(--ground); }
.hero-body .t-display { color: var(--ground); }
.hero-sub { color: rgba(247,242,232,.9); }
/* The copy is capped, never the container: the container has to keep the same
   left edge as the header and every section below it. */
/* Wide enough for each sentence of the headline to hold one line. The cap was
   46ch of body text, which is far narrower than the display size needs, so
   both sentences were breaking mid phrase. The subtext keeps its own measure. */
.hero-copy { max-width: min(66vw, 820px); }
.hero-copy .hero-sub { max-width: 52ch; }

/* Phone: the copy takes the screen. 66vw is a third of a phone given away for
   nothing - the headline broke to three lines, the paragraph to five, and the
   whole block sat in a narrow strip down the left while two thirds of the
   photograph carried no words at all. */
@media (max-width: 899px) {
  .hero-copy { max-width: none; }
  .hero-copy .hero-sub { max-width: none; }
}

/* The two hero buttons, one row. Together they are 363px of a 358px screen,
   so on a phone they give back a little side padding rather than stacking -
   four pixels is not worth a second row. */
.hero-cta { display: flex; flex-wrap: wrap; gap: 12px; margin-top: 26px; }
@media (max-width: 560px) {
  .hero-cta { flex-wrap: nowrap; gap: 10px; }
  .hero-cta .btn { padding: 0 16px; font-size: 0.9375rem; }
}

/* The phone's cat, above the headline. Hidden everywhere else: past this width
   the corner cat has room again and two would be one too many. */
.hero-cat-top { display: none; }
@media (max-width: 560px) {
  .hero-cat-top {
    display: block;
    width: min(70vw, 290px); height: auto;
    margin: 0 0 4px -10px;  /* the drawing carries its own air on the left */
    pointer-events: none; user-select: none;
  }
}
@media (min-width: 900px) {
  .hero::after {
    background: linear-gradient(to top, rgba(26,16,8,.78) 0%, rgba(26,16,8,.3) 38%, rgba(26,16,8,0) 66%),
                linear-gradient(to right, rgba(26,16,8,.6) 0%, rgba(26,16,8,.12) 40%, rgba(26,16,8,0) 62%);
  }
}

/* --- Field block --------------------------------------------------------
   A photograph of the product repeated edge to edge, with the line drawing of
   that same product laid over it. The drawing is one of ours, recoloured to
   cream in the browser, so it stays sharp and stays editable.
   ------------------------------------------------------------------------ */
.field {
  position: relative;
  overflow: hidden;
  min-height: clamp(420px, 62vw, 760px);
  display: grid;
  align-items: end;
}
/* Same arithmetic as .bleed-img: flush to the top, 142% tall, all 42% of the
   overhang below against a drift of 36% of the frame. */
.field-photo {
  position: absolute;
  top: 0; left: 0;
  width: 100%; height: 142%;
  object-fit: cover;
  z-index: 0;
}
/* Kept to the upper right, which is the calmest part of the photograph. */

.field-copy {
  position: relative;
  z-index: 2;
  padding: clamp(22px, 4vw, 44px) 0;
  color: var(--ground);
}
.field-copy .t-h2 { color: var(--ground); }
.field-copy .t-h2::after { background: var(--butter); }
/* Here the mark sits under the words, clear of the letters, not behind them. */
.field-copy .mark::after { bottom: -0.04em; height: 0.12em; border-radius: 99px; }
.field-scrim {
  position: absolute; inset: 0; z-index: 1;
  background: linear-gradient(to top, rgba(26,16,8,.72) 0%, rgba(26,16,8,.18) 44%, rgba(26,16,8,0) 72%);
}

/* --- Hero cat -----------------------------------------------------------
   A separate layer over the photograph, not part of it, so it can be moved,
   resized or removed without regenerating the hero. Sits in the lower right,
   clear of the headline and buttons on the left. Cream on a dark photo.
   ------------------------------------------------------------------------ */
.hero-cat {
  position: absolute;
  right: 4%;
  bottom: 0;
  /* The artwork is roughly 3:1, cat on the right with the yarn trailing left,
     so it is sized on width and sits low and wide rather than tall. */
  width: clamp(240px, 38vw, 520px);
  height: auto;
  z-index: 2;
  pointer-events: none;
  user-select: none;
}
@media (max-width: 899px) {
  .hero-cat { right: 2%; bottom: 0; width: clamp(200px, 50vw, 320px); }
}
/* Below this the hero copy takes the full width, so the cat would collide. */
@media (max-width: 560px) { .hero-cat { display: none; } }

/* --- Big image blocks ---------------------------------------------------- */
/* The frame crops, the picture inside is taller than it. Without that
   headroom the parallax lifts the image and leaves a strip of background
   showing along the bottom of the section. */
.bleed-frame {
  position: relative;
  height: clamp(320px, 62vw, 760px);
  overflow: hidden;
}
/* The image sits flush with the top of the frame, so every bit of its extra
   height is headroom below, exactly where the upward drift needs it. Centring
   it meant half the overhang sat above the frame doing nothing, and the height
   had to go to 160% to compensate, which crops the picture 1.6x and reads as a
   zoom. With no top offset all of the overhang works: at 142% the drift is 36%
   of the frame, which is plainly visible, and the crop is still mild. */
.bleed-img {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 142%;
  object-fit: cover;
  display: block;
  will-change: transform;
}
.strip-img { width: 100%; aspect-ratio: 3/4; object-fit: cover; border-radius: var(--r-card); }

/* ========================================================================
   SHOP PAGE
   ======================================================================== */

/* --- Page head ---------------------------------------------------------- */
.shop-head { padding-top: clamp(26px, 4vw, 52px); padding-bottom: clamp(20px, 3vw, 32px); }
.shop-head .t-h2 { max-width: 16ch; }

/* --- Filter pills ------------------------------------------------------- */
.filters { display: flex; flex-wrap: wrap; gap: 8px; }
.filter {
  display: inline-flex; align-items: center; min-height: 42px;
  padding: 0 18px; border-radius: var(--r-btn);
  text-decoration: none; font-weight: 600; font-size: 0.9375rem;
  box-shadow: inset 0 0 0 var(--line) rgba(20, 59, 36, 0.35);
  transition: background-color 120ms ease, color 120ms ease;
}
.filter:hover { background: rgba(20, 59, 36, 0.06); }
.filter[aria-current="true"] {
  background: var(--green); color: var(--ground);
  /* The selected pill wears the buttons' hard shadow, so it reads as the one
     that is on. */
  box-shadow: 4px 4px 0 var(--green-deep);
}

/* --- The menu and the order, one row -------------------------------------
   Below 1024 the order is a sheet across the bottom of the screen and this is
   a single column. From 1024 the order becomes a column of its own, sitting
   beside the menu and sticking as the menu scrolls past, so it can never cover
   the thing it is summarising.
   ------------------------------------------------------------------------- */
.shop-body { padding-bottom: clamp(48px, 7vw, 90px); }
.shop-layout { display: grid; gap: 40px; align-items: start; }
@media (min-width: 1024px) {
  /* Wider than the mobile gap: the cookie row's next card peeks into it. */
  .shop-layout { --shop-gap: 72px; gap: var(--shop-gap); grid-template-columns: minmax(0, 1fr) 356px; }
}

.shop-state {
  display: flex; flex-wrap: wrap; align-items: center;
  gap: 10px 18px; margin-top: 14px;
}
.filters { margin-top: 22px; }

/* The space between groups is a grid gap, not a sibling margin. A filtered
   view hides groups with display:none, and `.menu-group + .menu-group` still
   matches across a hidden element - which left the first visible group wearing
   a 56px margin meant to separate it from something that is not there. A gap
   only counts the boxes that actually generate one. */
.shop-menu { display: grid; align-content: start; gap: clamp(34px, 5vw, 56px); }
.menu-group-head { margin-bottom: 16px; }

/* --- Product grid --------------------------------------------------------
   Two up on a phone. The single column it replaces gave one cookie the whole
   screen, so choosing between flavours meant scrolling rather than looking.
   ------------------------------------------------------------------------- */
.p-grid { display: grid; gap: 12px; grid-template-columns: repeat(2, minmax(0, 1fr)); }
@media (min-width: 560px)  { .p-grid { gap: 16px; } }
@media (min-width: 1024px) { .p-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 18px; } }

/* Sideways row for a group of more than three, desktop only: cards stay the
   width of a three across row, the rest scroll in. Native scroll snapping,
   so a trackpad swipe works as well as the arrows. */
/* A grid child grows to fit its content unless told otherwise, and a sideways
   row's content is wider than the column. Without this the row pushes the menu
   out under the order column. */
.shop-menu, .menu-group { min-width: 0; }
.menu-group-top { display: flex; align-items: center; justify-content: space-between; gap: 12px; margin-bottom: 16px; }
.menu-group-top .menu-group-head { margin-bottom: 0; }
.slide-nav { display: none; gap: 12px; position: relative; }
.slide-nav button {
  width: 52px; height: 52px; border-radius: var(--r-pill); padding: 0;
  display: grid; place-items: center; cursor: pointer;
  border: 2px solid var(--green); background: var(--green); color: var(--ground);
  transition: transform 140ms ease, background-color 140ms ease;
}
.slide-nav button:hover:not(:disabled) { transform: translateY(-2px); }
/* Nothing further that way: a faint outline, like the reference. */
.slide-nav button:disabled {
  background: transparent; color: rgba(20, 59, 36, 0.3);
  border-color: rgba(20, 59, 36, 0.12); cursor: default;
}

/* "More cookies here", floating over the next arrow until the row is first
   moved. ui.js adds .is-seen to the nav on the first scroll. */
.slide-tip {
  /* Above the next arrow, pointing down at it. Right aligned with the nav, so
     the pointer (20px in) lands over the middle of the 52px button. */
  position: absolute; bottom: calc(100% + 12px); right: 0; z-index: 2;
  white-space: nowrap; pointer-events: none;
  background: var(--ink); color: var(--ground);
  font-size: 0.8125rem; font-weight: 600; padding: 6px 12px; border-radius: 10px;
  animation: tipFloat 1.8s ease-in-out infinite;
  transition: opacity 200ms ease;
}
.slide-tip::after {
  content: ""; position: absolute; top: 100%; right: 20px;
  border: 6px solid transparent; border-top-color: var(--ink);
}
.slide-nav.is-seen .slide-tip { opacity: 0; animation: none; }
@keyframes tipFloat { 0%, 100% { transform: translateY(0); } 50% { transform: translateY(-5px); } }
@media (prefers-reduced-motion: reduce) {
  .slide-tip { animation: none; }
  .slide-nav button { transition: none; }
  .slide-nav button:hover:not(:disabled) { transform: none; }
}
@media (min-width: 1024px) {
  .slide-nav { display: flex; }
  .p-grid.is-slide {
    grid-template-columns: none;
    grid-auto-flow: column;
    /* Cards exactly as wide as a three across row, so they line up with the
       loaf cards below. The row runs past the menu column by --shop-gap, into the
       gap before the order column, and the next card peeks into that gap. The
       matching padding-right below takes the same amount back out of the box
       the percentage is measured against, so 100% here is the
       menu column's width - the same width the loaf row divides by three. */
    grid-auto-columns: calc((100% - 36px) / 3);
    margin-right: calc(-1 * var(--shop-gap));
    overflow-x: auto; scroll-snap-type: x mandatory; scroll-behavior: smooth;
    scrollbar-width: none;
    padding: 4px 0 18px; margin-top: -4px; margin-bottom: -18px;   /* room for the hover lift and shadow */
    /* Room after the last card, so scrolled to the end the row stops on the
       loaf columns and keeps the gap to the order column. */
    padding-right: var(--shop-gap);
  }
  .p-grid.is-slide::-webkit-scrollbar { display: none; }
  .p-grid.is-slide > .p-card { scroll-snap-align: start; }
  /* The card cut by the row's edge fades out rather than stopping on a hard
     line. Each side fades only while there is more to scroll that way; ui.js
     sets .at-start and .at-end. */
  .p-grid.is-slide {
    /* Right edge: solid up to the end of the third card (the gap starts
       there), then the peeking card fades out and is gone 16px before the
       order column, so a strip of cream stays clear. */
    --fade-l: 0px; --fade-r: var(--shop-gap); --clear-r: 16px;
    -webkit-mask-image: linear-gradient(to right, transparent 0, #000 var(--fade-l),
      #000 calc(100% - var(--fade-r)), rgba(0, 0, 0, 0.55) calc(100% - var(--fade-r) + 24px),
      transparent calc(100% - var(--clear-r)));
            mask-image: linear-gradient(to right, transparent 0, #000 var(--fade-l),
      #000 calc(100% - var(--fade-r)), rgba(0, 0, 0, 0.55) calc(100% - var(--fade-r) + 24px),
      transparent calc(100% - var(--clear-r)));
  }
  .p-grid.is-slide:not(.at-start) { --fade-l: 48px; }
  .p-grid.is-slide.at-end { --fade-r: 0px; --clear-r: 0px; }
}
@media (prefers-reduced-motion: reduce) { .p-grid.is-slide { scroll-behavior: auto; } }

/* --- Product card --------------------------------------------------------
   Levain's arrangement: the card is a tinted block, the photograph is inset on
   it rather than bleeding to its edges, the name carries the card, and the
   control sits under everything, centred, in the same place on every card so
   the eye stops hunting for it.
   ------------------------------------------------------------------------- */
.p-card {
  background: var(--surface);
  border-radius: 18px;
  padding: 10px 10px 14px;
  display: flex; flex-direction: column;
  box-shadow: inset 0 0 0 var(--line) rgba(20, 59, 36, 0.14);
  transition: box-shadow 140ms ease;
}
.p-card.has-qty { box-shadow: inset 0 0 0 2.5px var(--green); }

/* The card lifts as one object. Only where there is a real pointer: on a
   touch screen a hover state has no way to end and sticks to whatever was
   tapped last. */
@media (hover: hover) and (pointer: fine) {
  .p-card {
    transition: transform 180ms cubic-bezier(0.2, 0.8, 0.3, 1),
                box-shadow 180ms ease;
  }
  .p-card:hover {
    transform: translateY(-2px);
    box-shadow: inset 0 0 0 var(--line) rgba(20, 59, 36, 0.14),
                0 6px 14px rgba(20, 59, 36, 0.09);
  }
  .p-card.has-qty:hover {
    box-shadow: inset 0 0 0 2.5px var(--green),
                0 6px 14px rgba(20, 59, 36, 0.09);
  }
}
@media (prefers-reduced-motion: reduce) {
  .p-card, .p-card:hover { transition: none; transform: none; }
}

/* The photograph is a button now, so it gives back everything a button brings
   with it and keeps only the pointer. */
.p-media {
  position: relative;
  display: block; width: 100%;
  padding: 0; border: none; background: none;
  font: inherit; color: inherit; text-align: inherit;
  cursor: zoom-in;
  -webkit-tap-highlight-color: transparent;
}

.p-media img {
  width: 100%;
  /* height:auto is load bearing. The img carries width/height attributes so the
     browser can reserve the box before the file arrives, and that attribute
     height would otherwise beat aspect-ratio and leave the photo full length. */
  height: auto;
  aspect-ratio: 1 / 1;
  object-fit: cover;
  /* The source frames are 9:16 portrait with the bake sitting low, so a centred
     square crop lands on baking paper. Bias the crop down onto the food. */
  object-position: 50% 68%;
  border-radius: 12px;
  display: block;
  background: rgba(20, 59, 36, 0.05);
}

.p-body { padding: 14px 8px 0; display: flex; flex-direction: column; gap: 6px; flex: 1; }
.p-name {
  /* The name is an <h4>, and an <h4> arrives with 1.33em of margin above and
     below it from the browser. That was 24px of nothing between the name and
     the description, and another 24px under it - the card's spacing was the
     user agent's, not ours. */
  margin: 0;
  font-family: 'Bricolage Grotesque', Archivo, sans-serif;
  font-weight: 700;
  font-size: clamp(1.05rem, 2.4vw, 1.375rem);
  line-height: 1.05;
  text-wrap: balance;
}
.p-blurb { margin: 0; }

/* Price and control. On a phone card there is not room for both on one line,
   so they stack - but the control keeps its full height either way, so the
   card does not change size when the plus becomes a stepper. */
.p-foot { margin-top: auto; padding: 12px 8px 0; display: grid; gap: 10px; }
@media (min-width: 560px) {
  .p-foot { display: flex; align-items: center; justify-content: space-between; gap: 10px; }
}
/* The round plus is the desktop card's control. On a phone it grows a word. */
.p-add-label { display: none; }
.p-price { color: var(--terracotta); font-weight: 700; font-size: 1.05rem; min-width: 0; white-space: nowrap; }
/* Only the number stays on one line; the unit ("per loaf, 14 x 7cm") may wrap. */
.p-price .p-unit { white-space: normal; }
/* The unit sits under the number rather than trailing it: "Rp 95.000" and
   "whole loaf" are two different facts, and on a narrow card the trailing
   version wrapped anyway, at the wrong place. */
.p-unit {
  display: block;
  color: var(--ink-muted); font-weight: 400; font-size: 0.8125rem;
  line-height: 1.25; margin-top: 1px;
}
/* Box nudge: "Add 4 more for a box of 6". Laid OVER the card, never in its
   flow, so adding a cookie does not change the card's height. Wide cards: a
   banner along the bottom of the photograph (a square the card's width, less
   its 10px padding). Phones: see the max-width block below. */
.p-card { position: relative; }
.p-nudge {
  position: absolute; left: 10px; right: 10px; top: 10px; aspect-ratio: 1;
  margin: 0; padding: 8px;
  display: flex; align-items: flex-end;
  pointer-events: none;   /* the photo underneath still opens the gallery */
}
.p-nudge[hidden] { display: none; }
.p-nudge-in {
  pointer-events: auto;
  width: 100%; padding: 7px 10px; border-radius: 10px; line-height: 1.35;
  background: var(--terracotta); color: var(--ground);
  box-shadow: 0 4px 14px rgba(126, 44, 34, 0.35);
}
.p-nudge-link {
  font: inherit; color: inherit; font-weight: 700; background: none; border: 0; padding: 0;
  text-decoration: underline; text-underline-offset: 3px; cursor: pointer;
  display: inline-block; transition: transform 140ms ease;
}
.p-nudge-link:hover { transform: translateY(-2px); }
@media (prefers-reduced-motion: reduce) { .p-nudge-link:hover { transform: none; } }
@media (max-width: 559px) {
  /* The photo is only 104px here and the card is short, so nothing fits over
     it. The nudge takes the description's place instead: both share one grid
     cell, and the description only turns invisible, so the cell - and the
     card - keeps its height. The box price is dropped for room. */
  .p-body { display: grid; grid-template-areas: "name" "blurb"; align-content: start; }
  .p-name { grid-area: name; }
  .p-blurb, .p-nudge { grid-area: blurb; align-self: start; }
  .p-nudge { position: static; aspect-ratio: auto; padding: 0; display: block; }
  .p-nudge-in { display: block; padding: 5px 8px; box-shadow: 0 2px 8px rgba(126, 44, 34, 0.3); }
  .p-nudge-price { display: none; }
  /* Hidden but still holding its space, so the card does not shrink. */
  .p-card.has-nudge .p-blurb { visibility: hidden; }
}


/* --- The add control, on the price row ----------------------------------
   The box is as wide as the stepper even while it only holds the plus. That
   reserved width is what stops the card reflowing the moment you press it.
   ------------------------------------------------------------------------- */
.p-act {
  display: flex; align-items: center; justify-content: flex-end;
  flex: 0 0 auto; width: 112px; min-height: 44px;
}

.p-add {
  width: 44px; height: 44px; border-radius: 50%;
  border: none; cursor: pointer;
  background: var(--ground); color: var(--ink);
  font-size: 1.5rem; line-height: 1;
  display: grid; place-items: center;
  box-shadow: 0 2px 0 rgba(20, 59, 36, 0.28);
  transition: background-color 120ms ease, transform 120ms ease;
}
.p-add:hover { background: var(--butter); }
.p-add:active { transform: translateY(1px); box-shadow: 0 1px 0 rgba(20, 59, 36, 0.28); }

.p-qty {
  display: none; align-items: center;
  background: var(--ground); border-radius: var(--r-pill);
  box-shadow: 0 2px 0 rgba(20, 59, 36, 0.28);
}
.p-card.has-qty .p-qty { display: inline-flex; }
.p-card.has-qty .p-add { display: none; }
.p-qty button {
  width: 42px; height: 44px; border: none; background: none; cursor: pointer;
  font-size: 1.25rem; line-height: 1; color: var(--ink);
  border-radius: var(--r-pill);
}
.p-qty button:hover { background: rgba(20, 59, 36, 0.08); }
.p-qty output { min-width: 28px; text-align: center; font-weight: 700; font-variant-numeric: tabular-nums; }

/* --- Phone: the card lies down ------------------------------------------
   Two cards across a 390px screen leaves each one about 180px wide. At that
   width the name wraps mid phrase, the description has to be hidden outright,
   and the price and the button stack - three compromises to fit a shape that
   was never going to fit. One card per row, laid out sideways, is the same
   information with none of them: the name gets a full line, the description
   comes back, and the photograph still earns its place at 104px.
   ------------------------------------------------------------------------- */
@media (max-width: 559px) {
  .p-grid { grid-template-columns: 1fr; gap: 10px; }

  .p-card {
    display: grid;
    grid-template-columns: minmax(0, 1fr) 104px;
    /* No row gap. The price row sits under a text block that is shorter than
       the photograph beside it, so it was already waiting on the image's
       height - a gap on top of that was slack on top of slack. */
    column-gap: 14px;
    row-gap: 0;
    padding: 13px;
    border-radius: 16px;
  }
  /* Set by column rather than by source order: the photograph is written
     first in the markup because that is the order it reads in on a desktop
     card, and nothing about this layout is worth moving markup for. */
  /* A little air above the name. On the text column only, not the card: the
     photograph keeps the card's own top edge, and the row is as tall as the
     photograph either way, so this costs no height. */
  .p-body  { grid-column: 1; grid-row: 1; padding: 7px 0 0; gap: 5px; }
  .p-media { grid-column: 2; grid-row: 1; align-self: start; }
  .p-media img { width: 104px; height: 104px; border-radius: 12px; }
  /* Full width, under both columns, so the button lands on the card's right
     edge under the photograph. Floating at the text column's edge it sat
     halfway across the card, lined up with nothing. */
  .p-foot { grid-column: 1 / -1; grid-row: 2; padding: 0; margin-top: -26px; }

  .p-name { font-size: 1.125rem; line-height: 1.1; text-wrap: pretty; }
  .p-blurb { line-height: 1.4; }

  /* The unit rides beside the price rather than under it. Stacked, it made the
     price block two lines tall next to a one line button and the row read as
     lopsided; on a full width card there is room for both on one line. */
  .p-unit { display: inline; margin-left: 7px; }

  /* Price and control share the last line again. The card is full width now,
     so there is room for both even once the plus has become a stepper. */
  .p-foot {
    display: flex; align-items: center; justify-content: space-between;
    gap: 10px; padding-top: 10px;
  }
  /* Lifted onto the bottom of the photograph rather than parked in the gap
     below it. The gap was dead space and the button was floating in it; over
     the image it reads as attached to the bake it adds. */
  /* As wide as the photograph above it and centred inside that, so the button
     sits on the picture's middle rather than hanging off its right edge. */
  .p-act {
    width: 104px; justify-content: center; min-height: 40px;
    /* The row itself now sits 26px higher than it used to, so the button needs
       no offset of its own any more - it already lands on the bottom edge of
       the photograph. Kept relative only for the z-index, which is what puts
       it over the picture rather than under it. */
    position: relative; z-index: 2;
  }

  /* A button with a word on it, not a pale disc. At this size the disc read as
     decoration - it sat on the card's own colour, carried no label, and gave
     the thumb a 40px target where the row had room for a real one. Filled
     green, it matches the stepper it turns into. */
  .p-add {
    width: auto; height: 40px;
    padding: 0 16px 0 13px;
    gap: 5px;
    border-radius: var(--r-btn);
    background: var(--green); color: var(--ground);
    font-family: 'Bricolage Grotesque', Archivo, sans-serif;
    font-weight: 700; font-size: 0.9375rem;
    display: inline-flex; align-items: center;
    /* A cream rim, because it now sits on a photograph: without it the dark
       green button loses its edge against a dark bake. */
    box-shadow: 0 0 0 3px var(--surface), 0 2px 0 3px var(--green-deep);
  }
  .p-add:hover { background: var(--green-deep); }
  .p-add-sign { font-size: 1.15rem; line-height: 1; }
  .p-add-label { display: inline; }

  .p-qty button { width: 38px; height: 40px; }
  .p-qty output { min-width: 24px; }
}

/* The filters are one line that scrolls, not a block that wraps. Wrapped, a
   third pill drops to a row of its own and the heading above it stops looking
   like it belongs to anything. Bled to the screen edges so the last pill is
   visibly cut off - which is what says there is more to the right. */
@media (max-width: 559px) {
  .filters {
    flex-wrap: nowrap;
    overflow-x: auto;
    overscroll-behavior-inline: contain;
    scrollbar-width: none;
    margin-inline: -16px;
    padding-inline: 16px;
  }
  .filters::-webkit-scrollbar { display: none; }
  .filter { flex: 0 0 auto; }
}

.p-card.is-out .p-media img { opacity: 0.42; }
.p-out-tag {
  position: absolute; left: 12px; top: 12px;
  background: var(--ground); color: var(--ink);
  border-radius: var(--r-btn); padding: 6px 14px;
  font-size: 0.75rem; letter-spacing: 0.08em; text-transform: uppercase; font-weight: 700;
}

/* --- The order -----------------------------------------------------------
   One block of markup, two behaviours. Phone: a sheet fixed to the bottom,
   capped, with the lines scrolling inside it so ten cookies cannot push the
   total and the send button off the screen. Desktop: a column that sticks.
   ------------------------------------------------------------------------- */
.slip {
  position: fixed; left: 0; right: 0; bottom: 0; z-index: 60;
  background: var(--green); color: var(--ground);
  border-top: none;
  display: flex; flex-direction: column;
  max-height: 68svh;
  box-shadow: 0 -8px 30px rgba(20, 59, 36, 0.18);
}

.slip-head {
  display: flex; align-items: center; justify-content: space-between;
  gap: 12px; padding: 12px 16px; min-height: var(--slip-h);
  flex: 0 0 auto;
}
.slip-head .js-slip-toggle {
  display: flex; align-items: baseline; gap: 8px;
  background: none; border: none; font: inherit; cursor: pointer;
  padding: 0; text-align: left; color: var(--ground);
  min-height: 44px;
}
.slip-title { font-weight: 700; }
#slipSummary { font-weight: 700; opacity: .82; }

/* Phone: a floating pill, not a bar welded to the bottom edge.
   It hovers clear of the screen edges so the page is visibly still there
   underneath it, and - the part that matters most - it does not exist at all
   until something is in the order. An empty order bar sitting over the hero is
   a checkout interrupting a shop window. */
@media (max-width: 1023px) {
  /* The wrapper carries nothing of its own now: it positions two separate
     cards and holds the gap between them. The pill is the dark one, the list
     is the light one, and they are read as two objects because they are two
     objects. */
  .slip {
    left: 12px; right: 12px;
    bottom: calc(12px + env(safe-area-inset-bottom));
    background: transparent;
    border-radius: 0;
    overflow: visible;
    box-shadow: none;
    max-height: none;
    gap: 10px;
  }
  /* Nothing picked, nothing drawn. */
  .slip.is-empty { display: none; }

  /* The head is the pill, and the pill does not move. Reversing the column
     means the sheet opens upward out of it rather than pushing it up the
     screen - the thumb stays where it was. */
  .slip { flex-direction: column-reverse; }

  .slip-head {
    background: var(--green-deep);
    color: var(--ground);
    /* Same radius as the button sitting inside it, so the pill and its send
       button read as one object rather than two shapes. */
    border-radius: var(--r-btn);
    /* The site's own shadow: hard, offset, no blur. Every button on the page
       is a printed object sitting on one, and these two cards are the largest
       objects on the screen - a soft drop shadow made them the only things
       lit differently from everything else. */
    box-shadow: 4px 4px 0 rgba(11, 58, 32, 0.32);
    padding: 9px 13px 9px 13px; gap: 10px; min-height: 0;
  }

  /* The list is its own card, same green as the pill. It was tried in cream to
     match the reference and it was wrong twice over: it washed out against the
     page, and everything inside it - the steppers, the rules between rows - is
     drawn in cream for a dark panel, so the minus and plus disappeared into
     the card. Two green cards with a gap between them read as two cards. */
  #slipBody {
    position: relative;
    background: var(--green-deep);
    border-radius: var(--r-btn);
    box-shadow: 4px 4px 0 rgba(11, 58, 32, 0.32);
    max-height: min(58svh, 500px);
  }
  .slip-head .js-slip-toggle {
    min-width: 0; flex: 1 1 auto;
    align-items: center; gap: 10px;
  }
  .slip-title { font-size: 0.9375rem; line-height: 1.25; }
  #slipSummary {
    font-weight: 500; font-size: 0.8125rem; opacity: .74;
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  }

  /* The send button stays put whether the sheet is open or shut. It is the
     one thing in the pill that never moves, so the thumb learns where it is.

     It keeps the site's hard offset shadow. That shadow is 4px of ink below
     the button and none above it, so a button centred to the pixel reads as
     sitting high - the fix is to centre what the eye sees, not what the box
     model says. Two pixels up puts 7px of clear pill above and 7px below the
     shadow. The pill's right padding carries the other 4px. */
  #slipSend {
    flex: 0 0 auto;
    padding: 0 15px; min-height: 40px; font-size: 0.875rem;
    position: relative; top: -2px;
  }

  /* Air above the first row: enough to clear the close button in the corner,
     which doubles as the balance the top of the card was missing.
     Prefixed with .slip on purpose - the unprefixed base rules sit further
     down this file and beat these on source order alone. */
  .slip .slip-lines { padding: 34px 14px 0; }
  .slip .slip-foot { padding: 12px 14px 14px; }
  .slip .slip-total { margin-bottom: 0; }
  /* The wide send button is the dialog's job now; inside the pill it would be
     the same action twice, one above the other. */
  .slip-send-wide { display: none; }
}
/* Points up while closed, because up is where the sheet comes from; flips
   down once it is open and the press would put it away. */
.slip-chev { flex: 0 0 auto; transition: transform 160ms ease; align-self: center; }
#slipToggle[aria-expanded="true"] .slip-chev { transform: rotate(180deg); }

/* The count, and the two lines beside it. The count is the only part of the
   bar readable in a glance, so it is a filled disc rather than text. */
/* The count and the send button swapped colours. Green on the disc reads as
   a tally; butter on the button makes it the one bright thing in the pill,
   which is what it should be - it is the action, the count is a fact. */
.slip-count {
  flex: 0 0 auto;
  display: grid; place-items: center;
  width: 30px; height: 30px; border-radius: 50%;
  background: #1FA855; color: #fff;
  font-weight: 700; font-size: 0.875rem; font-variant-numeric: tabular-nums;
}
.slip-labels { display: grid; gap: 1px; min-width: 0; text-align: left; }

/* The scroll region. min-height:0 is what actually lets it shrink inside the
   flex column; without it the lines push the total and the button off. */
#slipBody { display: flex; flex-direction: column; min-height: 0; overflow: hidden; }
#slipBody[hidden] { display: none; }

.slip-lines {
  flex: 1 1 auto; overflow-y: auto; min-height: 0; padding: 0 16px;
  overscroll-behavior: contain;
  scrollbar-width: thin;
  scrollbar-color: rgba(247, 242, 232, 0.34) transparent;
}
.slip-lines::-webkit-scrollbar { width: 6px; }
.slip-lines::-webkit-scrollbar-track { background: transparent; }
.slip-lines::-webkit-scrollbar-thumb {
  background: rgba(247, 242, 232, 0.34); border-radius: var(--r-pill);
}
.slip-lines::-webkit-scrollbar-thumb:hover { background: rgba(247, 242, 232, 0.5); }
.slip-line {
  display: grid; grid-template-columns: 1fr auto auto;
  gap: 8px 12px; align-items: center; padding: 10px 0;
  font-size: 0.9375rem;
  border-top: var(--line) solid rgba(247, 242, 232, 0.22);
}
.slip-line:first-child { border-top: none; }
.slip-name { min-width: 0; font-weight: 600; }
.slip-line .slip-cost {
  font-weight: 700; font-variant-numeric: tabular-nums;
  min-width: 8ch; text-align: right;
}

/* The card's stepper, restyled for a dark panel: a hairline outline instead of
   a filled tile, so five of them stacked do not read as five buttons. */
.slip-stepper {
  background: transparent;
  box-shadow: inset 0 0 0 var(--line) rgba(247, 242, 232, 0.32);
}
.slip-stepper button { width: 36px; height: 36px; font-size: 1.15rem; color: var(--ground); }
.slip-stepper output { min-width: 22px; color: var(--ground); font-variant-numeric: tabular-nums; }
/* Empty state. Left aligned, on the same axis as the title above it and the
   order rows that replace it - a centred block under a left aligned title
   reads as two things that were laid out separately. */
/* The cross in the corner of the order card. Hidden on a desktop, where the
   card is a column that is always open and has nothing to close. */
.slip-close {
  position: absolute; top: 9px; right: 9px; z-index: 3;
  width: 30px; height: 30px; border-radius: 50%;
  display: grid; place-items: center;
  border: none; cursor: pointer;
  background: rgba(247, 242, 232, 0.13);
  color: var(--ground);
  transition: background-color 120ms ease;
}
.slip-close:hover { background: rgba(247, 242, 232, 0.24); }

/* Emptying the order in one press. A link, not a button: it is a way out of
   the list, not one of its controls. */
.slip-total-fig { display: flex; align-items: baseline; gap: 10px; }
.slip-clear {
  display: inline-block;
  background: none; border: none; padding: 0;
  font: inherit; font-size: 0.8125rem;
  color: var(--ground); opacity: 0.7;
  text-decoration: underline; text-underline-offset: 3px;
  cursor: pointer;
  transition: opacity 120ms ease;
}
.slip-clear:hover { opacity: 1; }

.slip-empty { display: none; padding: 2px 16px 18px; }
.slip.is-empty .slip-empty { display: block; }
.slip-empty-art { display: block; margin: 0 0 10px; opacity: .62; filter: url(#crayon); }
.slip-empty-head { font-weight: 700; font-size: 1rem; margin-bottom: 3px; }
.slip-empty-note { opacity: .72; margin: 0; max-width: 30ch; line-height: 1.5; }

/* The chip is an inline-block, so its full height counts towards the line box
   and pushed the second line away from the first. The negative block margins
   pull that contribution back under the text's own line height; the chip still
   draws at 20px, it just stops setting the leading. */
.slip-empty-plus {
  display: inline-grid; place-items: center;
  width: 18px; height: 18px; border-radius: 50%;
  background: var(--ground); color: var(--ink);
  /* The plus is drawn rather than typed. As a text glyph it inherited the
     font's descender space and sat high in the circle; an SVG has no metrics
     to inherit, so its centre is the circle's centre. */
  line-height: 0;
  margin-block: -6px;
  /* middle, not a hand-picked offset: it lines the chip's centre up with the
     text's optical centre (baseline plus half an x-height). A pixel nudge was
     leaving it sitting 6px low. */
  vertical-align: middle;
}

/* The head carries the title on its own while there is nothing to count, and
   gives up the 44px touch target it only needs when it is a real toggle. */
.slip.is-empty #slipSummary { display: none; }
.slip.is-empty .slip-head .js-slip-toggle { min-height: 0; }

.slip-foot { flex: 0 0 auto; padding: 14px 16px calc(16px + env(safe-area-inset-bottom)); }
.slip.is-empty .slip-foot { display: none; }
.slip-total {
  display: flex; justify-content: space-between; align-items: baseline;
  margin-bottom: 10px; padding-top: 12px;
  border-top: var(--line) solid rgba(247, 242, 232, 0.22);
}
.slip-total .t-small { font-weight: 700; opacity: .85; }
.slip-send-wide { width: 100%; }
.slip .btn-primary { --btn-fill: var(--butter); --btn-shade: #C39A2E; color: var(--ink); }
/* Butter here, not WhatsApp green: on a dark pill the brand's yellow carries
   further, and the WhatsApp mark on the button already says where it goes.
   The confirm dialog's send button keeps the green - it sits on cream. */
#slipSend { --btn-fill: var(--butter); --btn-shade: #C39A2E; color: var(--ink); }
.slip[hidden] { display: none; }

/* Desktop: the second column. Sticks below the 68px header, never taller than
   the screen, and always open, because there is nothing for it to cover. */
@media (min-width: 1024px) {
  .slip {
    position: sticky;
    top: 84px;
    left: auto; right: auto; bottom: auto;
    z-index: 1;
    border-radius: 18px;
    overflow: hidden;
    max-height: calc(100svh - 104px);
    box-shadow: 0 10px 30px rgba(20, 59, 36, 0.14);
  }
  .slip-head { padding: 18px 20px 14px; min-height: 0; }
  .slip-head .js-slip-toggle { cursor: default; flex-direction: column; align-items: flex-start; gap: 2px; }
  .slip-title { font-size: 1.125rem; }
  .slip-chev { display: none; }
  .slip-count { display: none; }
  .slip-close { display: none; }
  #slipSend { display: none; }
  /* Capped so the total and the send button stay on the first screen even
     before the column has pinned. Past this the lines scroll inside. */
  .slip-lines { padding: 0 20px; max-height: 30svh; }
  .slip-empty { padding: 2px 20px 20px; }
  .slip-foot { padding: 14px 20px 20px; }

  /* Once the menu has scrolled out of the window the column has nothing left
     to sit beside, so the order lifts off the page and follows instead -
     centred on the bottom edge, the same place the phone keeps it. It goes
     back into the column the moment the menu is on screen again, because
     there it would be covering the thing it is summarising. */
  .slip {
    transition: opacity 220ms ease, transform 220ms cubic-bezier(0.2, 0.8, 0.3, 1);
  }
  /* Off the menu, it is the phone's pill: a shut bar carrying the count, the
     total and the send button, which opens upward when pressed. Expanded, it
     was a card the size of a dialog parked over whatever you had scrolled to.
     The column beside the menu is untouched - that one stays open, because
     there it has a job and nothing to cover. */
  .slip.is-floating {
    position: fixed;
    top: auto;
    left: 0; right: 0;
    bottom: 24px;
    width: 420px;
    margin-inline: auto;        /* centred without spending the transform */
    z-index: 60;
    max-height: none;
    background: transparent;
    border-radius: 0;
    overflow: visible;
    box-shadow: none;
    flex-direction: column-reverse;
    gap: 10px;
  }
  .slip.is-floating .slip-head {
    background: var(--green-deep);
    border-radius: var(--r-btn);
    box-shadow: 4px 4px 0 rgba(11, 58, 32, 0.32);
    padding: 9px 13px;
    gap: 10px;
    min-height: 0;
  }
  .slip.is-floating .slip-head .js-slip-toggle {
    flex-direction: row;
    align-items: center;
    gap: 10px;
    cursor: pointer;
    flex: 1 1 auto;
    min-width: 0;
  }
  .slip.is-floating .slip-count { display: grid; }
  .slip.is-floating .slip-chev { display: block; }
  .slip.is-floating .slip-close { display: grid; }
  .slip.is-floating #slipSend {
    display: inline-flex;
    flex: 0 0 auto;
    padding: 0 15px; min-height: 40px; font-size: 0.875rem;
    position: relative; top: -2px;
  }
  .slip.is-floating .slip-title { font-size: 0.9375rem; line-height: 1.25; }
  .slip.is-floating #slipSummary {
    font-weight: 500; font-size: 0.8125rem; opacity: 0.74;
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  }
  .slip.is-floating #slipBody {
    position: relative;
    background: var(--green-deep);
    border-radius: var(--r-btn);
    box-shadow: 4px 4px 0 rgba(11, 58, 32, 0.32);
    max-height: min(60svh, 520px);
  }
  .slip.is-floating .slip-lines { padding: 34px 20px 0; max-height: none; }
  .slip.is-floating .slip-foot { padding: 12px 20px 16px; }
  .slip.is-floating .slip-send-wide { display: none; }
  /* Both ends of the fade. @starting-style is no use here: it only fires when
     an element is first rendered, and this one is already on the page as the
     column - it is a class that changes, not an element that appears. So the
     two ends are their own classes, added for one frame on the way in and for
     the length of the fade on the way out. */
  .slip.is-floating.is-entering,
  .slip.is-floating.is-leaving {
    opacity: 0;
    transform: translateY(12px);
  }
  .slip.is-floating.is-leaving { pointer-events: none; }
}
@media (min-width: 1024px) and (prefers-reduced-motion: reduce) {
  .slip { transition: none; }
}

/* The drop bar's shortcut to the menu. Terracotta, because it is the one
   thing in that bar you can press. */
.drop-link {
  font-weight: 700;
  color: var(--terracotta);
  text-decoration: none;
  border-bottom: 2px solid rgba(156, 58, 45, 0.35);
  padding-bottom: 2px;
  transition: border-color 140ms ease, color 140ms ease;
}
.drop-link:hover { color: var(--terracotta-dark); border-bottom-color: var(--terracotta-dark); }

/* The line under the menu heading that says there is no checkout here. */
.shop-lead { margin: 10px 0 0; max-width: 46ch; }

/* --- The photographs ------------------------------------------------------
   A full screen dialog holding one horizontal row of images. The row is the
   slider: scroll-snap does the paging, the browser does the momentum, and a
   swipe on a phone and a trackpad drag on a desktop both land on a whole
   photograph. No carousel library, no transform maths, no autoplay.
   -------------------------------------------------------------------------- */
.lightbox {
  border: none;
  padding: 0;
  width: 100%;
  max-width: 100%;
  height: 100%;
  max-height: 100%;
  margin: 0;
  background: rgba(11, 58, 32, 0.96);
  color: var(--ground);
  overflow: hidden;
  /* Same discrete transition trick as the confirm dialog: fade the whole
     thing, and let allow-discrete keep it on screen long enough to fade out. */
  opacity: 0;
  transition:
    opacity 200ms ease,
    display 200ms allow-discrete,
    overlay 200ms allow-discrete;
}
.lightbox[open] {
  display: grid;
  /* minmax(0, 1fr), not 1fr. A plain 1fr row refuses to shrink below its
     content, so a 4032px tall photograph pushed the row past the bottom of the
     screen and the picture was cut off at both ends. */
  grid-template-rows: minmax(0, 1fr) auto auto;
  opacity: 1;
}
@starting-style { .lightbox[open] { opacity: 0; } }
.lightbox::backdrop { background: rgba(11, 58, 32, 0.6); }

.lightbox-track {
  display: flex;
  overflow-x: auto;
  overflow-y: hidden;
  scroll-snap-type: x mandatory;
  scrollbar-width: none;
  overscroll-behavior-x: contain;
  min-height: 0;
}
.lightbox-track::-webkit-scrollbar { display: none; }

.lightbox-slide {
  flex: 0 0 100%;
  scroll-snap-align: center;
  scroll-snap-stop: always;
  display: grid;
  /* An explicit, definite row. Without one the single implicit row is sized by
     its content, the photograph's `height: 100%` has nothing definite to
     resolve against, and it silently falls back to the image's own 4032px. */
  grid-template-rows: minmax(0, 1fr);
  place-items: center;
  padding: clamp(46px, 9vw, 72px) clamp(14px, 4vw, 40px) 8px;
  min-width: 0;
}
/* The slide is the box, the photograph fits inside it. Sizing the image by its
   own intrinsic height and hoping max-height caught it is what let a portrait
   shot run off the screen; filling the slide and letting object-fit do the
   letterboxing cannot overflow by construction. */
.lightbox-slide img {
  width: 100%;
  height: 100%;
  object-fit: contain;
  border-radius: var(--r-card);
  display: block;
}

.lightbox-name {
  margin: 0;
  padding: 4px 20px 0;
  text-align: center;
  font-weight: 700;
  font-size: 0.9375rem;
}

.lightbox-dots {
  display: flex; justify-content: center; gap: 8px;
  padding: 12px 20px calc(18px + env(safe-area-inset-bottom));
}
.lightbox-dot {
  width: 8px; height: 8px; padding: 0;
  border: none; border-radius: 50%;
  background: rgba(247, 242, 232, 0.32);
  cursor: pointer;
  transition: background-color 140ms ease, transform 140ms ease;
}
.lightbox-dot[aria-current="true"] {
  background: var(--butter);
  transform: scale(1.25);
}

/* Forward and back. They sit over the photograph's edges rather than beside
   it: at this size the picture is the screen, and a column of its own for an
   arrow would be a column taken off the picture. */
.lightbox-nav {
  position: absolute; top: 50%; z-index: 2;
  transform: translateY(-50%);
  width: 46px; height: 46px; border-radius: 50%;
  display: grid; place-items: center;
  border: none; cursor: pointer;
  background: rgba(247, 242, 232, 0.14); color: var(--ground);
  transition: background-color 120ms ease;
}
.lightbox-nav:hover { background: rgba(247, 242, 232, 0.26); }
.lightbox-prev { left: clamp(8px, 2vw, 28px); }
.lightbox-next { right: clamp(8px, 2vw, 28px); }
/* One photograph, nothing to step through. */
.lightbox.is-single .lightbox-nav,
.lightbox.is-single .lightbox-dots { display: none; }
/* With no dots under it the name was sitting on the bottom edge. It gets the
   room the dots had; the photograph's row gives it up. */
.lightbox.is-single .lightbox-name { padding: 12px 20px calc(28px + env(safe-area-inset-bottom)); }

.lightbox-close {
  position: absolute; top: 12px; right: 12px; z-index: 2;
  width: 40px; height: 40px; border-radius: 50%;
  display: grid; place-items: center;
  border: none; cursor: pointer;
  background: rgba(247, 242, 232, 0.14); color: var(--ground);
  transition: background-color 120ms ease;
}
.lightbox-close:hover { background: rgba(247, 242, 232, 0.26); }

@media (prefers-reduced-motion: reduce) {
  .lightbox { transition: none; }
}

/* --- Confirm order -------------------------------------------------------
   A native <dialog>, so the backdrop, the Escape key and the focus trap are
   the browser's rather than ours. On a phone it is a sheet off the bottom
   edge, which is where the button that opened it lives; from 640px it is a
   centred card. It is the last look at the order before it leaves, and the
   only place a name is asked for - a name is what turns a list of items into
   a message from a person.
   ------------------------------------------------------------------------- */
.confirm {
  border: none;
  padding: 22px 18px calc(22px + env(safe-area-inset-bottom));
  width: 100%;
  max-width: 480px;
  max-height: 90svh;
  margin: auto auto 0;              /* a sheet on the bottom edge */
  background: var(--ground);
  color: var(--ink);
  border-radius: 24px 24px 0 0;
  box-shadow: 0 -14px 44px rgba(11, 58, 32, 0.3);
  overflow-y: auto;
  overscroll-behavior: contain;
}
/* Opening and closing, both animated, with no javascript driving either.
   `allow-discrete` is what makes the closing half possible: display and
   overlay normally snap, so without it the dialog would slide in and then
   vanish. @starting-style is the state it animates FROM on the way in.
   A browser with neither simply cuts, which is what it does today. */
.confirm {
  opacity: 0;
  translate: 0 100%;
  transition:
    opacity 220ms ease,
    translate 300ms cubic-bezier(0.2, 0.8, 0.3, 1),
    scale 300ms cubic-bezier(0.2, 0.8, 0.3, 1),
    display 300ms allow-discrete,
    overlay 300ms allow-discrete;
}
.confirm[open] { display: flex; flex-direction: column; opacity: 1; translate: 0 0; }
@starting-style {
  .confirm[open] { opacity: 0; translate: 0 100%; }
}

.confirm::backdrop {
  background: rgba(11, 58, 32, 0.52);
  opacity: 0;
  transition:
    opacity 240ms ease,
    display 240ms allow-discrete,
    overlay 240ms allow-discrete;
}
.confirm[open]::backdrop { opacity: 1; }
@starting-style {
  .confirm[open]::backdrop { opacity: 0; }
}

@media (min-width: 640px) {
  .confirm {
    margin: auto;
    border-radius: var(--r-card);
    padding: 26px 26px 26px;
    box-shadow: 0 24px 60px rgba(11, 58, 32, 0.32);
    /* Centred rather than hinged on the bottom edge, so it rises a little and
       settles instead of travelling the height of the screen. */
    translate: 0 14px;
    scale: 0.98;
  }
  .confirm[open] { translate: 0 0; scale: 1; }
  @starting-style {
    .confirm[open] { opacity: 0; translate: 0 14px; scale: 0.98; }
  }
}

@media (prefers-reduced-motion: reduce) {
  .confirm, .confirm::backdrop { transition: none; }
}

.confirm-head { display: flex; align-items: center; justify-content: space-between; gap: 16px; }
.confirm-head h2 { margin: 0; }
.confirm-close {
  display: grid; place-items: center;
  width: 38px; height: 38px; border-radius: 50%;
  border: none; cursor: pointer; color: var(--ink);
  background: var(--surface);
  transition: background-color 140ms ease;
}
.confirm-close:hover { background: var(--ground-2); }

/* The order itself, on butter, so it reads as the receipt rather than as more
   page. */
.confirm-slip {
  background: var(--butter);
  border-radius: var(--r-card);
  padding: 4px 16px 14px;
  margin: 16px 0 20px;
}
.confirm-line {
  display: grid; grid-template-columns: 1fr auto;
  gap: 4px 12px; align-items: baseline;
  padding: 12px 0;
  border-bottom: var(--line) solid rgba(20, 59, 36, 0.18);
}
.confirm-line:last-child { border-bottom: none; }
.confirm-line b { font-weight: 700; }
.confirm-line .confirm-cost { font-weight: 700; font-variant-numeric: tabular-nums; }
.confirm-total {
  display: flex; justify-content: space-between; align-items: baseline;
  padding-top: 13px;
  border-top: 2px solid rgba(20, 59, 36, 0.28);
  font-size: 1.0625rem;
}
.confirm-total strong { font-variant-numeric: tabular-nums; }

.confirm-label {
  display: block;
  font-size: 0.75rem; letter-spacing: 0.08em; text-transform: uppercase;
  font-weight: 700; color: var(--ink-muted);
  margin-bottom: 8px;
}
.confirm-input {
  width: 100%;
  font: inherit;
  padding: 13px 14px;
  border-radius: var(--r-btn);
  border: var(--line) solid rgba(20, 59, 36, 0.3);
  background: #fff;
  color: var(--ink);
}
.confirm-input:focus-visible { outline: 2px solid var(--green); outline-offset: 1px; }
.confirm-hint { color: var(--ink-muted); margin: 8px 0 18px; }
.confirm-send { width: 100%; }

/* --- Shop notes accordion ----------------------------------------------- */
.notes-grid { display: grid; gap: 16px; }
@media (min-width: 900px) { .notes-grid { grid-template-columns: 1fr 1fr; align-items: start; } }

/* ========================================================================
   THE BAKE SCENE
   A pinned, scroll scrubbed sequence: one cookie goes into the oven, comes
   out golden, and becomes the three things on the menu. It dramatises the
   mechanism the whole business runs on, which is why it earns the scroll.

   Every value below is the state the page shows with no javascript at all:
   no oven panels, cookie already golden, all three panels visible in a row.
   GSAP overwrites these on load. If it never loads, the section still reads.
   ======================================================================== */

/* --- Caption ------------------------------------------------------------ */
/* Stacked in one place so they can cross fade. Beat one shows by default. */

/* --- Scene -------------------------------------------------------------- */

/* The oven. Two green panels that close over the cookie. Off screen by
   default so a javascript free page never shows half an oven. */
.oven-panel {
  position: absolute;
  left: 50%;
  translate: -50% 0;
  width: min(86vw, 620px);
  height: 42%;
  background: var(--green);
  border-radius: 20px;
  z-index: 3;
  pointer-events: none;
}
.oven-top    { top: 0;    transform: translateY(-140%); }
.oven-bottom { bottom: 0; transform: translateY(140%); }

/* A thin butter line on the closing edge reads as the oven light inside. */
.oven-edge {
  position: absolute; left: 12%; right: 12%; height: 3px;
  background: var(--butter); border-radius: 3px; opacity: 0;
}
.oven-top .oven-edge { bottom: 0; }
.oven-bottom .oven-edge { top: 0; }

/* Heat rising, and steam afterwards. Hidden unless the timeline runs. */

/* --- The three panels, fanning out at the end --------------------------- */

/* ========================================================================
   LOWER PAGE, REBUILT
   The run from the statement block down was four sections of centred or
   left-aligned text floating on a colour field, each with more empty space
   than content. These give every one of them a real composition.
   ======================================================================== */

/* --- Statement: type against a photograph that runs off the edge --------
   The copy sits in the left half and is vertically centred; the photograph
   fills the right half and bleeds to the edge of the screen. The band is a
   full viewport tall, so whenever it fills the screen the copy is centred in
   the viewport rather than sitting near the top of a short band.

   Order matters here: the base rules come first, then the desktop overrides.
   With the base block after the media query its width: 100% was overriding
   the 50% and the photograph covered the whole section.
   ------------------------------------------------------------------------ */
.statement-band {
  position: relative;
  display: grid;
  align-items: stretch;
  overflow: hidden;
}

.statement-copy {
  display: flex;
  flex-direction: column;
  justify-content: center;
  padding-block: clamp(48px, 7vw, 92px);
  padding-inline: 16px;
}

.statement-copy .t-h1 {
  font-family: 'Bricolage Grotesque', Archivo, sans-serif;
  font-weight: 800;
  font-size: clamp(2.4rem, 5.6vw, 4.4rem);
  line-height: 0.96;
  letter-spacing: -0.03em;
  margin: 0;
}
.statement-copy p { margin-top: clamp(16px, 2vw, 24px); opacity: .9; }

/* The highlighted word. A butter underline rather than a colour swap, so the
   headline still reads as one sentence. */
.mark {
  font-style: normal;
  position: relative;
  display: inline-block;
}
.mark::after {
  content: '';
  position: absolute;
  left: -0.04em;
  right: -0.04em;
  bottom: 0.08em;
  height: 0.16em;
  background: var(--butter);
  border-radius: 2px;
  z-index: -1;
}

/* Gives the column a bottom edge and something to do. */
.statement-foot {
  margin-top: clamp(28px, 4vw, 44px);
  display: flex;
  align-items: center;
  gap: 24px;
  flex-wrap: wrap;
}
.statement-marks { display: inline-flex; gap: 10px; opacity: .55; }
.statement-marks .asterisk { color: var(--butter); width: 20px; height: 20px; }
.statement-marks .asterisk:nth-child(2) { animation-delay: .5s; }
.statement-marks .asterisk:nth-child(3) { animation-delay: 1s; }

.statement-media {
  width: 100%;
  height: 100%;
  min-height: 300px;
  object-fit: cover;
  display: block;
}

@media (min-width: 900px) {
  .statement-band {
    grid-template-columns: 1fr 1fr;
    min-height: 100svh;
  }
  /* Out of the flow on desktop: as a grid child the photograph's own 3:4 ratio
     was forcing the row taller than the viewport, which broke the full height. */
  .statement-media {
    position: absolute;
    top: 0;
    right: 0;
    bottom: 0;
    width: 50%;
    height: 100%;
    min-height: 0;
  }
}

@media (min-width: 1024px) {
  .statement-copy {
    /* Line the text up with the page gutter rather than with the grid cell. */
    padding-left: max(40px, calc((100vw - 1400px) / 2 + 40px));
    padding-right: clamp(24px, 4vw, 64px);
  }
}

/* --- Ordering steps ------------------------------------------------------
   The drawings carry two colours of their own now, so the terracotta rules
   above each column were saying the same thing twice and crowding them. The
   sequence is carried instead by an oversized numeral sitting behind each
   drawing, and by a staircase: each step sits a little lower than the last,
   so the row reads as a progression rather than as a table of four cells.
   ------------------------------------------------------------------------ */
/* Two across from the smallest width: one column made each step enormous and
   the section endless to scroll. */
.steps {
  display: grid;
  gap: clamp(22px, 3vw, 44px);
  grid-template-columns: repeat(2, 1fr);
}
@media (min-width: 1000px) { .steps { grid-template-columns: repeat(4, 1fr); align-items: start; } }

/* Prefixed with .steps so these beat the base rules further down the file.
   Unprefixed they were the same specificity and lost on source order, so the
   whole block did nothing: every step reserved a 200px art row instead of
   140px, the numeral stayed at its desktop size, and the description stayed
   capped at 26 characters. */
@media (max-width: 999px) {
  .steps .step-item { grid-template-rows: 140px auto auto; }
  .steps .step-num { font-size: clamp(3.4rem, 13vw, 5rem); }
  .steps .step-item p { max-width: none; }
}

.step-item {
  position: relative;
  display: grid;
  grid-template-rows: 200px auto auto;
  gap: 10px;
  align-content: start;
}

/* The numeral sits behind the drawing, large and quiet. */
.step-num {
  position: absolute;
  top: -0.18em;
  left: -0.04em;
  z-index: 0;
  font-family: 'Bricolage Grotesque', Archivo, sans-serif;
  font-weight: 800;
  font-size: clamp(5rem, 9vw, 8.5rem);
  line-height: 1;
  letter-spacing: -0.04em;
  color: var(--terracotta);
  opacity: 0.16;
  pointer-events: none;
  user-select: none;
}

.step-item .draw {
  position: relative;
  z-index: 1;
  height: 100%;
  width: auto;
  max-width: 100%;
  object-fit: contain;
  object-position: left bottom;
  transform: scale(var(--art-scale, 1));
  transform-origin: left bottom;
}

.step-item h3 { font-size: clamp(1.3rem, 2.6vw, 1.7rem); position: relative; z-index: 1; }
.step-item p {
  color: var(--ink-muted);
  font-size: 0.9375rem;
  max-width: 26ch;
  position: relative;
  z-index: 1;
}

/* The staircase. Each column drops a little further than the one before it. */
@media (min-width: 1000px) {
  .steps .step-item:nth-child(2) { margin-top: 34px; }
  .steps .step-item:nth-child(3) { margin-top: 68px; }
  .steps .step-item:nth-child(4) { margin-top: 102px; }
}

/* --- Fact block: drawing and statement side by side --------------------- */
.fact-row { display: grid; gap: clamp(24px, 4vw, 56px); align-items: center; }
@media (min-width: 900px) { .fact-row { grid-template-columns: 1fr 1fr; } }
.fact-row .draw { max-width: 100%; }
.fact-row .t-h2 { font-size: clamp(1.9rem, 3.6vw, 3rem); }

/* --- Getting it to you --------------------------------------------------
   Two solid blocks, not two outlined boxes. The previous version drew a faint
   border around a fill that matched the section, so both cards read as empty
   rectangles with the drawing stranded in one corner. Colour does the
   separating now, the drawing is large enough to be the subject, and the
   details are one line each rather than a table.
   ------------------------------------------------------------------------ */
.ways { display: grid; gap: clamp(18px, 2.4vw, 28px); }
@media (min-width: 820px) { .ways { grid-template-columns: 1fr 1fr; } }
/* Room on the right and below for the offset shadows. */
.ways { padding-right: 8px; padding-bottom: 8px; }

.way {
  --way-pad: clamp(24px, 3vw, 40px);
  display: flex;
  flex-direction: column;
  border-radius: 20px;
  padding: var(--way-pad);
  overflow: hidden;
  /* The buttons' printed offset, doubled for a block this size: a hard 8px
     shadow, no blur, in a darker tone of the card's own fill. */
  box-shadow: 8px 8px 0 var(--way-shade);
}
/* The delivery card is green, so its drawing has to be the inverse of the
   others: cream ink on a green ground, the same way draw-8-oven works. */
/* The buttons' press (.btn:hover / :active) at the card's scale: hover pushes
   it halfway into its shadow, a press pushes it flat. */
@media (hover: hover) and (pointer: fine) {
  .way {
    transition: transform 140ms cubic-bezier(.2, .8, .3, 1),
                box-shadow 140ms cubic-bezier(.2, .8, .3, 1);
  }
  .way:hover  { transform: translate(4px, 4px); box-shadow: 4px 4px 0 var(--way-shade); }
  .way:active { transform: translate(8px, 8px); box-shadow: 0 0 0 var(--way-shade); }
}
@media (prefers-reduced-motion: reduce) {
  .way, .way:hover { transition: none; transform: none; }
}

.way-pickup  { background: var(--surface); color: var(--ink);    --way-shade: #D8C6AA; }
.way-deliver { background: var(--green);   color: var(--ground); --way-shade: var(--green-deep); }

/* The drawing is the top band of the card, edge to edge, not a small object
   parked in the corner. Held at a fixed ratio rather than its own, so the two
   cards keep the same art height even though the two files do not match, and
   bled into the card's padding so there is no cream margin around a drawing
   that already has empty space built into it. */
.way-art {
  display: block;
  width: calc(100% + var(--way-pad) * 2);
  /* The global img rule caps every image at the width of its box, which would
     undo the bleed on its own. */
  max-width: none;
  margin-inline: calc(var(--way-pad) * -1);
  margin-top: calc(var(--way-pad) * -0.5);
  /* A band, not a panel. At 3/2 the drawing was two thirds of a card 660px
     wide and pushed the heading and the facts off the bottom of the screen;
     the whole drawing still fits, it is simply smaller. The phone override
     below is unchanged - at 350px wide the band was never the problem. */
  aspect-ratio: 5 / 2;
  height: auto;
  object-fit: contain;
  object-position: center bottom;
  margin-bottom: clamp(14px, 2vw, 22px);
}
/* Shorter on a phone: portrait screen, and the words matter more than the
   drawing once the two cards are stacked. */
@media (max-width: 560px) {
  .way-art { aspect-ratio: 16 / 9; }
}
/* Matching the two drawings by the size of the CAT, not the size of the file.
   The delivery file gives its cat more of its frame than the pickup file does,
   so in an identical band its cat rendered noticeably larger and the pair
   stopped reading as a set. Padding shrinks the drawing inside its box without
   changing the box, so both cards stay the same height. */
.way-deliver .way-art { padding: 0 10%; }

/* Each side gets the blend that drops its own paper out: darken for dark ink
   on cream, lighten for cream ink on green. */
.way-pickup .way-art { mix-blend-mode: darken; }
.way-deliver .way-art { mix-blend-mode: lighten; }

.way h3 {
  font-size: clamp(1.7rem, 3.2vw, 2.4rem);
  line-height: 1;
  margin-bottom: 10px;
}
.way-body { display: flex; flex-direction: column; flex: 1; min-width: 0; }
.way-body > p { max-width: 38ch; }
.way-body > p { color: var(--ink-muted); }
.way-deliver .way-body > p { color: rgba(247, 242, 232, 0.88); }

/* One line of facts, not a table. */
.way-facts {
  margin-top: auto;
  padding-top: clamp(20px, 3vw, 30px);
  display: flex;
  flex-wrap: wrap;
  gap: 8px 28px;
  font-size: 0.9375rem;
}
.way-facts b { font-weight: 700; }
.way-facts { color: var(--ink-muted); }
.way-facts b { color: var(--terracotta); }
.way-deliver .way-facts { color: rgba(247, 242, 232, 0.75); }
.way-deliver .way-facts b { color: var(--butter); }

/* Inside each card: the drawing on the left, everything that is words on the
   right. Only from 1000px up - the two cards are already side by side by then,
   so splitting each one again below that leaves a drawing too small to read
   next to a column too narrow to set text in.

   This has to sit after the .way rules above: same specificity, so the later
   block is the one that wins. */
@media (min-width: 1000px) {
  .way {
    display: grid;
    grid-template-columns: minmax(0, 0.79fr) minmax(0, 1fr);
    align-items: center;
    column-gap: clamp(14px, 1.4vw, 22px);
    /* Less air above and below than the card carries at its sides: there is
       one short block of text in here, not a page of it. */
    padding-block: clamp(20px, 2.2vw, 28px);
  }
  /* Square and inside the padding. The band shape and the full bleed both
     belong to the stacked layout; here the drawing is a companion to the text,
     not a lid on top of it. */
  .way-art {
    width: 100%;
    max-width: none;
    margin: 0;
    /* The drawing's own shape, not a square. Forced into a square box the wide
       delivery drawing sat in a band of empty green with 40px of nothing above
       and below it, and the card grew to hold the emptiness. */
    aspect-ratio: auto;
    height: auto;
    object-position: center;
  }
  .way-facts { padding-top: clamp(16px, 2vw, 22px); }
  /* Smaller alongside the bigger drawing: the two share one card, and at the
     full size the pair were shouting over each other. */
  .way h3 { font-size: clamp(1.4rem, 2vw, 1.75rem); }

  /* Matching the two cats again, and the correction flips direction here. In a
     square box the wide delivery drawing has to fit by its width, so it lands
     short and its cat comes out small; the taller pickup drawing fills the box
     and its cat comes out large. So here it is the pickup one that is held
     back, and the delivery one runs at full size. */
  .way-deliver .way-art { padding: 0; }
  .way-pickup  .way-art { padding: 0 2%; }
}

.ways-after {
  margin-top: clamp(20px, 3vw, 30px);
  font-size: 0.9375rem;
  color: var(--ink-muted);
}

/* --- Questions on butter ------------------------------------------------ */
.qa-wide { max-width: 900px; }

/* ========================================================================
   FOOTER, REVEALED
   The page scrolls away to uncover the footer sitting behind it, rather than
   the footer arriving as one more flat band. Everything above it lives in
   .page-body, which carries the page colour and sits on top.
   ======================================================================== */
.page-body {
  position: relative;
  z-index: 1;
  background: var(--ground);
}

.site-foot {
  padding-block: clamp(48px, 7vw, 96px);
}
/* On a phone the footer is the last thing on the page and everything in it is
   full width, so it needs its own gutter rather than the page's 16px, and room
   under the last row before the edge of the screen. */
@media (max-width: 899px) {
  .site-foot { padding-block: 44px 36px; }
  .site-foot .wrap { padding-inline: 24px; }
  .site-foot .foot-grid { gap: 30px; }
  .site-foot .foot-base { padding-top: 22px; margin-top: 6px; }
}
/* The reveal is switched on by the script rather than by a breakpoint, because
   it only works while the whole footer fits on one screen: fixed to the bottom,
   anything taller than the viewport loses its top edge with no way to scroll to
   it. The script measures and adds the class, on a phone as well as a desktop,
   and takes it away again when an opened column makes the footer too tall. */
.site-foot.is-revealed {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 0;
}

.foot-grid { display: grid; gap: clamp(32px, 5vw, 56px); position: relative; }
@media (min-width: 900px) {
  .foot-grid { grid-template-columns: 1fr 1fr; align-items: start; }
}

.foot-mark { width: 84px; height: 84px; display: block; margin-bottom: 20px; }
.foot-line {
  font-family: 'Bricolage Grotesque', Archivo, sans-serif;
  font-weight: 800;
  font-size: clamp(2.2rem, 5vw, 3.6rem);
  line-height: 0.98;
  letter-spacing: -0.03em;
  margin: 0;
}
.foot-sub { margin-top: 14px; max-width: 34ch; opacity: .88; }
.foot-lead .btn { margin-top: 26px; }

.foot-cols { display: grid; gap: 0; }
@media (min-width: 900px) { .foot-cols { gap: clamp(22px, 3vw, 36px); } }
@media (min-width: 620px) { .foot-cols { grid-template-columns: repeat(3, 1fr); } }
.foot-head {
  font-size: 0.75rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  font-weight: 700;
  opacity: .7;
  margin-bottom: 14px;
}

/* The columns fold on mobile. Stacked open, the footer ran to three screens. */
.foot-fold { border-top: var(--line) solid rgba(247, 242, 232, 0.22); }
.foot-fold > summary {
  list-style: none;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  padding: 16px 0;
  margin: 0;
}
.foot-fold > summary::-webkit-details-marker { display: none; }
.foot-fold > summary::after {
  content: '+';
  font-size: 1.25rem;
  line-height: 1;
  opacity: .7;
  transition: transform 200ms ease;
}
.foot-fold[open] > summary::after { transform: rotate(45deg); }
.foot-fold > ul { padding-bottom: 18px; }

@media (min-width: 900px) {
  /* On desktop they are plain columns again: no border, no marker, no pointer. */
  .foot-fold { border-top: 0; }
  .foot-fold > summary { cursor: default; padding: 0; pointer-events: none; }
  .foot-fold > summary::after { content: none; }
  .foot-fold > ul { padding-bottom: 0; }
}
/* Find us: two icons, no fold. On a phone the row sits level with the fold
   rows above and below it, so the column reads as one list. */
.foot-social { padding: 16px 0; }
.foot-social .foot-head { display: block; margin-bottom: 12px; }
.foot-ico-row { display: flex; gap: 12px; }
.foot-ico {
  display: grid;
  place-items: center;
  width: 44px;
  height: 44px;
  border-radius: 999px;
  color: var(--ground);
  border: var(--line) solid rgba(247, 242, 232, 0.35);
  transition: background-color 160ms ease, border-color 160ms ease;
}
.foot-ico:hover { background: rgba(247, 242, 232, 0.12); border-color: rgba(247, 242, 232, 0.6); }
.foot-ico:active { background: rgba(247, 242, 232, 0.2); }
@media (min-width: 900px) { .foot-social { padding: 0; } }

.foot-cols ul { list-style: none; margin: 0; padding: 0; display: grid; gap: 10px; }
.foot-cols a { text-decoration: none; }
.foot-cols a:hover { text-decoration: underline; text-underline-offset: 4px; }

/* The cat sits along the bottom edge, cream on green, at low emphasis. */
.foot-cat {
  display: none;
  position: absolute;
  right: 0;
  bottom: 84px;          /* clear of the base row, which runs full width */
  width: min(26vw, 300px);
  height: auto;
  opacity: .42;
  pointer-events: none;
}
@media (min-width: 900px) { .foot-cat { display: block; } }

.foot-base {
  grid-column: 1 / -1;
  display: flex;
  flex-wrap: wrap;
  gap: 8px 24px;
  padding-top: clamp(24px, 3vw, 40px);
  border-top: var(--line) solid rgba(247, 242, 232, 0.25);
  opacity: .72;
}

/* ========================================================================
   THE LAST SIX TAILWIND CLASSES
   shop.php used Tailwind for exactly two grids. Rather than ship a whole
   framework for six rules, they are written out here at Tailwind's own
   default values, so the markup did not have to change: gap-4 is 1rem,
   gap-8 is 2rem, and the breakpoints are sm 640, md 768, lg 1024.
   minmax(0, 1fr) rather than 1fr - it stops a wide product image from
   forcing its column past its share of the row.
   ======================================================================== */
.grid  { display: grid; }
.gap-4 { gap: 16px; }
.gap-8 { gap: 32px; }

@media (min-width: 640px) {
  .sm\:grid-cols-2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (min-width: 768px) {
  .md\:grid-cols-2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (min-width: 1024px) {
  .lg\:grid-cols-3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}

/* Box lines in the order: the single-price total struck through above the box
   price. */
.cost-was {
  display: block; font-size: 0.8em; font-weight: 400; opacity: 0.75;
  text-align: right; line-height: 1.2;
}
