/* ---------------------------------------------------------------------------
   Template #1 - restaurant / cafe / bar.  v4, "soft modern".
   One stylesheet, no build step, no framework. Mobile-first from 360px.

   Everything that changes between a taco shop and a wine bar is a custom
   property below; build-mockup.js re-declares them in an inline <style> from
   content.json -> brand.colors / brand.fonts. Nothing else should need editing.

   THE DIRECTION. Restraint. Separation comes from space, not from lines and
   boxes; one accent; no ornament. v3 went the other way - dark slabs, numbered
   sections, dot leaders, badge clutter - and read as decorated rather than
   designed. What sells to a local owner is a page that looks calm, current and
   obviously fast.

   THE PALETTE BELOW IS A DEFAULT, NOT THE DESIGN. The builder overrides every
   colour and both families from content.json, so never hardcode a brand colour
   in a rule - derive it (see --tint) or take it from a token, or a taqueria and
   a wine bar end up looking identical.
   --------------------------------------------------------------------------- */

:root {
  /* palette - always overridden by the builder */
  --bg: #fdfcfa;
  --surface: #ffffff;
  --ink: #221f1c;
  --muted: #6f6a63;
  --line: #ebe7e1;
  --brand: #8f2d24;
  --brand-dark: #6d211a;
  --on-brand: #ffffff;
  --accent: #b06a2c;
  --hero-ink: #ffffff;
  --foot-bg: #221f1c;
  --foot-ink: #fdfcfa;

  /* type - the Google families come first, a system stack always backs them up */
  --font-display: "Inter", system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  --font-body: "Inter", system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  /* Overridden per business from content.json brand.fonts.display_weight -
     a single-weight display face renders a fake bold if asked for more. */
  --display-weight: 600;
  --display-track: -0.03em;
  --display-transform: none;

  /* shape + rhythm */
  --r: 18px;
  --r-btn: 999px;
  --maxw: 1312px;   /* the measure the design was drawn at: 1440 - 2*64 */
  --gutter: 1.4rem;
  --band: clamp(4rem, 12vw, 7rem);
  --bar-h: 70px;

  /* scale */
  --hero-h: 700px;
  --hero-pad: 3.25rem;
  /* Each clamp is set so its value at 1440px equals the spec exactly.
     82px hero, 48px section heading. Below 1440 it scales; at 1440 it matches. */
  --hero-size: clamp(1.95rem, 6.4vw, 5.125rem);   /* caps at 82px, the spec value */
  --h2-size: clamp(1.7rem, 3.9vw, 3rem);          /* caps at 48px */
  --quote-size: clamp(1.15rem, 3.6vw, 1.45rem);

  /* derived, so a rebrand cannot leave a stale hex behind */
  --tint: color-mix(in srgb, var(--brand) 5%, var(--bg));
  --veil-bottom: .8;
  --photo-ratio: 4 / 5;
  --ease: cubic-bezier(.22, .7, .28, 1);

  /* One texture, reused by every surface. An inline SVG filter: a few hundred
     bytes, no image request, and it scales to any band size. */
  --grain: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='300' height='300'%3E%3Cfilter id='g'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='300' height='300' filter='url(%23g)' opacity='0.3'/%3E%3C/svg%3E");
  /* The same noise at plaster strength. A wall is grainier than paper, and the
     artboard says so: 0.42 there against 0.22 on the hero's wash. Running one
     opacity everywhere left every plaster band about five levels light, which
     reads as the flatness the design does not have. */
  --grain-wall: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='300' height='300'%3E%3Cfilter id='gw'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='300' height='300' filter='url(%23gw)' opacity='0.42'/%3E%3C/svg%3E");
}

*, *::before, *::after { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; scroll-behavior: smooth; }

body {
  margin: 0;
  background: var(--bg);
  color: var(--ink);
  font-family: var(--font-body);
  font-size: 17px;
  line-height: 1.65;
  -webkit-font-smoothing: antialiased;
  padding-bottom: calc(var(--bar-h) + env(safe-area-inset-bottom, 0px));
  overflow-x: hidden;
}

img { max-width: 100%; display: block; }

h1, h2, h3 {
  font-family: var(--font-display);
  font-weight: var(--display-weight);
  letter-spacing: var(--display-track);
  text-transform: var(--display-transform);
  line-height: 1.08;
  margin: 0;
  text-wrap: balance;
}

p, blockquote, figure { margin: 0; text-wrap: pretty; }
ul, ol, dl, dd { margin: 0; padding: 0; }
li { list-style: none; }
a { color: inherit; }

:where(a, button, [tabindex]):focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 3px;
  border-radius: 6px;
}
.hero :focus-visible, .topbar :focus-visible, .foot :focus-visible { outline-color: var(--hero-ink); }

[id] { scroll-margin-top: 4.5rem; }

.skip-link {
  position: absolute; left: 12px; top: -60px; z-index: 100;
  background: var(--surface); color: var(--ink);
  padding: 10px 18px; border-radius: var(--r-btn);
  box-shadow: 0 12px 30px -12px rgb(0 0 0 / .3);
  transition: top .18s var(--ease);
}
.skip-link:not(:focus) { box-shadow: none; }  /* parked off-screen; the shadow would still bleed into view */
.skip-link:focus { top: 12px; }

/* THE CONTAINER RULE, and it is not negotiable per-section:
     stacked content  ->  .wrap      = min(1200px, 100% - 2*gutter), centred
     full-width bands ->  the SECTION spans 100%, with a .wrap inside it
   Always 100%, NEVER 100vw: 100vw includes the scrollbar, so on any desktop with
   a classic scrollbar it is ~15px wider than the page and every such band pushes
   a horizontal scrollbar. That is the bug behind "your designs go off screen". */
.wrap { width: min(var(--maxw), 100% - calc(var(--gutter) * 2)); margin-inline: auto; }
.bleed { width: 100%; margin-inline: 0; }
.ico { font-style: normal; line-height: 1; }

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

.btn {
  --btn-bg: transparent;
  --btn-ink: var(--ink);
  --btn-line: transparent;
  position: relative;
  display: inline-flex; align-items: center; justify-content: center; gap: 12px;
  min-height: 50px; padding: .78rem 1.6rem;
  outline: 1px solid var(--btn-line); outline-offset: -1px; border: 0; border-radius: var(--r-btn);
  background: var(--btn-bg); color: var(--btn-ink);
  font: inherit; font-weight: 600; font-size: .98rem;
  text-decoration: none; cursor: pointer;
  transition: background-color .22s var(--ease), outline-color .22s var(--ease),
              color .22s var(--ease), transform .22s var(--ease), box-shadow .22s var(--ease);
}
.btn--brand {
  --btn-bg: var(--brand); --btn-ink: var(--on-brand);
  box-shadow: 0 1px 2px rgb(0 0 0 / .06);
}
.btn--brand:hover {
  --btn-bg: var(--brand-dark);
  transform: translateY(-2px);
  box-shadow: 0 12px 26px -14px color-mix(in srgb, var(--brand) 80%, transparent);
}
.btn--quiet { --btn-line: var(--line); }
.btn--quiet:hover { --btn-line: var(--ink); transform: translateY(-2px); }
.btn--glass {
  --btn-ink: var(--hero-ink);
  --btn-line: rgb(255 255 255 / .42);
  backdrop-filter: blur(10px); -webkit-backdrop-filter: blur(10px);
}
.btn--glass:hover { background: rgb(255 255 255 / .16); transform: translateY(-2px); }
.btn:active { transform: translateY(0); }
.btn--lg { min-height: 60px; padding: 0 2rem; font-size: 1.0625rem; }   /* 17px / 32px, per spec */
.btn { font-family: var(--font-display); letter-spacing: .12em; text-transform: var(--display-transform); }
.btn--sm { min-height: 44px; padding: .5rem 1.15rem; font-size: .88rem; }  /* 44px floor: tap target */

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

/* In normal flow, not floated over the hero. With the split hero the top-right of
   the page is photo and the top-left is paper, so an overlaid bar would need its
   text to be legible against both at once - which is only solvable with a scrim,
   which is the thing the split hero exists to remove. Sitting in flow, it is
   simply dark text on the page background. */
.topbar {
  position: relative; z-index: 20;
  padding: clamp(.9rem, 2.2vw, 1.35rem) 0;
  background: var(--bg);
  color: var(--ink);
}
.topbar__in { display: flex; align-items: center; gap: 1rem; }

.mark {
  display: inline-flex; align-items: center; gap: .6rem;
  min-height: 44px;  /* tap-target floor: it is a link back to the top */
  padding: .35rem 0;
  text-decoration: none; color: var(--ink);
  font-family: var(--font-display); font-size: 1.12rem; font-weight: 600;
  letter-spacing: -0.01em; margin-right: auto; max-width: 62%;
}
.mark img { height: 36px; width: auto; border-radius: 8px; }
.mark__text { display: block; }
.mark__stack { display: flex; flex-direction: column; gap: 1px; line-height: 1; }
.mark__sub {
  font-family: var(--font-body);
  font-size: .62rem; font-weight: 500; letter-spacing: .3em; text-transform: uppercase;
  color: var(--muted);
}

.topnav { display: none; }
.topnav a {
  position: relative;
  text-decoration: none; font-size: .93rem; font-weight: 500;
  opacity: .82; transition: opacity .2s var(--ease);
}
/* An underline that grows from the left rather than blinking on. */
.topnav a::after {
  content: ""; position: absolute; left: 0; bottom: -4px;
  width: 100%; height: 1px; background: currentColor;
  transform: scaleX(0); transform-origin: left;
  transition: transform .3s var(--ease);
}
.topnav a:hover { opacity: 1; }
.topnav a:hover::after { transform: scaleX(1); }

.topbar__call { flex: none; }

/* --- hero ------------------------------------------------------------------
   Split: copy on paper, photo beside it. Everything the old overlay hero needed
   in order to stay readable - the scrim, the text-shadow, the "is this photo too
   bright" guesswork - is gone, because nothing is set over the photograph any
   more. On mobile the photo leads (it is a restaurant; the food should be the
   first thing) but stays short enough that the headline and the call button are
   still on the first screen of a small phone. */

.hero { display: grid; }

/* Photo first on a phone, but the photo is not allowed to cost the call button.
   Measured, not assumed: with the copy block below it, tap-to-call clears the fold
   on a 390x844 phone and a 412x915 one. On a 667px iPhone SE it does not - the
   short-viewport rule further down is what buys that back. Raise this cap and you
   push the conversion path off the first screen on every small phone. */
.hero__media { order: -1; height: min(32svh, 240px); }

.hero__media { position: relative; overflow: hidden; background: var(--tint); }
.hero__img {
  width: 100%; height: 100%; object-fit: cover; display: block;
}

.hero__in {
  padding: clamp(1.6rem, 5.5vw, 3rem) var(--gutter) clamp(2.25rem, 7vw, 3.5rem);
}

/* Short viewports - an SE, or any phone in landscape. The photo yields first,
   because on a screen this size the call button matters more than the food does. */
@media (max-height: 700px) {
  .hero__media { height: min(24svh, 170px); }
  /* The dial yields first on a short screen, same rule as the photo: tap-to-call
     matters more than the plate does on a 667px phone. */
  .dial { width: min(58%, 216px); }
  .hero__in { padding-top: 1.35rem; }
  .facts { margin-top: 1.2rem; gap: .45rem 1.2rem; }
  .hero__cta { margin-top: 1.5rem; }
}

.eyebrow {
  font-family: var(--font-display);
  font-size: .82rem; font-weight: 600; letter-spacing: .28em; text-transform: uppercase;
  color: var(--muted);
  margin-bottom: 1.25rem;
}

.hero__title { font-size: var(--hero-size); }

.hero__tagline {
  margin-top: 1.15rem;
  font-size: clamp(1.05rem, 3.4vw, 1.24rem);
  line-height: 1.55; font-weight: 400;
  color: var(--muted);
  max-width: 34ch;
}

/* The at-a-glance row: open state first, then whatever the business is proud of.
   Reads as information rather than as tags, which is what the pills got wrong. */
.facts {
  display: flex; flex-wrap: wrap;
  gap: .55rem 1.35rem;
  margin-top: 1.45rem;
  font-size: .88rem; font-weight: 400;
  color: var(--muted);
}
.facts > li { display: inline-flex; align-items: center; gap: .5rem; }
.facts > li::before {
  content: ""; width: 4px; height: 4px; border-radius: 50%;
  background: var(--accent); opacity: .55; flex: none;
}
/* A badge that brought its own icon does not also get a bullet. */
.facts > li.fact--icon::before { display: none; }
.fact__icon { flex: none; color: color-mix(in srgb, var(--muted) 82%, transparent); }
.fact--status { color: var(--ink); font-weight: 500; }
.fact--status::before { display: none; }

.status__dot { width: 8px; height: 8px; border-radius: 50%; background: #bdbdbd; flex: none; }
.fact--status[data-state="open"] .status__dot {
  background: #17a34a;
  animation: pulse 2.6s var(--ease) infinite;
}
.fact--status[data-state="closed"] .status__dot { background: #d1782a; }
@keyframes pulse {
  0%   { box-shadow: 0 0 0 0 rgb(23 163 74 / .45); }
  70%  { box-shadow: 0 0 0 7px rgb(23 163 74 / 0); }
  100% { box-shadow: 0 0 0 0 rgb(23 163 74 / 0); }
}

.hero__cta { display: flex; flex-wrap: wrap; gap: .6rem; margin-top: 1.8rem; }
.hero__cta .btn { flex: 1 1 auto; min-width: 46%; }
.hero__cta-map { display: none; }

/* --- sections: space, not lines --------------------------------------------- */

.section { padding: var(--band) 0; }
.section--reviews { background: var(--tint); }
.section--gallery { padding-block: 0; }

.section__head { margin-bottom: clamp(2.25rem, 5vw, 3.5rem); max-width: 58ch; }
.section__head--row {
  display: flex; flex-wrap: wrap; align-items: end; justify-content: space-between;
  gap: 1rem; max-width: none;
}
.kicker {
  font-size: .78rem; font-weight: 500; letter-spacing: .1em; text-transform: uppercase;
  color: var(--accent); margin-bottom: .95rem;
}
.section__title { font-size: var(--h2-size); }
.section__title--sm { font-size: clamp(1.5rem, 3.4vw, 2.1rem); margin-bottom: 0; }
.section__lede { margin-top: .95rem; color: var(--muted); font-size: 1.06rem; max-width: 44ch; font-weight: 400; }
.section__foot { margin-top: 2.5rem; }
.section__actions { display: flex; flex-wrap: wrap; gap: .6rem; margin-top: 1rem; }
.section__actions .btn { flex: 1 1 auto; }
.fineprint { color: var(--muted); font-size: .88rem; line-height: 1.55; }

/* --- menu ------------------------------------------------------------------
   A menu is a list of things with prices. The less furniture around it, the more
   it reads like one. */

.menu__layout { display: grid; gap: clamp(2.25rem, 5vw, 3.5rem); }
.menu__rail .section__lede { margin-top: 1rem; }
.menu__actions { display: flex; flex-wrap: wrap; gap: .6rem; margin-top: clamp(1.6rem, 3vw, 2.25rem); }
.menu__actions .btn { flex: 1 1 auto; }
.menu__note-foot { margin-top: 1.5rem; }

.menu { display: grid; gap: 1.25rem; }
.menu__section {
  padding: clamp(1.75rem, 4vw, 2.25rem) clamp(1.5rem, 3.5vw, 1.9rem);
  background: var(--surface);
  border: 1px solid color-mix(in srgb, var(--line) 80%, var(--bg));
  border-radius: var(--r);
}
.menu__h {
  display: flex; align-items: center; gap: .65rem;
  font-family: var(--font-body);
  font-size: .78rem; font-weight: 600; letter-spacing: .16em; text-transform: uppercase;
  color: var(--accent); margin-bottom: 1.75rem;
}
.menu__icon { flex: none; }
.menu__note { margin-top: .6rem; color: var(--muted); font-size: .88rem; }
.menu__items { display: grid; gap: 1.5rem; }

.item__top { display: flex; align-items: baseline; gap: 1rem; }
.item__name { font-weight: 500; font-size: 1.06rem; }
.item__lead { flex: 1; }  /* a plain gap, not a dotted leader - the leader read as a takeaway flyer */
.item__price {
  font-weight: 600; font-size: 1.02rem;
  font-variant-numeric: tabular-nums; white-space: nowrap;
}
.item__desc {
  margin-top: .32rem; color: var(--muted);
  font-size: .95rem; line-height: 1.55; max-width: 46ch; font-weight: 400;
}
.item__tags { display: inline-flex; gap: .35rem; }
.item__tag {
  font-size: .68rem; font-weight: 600; letter-spacing: .04em; text-transform: uppercase;
  color: var(--accent);
}

/* --- services cards --------------------------------------------------------- */

.cards { display: grid; gap: 1rem; }
.card {
  padding: 1.7rem 1.6rem;
  border: 1px solid var(--line); border-radius: var(--r);
  background: var(--surface);
  transition: transform .4s var(--ease), box-shadow .4s var(--ease);
}
.card__h { font-size: 1.1rem; margin-bottom: .45rem; }
.card__p { color: var(--muted); font-size: .95rem; font-weight: 400; }
.card__price { margin-top: .75rem; font-weight: 600; }

/* --- craft ------------------------------------------------------------------
   Asymmetric on purpose: the page has been a run of full-width bands up to here,
   and one section that breaks the rhythm is what stops it reading as a stack. */

.craft { display: grid; gap: clamp(2.5rem, 6vw, 4rem); align-items: center; }

.craft__body { margin-top: 1.4rem; color: var(--muted); font-size: 1.06rem; line-height: 1.7; max-width: 40ch; font-weight: 400; }

.stats {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
  gap: 1.75rem;
  margin-top: clamp(2rem, 4vw, 3rem);
}
.stat__n { font-family: var(--font-display); font-weight: var(--display-weight); font-size: 2.5rem; line-height: 1; }
.stat__l { margin-top: .5rem; font-size: .86rem; line-height: 1.5; color: var(--muted); }

.craft__img {
  width: 100%; aspect-ratio: 4 / 5; object-fit: cover;
  border-radius: var(--r); background: var(--tint);
}

/* --- invitation -------------------------------------------------------------
   The one band where copy sits over a photograph. The veil is mandatory here:
   the split hero can drop its scrim because the copy has paper behind it, and
   this band has nothing but the picture. */

.invite { position: relative; isolation: isolate; overflow: hidden; display: grid; }
.invite__img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; z-index: -2; }
.invite__veil {
  position: absolute; inset: 0; z-index: -1;
  background: linear-gradient(to top, rgb(18 14 11 / .88) 0%, rgb(18 14 11 / .66) 48%, rgb(18 14 11 / .5) 100%);
}
.invite__in {
  position: relative;
  padding-block: clamp(4.5rem, 11vw, 7.5rem);
  text-align: center; color: #fff;
  display: flex; flex-direction: column; align-items: center;
}
.invite__eyebrow {
  font-size: .78rem; font-weight: 500; letter-spacing: .2em; text-transform: uppercase;
  color: rgb(255 255 255 / .74);
}
.invite__title {
  font-family: var(--font-display); font-weight: var(--display-weight);
  letter-spacing: var(--display-track);
  font-size: clamp(2rem, 6vw, 3.4rem); line-height: 1.06;
  margin-top: 1.1rem; max-width: 18ch;
  text-shadow: 0 1px 2px rgb(0 0 0 / .4), 0 2px 22px rgb(0 0 0 / .34);
}
.invite__cta { display: flex; flex-wrap: wrap; gap: .65rem; justify-content: center; margin-top: 2.2rem; }
.invite__btn { --btn-bg: #fff; --btn-ink: #14100b; }
.invite__btn:hover { --btn-bg: #fff; opacity: .9; transform: translateY(-2px); }
.invite__btn--ghost { --btn-bg: transparent; --btn-ink: #fff; --btn-line: rgb(255 255 255 / .5); }
.invite__btn--ghost:hover { background: rgb(255 255 255 / .14); }

/* --- gallery ---------------------------------------------------------------- */

.gallery__head {
  padding-block: var(--band) 0;
  margin-bottom: 1.7rem;
  display: flex; flex-wrap: wrap; align-items: baseline; justify-content: space-between; gap: 1rem;
}
.gallery__more {
  font-size: .92rem; color: var(--muted); text-decoration: none;
  display: inline-flex; align-items: center; min-height: 44px;  /* tap-target floor */
}
.gallery__more:hover { color: var(--ink); }

.strip {
  display: flex; gap: .55rem;
  overflow-x: auto; scroll-snap-type: x mandatory;
  padding: 0 var(--gutter) var(--band);
  scrollbar-width: none; -webkit-overflow-scrolling: touch;
}
.strip::-webkit-scrollbar { display: none; }
.strip li { flex: 0 0 auto; width: min(76%, 400px); scroll-snap-align: center; }
.strip img {
  width: 100%; aspect-ratio: var(--photo-ratio); object-fit: cover;
  border-radius: var(--r); background: var(--tint);
}

/* --- reviews ---------------------------------------------------------------- */

.rating { font-size: .95rem; color: var(--muted); }
.rating strong { color: var(--ink); font-size: 1.75rem; font-weight: 600; font-variant-numeric: tabular-nums; }
.rating { display: inline-flex; align-items: center; gap: .5rem; }

.quotes { display: grid; gap: 1rem; }
.quote {
  padding: 1.8rem 1.7rem;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--r);
  transition: transform .4s var(--ease), box-shadow .4s var(--ease);
}
.quote__stars { color: var(--accent); letter-spacing: .1em; font-size: .85rem; }
.quote__text {
  margin-top: .85rem;
  font-family: var(--font-display);
  font-weight: var(--display-weight);
  letter-spacing: var(--display-track);
  font-size: var(--quote-size); line-height: 1.45;
}
.quote__by { margin-top: 1.1rem; font-size: .87rem; color: var(--muted); font-weight: 500; }
.quote--pending .quote__text { color: var(--muted); font-style: italic; }

/* --- visit / hours ---------------------------------------------------------- */

.visit { display: grid; gap: clamp(2rem, 5vw, 3.5rem); }
.visit__about { margin-top: 1rem; color: var(--muted); max-width: 44ch; font-weight: 400; }
.visit__address { margin-top: 1.7rem; font-size: 1.06rem; line-height: 1.7; }
.visit__address .muted { color: var(--muted); font-size: .93rem; }

.hourscard {
  padding: 1.8rem 1.7rem;
  border: 1px solid var(--line); border-radius: var(--r);
  background: var(--surface);
}
.hourscard__h {
  font-family: var(--font-body);
  font-size: .78rem; font-weight: 600; letter-spacing: .1em; text-transform: uppercase;
  color: var(--muted); margin-bottom: 1.2rem;
}
.hours {
  display: grid; grid-template-columns: auto 1fr;
  gap: .45rem 1rem; font-size: .98rem; font-variant-numeric: tabular-nums;
}
.hours dt { color: var(--muted); font-weight: 400; }
.hours dd { text-align: right; }
.hours dt[data-today], .hours dd[data-today] { color: var(--ink); font-weight: 600; }
.hours .closed { color: var(--muted); }
.hourscard .fineprint { margin-top: 1.3rem; }

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

/* One hairline, no slab. A dark block at the foot ends the page loudly and adds
   nothing; the rule plus space does the same job quietly. --foot-bg/--foot-ink stay
   in the token set so a business that wants a dark foot can still have one. */
.foot {
  background: var(--foot-bg);
  color: var(--foot-ink);
  padding: 0 0 clamp(2.5rem, 6vw, 3.5rem);
}
/* A surface card, not a bare row - the same treatment as the menu cards and the
   hours card, so the foot belongs to the page rather than trailing off it. */
.foot__in {
  display: flex; flex-wrap: wrap; align-items: baseline;
  justify-content: space-between; gap: 1.25rem 2rem;
  background: var(--surface);
  border: 1px solid color-mix(in srgb, var(--line) 80%, var(--bg));
  border-radius: var(--r);
  padding: clamp(1.5rem, 3.2vw, 1.9rem);
}
.foot__nav { display: flex; flex-wrap: wrap; gap: .75rem 1.9rem; font-size: .94rem; color: var(--muted); }
.foot__nav a { text-decoration: none; }
.foot__nav a:hover { color: var(--ink); }
.foot__fine {
  display: flex; flex-wrap: wrap; align-items: flex-start;
  justify-content: space-between; gap: 1rem 2.5rem;
  margin-top: clamp(1.6rem, 3.5vw, 2.5rem);
}
.foot__notice { max-width: 52ch; }
.foot__name { font-family: var(--font-display); font-size: 1.4rem; font-weight: var(--display-weight); }
.foot__meta { color: var(--muted); font-size: .92rem; margin-top: .32rem; }
.foot__meta a { text-decoration: none; border-bottom: 1px solid color-mix(in srgb, var(--muted) 40%, transparent); }
.foot__social { display: flex; gap: 1.2rem; flex-wrap: wrap; margin-top: .95rem; }
.foot__by { margin-top: .95rem; font-size: .82rem; color: var(--muted); }

/* Required on every generated page - docs/05 rule 2 for a pitch mockup, docs/09
   for the fictional/permission line on a portfolio piece. build-mockup.js refuses
   to write the file without it, so never restyle this into invisibility. */
/* On a light footer this must stay legible without a slab behind it -
   build-mockup.js refuses to write a page without this line (docs/05 rule 2,
   docs/09), so it can be quiet but never faint. */
.disclaimer {
  margin-top: 0;
  color: var(--muted);
  font-size: .84rem; line-height: 1.6; max-width: 46ch;
}

/* --- fixed tap-to-call ------------------------------------------------------- */

.callbar {
  position: fixed; left: 0; right: 0; bottom: 0; z-index: 40;
  display: flex; gap: .5rem;
  padding: .6rem max(var(--gutter), env(safe-area-inset-left, 0px))
           calc(.6rem + env(safe-area-inset-bottom, 0px))
           max(var(--gutter), env(safe-area-inset-right, 0px));
  background: color-mix(in srgb, var(--surface) 88%, transparent);
  backdrop-filter: blur(16px); -webkit-backdrop-filter: blur(16px);
  border-top: 1px solid var(--line);
}
.callbar__main {
  flex: 1; display: inline-flex; align-items: center; justify-content: center; gap: .55rem;
  min-height: 50px; border-radius: var(--r-btn);
  background: var(--brand); color: var(--on-brand);
  font-weight: 600; font-size: 1rem; text-decoration: none;
  transition: background-color .22s var(--ease);
}
.callbar__main:hover { background: var(--brand-dark); }
.callbar__alt {
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 50px; min-height: 50px; padding: 0 .95rem;
  border: 1px solid var(--line); border-radius: var(--r-btn);
  color: var(--ink); text-decoration: none;
  transition: border-color .22s var(--ease), color .22s var(--ease);
}
.callbar__alt:hover { border-color: var(--brand); color: var(--brand); }

/* --- motion -----------------------------------------------------------------
   Scroll-driven, declared entirely in CSS. No IntersectionObserver, no rAF loop,
   nothing on the main thread. Two guards make it safe to ship:

     @supports  browsers without scroll timelines never see a keyframe, so content
                is never hidden waiting for an animation that cannot run.
     @media     anyone who asked for less motion gets none of it, by the rules not
                existing rather than by being fought afterwards.

   Every keyframe runs FROM a hidden state TO the element's natural one, so a
   dropped animation renders normally. Ranges stay inside a single named range
   (entry): mixing two - "entry 5% cover 20%" - can resolve inverted and strand an
   element at opacity 0 forever, which is the bug this comment exists to prevent
   anyone reintroducing.
   --------------------------------------------------------------------------- */

@media (hover: hover) {
  .card:hover, .quote:hover {
    transform: translateY(-3px);
    box-shadow: 0 18px 40px -22px rgb(0 0 0 / .3);
  }
  .strip img { transition: transform .6s var(--ease); }
  .strip li:hover img { transform: scale(1.04); }
}

@supports (animation-timeline: view()) {
  @media (prefers-reduced-motion: no-preference) {

    /* TRANSFORM ONLY - opacity is deliberately not animated here, and this is not
       a style preference.
       A reveal that fades from opacity 0 makes the animation load-bearing for
       whether the content can be read at all. If a scroll timeline is ever wrong -
       inactive container, a range that resolves oddly, an engine bug - the element
       is stranded invisible and a prospect opens a blank page. I could not verify
       scroll-timeline behaviour reliably in headless Chrome (three instruments,
       three different answers), so the design must not depend on being right.
       Worst case here is an element sitting 18px low, which nobody notices. */
    @keyframes rise { from { transform: translateY(18px); } to { transform: none; } }
    @keyframes riseSm { from { transform: translateY(11px); } to { transform: none; } }
    /* The photo is never quite still - most of what reads as considered, for free. */
    @keyframes heroDrift { from { transform: scale(1.1); } to { transform: scale(1); } }

    /* The photo keeps drifting as the page moves. The copy no longer parallaxes:
       it sits on paper now, and sliding body text around is the kind of motion
       that reads as a gimmick rather than as craft. */
    .hero__img {
      animation: heroDrift linear both;
      animation-timeline: scroll(root block);
      animation-range: 0 110svh;
    }

    /* Each element carries its own view() timeline, so a row staggers naturally
       as it scrolls past - no per-child delays to maintain. */
    .section__head, .visit__col, .hourscard, .section__foot {
      animation: rise linear both;
      animation-timeline: view();
      animation-range: entry 0% entry 82%;
    }
    .menu__section, .quote, .card {
      animation: rise linear both;
      animation-timeline: view();
      animation-range: entry 0% entry 78%;
    }
    .strip li {
      animation: riseSm linear both;
      animation-timeline: view();
      animation-range: entry 0% entry 72%;
    }
    .foot__col {
      animation: riseSm linear both;
      animation-timeline: view();
      animation-range: entry 0% entry 80%;
    }
  }
}

/* --- breakpoints ------------------------------------------------------------
   Desktop is not "the phone, wider". The hero gets a bigger type ramp and its
   copy is held to a column so a 1600px screen does not stretch a tagline across
   the whole viewport; the menu becomes two columns; reviews and photos become
   real grids; the call bar stops being a bar and becomes a floating pill. */

@media (min-width: 600px) {
  :root { --gutter: 2rem; }
  .hero__cta .btn { flex: 0 1 auto; min-width: 0; }
  .strip li { width: min(44%, 340px); }
  .section__actions .btn { flex: 0 1 auto; }
  .cards  { grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); }
  .quotes { grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); }
}

@media (min-width: 880px) {
  /* The real split. The copy column is padded so its left edge lands on the same
     line as .wrap content everywhere else on the page - calc() against 100% of the
     full-bleed hero, never 100vw, which would be wrong by the scrollbar width. */
  .hero {
    /* 1fr / 620px with a 48px gap, per the spec: at 1440 that makes the copy
       column exactly 644px wide, which is what its line breaks depend on. */
    grid-template-columns: minmax(0, 1fr) 620px;
    gap: 3rem;
    align-items: center;
    min-height: min(var(--hero-h), 88svh);
  }
  .hero__media { order: 0; height: 100%; min-height: inherit; }
  .dial { order: 0; }
  /* Softens the seam between photograph and paper without painting a box over the
     image, so it works whatever the photo happens to be. */
  .hero__media {
    -webkit-mask-image: linear-gradient(to right, transparent 0, #000 9%);
    mask-image: linear-gradient(to right, transparent 0, #000 9%);
  }
  .hero { padding-inline: 4rem; }
  .hero__in { padding-inline: 0; padding-block: var(--band); }
  .hero__cta .btn { flex: 0 1 auto; min-width: 0; }

  /* --- banner variant (photos.hero.layout: "banner") ------------------------
     One full-bleed image with the copy over its empty side. Only correct for a
     photograph actually shot with negative space for text - a wide plate-on-white
     banner, not a lead's phone snap. Opt in per piece; the split above stays the
     default precisely because it cannot be ruined by a bad photo.
     Desktop only: cropping a 1.8:1 banner to a phone throws away the very
     negative space the layout depends on, so a phone keeps the stacked hero. */
  .hero--banner { grid-template-columns: 1fr; position: relative; isolation: isolate; }
  .hero--banner .hero__media {
    position: absolute; inset: 0; height: 100%;
    -webkit-mask-image: none; mask-image: none;
  }
  /* A wash in the PAGE colour, not a dark scrim. On a banner that already carries
     its own white space this is invisible; on one that does not, it is what keeps
     the headline readable instead of hoping. */
  .hero--banner .hero__media::after {
    content: ""; position: absolute; inset: 0;
    background: linear-gradient(to right,
      var(--bg) 0%,
      color-mix(in srgb, var(--bg) 90%, transparent) 24%,
      color-mix(in srgb, var(--bg) 45%, transparent) 42%,
      transparent 58%);
  }
  .hero--banner .hero__in {
    position: relative; z-index: 1;
    max-width: min(46rem, 54%);
    padding-right: 0;
  }
}

@media (min-width: 760px) {
  :root {
    --hero-h: 760px;
    --hero-pad: 4rem;
    --hero-size: clamp(3rem, 6.4vw, 5.125rem);
    --h2-size: clamp(2.1rem, 3.9vw, 3rem);
    --band: clamp(5rem, 9vw, 8rem);
  }
  body { font-size: 17.5px; }
  .topnav { display: flex; gap: 1.9rem; margin-right: 1.6rem; }
  /* Cards side by side. auto-fit rather than a fixed count so three categories
     make three columns and five make a sensible wrap - the count varies per
     business and a fixed track leaves dead columns. */
  .menu { grid-template-columns: repeat(auto-fit, minmax(210px, 1fr)); gap: 1.25rem; }
  .visit { grid-template-columns: 1.2fr .8fr; align-items: start; }
  .menu__layout { grid-template-columns: minmax(240px, 340px) minmax(0, 1fr); align-items: start; }
  .menu__actions { flex-direction: column; }
  .menu__actions .btn { flex: none; }
  .craft { grid-template-columns: minmax(0, 1.05fr) minmax(0, 1fr); }
  .stat__n { font-size: 2.9rem; }
  .foot__in { grid-template-columns: 1fr 1fr; gap: 2.5rem; }
  .foot__col--notice { justify-self: end; max-width: 44ch; }
  .hero__cta-map { display: inline-flex; }
  .hourscard { padding: 2rem 1.9rem; }
}

@media (min-width: 1000px) {
  :root { --hero-h: 780px; --gutter: 4rem; --r: 20px; }
  /* auto-fit, not a fixed track count. Businesses have 3, 4 or 6 photos and the
     grid has to hold all of them. An earlier version gave the first frame a
     two-column span, which needs five slots in a four-column grid - with exactly
     four photos the last one wrapped onto a row of its own beside a large void.
     A flourish that only works at one photo count is not worth the hole. */
  .strip {
    display: grid; grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));
    gap: .7rem;
    overflow: visible; padding-inline: 0;
    width: min(var(--maxw), 100% - calc(var(--gutter) * 2)); margin-inline: auto;
  }
  .strip li { width: auto; }

  .section__head { margin-bottom: 3.75rem; }
  .quote { padding: 2rem 1.9rem; }

  /* the call bar stops spanning the screen and becomes a floating action pill */
  body { padding-bottom: 0; }
  .callbar {
    left: auto; right: 1.75rem; bottom: 1.75rem;
    border: 1px solid var(--line); border-radius: var(--r-btn);
    padding: .4rem; box-shadow: 0 20px 46px -22px rgb(0 0 0 / .34);
  }
}

@media (min-width: 1320px) {
  :root { --hero-h: 820px; }
}

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after {
    animation-duration: .001ms !important;
    transition-duration: .001ms !important;
    /* Scroll-driven animations are scrubbed by position, not duration, so the two
       lines above do not touch them. Drop the timeline and the name outright. */
    animation-timeline: auto !important;
    animation-name: none !important;
  }
}

@media print {
  .callbar, .skip-link, .topbar__call { display: none; }
  .hero { min-height: 0; }
}

/* ===========================================================================
   MERCADO layer — texture, badge, dial, and the motion system.

   WHY JS DRIVES THE REVEALS. An earlier version used CSS scroll-timelines and
   animated opacity from 0. That makes the animation load-bearing for whether the
   text can be read at all, and I could not verify scroll-timeline behaviour
   reliably in headless. Here the hidden state is applied ONLY under `.js`, which
   the inline script sets on itself. No script, no hidden state, no blank page —
   the failure mode is "no animation", never "no content".
   =========================================================================== */

/* --- surfaces -------------------------------------------------------------- */

.tex-paper, .tex-wall, .tex-slate { position: relative; }
.tex-paper {
  background-color: var(--surface);
  background-image:
    radial-gradient(80% 70% at 78% 22%, color-mix(in srgb, var(--brand) 7%, transparent) 0%, transparent 62%),
    radial-gradient(70% 60% at 8% 92%, color-mix(in srgb, var(--ink) 6%, transparent) 0%, transparent 60%),
    /* The warm wash the whole hero sits on: white at the top edge falling to a
       tint of the page ground. Without it the band reads cold and grey. */
    radial-gradient(100% 80% at 50% 0%, #fff 0%, color-mix(in srgb, var(--bg) 70%, #fff) 100%),
    var(--grain);
  background-blend-mode: normal, normal, normal, multiply;
}
/* This is the artboard's `.plaster`: a wall lit from the top-left, with a breath
   of the brand in the opposite corner. The second gradient was missing and the
   highlight was a shade weak, which is most of why the lower bands read as flat
   fills next to the design's graded ones. */
.tex-wall {
  background-color: var(--bg);
  background-image:
    radial-gradient(120% 90% at 12% 8%, rgba(255,255,255,.9) 0%, transparent 55%),
    radial-gradient(90% 70% at 88% 20%, color-mix(in srgb, var(--brand) 5%, transparent) 0%, transparent 60%),
    var(--grain-wall);
  background-blend-mode: normal, normal, multiply;
}
.tex-slate {
  background-color: var(--ink);
  color: var(--bg);
  background-image:
    radial-gradient(80% 60% at 20% 10%, color-mix(in srgb, var(--brand) 18%, transparent) 0%, transparent 60%),
    var(--grain);
  background-blend-mode: normal, screen;
}

/* --- badge ----------------------------------------------------------------- */

.badge {
  width: 92px; height: 92px; flex: none;
  font-family: var(--font-display);
}
@media (min-width: 760px) { .badge { width: 108px; height: 108px; } }

/* --- dial ------------------------------------------------------------------
   The plate is the menu. The ring carries the item names on a real arc, so they
   curve with it instead of being rotated one by one. */

.dial {
  position: relative; width: min(100%, 560px); aspect-ratio: 1;
  margin-inline: auto; color: var(--ink); justify-self: center;
  /* Labels on a textPath report a bounding box past the viewBox even though the
     SVG clips them, which is enough to widen documentElement.scrollWidth. */
  overflow: hidden;
}
.dial__ring { position: absolute; inset: 0; width: 100%; height: 100%; font-family: var(--font-display); }
.dial__plate {
  position: absolute; left: 50%; top: 50%;
  width: 68%; aspect-ratio: 1; transform: translate(-50%, -50%);
  border-radius: 50%; overflow: hidden;
  box-shadow: 0 40px 80px -38px rgb(0 0 0 / .55);
}
.dial__plate .hero__img { width: 100%; height: 100%; object-fit: cover; }
.dial__now {
  position: absolute; left: 50%; bottom: 8%; transform: translateX(-50%);
  display: inline-flex; align-items: center; white-space: nowrap;
  height: 44px; padding: 0 22px; border-radius: 999px;
  background: var(--ink); color: var(--bg);
  font-family: var(--font-display); font-size: .88rem; font-weight: 700;
  letter-spacing: .16em; text-transform: uppercase;
}

/* --- motion ----------------------------------------------------------------
   Everything below only applies once the inline script adds `.js`. */

@media (prefers-reduced-motion: no-preference) {

  .js .reveal, .js .reveal-self { opacity: 0; transform: translateY(24px); }
  .js .reveal.is-in, .js .reveal-self.is-in {
    opacity: 1; transform: none;
    transition: opacity .7s var(--ease), transform .7s var(--ease);
  }
  /* Children stagger off the parent's reveal, so a row arrives as a run. */
  .js .reveal[data-stagger] > * { opacity: 0; transform: translateY(18px); }
  .js .reveal[data-stagger].is-in > * {
    opacity: 1; transform: none;
    transition: opacity .6s var(--ease), transform .6s var(--ease);
  }
  .js .reveal[data-stagger].is-in > *:nth-child(1) { transition-delay: .04s; }
  .js .reveal[data-stagger].is-in > *:nth-child(2) { transition-delay: .12s; }
  .js .reveal[data-stagger].is-in > *:nth-child(3) { transition-delay: .20s; }
  .js .reveal[data-stagger].is-in > *:nth-child(4) { transition-delay: .28s; }
  .js .reveal[data-stagger].is-in > *:nth-child(5) { transition-delay: .36s; }
  .js .reveal[data-stagger].is-in > *:nth-child(6) { transition-delay: .44s; }

  /* The hero arrives on load rather than on scroll: it is already in view. */
  .js [data-reveal="hero"] > * { opacity: 0; transform: translateY(26px); }
  .js [data-reveal="hero"].is-in > * {
    opacity: 1; transform: none;
    transition: opacity .8s var(--ease), transform .8s var(--ease);
  }
  .js [data-reveal="hero"].is-in > *:nth-child(1) { transition-delay: .05s; }
  .js [data-reveal="hero"].is-in > *:nth-child(2) { transition-delay: .14s; }
  .js [data-reveal="hero"].is-in > *:nth-child(3) { transition-delay: .23s; }
  .js [data-reveal="hero"].is-in > *:nth-child(4) { transition-delay: .32s; }
  .js [data-reveal="hero"].is-in > *:nth-child(5) { transition-delay: .41s; }

  /* The dial settles into place: the ring unwinds, the plate eases up. */
  .js .reveal-self.dial { opacity: 1; transform: none; }
  .js .dial__ring { opacity: 0; transform: rotate(-14deg); transform-origin: 50% 50%; }
  .js .dial.is-in .dial__ring {
    opacity: 1; transform: rotate(0deg);
    transition: opacity 1s var(--ease), transform 1.5s var(--ease);
  }
  .js .dial__plate { opacity: 0; transform: translate(-50%, -50%) scale(.9); }
  .js .dial.is-in .dial__plate {
    opacity: 1; transform: translate(-50%, -50%) scale(1);
    transition: opacity .8s var(--ease), transform 1.1s var(--ease);
    transition-delay: .1s;
  }
  .js .dial__now { opacity: 0; transform: translateX(-50%) translateY(10px); }
  .js .dial.is-in .dial__now {
    opacity: 1; transform: translateX(-50%);
    transition: opacity .6s var(--ease), transform .6s var(--ease);
    transition-delay: .7s;
  }
  .dial__halo { animation: dialPulse 3s var(--ease) infinite; transform-origin: 26px 290px; }
  @keyframes dialPulse {
    0%, 100% { opacity: .4; r: 13; }
    50%      { opacity: 0;  r: 22; }
  }

  /* A slow drift on the plate as the page moves, so the hero is never quite still. */
  @supports (animation-timeline: scroll()) {
    .dial__plate img {
      animation: plateDrift linear both;
      animation-timeline: scroll(root block);
      animation-range: 0 110svh;
    }
    @keyframes plateDrift { from { transform: scale(1.08); } to { transform: scale(1); } }
  }
}

/* Hover: the card lifts, the photo pushes in behind it. */
@media (hover: hover) {
  .menu__section { overflow: hidden; }
  .strip li img, .craft__img { transition: transform .7s var(--ease); }
  .strip li:hover img { transform: scale(1.05); }
  .badge { transition: transform .6s var(--ease); }
  .mark:hover .badge { transform: rotate(-8deg); }
}

/* --- dial, responsive ------------------------------------------------------
   These live at the END on purpose. The Mercado layer above declares the base
   .dial width, and an earlier mobile rule for the same selector loses to it on
   source order alone — which is exactly how the dial stayed 375px tall on a
   667px phone and pushed tap-to-call below the fold. */

/* Copy first on mobile, dial after it. The old split hero led with the photo
   because the headline was just the business name; here the headline IS the
   message, and putting the plate above it pushed tap-to-call 233px below the
   fold on a 667px phone. */
.dial { width: min(84%, 320px); order: 0; margin: .5rem auto 0; }

@media (min-width: 880px) {
  .dial { width: min(100%, 560px); order: 0; margin-bottom: 0; }
}

/* Short screens - an SE, or any phone in landscape. The plate yields first,
   because on a screen this size the call button matters more than the food. */
@media (max-height: 700px) {
  .dial { width: min(52%, 190px); }
  /* A four-line condensed headline is what pushed the call button 32px past the
     fold on a 667px phone. The type yields, the button does not. */
  :root { --hero-size: clamp(1.85rem, 7.4vw, 2.4rem); }
  .hero__tagline { margin-top: .85rem; font-size: 1rem; line-height: 1.45; }
  .facts { margin-top: 1rem; gap: .4rem 1.1rem; }
  .hero__cta { margin-top: 1.25rem; }
  .hero__in { padding-top: 1.1rem; }
}

/* --- menu: category row + photo cards --------------------------------------
   The reference's menu leads with pictures, not a list. Both shapes exist here:
   photo cards when the items carry photography, the readable list when they do
   not — which is what most leads will actually have. */

.menu__head { text-align: center; max-width: 60ch; margin-inline: auto; }
.menu__head .section__lede { margin-inline: auto; }

.cats {
  display: flex; flex-wrap: wrap; justify-content: center;
  gap: .9rem clamp(1.5rem, 4vw, 2.9rem);
  margin-top: clamp(1.5rem, 3vw, 2rem);
}
.cat {
  display: inline-flex; align-items: center; gap: .6rem;
  font-family: var(--font-display);
  font-size: .82rem; font-weight: 600; letter-spacing: .14em; text-transform: uppercase;
  color: var(--muted);
}
.cat--on { color: var(--accent); }
.cat .menu__icon { width: 22px; height: 22px; }

.fcards {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(210px, 1fr));
  gap: clamp(1rem, 2.2vw, 1.4rem);
  margin-top: clamp(2rem, 4vw, 2.75rem);
}
.fcard { position: relative; }
.fcard__img {
  width: 100%; aspect-ratio: 4 / 3; object-fit: cover;
  border-radius: calc(var(--r) * .35);
  background: var(--tint);
}
/* The white card rides up over the photo — the overlap is what stops this
   reading as a plain image grid. */
.fcard__body {
  position: relative;
  margin: -2.25rem .85rem 0;
  padding: 1.25rem 1rem 1.35rem;
  background: var(--surface);
  border-radius: calc(var(--r) * .4);
  box-shadow: 0 16px 32px -20px rgb(0 0 0 / .45);
  text-align: center;
}
.fcard__dot {
  position: absolute; top: .9rem; left: .9rem;
  width: 12px; height: 12px; border-radius: 50%;
  border: 2px solid var(--brand);
}
.fcard__dot::after {
  content: ""; display: block; width: 4px; height: 4px; margin: 2px auto 0;
  border-radius: 50%; background: var(--brand);
}
.fcard__dot--veg { border-color: #4E7A2E; }
.fcard__dot--veg::after { background: #4E7A2E; }
.fcard__name {
  font-family: var(--font-display); font-weight: var(--display-weight);
  font-size: 1.28rem; letter-spacing: .06em; text-transform: uppercase;
}
.fcard__price {
  margin-top: .35rem;
  font-family: var(--font-display);
  font-size: .82rem; font-weight: 600; letter-spacing: .1em; text-transform: uppercase;
  color: var(--accent);
}
.fcard__cta {
  display: inline-flex; align-items: center; justify-content: center;
  min-height: 44px; margin-top: .9rem; padding: 0 1.35rem;
  border-radius: var(--r-btn);
  background: color-mix(in srgb, var(--ink) 8%, var(--surface));
  color: var(--muted);
  font-family: var(--font-display);
  font-size: .78rem; font-weight: 700; letter-spacing: .14em; text-transform: uppercase;
  text-decoration: none;
  transition: background-color .22s var(--ease), color .22s var(--ease);
}
.fcard:first-child .fcard__cta { background: var(--brand); color: var(--on-brand); }
.fcard__cta:hover { background: var(--brand); color: var(--on-brand); }

/* --- utilities -------------------------------------------------------------
   Three tonal bands, edge to edge. Each steps a shade darker so the row reads as
   one object rather than three cards. */

.utils { display: grid; grid-template-columns: 1fr; }
.util {
  display: flex; gap: 1.15rem;
  padding: clamp(2.25rem, 5vw, 2.9rem) var(--gutter);
}
.util--1 { background: var(--bg); }
.util--2 { background: color-mix(in srgb, var(--ink) 5%, var(--bg)); }
.util--3 { background: color-mix(in srgb, var(--ink) 10%, var(--bg)); }
.util__img { width: 62px; height: 62px; border-radius: 50%; object-fit: cover; flex: none; }
.util__h {
  font-family: var(--font-display); font-weight: var(--display-weight);
  font-size: 1.16rem; letter-spacing: .08em; text-transform: uppercase;
}
.util__p { margin-top: .55rem; font-size: .94rem; line-height: 1.65; color: var(--muted); }

@media (min-width: 760px) {
  .utils { grid-template-columns: repeat(3, 1fr); }
  .util { padding-inline: clamp(1.75rem, 3vw, 2.5rem); }
}

/* --- the hanging sign + visit pair ------------------------------------------ */

.sign {
  display: flex; flex-wrap: wrap; justify-content: center;
  border-radius: var(--r-btn); overflow: hidden;
  box-shadow: 0 20px 42px -22px rgb(0 0 0 / .5);
  margin-top: calc(clamp(1.5rem, 3vw, 2rem) * -1);
  width: fit-content; max-width: 100%;
  font-family: var(--font-display);
  font-size: .9rem; letter-spacing: .1em; text-transform: uppercase;
}
.sign__brand { display: inline-flex; align-items: center; min-height: 52px; padding: 0 1.6rem;
  background: var(--brand); color: var(--on-brand); font-weight: 700; }
.sign__addr { display: inline-flex; align-items: center; min-height: 52px; padding: 0 1.6rem;
  background: var(--ink); color: color-mix(in srgb, var(--bg) 82%, transparent);
  font-family: var(--font-body); letter-spacing: 0; text-transform: none; font-size: .95rem; }
.sign__cta { display: inline-flex; align-items: center; min-height: 52px; padding: 0 1.5rem;
  background: color-mix(in srgb, var(--ink) 88%, var(--bg)); color: var(--accent);
  font-weight: 700; text-decoration: none; }
.sign__cta:hover { background: var(--ink); }

.visit__pics { position: relative; }
.visit__pic-a { width: 100%; aspect-ratio: 3 / 2; object-fit: cover; border-radius: calc(var(--r) * .35); }
.visit__pic-b { display: none; }

.hours + .fineprint { margin-top: 1rem; }

@media (min-width: 880px) {
  .visit { grid-template-columns: minmax(0, 1.15fr) minmax(0, .85fr); align-items: center; }
  .visit__pic-b {
    display: block; position: absolute; right: -1.75rem; bottom: -2rem;
    width: 42%; aspect-ratio: 1; object-fit: cover;
    border: 10px solid var(--bg); border-radius: calc(var(--r) * .35);
    box-shadow: 0 22px 44px -24px rgb(0 0 0 / .5);
  }
}

/* --- corkboard prints on the slate band ------------------------------------- */

.prints { position: relative; min-height: 320px; }
.print {
  width: 62%; object-fit: cover;
  border: 9px solid var(--surface);
  box-shadow: 0 26px 48px -26px rgb(0 0 0 / .85);
}
.print--1 { position: relative; aspect-ratio: 4 / 3; transform: rotate(-4deg); z-index: 1; }
.print--2 { position: absolute; right: 0; top: -6%; width: 54%; aspect-ratio: 3 / 4; transform: rotate(3deg); z-index: 2; }
.print--3 { position: absolute; left: 16%; bottom: -14%; width: 52%; aspect-ratio: 4 / 3; transform: rotate(-2deg); z-index: 3; }

@media (max-width: 879px) {
  .prints { display: grid; grid-template-columns: repeat(2, 1fr); gap: .75rem; min-height: 0; }
  .print { position: static; width: 100%; transform: none; border-width: 6px; }
  .print--3 { grid-column: span 2; width: 60%; margin-inline: auto; }
}

/* --- footer, centred as the spec has it ------------------------------------- */

.foot__in {
  flex-direction: column; align-items: center; text-align: center;
  background: none; border: 0; border-radius: 0; padding: 0;
}
.foot__fine { flex-direction: column; align-items: center; text-align: center; gap: .75rem; }
.foot__notice { max-width: 60ch; }
.foot__nav { justify-content: center; }

/* ===========================================================================
   SPEC PARITY — values measured against the design with tools/compare-spec.js
   and set to match at 1440px. Each number here came off that diff, not off an
   impression. Re-run the tool before changing any of them.
   =========================================================================== */

:root { --h2-track: .035em; }

/* Type set to the spec's exact metrics */
.hero__title { line-height: .96; }
.section__title { letter-spacing: var(--h2-track); line-height: 1.12; }

.eyebrow { font-size: .8125rem; letter-spacing: .28em; line-height: normal; margin-bottom: 1.25rem; }

.hero__tagline {
  margin-top: 1.625rem;
  font-size: 1.125rem;
  line-height: 1.65;
  font-weight: 400;
  color: color-mix(in srgb, var(--ink) 82%, var(--bg));
  max-width: 40ch;
}

.topnav a {
  font-family: var(--font-display);
  font-size: .9375rem; font-weight: 600;
  letter-spacing: .13em; text-transform: uppercase;
  line-height: normal; opacity: 1; color: var(--ink);
}

.btn { line-height: normal; }
.btn--lg { font-weight: 700; letter-spacing: .12em; }

.dial__now { font-size: .875rem; letter-spacing: .16em; padding: 0 1.5rem; line-height: normal; }

/* The slate band inverts the palette. Without this the body copy and the stat
   labels render in the light-ground muted grey ON the dark band — the single
   worst thing the spec diff caught, because it is a legibility failure rather
   than a spacing one. */
.tex-slate .craft__body,
.tex-slate .stat__l { color: color-mix(in srgb, var(--bg) 74%, transparent); }
.tex-slate .kicker { color: var(--accent); }
.tex-slate .stat__n { color: var(--accent); font-size: 2.75rem; line-height: normal; }
.tex-slate .stat__l { font-size: .8125rem; line-height: 1.55; margin-top: .375rem; }
.tex-slate .craft__body { font-size: 1.03125rem; line-height: 1.8; margin-top: 1.625rem; }
.tex-slate .stats { border-top-color: color-mix(in srgb, var(--bg) 26%, transparent); }

/* --- spec parity, pass two -------------------------------------------------- */

:root {
  /* Body copy is a shade of its own, not a mix of ink and ground: the spec's
     value is not on the line between them, so deriving it lands near but never on.
     Overridable per business like every other colour. */
  --body-ink: #4A443A;
  --slate-ink: rgba(242, 239, 233, .74);
  --slate-mute: rgba(242, 239, 233, .58);
}

.hero__tagline { color: var(--body-ink); }
.craft__body { color: var(--body-ink); }
.tex-slate .craft__body { color: var(--slate-ink); }
.tex-slate .stat__l { color: var(--slate-mute); }

/* Stats sit in a row at their natural width; a grid gave them equal columns and
   the numbers stopped lining up with the spec. */
.stats { display: flex; flex-wrap: wrap; gap: 2.875rem; }
.stat__n {
  font-family: var(--font-display);
  text-transform: var(--display-transform);
  line-height: normal;
}

.util__h { font-size: 1.1875rem; font-weight: 700; letter-spacing: .1em; line-height: normal; }
.stat__l { max-width: 22ch; }
.fcard__cta { margin-top: 0; font-size: .75rem; letter-spacing: .14em; }
.fcard__price { margin-bottom: .875rem; }

/* --- spec parity, pass three ------------------------------------------------ */

@media (min-width: 1024px) {
  /* The spec puts the photo stack on the left and the copy on the right, in the
     narrower track. Order, not markup: on a phone the copy still leads. */
  .craft {
    grid-template-columns: minmax(0, 1.05fr) minmax(0, 1fr);
    gap: 4rem;
  }
  .craft__col { order: 2; }

  .util { padding: 44px 40px; gap: 20px; }
}

.craft__body { margin-top: 26px; max-width: 44ch; }

.fcard__cta { line-height: normal; padding-inline: 22px; }

/* The icons are inline SVG, never a dingbat: a text glyph renders at whatever
   width the fallback font happens to give it, and picks up the font's colour
   rather than the button's. */
.ico { flex: none; }

/* --- band rhythm ------------------------------------------------------------
   The spec does not pad every band the same. Each one is set to the space its
   own content needs, and the grounds step around the page colour so the stack
   reads as a sequence rather than one flat sheet. The numbers are the design's;
   the colours come from content, so another palette re-tints without a fork. */

@media (min-width: 1024px) {
  .hero { padding-bottom: 4rem; }

  /* background-COLOR, never the shorthand. These sections carry .tex-paper /
     .tex-wall, and `background:` resets background-image - so this line was
     silently throwing away the gradient and grain the texture class had just
     set, one declaration earlier in the cascade. Sampled down the visit band the
     artboard runs #fafaf9 at the top to #edece9 at the bottom; ours was #fbfaf7
     the whole way, which is the "flat" the design never looks. */
  .section--visit { background-color: var(--band-up); padding-block: 0 5.5rem; }
  .section--menu  { background-color: var(--band-down); padding-block: 5.125rem 5.625rem; }
  .section--craft { padding-block: 5.625rem 6rem; }
  /* The artboard's utility strip has no band padding at all: the three columns
     tile edge to edge and their own 44px is the entire height of the band. Ours
     added 62/54 around them, which made the strip 119px too tall. */
  .utils          { background-color: transparent; padding-block: 0; }
}

/* The hero CTA carries the spec's coloured glow rather than a generic drop
   shadow - it is the one button the page is built around. */
.btn--brand.btn--lg { box-shadow: 0 18px 34px -18px color-mix(in srgb, var(--brand) 70%, transparent); }

/* The spec's quiet button is outlined in ink at 1.5px, not in the hairline rule
   colour - it is the hero's second action and has to hold its own next to the
   brand fill. */
.btn--quiet { --btn-line: var(--ink); outline-width: 1.5px; }
.btn--quiet:hover { --btn-line: var(--brand); }

/* One phrase in the headline carries the brand colour - the design's emphasis,
   not decoration. Which phrase is a content decision. */
.hero__title .hl { color: var(--brand); }

/* --- hero rhythm and dial scale, per the spec -------------------------------
   The hero was sized by a min-height and a full band of padding, which pushed
   the copy a hundred pixels down the page and stretched the band past the
   design. The spec sizes it from its content: 40px above the copy, nothing
   below, and a 620px dial that sets the height. */

@media (min-width: 1024px) {
  .hero { min-height: 0; }
  .hero__in { padding-block: 2.5rem 0; }
}

/* Scoped to the desktop grid. Unscoped, this beat the phone's own 320px cap on
   source order alone and blew the dial out to the full 390px, edge to edge,
   while the copy beside it stayed inset 22px - which is the exact mistake the
   comment above the mobile rules warns about. */
@media (min-width: 880px) {
  .dial { width: min(100%, 620px); }
}
.dial__now {
  bottom: 10.6%;   /* 66px on a 620px dial */
  height: 46px; padding: 0 24px;
}

/* --- hours, per the spec ----------------------------------------------------
   The day label is a kicker, not a table header: small, tracked, muted, and set
   in the display face so it reads as a label beside the time rather than as a
   competing column of text. */

.hours {
  gap: 10px 18px;
  margin-top: 30px;
  font-size: .97rem; line-height: 1.7; color: var(--body-ink);
}
.hours dt {
  font-family: var(--font-display);
  font-size: .75rem; font-weight: 600; letter-spacing: .18em;
  text-transform: var(--display-transform);
  color: var(--muted); padding-top: 5px;
}
.hours dd { text-align: left; }

/* --- the print collage, to the spec's geometry -------------------------------
   Three prints of roughly equal size, pinned like a corkboard. The earlier
   sizes were half again too big, so the stack read as one photo with corners
   sticking out rather than as three. */

@media (min-width: 880px) {
  .prints { position: relative; height: 470px; }
  .print {
    position: absolute; object-fit: cover;
    border: 11px solid var(--surface);
    box-shadow: 0 28px 50px -26px rgb(0 0 0 / .85);
  }
  .print--1 { left: 1%;    top: 9.8%; width: 41%;   aspect-ratio: 4 / 3; transform: rotate(-5deg); z-index: 1; }
  .print--2 { left: 33.5%; top: 0;    width: 38.5%; aspect-ratio: 3 / 4; transform: rotate(3deg);  z-index: 2; }
  .print--3 { left: 18.5%; top: 54.5%; width: 36.6%; aspect-ratio: 4 / 3; transform: rotate(-2deg); z-index: 3; }
  .print--2, .print--3 { right: auto; bottom: auto; }
}

/* --- footer, to the spec ---------------------------------------------------- */

.foot__nav a {
  font-family: var(--font-display);
  font-size: .8125rem; font-weight: 600;
  letter-spacing: .16em; text-transform: uppercase;
}
.foot__ask { margin-top: 1.1rem; font-size: .97rem; color: var(--body-ink); }
.foot__ask a { color: var(--brand); }
.foot__social { margin-top: .9rem; display: flex; gap: 1.25rem; justify-content: center; }
.foot__social a { font-size: .92rem; color: var(--muted); }

/* --- spec parity, pass three: the top band ----------------------------------
   Measured element by element against the artboard at 1440px rather than judged
   by eye, because the eye kept reporting "close enough" on a band that was 22px
   too tall with a seam across it. Every number in a comment below is what the
   artboard does.

   These live at the END for the same reason the dial rules do: they correct
   earlier declarations of the same selectors and have to win on source order. */

/* ONE ground behind header AND hero.
   The artboard has no header band at all - a single .wash wraps header and hero
   together, so the warm gradient runs unbroken from the top of the page down
   into the hero. Painting .topbar separately laid a hard edge across the full
   width at the header's bottom (#F2EFE9 above it, #FEFCFB below), which was the
   loudest single difference from the design. Moving the wash to <body> and
   sizing it to the BAND rather than to the document reproduces the artboard
   exactly, because the gradient percentages then resolve against the same box
   they resolve against there.

   Every section below the hero paints its own opaque ground over the top, so
   over-estimating the band costs nothing while under-estimating it puts the seam
   straight back - which is why the narrow-screen value is deliberately generous
   and only the artboard's own width gets the exact figure. */
:root { --topband: 1400px; }
@media (min-width: 1320px) { :root { --topband: 864px; } }

body {
  position: relative;
  /* Stays --bg, not --surface: the third gradient layer is opaque, so this
     colour is only ever visible OUTSIDE the band - under the footer, in the
     strip the mobile call bar reserves - where the page ground is what belongs. */
  background-color: var(--bg);
  background-image:
    radial-gradient(80% 70% at 78% 22%, color-mix(in srgb, var(--brand) 7%, transparent) 0%, transparent 62%),
    radial-gradient(70% 60% at 8% 92%, color-mix(in srgb, var(--ink) 6%, transparent) 0%, transparent 60%),
    radial-gradient(100% 80% at 50% 0%, #fff 0%, color-mix(in srgb, var(--bg) 70%, #fff) 100%),
    var(--grain);
  background-repeat: no-repeat;
  background-size: 100% var(--topband);
  background-blend-mode: normal, normal, normal, multiply;
}
.topbar { background: transparent; }
/* .hero keeps .tex-paper for the position:relative it brings, but has to stop
   painting its own ground or the seam simply moves down by the header's height. */
.hero.tex-paper { background-color: transparent; background-image: none; }

/* The drawn props. Absolute artboard coordinates are only meaningful while the
   layout still matches the artboard, so below that width they are hidden rather
   than left to collide with reflowed content. */
.props { display: none; }
.prop { position: absolute; pointer-events: none; }
@media (min-width: 1320px) {
  /* Centred on --page, not stretched across the viewport: these are placed
     against the artboard's frame, so on a wide screen a viewport-anchored layer
     would leave the chilli and the leaf stranded out in the margin. */
  .props--top {
    display: block;
    position: absolute; top: 0; left: 50%; transform: translateX(-50%);
    width: min(100%, var(--page)); height: var(--topband);
    overflow: hidden; pointer-events: none;
  }
  .props--foot {
    display: block;
    position: absolute; top: 0; bottom: 0; left: 50%; transform: translateX(-50%);
    width: min(100%, var(--page)); pointer-events: none;
  }
}
.foot { position: relative; overflow: hidden; }
/* The footer's own content has to be positioned to paint above the props, the
   same way the artboard lifts its footer nav with position: relative. */
.foot > .wrap { position: relative; }

/* Header. The artboard puts 24px above a 116px badge and NOTHING below it - the
   hero's own 40px top padding makes the gap under the header. Ours added 21.6px
   both sides of a 108px badge, which is the whole of the extra 22px of header
   height, and it pushed every element in the hero down by that much. */
.topbar { padding: 24px 0 0; }
.mark { padding: 0; }
@media (min-width: 760px) { .badge { width: 116px; height: 116px; } }
/* The artboard spaces this row with explicit gaps only. Leaving the flex row's
   own 1rem in play added itself to the nav's margin and put the whole cluster
   16px left of where it belongs. */
.topbar__in { gap: 0; }
.topnav { gap: 32px; margin-right: 32px; }
.topbar__call {
  min-height: 48px; padding: 0 24px; gap: 11px;
  font-size: 15px; letter-spacing: .12em;
}

/* The quiet button's ring is a real 1.5px border on the artboard, not an
   outline. An outline is painted outside the box and costs no layout, so ours
   came out 2px short in both directions. */
.btn--quiet { outline: 0; border: 1.5px solid var(--ink); }
.btn--quiet:hover { border-color: var(--brand); }
/* box-sizing is border-box here and content-box on the artboard, so the ring ate
   into the 60px instead of sitting outside it. Adding the two edges back keeps
   the ink box 60px tall, which is what the artboard measures. */
.btn--quiet.btn--lg { min-height: 63px; }
/* flex-start, not the default stretch: the artboard gives each button its own
   explicit height, so the ringed one being 3px taller must not drag the solid
   one up to match it. */
.hero__cta { gap: 12px; margin-top: 34px; align-items: flex-start; }

/* One phrase in the headline is an inline-block on the artboard, which is what
   gives the red line its own line box rather than the parent's. */
.hero__title .hl { display: inline-block; position: relative; }

/* Facts row. Three badges on one line at 14.5px with a 30px column gap, in a
   muted ink of its own - #5C5549 is near --muted but not it, and deriving one
   from the other lands close but never on. */
:root { --fact-ink: #5C5549; }
.facts {
  gap: 12px 30px; margin-top: 32px;
  font-size: 14.5px; color: var(--fact-ink);
  /* The artboard sets these on one leading-free line. Inheriting the body's 1.65
     made each chip 24px instead of 18px, and the extra 6px went through the
     grid's align-items:center as a 2px lift on everything above it. */
  line-height: normal;
}
.facts > li { gap: 9px; }
.fact__icon { color: var(--fact-ink); }
.fact--status { color: var(--fact-ink); font-weight: 400; }
.status__state { font-weight: 600; color: var(--ink); }
.status__state:empty { display: none; }
.status__dot { width: 9px; height: 9px; }
/* Closed reads in the brand red on the artboard, not in a warning orange. */
.fact--status[data-state="closed"] .status__dot { background: var(--brand); }

@media (min-width: 880px) {
  /* The artboard's 40px sits on the GRID, not on the copy column. With it on the
     column, the row's align-items:center split the difference and dropped the
     dial 22px above where the artboard puts it - the copy moved, the plate did
     not, and the two stopped sharing a top edge. */
  .hero { padding-top: 40px; }
  .hero__in { padding-block: 0; }
  .dial { margin-top: 0; }
  /* The artboard has no floating call bar, and on a wide screen ours sat on top
     of the hero's bottom-right corner. It stays on phones, where a fixed
     tap-to-call is the entire point (docs/05), and comes off where the design
     does not have one. */
  .callbar { display: none; }
}

/* The hanging sign: 48px tall, 30/30/26 padding, 17/15/14px type, and it laps
   the band above by exactly 25px. */
.sign { margin-top: -25px; font-size: 17px; }
.sign__brand { min-height: 48px; padding: 0 30px; font-size: 17px; }
.sign__addr { min-height: 48px; padding: 0 30px; font-size: 15px; color: #E4DED2; }
.sign__cta { min-height: 48px; padding: 0 26px; font-size: 14px; background: #2A2620; }
.sign__cta:hover { background: var(--ink); }

/* The plate starts at rest and drifts as the page moves. The old keyframe ran
   the other way, from 1.08 down to 1, so before the reader scrolled the food was
   cropped 8% tighter than the design - the hero's first impression was the one
   frame that did not match. */
@keyframes plateDrift { from { transform: scale(1); } to { transform: scale(1.06); } }

/* --- spec parity, pass four: the page container and the lower bands ---------

   THE CONTAINER. Grounds bleed, content does not. Every band paints the full
   viewport width and puts its content in the same measure, so the page reads as
   full-bleed bands rather than a boxed document on a wide screen.

   The hero was the one band that ignored the measure: it had a 64px inset and no
   ceiling, so past 1440 it kept widening while the header and every section
   stayed at 1312 and the header read as a narrow strip inside it. Giving the
   hero the same formula as .wrap fixes that at every width, without capping the
   grounds.

   --page is the measure PLUS its gutters - the artboard's full 1440 frame, not
   its 1312 content box. The drawn props are placed against that frame (the
   chilli sits 20px outside the copy edge, the leaf 14px inside the button's),
   so anchoring their layer to --page keeps every one of them correct as the
   container grows instead of stranding them out in the margin. */
:root { --page: calc(var(--maxw) + var(--gutter) * 2); }

@media (min-width: 880px) {
  .hero {
    width: min(var(--maxw), 100% - var(--gutter) * 2);
    margin-inline: auto;
    padding-inline: 0;
  }
}

/* Above 1600 the fixed 1312 starts to read as a stamp in the middle of the
   screen. 82vw is the width that is *already* in force at 1600, so the measure
   grows out of the fixed one with no jump at the breakpoint, and stops at 1520
   before the copy column turns into an unreadable line length. */
@media (min-width: 1600px) {
  :root { --maxw: min(1520px, 82vw); }
}

/* The artboard sets 68px between the hanging sign and the visit grid. Ours had
   none, which is the whole of the 65px the second band was short. */
@media (min-width: 880px) {
  .sign + .visit { margin-top: 68px; }
}

/* The three utility columns are a graded run on the artboard - #F2EFE9, #E9E7DF,
   #DEDBD1 - not one ground with two tints mixed off it, and each one is plaster
   like every other band rather than a flat fill. */
.util--1, .util--2, .util--3 {
  background-color: var(--bg);
  background-image:
    radial-gradient(120% 90% at 12% 8%, rgba(255,255,255,.9) 0%, transparent 55%),
    radial-gradient(90% 70% at 88% 20%, color-mix(in srgb, var(--brand) 5%, transparent) 0%, transparent 60%),
    var(--grain-wall);
  background-blend-mode: normal, normal, multiply;
}
.util--2 { background-color: var(--band-down); }
.util--3 { background-color: #DEDBD1; }
/* 14px, not .94rem: the extra pixel took every column's copy to 50px of leading
   where the artboard has 46, and three columns of that is a taller band. */
.util__p { font-size: 14px; margin-top: 9px; color: var(--fact-ink); }

/* Footer. The artboard is a centred stack - links, the feedback line, then three
   lines of fine print - inside 62px above and 54px below. Ours had no top
   padding and carried the fine print as separate blocks with their own margins,
   which ran the band 131px long. */
.foot { padding: 62px 0 54px; }
/* The column's own gap stacks on top of each child's margin, so the artboard's
   26px was arriving as 66. One or the other, not both - the margins win because
   the artboard states them per element. */
.foot__in { gap: 0; }
.foot__nav { gap: 34px; }
.foot__nav a { font-size: 14px; line-height: normal; letter-spacing: .14em; }
.foot__ask { margin: 26px 0 0; font-size: 15px; color: var(--fact-ink); }
.foot__social { margin: 18px 0 0; }
.foot__fine { margin-top: 22px; gap: 0; }
.foot__notice { max-width: none; }
.foot__meta,
.foot__by,
.disclaimer {
  margin-top: 0; max-width: none;
  font-size: 13.5px; line-height: 1.75; color: #7C7466;
}

/* Section headings are NOT one size on the artboard: the craft band is 48/1.12,
   which is what --h2-size already gives, but visit is 44/1.06 and menu is
   42/normal. Running all three at the craft size made the visit heading wrap two
   lines of 54px where the design has two of 46. */
.section--visit .section__title { font-size: 44px; line-height: 1.06; letter-spacing: .04em; }
.section--menu .section__title { font-size: 42px; line-height: normal; letter-spacing: .05em; }

/* Menu category row and card grid, to the artboard's own numbers. */
.cats { gap: .9rem 46px; margin-top: 28px; }
.cat .menu__icon { width: 24px; height: 24px; }
.fcards { gap: 22px; margin-top: 42px; }

/* The categories are buttons now, so they need the button reset - a UA button
   brings its own font, background and border and would otherwise break the row
   the artboard drew. Everything visual about them is unchanged. */
.cat {
  appearance: none; background: none; border: 0; padding: 0;
  font: inherit; cursor: pointer;
  font-family: var(--font-display); font-size: .82rem; font-weight: 600;
  letter-spacing: .14em; text-transform: uppercase;
  transition: color .2s var(--ease);
}
.cats li { list-style: none; }
.cat:hover { color: var(--ink); }
.cat[aria-selected="true"] { color: var(--accent); }
.cat:focus-visible { outline: 2px solid var(--brand); outline-offset: 4px; border-radius: 2px; }

/* An item with no photograph of its own. Borrowing a stock shot of somebody
   else's food is the one thing docs/05 says never to do, so the card carries the
   category mark on plaster instead - it reads as a deliberate part of the set
   rather than as a missing image. */
.fcard__img--none {
  display: flex; align-items: center; justify-content: center;
  aspect-ratio: 4 / 3; border-radius: calc(var(--r) * .35);
  background-color: color-mix(in srgb, var(--ink) 7%, var(--band-down));
  background-image:
    radial-gradient(120% 90% at 12% 8%, rgba(255,255,255,.9) 0%, transparent 55%),
    var(--grain-wall);
  background-blend-mode: normal, multiply;
  color: color-mix(in srgb, var(--ink) 26%, transparent);
}
.fcard__img--none .menu__icon { width: 42%; height: auto; max-width: 96px; }

/* Set by the tab script on the first card that is actually visible. The
   :first-child rule further up stays as the no-JS default. */
.fcard--lead .fcard__cta { background: var(--brand); color: var(--on-brand); }

/* The inset photo is 196px on the artboard, not 42% of whatever the photo beside
   it happens to be - at 1440 that percentage made it half again too big and
   dragged it up over the picture it is supposed to be leaning on. Its border is
   the band's own ground, so it reads as a cut-out; --bg is the page ground and
   left a visible edge of the wrong colour against the plaster. */
/* 216, not 196: box-sizing is border-box here, so the artboard's 196px of photo
   plus its 10px frame on each side is a 216px box. */
.visit__pic-b { width: min(42%, 216px); border-color: var(--band-up); }

/* --- motion, second pass ----------------------------------------------------
   The reveal system already carries the page in. These add the movements a
   static artboard cannot show, behind the same two guards as everything else:
   .js, so none of it is load-bearing, and prefers-reduced-motion. */

/* Counting digits must not reflow the row they sit in. */
.stat__n { font-variant-numeric: tabular-nums; }
/* The photo is about to be pushed past its own edges. */
.invite { overflow: hidden; }

@media (prefers-reduced-motion: no-preference) {
  /* The invitation photo drifts closer as its band crosses the screen. This is
     the one full-bleed image on the page and the last thing read before the
     practical sections, which is the one place a slow push reads as intent
     rather than as decoration. */
  @supports (animation-timeline: view()) {
    .js .invite__img {
      animation: inviteDrift linear both;
      animation-timeline: view();
      animation-range: entry 0% exit 100%;
    }
    @keyframes inviteDrift {
      from { transform: scale(1.14); }
      to   { transform: scale(1); }
    }
  }

  /* The menu photographs settle rather than appear, a beat behind their card. */
  .js .fcards .fcard__img {
    transform: scale(1.05);
    transition: transform 1.1s var(--ease);
  }
  .js .fcards.is-in .fcard__img { transform: none; }

  /* Pointer parallax, driven by the script. It uses the independent `translate`
     property rather than `transform` ON PURPOSE: every one of these elements is
     already animating transform for something else - the ring rotates in on
     reveal, the plate is centred by translate(-50%,-50%) and drifts on scroll -
     and `translate` composes with all of it instead of fighting it. A browser
     without the property simply ignores this and loses only the effect.

     Depth is set per element in the script, not here: furthest back moves most,
     which is the whole reason it reads as depth rather than as a wobble. */
  .has-parallax .props--top .prop,
  .has-parallax .dial__ring,
  .has-parallax .dial__plate {
    translate: var(--px, 0px) var(--py, 0px);
    will-change: translate;
  }
}

/* --- the phone -------------------------------------------------------------
   There is no mobile artboard for this direction - Mobile.dc.html on the canvas
   is a different exploration entirely (Instrument Serif, #A03B2A), not this one
   at 390px. So the job here is to carry THIS design down: the condensed display
   type keeps its presence, the plaster keeps its grain, and nothing is allowed
   to fall back to a generic stacked layout. */

@media (max-width: 879px) {
  /* The hanging sign cannot stay a pill on a 390px screen - the three segments
     wrapped into three different widths, each still carrying the 999px radius,
     which read as three loose lozenges rather than one sign. Stacked full width
     with a single radius on the group, it is still one object. */
  .sign {
    flex-direction: column;
    /* The .wrap measure, restated: the base .sign rule sets width:fit-content
       and wins over .wrap on source order, so "100%" here would run the sign
       edge to edge while every other block on the phone stays inset. */
    width: min(var(--maxw), 100% - var(--gutter) * 2);
    border-radius: calc(var(--r) * .5);
    margin-top: -20px;
  }
  .sign > * {
    width: 100%; justify-content: center;
    min-height: 46px; padding-inline: 1.25rem;
  }
  .sign__addr { font-size: .9375rem; }
}

/* The headline is the design. A 1.95rem floor made it read as body copy with a
   colour on one line; the type is what a reader recognises before they read a
   word of it. The short-screen rule further down still overrides this, so a
   667px phone keeps tap-to-call above the fold. */
:root { --hero-size: clamp(2.35rem, 6.4vw, 5.125rem); }
/* Re-asserted AFTER the line above, because that one is later in the file than
   the original guard and would otherwise outrank it. On a short screen - an SE,
   any phone in landscape - the type yields and the call button does not. This
   is the rule that keeps tap-to-call above the fold; check-fold.js proves it. */
@media (max-height: 700px) {
  :root { --hero-size: clamp(1.85rem, 7.4vw, 2.4rem); }
}

/* --- the order page ---------------------------------------------------------
   A second page, not a second design: it borrows the wash, the plaster, the
   badge, the buttons and the reveal system from the home page, and adds only
   what a basket needs. Anything here that looks like a new idea is a mistake. */

/* Any explicit `display` beats the UA stylesheet's `[hidden] { display: none }`,
   so a flex or grid element with the hidden attribute stays on screen. The empty
   basket showed its own "Total $0.00" that way. This has to outrank the layout
   rules, which is the one honest use for !important here. */
[hidden] { display: none !important; }

.ohead { padding: 40px 0 56px; }
.ohead__in { width: min(var(--maxw), 100% - var(--gutter) * 2); margin-inline: auto; }
.ohead__title {
  font-family: var(--font-display); text-transform: var(--display-transform);
  font-size: clamp(2.4rem, 5.6vw, 4rem); font-weight: 700;
  line-height: .98; letter-spacing: .01em; margin: 0;
}
.ohead__title .hl { color: var(--brand); display: inline-block; }
.ohead__lede {
  margin: 22px 0 0; max-width: 46ch;
  font-size: 1.0625rem; line-height: 1.65; color: var(--body-ink);
}
.ohead .facts { margin-top: 26px; }

.section--order { padding-block: 64px 88px; }
.oform__grid { display: grid; gap: 2.5rem; align-items: start; }
@media (min-width: 980px) {
  .oform__grid { grid-template-columns: minmax(0, 1fr) 372px; gap: 3.5rem; }
  /* Sticky, because the basket IS the page - making someone scroll back up to
     see what they picked is how an order gets abandoned half-built. */
  .basket { position: sticky; top: 24px; }
}

.ogroup + .ogroup { margin-top: 2.5rem; }
.ogroup__h {
  display: flex; align-items: center; gap: .6rem; margin: 0 0 .9rem;
  font-family: var(--font-display); font-size: 1.0625rem; font-weight: 700;
  letter-spacing: .14em; text-transform: uppercase; color: var(--ink);
}
.ogroup__h .menu__icon { width: 22px; height: 22px; color: var(--brand); flex: none; }
.orows { list-style: none; margin: 0; padding: 0; }
.orow {
  display: grid; grid-template-columns: minmax(0, 1fr) auto auto;
  align-items: center; gap: .5rem 1.25rem;
  padding: 1rem 0; border-top: 1px solid color-mix(in srgb, var(--ink) 12%, transparent);
}
.orow:last-child { border-bottom: 1px solid color-mix(in srgb, var(--ink) 12%, transparent); }
.orow__name { margin: 0; font-weight: 600; font-size: 1.0125rem; }
.orow__desc { margin: .25rem 0 0; font-size: .9rem; line-height: 1.55; color: var(--fact-ink); }
.orow__price {
  margin: 0; font-family: var(--font-display); font-weight: 700;
  letter-spacing: .06em; color: var(--brand); font-variant-numeric: tabular-nums;
}

/* Stepper. 44px targets on both buttons - this is the one control on the page
   that gets tapped repeatedly, often one-handed. */
.qty { display: inline-flex; align-items: center; gap: .25rem; }
.qty__btn {
  width: 44px; height: 44px; flex: none;
  display: inline-flex; align-items: center; justify-content: center;
  border: 1.5px solid color-mix(in srgb, var(--ink) 20%, transparent);
  border-radius: 50%; background: transparent; color: var(--ink);
  font-size: 1.15rem; line-height: 1; cursor: pointer;
  transition: border-color .2s var(--ease), background-color .2s var(--ease),
              transform .2s var(--ease);
}
.qty__btn:hover { border-color: var(--brand); color: var(--brand); }
.qty__btn:active { transform: scale(.92); }
.qty__n {
  min-width: 2ch; text-align: center;
  font-family: var(--font-display); font-weight: 700; font-size: 1.05rem;
  font-variant-numeric: tabular-nums; color: color-mix(in srgb, var(--ink) 45%, transparent);
  transition: color .2s var(--ease);
}
.qty--on .qty__n { color: var(--brand); }

.basket {
  background: var(--surface);
  border: 1px solid color-mix(in srgb, var(--line) 80%, var(--bg));
  border-radius: var(--r);
  padding: clamp(1.4rem, 3vw, 1.9rem);
  box-shadow: 0 24px 50px -30px rgb(0 0 0 / .4);
}
.basket__h {
  margin: 0 0 1rem; font-family: var(--font-display);
  font-size: 1.25rem; font-weight: 700; letter-spacing: .1em; text-transform: uppercase;
}
.basket__empty { margin: 0; font-size: .94rem; color: var(--fact-ink); }
.basket__list { list-style: none; margin: 0; padding: 0; }
.basket__row {
  display: grid; grid-template-columns: auto minmax(0, 1fr) auto;
  gap: .75rem; padding: .5rem 0; font-size: .95rem;
}
.basket__qty { color: var(--brand); font-weight: 700; font-variant-numeric: tabular-nums; }
.basket__cost { font-variant-numeric: tabular-nums; color: var(--fact-ink); }
.basket__total {
  display: flex; justify-content: space-between; align-items: baseline;
  margin: .9rem 0 0; padding-top: .9rem;
  border-top: 1px solid color-mix(in srgb, var(--ink) 14%, transparent);
  font-family: var(--font-display); font-weight: 700;
  letter-spacing: .1em; text-transform: uppercase;
}
.basket__sum { font-size: 1.35rem; color: var(--brand); font-variant-numeric: tabular-nums; }
.basket__go { width: 100%; margin-top: 1.25rem; }
.basket__note { margin: .9rem 0 0; font-size: .82rem; line-height: 1.6; color: var(--fact-ink); }

.ofields { display: grid; gap: .8rem; margin-top: 1.4rem; }
@media (min-width: 460px) { .ofields { grid-template-columns: 1fr 1fr; } }
.ofield { margin: 0; display: flex; flex-direction: column; gap: .3rem; }
.ofield--wide { grid-column: 1 / -1; }
.ofield label {
  font-family: var(--font-display); font-size: .75rem; font-weight: 600;
  letter-spacing: .16em; text-transform: uppercase; color: var(--fact-ink);
}
.ofield input, .ofield select, .ofield textarea {
  font: inherit; font-size: .95rem; color: var(--ink);
  padding: .6rem .75rem; border-radius: calc(var(--r) * .3);
  border: 1.5px solid color-mix(in srgb, var(--ink) 16%, transparent);
  background: var(--bg); width: 100%;
  transition: border-color .2s var(--ease);
}
.ofield textarea { resize: vertical; }
.ofield input:focus-visible, .ofield select:focus-visible, .ofield textarea:focus-visible {
  border-color: var(--brand); outline: 2px solid color-mix(in srgb, var(--brand) 35%, transparent);
  outline-offset: 1px;
}
.oerror {
  margin: .9rem 0 0; padding: .6rem .8rem;
  border-radius: calc(var(--r) * .3);
  background: color-mix(in srgb, var(--brand) 10%, transparent);
  color: var(--brand-dark); font-size: .9rem; font-weight: 600;
}

/* The confirmation reuses the slate band, so the page ends the way the home
   page's dark band reads rather than as a browser alert. */
.odone {
  background-color: var(--ink); color: var(--bg);
  padding-block: clamp(4rem, 9vw, 7rem);
  background-image:
    radial-gradient(80% 60% at 20% 10%, color-mix(in srgb, var(--brand) 18%, transparent) 0%, transparent 60%),
    var(--grain);
  background-blend-mode: normal, screen;
}
.odone__in { text-align: center; }
.odone__tick {
  display: inline-flex; align-items: center; justify-content: center;
  width: 62px; height: 62px; border-radius: 50%;
  background: var(--brand); color: var(--on-brand); margin-bottom: 1.5rem;
}
.odone__tick svg { width: 30px; height: 30px; }
.odone__eyebrow {
  margin: 0 0 .6rem; font-family: var(--font-display);
  font-size: .8125rem; font-weight: 600; letter-spacing: .28em; text-transform: uppercase;
  color: color-mix(in srgb, var(--bg) 66%, transparent);
}
.odone__title {
  margin: 0; font-family: var(--font-display); text-transform: var(--display-transform);
  font-size: clamp(1.9rem, 4.2vw, 3rem); font-weight: 700; line-height: 1.08; letter-spacing: .02em;
}
.odone__lede {
  margin: 1.1rem auto 0; max-width: 46ch;
  font-size: 1.03125rem; line-height: 1.75;
  color: color-mix(in srgb, var(--bg) 74%, transparent);
}
.odone__list {
  list-style: none; margin: 2rem auto 0; padding: 0;
  max-width: 30rem; text-align: left;
}
.odone__list li {
  display: flex; justify-content: space-between; gap: 1rem;
  padding: .55rem 0; font-size: .95rem;
  border-bottom: 1px solid color-mix(in srgb, var(--bg) 16%, transparent);
  color: color-mix(in srgb, var(--bg) 82%, transparent);
}
.odone__list li span { font-variant-numeric: tabular-nums; }
.odone__total {
  display: flex; justify-content: space-between; gap: 1rem;
  max-width: 30rem; margin: 1rem auto 0;
  font-family: var(--font-display); font-weight: 700;
  letter-spacing: .1em; text-transform: uppercase;
  font-size: 1.15rem; color: var(--accent);
}
.odone__cta {
  display: flex; flex-wrap: wrap; justify-content: center;
  gap: 12px; margin-top: 2.25rem;
}
.odone .btn--quiet { --btn-ink: var(--bg); border-color: color-mix(in srgb, var(--bg) 42%, transparent); }
.odone .btn--quiet:hover { border-color: var(--bg); }
.odone__demo {
  margin: 2rem 0 0; font-size: .82rem;
  color: color-mix(in srgb, var(--bg) 54%, transparent);
}

@media (prefers-reduced-motion: no-preference) {
  /* The confirmation rises once, on arrival. */
  .js .odone { opacity: 0; transform: translateY(18px); }
  .js .odone.is-in {
    opacity: 1; transform: none;
    transition: opacity .6s var(--ease), transform .6s var(--ease);
  }
}
@media (min-width: 880px) {
  .visit__pic-b { right: -28px; bottom: -34px; }
}
