/* Beachside Gyros · CERAMIC / CYCLADIC
 * ═════════════════════════════════════════════════════════════════════════
 * display: Gorditas Bold, title case · body: Work Sans · tokens: tokens.css
 *
 * THE ONE RULE THAT DEFINES THIS BUILD:
 *   Separation comes from flat blocks of colour and space. Nothing else.
 *   A tiled wall has no borders — the tiles simply meet.
 *
 * Therefore, permanently banned. Every one of these was in a previous version
 * of this site and each is why it read either generic or childish:
 *   ✗ borders / outlines on cards, tags, buttons        (read neo-brutalist)
 *   ✗ box-shadow of any kind, hard OR soft              (nothing floats here)
 *   ✗ border-radius                                     (tiles are square)
 *   ✗ ALL-CAPS display headings                         (shouting)
 *   ✗ solid + ghost button PAIRS                        (AI-page fingerprint)
 *     (one outlined button alone on a photograph is fine — it is the pair,
 *      shown side by side, that is the tell)
 *   ✗ pill badges above an h1                           (AI-page fingerprint)
 *   ✗ hover lifts — translateY(-Npx)                    (AI-page fingerprint)
 *   ✗ "→" appended to link text                         (AI-page fingerprint)
 *   ✗ frosted-glass backdrop-filter                     (phone-OS idiom)
 *   ✗ the tile pattern used as a repeating border       (souvenir shop)
 *   ✗ the diagonal "glaze lattice" on the dark bands — REMOVED 2026-08-08.
 *     Justified to myself as glazed tile catching light; at full width on a
 *     navy field it read as cheap woven fabric. Dark bands are FLAT COLOUR.
 *   ✗ the dashed/lozenge band — removed 2026-08-08. At full width it read as
 *     a perforated tear-off strip. It was replaced by a real drawn Greek key
 *     (meander), which is the site's ONLY ornament and runs in exactly ONE
 *     place: leading the masthead. The footer needs nothing — deep navy
 *     meeting plaster is already maximum contrast. Do not add a second one.
 *
 * That last line has now been tested twice and held twice. On 2026-08-09 a
 * second run was added under the masthead bar to frame it, and a third had
 * been sitting above the footer since the rebuild; Marcus cut both the same
 * day — "it's too much". ONE full-width band per page. That restraint is the
 * whole difference between "Cycladic" and "taverna placemat".
 * ═════════════════════════════════════════════════════════════════════════ */

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

html {
  scroll-behavior: smooth;
  scroll-padding-top: 5rem;   /* clears the condensed sticky masthead */
  -webkit-text-size-adjust: 100%;
  overflow-x: clip;
}

body {
  margin: 0;
  overflow-x: clip;
  background: var(--color-paper);
  color: var(--color-ink);
  font: 400 var(--text-base)/1.65 var(--font-body);
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

/* height:auto is load-bearing: the width/height attributes on every <img> are
   presentational hints, and an explicit height silently defeats aspect-ratio. */
img { max-width: 100%; display: block; height: auto; }

/* Title case, never caps — setting a display face in caps as well is how this
   build shouted the first time. Size and colour carry hierarchy, since Fugaz
   One ships a single weight. */
h1, h2 {
  font-family: var(--font-display);
  font-weight: var(--weight-display);
  font-style: normal;
  /* Gorditas has long ascenders and real descenders, so it needs more leading
     than the condensed faces that preceded it — but nowhere near the 1.34 the
     old brush script wanted for its tails. */
  line-height: 1.22;
  letter-spacing: var(--track-display);
  margin: 0;
  text-wrap: balance;
  overflow-wrap: anywhere;
  min-width: 0;
}
/* h3 is a subhead, not a headline. It stays in the body face at 700 so the
   display voice means something when it does appear. */
h3 {
  font-family: var(--font-body);
  font-weight: 700;
  line-height: 1.25;
  letter-spacing: 0;
  margin: 0;
  text-wrap: balance;
  overflow-wrap: anywhere;
  min-width: 0;
}
p { margin: 0 0 1.15em; }
a { color: inherit; }

::selection { background: var(--color-accent-pale); color: var(--color-ink); }

:focus-visible { outline: 3px solid var(--color-focus); outline-offset: 3px; }

.skip {
  position: absolute; left: -9999px; top: 0; z-index: var(--z-skip);
  background: var(--color-ink); color: var(--color-paper);
  padding: var(--space-xs) var(--space-md); font-weight: 700;
}
.skip:focus { left: var(--space-sm); top: var(--space-sm); }

.h-display { font-size: var(--text-display-s); max-width: 22ch; }
/* Section level: three units of the same key, left-aligned under the heading.
   Short, so it reads as a mark rather than another band. */
.h-display::after,
.phead__title::after,
.slate__inner > h2::after,
.praise__say > h2::after {
  content: "";
  display: block;
  width: 75px; height: 20px;
  margin-top: var(--space-md);
  background-color: var(--color-accent);
  -webkit-mask: var(--meander) repeat-x left / var(--meander-size);
          mask: var(--meander) repeat-x left / var(--meander-size);
}
/* On the deep bands it steps to the pale tone. */
.kitchen .h-display::after,
.slate .h-display::after,
.slate__inner > h2::after,
.praise__say > h2::after { background-color: var(--color-accent-pale); }


/* ── Links ────────────────────────────────────────────────
   No arrow glyph, no sliding. A rule that thickens under the word. */
.tlink {
  display: inline-block;
  font-size: var(--text-sm);
  font-weight: 700;
  letter-spacing: var(--track-caps);
  text-transform: uppercase;
  text-decoration: none;
  color: var(--color-accent);
  padding-bottom: 4px;
  box-shadow: inset 0 -1px 0 var(--color-accent-pale);
  transition: box-shadow var(--dur-short) var(--ease-out),
              color var(--dur-short) var(--ease-out);
}
.tlink:hover { color: var(--color-accent-2); box-shadow: inset 0 -2px 0 var(--color-accent); }
.tlink--strong {
  font-family: var(--font-body);
  font-size: var(--text-lg);
  font-weight: 700;
  text-transform: none;
}
/* Arrows are gone from the markup; neutralise any that survive an edit. */
.tlink span[aria-hidden], .btn span[aria-hidden] { display: none; }

/* ── Button ───────────────────────────────────────────────
   A solid glazed tile. No border, no shadow, no lift — it deepens on hover.
   There is one button style. A solid/outline pair is an AI-page fingerprint. */
.btn {
  display: inline-flex; align-items: center; justify-content: center;
  min-height: 52px;
  padding: 0 var(--space-xl);
  border: 0;
  background: var(--color-accent);
  color: var(--color-accent-ink);
  font-family: var(--font-body);
  font-size: var(--text-sm); font-weight: 700;
  letter-spacing: var(--track-caps); text-transform: uppercase;
  text-decoration: none; cursor: pointer;
  transition: background var(--dur-short) var(--ease-out);
}
.btn:hover { background: var(--color-accent-2); }
/* `.btn--outline` was DELETED on 2026-08-09. It was a transparent tile with a
   1px inset ring for use on photographs, it was the only outlined object on a
   site whose tokens ban outlines, and Marcus called it out on sight as reading
   like generic AI. The hero's `.callboard` replaced its one use. The rule is
   gone rather than left unreferenced so it cannot quietly come back. */
.btn--onphoto { background: var(--color-paper); color: var(--color-ink); }
.btn--onphoto:hover { background: #fff; }

/* ── Masthead ─────────────────────────────────────────────
   ONE horizontal row: nav left, wordmark centre, contact right, with the Greek
   key band pinned above it. It was a four-row stack (standfirst / wordmark /
   nav / band) which ran 206px on a laptop and 260px on a phone — a quarter of
   the screen spent before any content, and the reason the full-screen hero
   still opened on a slab of empty paper. Set to Marcus's catering mockup,
   2026-08-09.

   The standfirst line came out with the stack. "Home of Maria's famous
   tzatziki" still opens the tzatziki band on the home page, so the phrase is
   not lost — only its second, chrome-level printing.

   Sticky and condensing: the nav and the phone number are what a hungry
   visitor wants at any moment, and letting them scroll away costs the page
   its usefulness. Opaque — frosted glass is a phone-OS idiom, not a tiled one. */
.mast {
  position: sticky; top: 0; z-index: var(--z-sticky);
  /* The glazed-tile field, same token as the catering strip and the menu
     board — NOT the warm plaster the rest of the page sits on. Set on
     Marcus's call, 2026-08-09, pointing at the "Feeding a crowd?" strip. It
     also does the job the removed second meander band was reaching for: the
     bar now separates from the page by ground colour, which is the direction's
     own rule, instead of by a second run of ornament. */
  background: var(--color-paper-2);
}
/* ONE band, leading. As the first thing on the page it reads as a printed
   border rather than as a divider under a block of chrome, and it is what
   stays pinned to the top edge once the page scrolls.

   A matching band was added under the bar on 2026-08-09 to frame it and
   Marcus cut it the same day — "it's too much". Do not re-add it: two runs of
   meander around a bar this short is more ornament than masthead.

   It runs at 18px where the rest of the site's keys run at 20px, which is the
   part of that change worth keeping — Marcus asked for the pattern itself to
   be shorter. 18px is the FLOOR, not a preference: `tokens.css` records that
   below ~18px a single meander stops reading as a key and turns to texture.
   The source is 25×20, so the width steps to 22px with it and the key keeps
   its proportion rather than stretching. The footer band stays at 20px. */
.mast {
  --mast-key-h: 18px;
  --mast-key-size: 22px 18px;
}
.mast::before {
  content: ""; display: block;
  height: var(--mast-key-h);
  background-color: var(--color-accent);
  -webkit-mask: var(--meander) repeat-x center / var(--mast-key-size);
          mask: var(--meander) repeat-x center / var(--mast-key-size);
}
.mast__bar {
  max-width: var(--shell); margin: 0 auto;
  /* Bottom-heavy on purpose: there is a band above the bar and nothing below,
     so even padding would sit the wordmark high against the ornament. */
  padding: var(--space-2xs) var(--page-gutter) var(--space-xs);
  display: grid;
  /* Equal side tracks, so the wordmark is centred on the PAGE and not merely
     placed between two unequal runs of links. */
  grid-template-columns: 1fr auto 1fr;
  align-items: center;
  gap: var(--space-md);
  transition: padding var(--dur-medium) var(--ease-out);
}
/* Without this the nav and the wordmark size to max-content and overflow the
   bar, which overflow-x:clip then hides rather than wraps. Cost one mobile bug
   a scrollWidth check could not see. */
.mast__name, .mast__nav { min-width: 0; max-width: 100%; }

.mast.is-stuck .mast__bar { padding-block: var(--space-3xs); }
/* font-size did nothing once the wordmark became an image — this has to be
   height or the bar stops condensing on scroll. */
.mast.is-stuck .mast__logo { height: clamp(1.25rem, 2vw, 1.75rem); }

/* ── The wordmark stands down over the hero ──────────────────────────────
   THE HOME PAGE CARRIES THIS MARK TWICE. The masthead wordmark is the truck's
   own painted lettering, and so is the hand-painted sign in the photograph
   directly beneath it — the same object, printed within ~150px of each other,
   one centred and cut out, one tilted and left of centre. Marcus called the
   stagger on 2026-08-16 and he is right: it reads as a mistake rather than as
   branding, because the eye tries to line them up and cannot.

   So only one of them is on screen at a time. While the photograph is up the
   REAL sign is the logo — it is the better one, it is in situ, and it is the
   reason the crop is top-pinned. The masthead's copy returns with the
   condensed bar, at which point the photograph is gone and the bar is the only
   thing carrying the name.

   `visibility`, not `display`: the box stays in the grid, so nothing on either
   side moves when the mark comes back mid-scroll. It is in the transition so
   the fade runs both ways rather than the mark popping in at full opacity.

   ⚠️ HOOKED TO `.mast--defer-brand`, WHICH SCRIPT.JS ADDS. Not a body class:
   the mark only reappears via `.is-stuck`, which nothing sets without JS, so a
   no-JS visitor has to keep the wordmark or the home page has no logo at all.

   Above 40rem only. Below it `.mast__name` is `order: -1` — its own row above
   the nav — and hiding it would leave an empty band across the top of the
   shortest masthead on the site. Two SMALL marks on a phone is the cheaper
   problem. */
@media (min-width: 40.0625rem) {
  .mast--defer-brand:not(.is-stuck) .mast__name { opacity: 0; visibility: hidden; }
}
.mast__name {
  transition: opacity var(--dur-medium) var(--ease-out),
              visibility var(--dur-medium);
}

/* Without mask support the band would render as a solid blue bar — fall back
   to the hairline instead. */
@supports not ((mask-image: url("#x")) or (-webkit-mask-image: url("#x"))) {
  .mast::before { height: 1px; background-color: var(--color-rule); }
  .h-display::after, .phead__title::after,
  .slate__inner > h2::after, .praise__say > h2::after { display: none; }
}

/* The wordmark is now the truck's OWN painted lettering, not type. Marcus
   supplied it on 2026-08-09 and it replaced a type-set rendering of the
   name — which was a good stand-in, but this is the real thing: the same
   hand-painted mark that hangs over the service window in the hero
   photograph, cream offset shadow and all. The site now carries one logo
   instead of an impression of one.

   It stays an <a> wrapping an <img alt="Beachside Gyros">, so the link still
   has a text accessible name and the masthead still announces correctly. */
.mast__name {
  display: block;
  text-decoration: none;
  text-align: center;
  justify-self: center;
}
/* Height-driven, because the condense-on-scroll below animates height and an
   image has no font-size to shrink.

   ⚠️ THIS IS A ~7:1 SINGLE-LINE MARK. It replaced a two-line stacked version
   on 2026-08-09, and the height had to come down hard with it: at the 2.9rem
   the stacked one used, one line of lettering is over 320px wide and shoves
   the nav on either side. Sized off height so it stays optically level with
   the nav type, with a width cap for the narrow middle track. If the artwork
   is ever swapped back to a stacked lockup, this clamp is wrong. */
.mast__logo {
  display: block; margin-inline: auto;
  height: clamp(1.6rem, 2.5vw, 2.2rem); width: auto;
  max-width: min(100%, 24rem);
  transition: height var(--dur-medium) var(--ease-out);
}
.mast__nav ul {
  display: flex; flex-wrap: wrap; align-items: center;
  gap: var(--space-2xs) var(--space-sm);
  list-style: none; padding: 0; margin: 0;
}
.mast__nav--start ul { justify-content: flex-start; }
.mast__nav--end ul { justify-content: flex-end; }
.mast__nav a {
  display: inline-block;
  white-space: nowrap;                 /* clickable text never wraps */
  padding: var(--space-2xs);
  font-size: var(--text-xs);
  font-weight: 700;
  letter-spacing: var(--track-caps);
  text-transform: uppercase;
  text-decoration: none;
  color: var(--color-ink-2);
  transition: color var(--dur-short) var(--ease-out),
              background var(--dur-short) var(--ease-out);
}
/* The nav item becomes a glazed tile. No underline sweep.
   It steps to paper-3 rather than paper-2 because the BAR is paper-2 now — a
   paper-2 hover on a paper-2 bar is an invisible hover. */
.mast__nav a:hover { background: var(--color-paper-3); color: var(--color-accent); }
.mast__nav a.is-here { background: var(--color-accent); color: var(--color-accent-ink); }
.mast__nav .mast__tel { color: var(--color-accent); font-variant-numeric: tabular-nums; }
.mast__nav .mast__tel:hover { background: var(--color-paper-3); }
.mast__rule { display: none; }       /* the ::before band is the border */

/* ── Hero ─────────────────────────────────────────────────
   One screen. Centred, one line of brush script, one action, and the four
   facts along the floor. Full-bleed photograph under a deep Aegean scrim —
   the scrim pulls the photo into the palette AND masks the softness in the
   supplied images that a bright contained crop exposed.

   Sized to the rest of the viewport rather than to a clamp, so the facts land
   ON the fold instead of just under it. svh, not vh or dvh: the small viewport
   is the one a phone shows before the URL bar collapses, so the band is
   visible on arrival rather than after the first scroll. --mast-h is measured
   in script.js — the fallback is deliberately generous, because guessing high
   only shortens the hero while guessing low pushes the band off-screen, which
   is the exact bug this layout exists to fix. */
.hero {
  position: relative; background: var(--color-deep);
  /* ⚠️ THE TEAR LIVES BELOW THE FOLD, AND THAT IS WHAT THE PADDING BUYS.
     Marcus, 2026-08-16: "I want the dividing paper pushed further down and not
     appear when the user lands on the hero." It used to be drawn over the last
     66px of the hero — inside the first screen by construction, and biting into
     "187 GOOGLE REVIEWS" whenever anything trimmed the proof strip's padding.

     So the box grows by exactly one tear and spends it as bottom padding. The
     CONTENT box is still one clean screen (`100svh - --mast-h`), so the proof
     strip still lands flush on the fold; the extra strip hangs below it, and
     `.hero__shot` and `.hero::after` are `inset: 0` so the photograph and its
     scrim run down through it. The tear therefore still has a photograph to
     bite into — which is the whole reason it cannot simply be moved onto the
     section below; see the note on `.hero::before`. It is now the first thing
     a scroll reveals rather than the last thing in the first view.

     Declared here as a variable because three rules have to agree on it: this
     padding, the min-height it pays for, and `.hero::before`'s own height. */
  --hero-tear-h: clamp(1.5rem, 4.6vw, 4.4rem);
  min-height: calc(100svh - var(--mast-h, 14rem) + var(--hero-tear-h));
  padding-bottom: var(--hero-tear-h);
  display: grid; grid-template-rows: 1fr auto;
}
.hero__inner {
  position: relative; z-index: var(--z-raised);
  width: 100%; max-width: var(--shell); margin: 0 auto;
  /* The top figure is a floor, not a spacer. Content is bottom-aligned inside a
     1fr row, so on a tall screen the air above is free — but the padding still
     counts toward the box's intrinsic height, and at 7rem it was pushing the
     proof band off a 720-tall laptop on its own. */
  padding: var(--space-xl) var(--page-gutter) var(--space-xl);
  /* Bottom-aligned, not centred. The photograph has its own giant lettering —
     the hand-painted sign — and centred copy landed straight on top of it.
     Dropping to the lower third lets the sign read AND the headline read. */
  display: grid; align-content: end; justify-items: center;
  text-align: center;
}
.hero__shot { position: absolute; inset: 0; margin: 0; overflow: hidden; }
/* The landscape exterior landed 2026-08-09 and this is now a composition
   choice rather than the rescue value it used to be. History, because the
   numbers are the reason the photograph was worth chasing: the old 1200×1600
   portrait had to be enlarged 1.2× at 1440 and 1.6× at 1920 to fill this band
   and still showed only ~40% of its height, which is why "zoom the background
   out" had no answer in CSS. The 1360×1020 landscape is enlarged 1.06× at
   1440 and shows ~76% — the truck, the sign and the tables all fit now.

   TOP-ALIGNED, and it has to be. The file is already cropped to 1.73 — close
   enough to the band that only a few percent of height is ever cut — but at
   1920 the cut grows to ~9%, and centred that would slice the top off the
   hand-painted sign, which is the only real brand mark this business owns.
   Pinned to the top, every viewport spends its whole crop budget on the empty
   asphalt along the bottom instead. Do not "balance" this to 50%. */
.hero__shot img { width: 100%; height: 100%; object-fit: cover; object-position: center top; }
.hero__shot figcaption {
  position: absolute; z-index: 2;
  left: var(--page-gutter); bottom: var(--space-sm);
  font-size: var(--text-xs); letter-spacing: 0.04em;
  color: oklch(100% 0 0 / 0.55);
}
/* Even hold across the whole frame — the copy is centred now, so a
   left-weighted gradient would leave the headline half-lit. */
.hero::after {
  content: ""; position: absolute; inset: 0; z-index: 1;
  /* Bottom-weighted, but LIGHTLY. It ran 0.94 at the floor and 0.88 a third of
     the way up, which is very nearly opaque — the bottom of the photograph,
     where the picnic tables and the whole forecourt are, went dead black and
     the proof strip sat on a slab rather than on a picture. Marcus called it
     on 2026-08-09.

     These numbers are not eyeballed: they are the lowest the ramp goes while
     every piece of type over it still clears 4.5:1 against the actual
     composited pixels behind it, sampled at 1440 and 390. If the photograph is
     ever swapped, re-run that check before trusting them. */
  background: linear-gradient(to top,
    oklch(16% 0.052 248 / 0.74) 0%,
    oklch(16% 0.052 248 / 0.70) 26%,
    oklch(16% 0.052 248 / 0.62) 46%,
    oklch(16% 0.052 248 / 0.40) 64%,
    oklch(16% 0.052 248 / 0.13) 82%,
    oklch(16% 0.052 248 / 0.00) 100%);
}

/* ── The tear under the hero ──────────────────────────────────────────────
   The photograph used to stop on a ruled horizontal line here, which was the
   only hard edge left on the page: the tzatziki band is torn top and bottom and
   the footer is torn, so this one boundary read as unfinished next to them.
   Marcus, 2026-08-15: "I want that same dividing line at the top after the
   hero." Same asset as the footer, same mask, same direction of bite.

   ⚠️ THE PAPER TEARS UPWARD HERE. IT IS NOT THE FOOTER'S STRIP RECOLOURED
   NAVY. At the footer the strip is filled with --foot-navy because the footer
   really is one flat colour edge to edge. There is nothing flat to fill with up
   here: the bottom of the hero is a 0.74 scrim over a photograph, so its actual
   pixels are asphalt and picnic table and a strip of flat navy biting down into
   the parchment would read as a navy shape sitting ON a picture rather than as
   an edge OF it. Filled with --parchment instead, the opaque half meets the
   menu sheet's own paper below and the ragged transparent half lets the
   photograph through. The object being torn is the paper, which is the only
   one of the two that can be.

   ⚠️ IT HANGS OFF .hero AND IT STAYS THERE. Two other homes were tried on
   2026-08-15 and both are dead ends. .sheet::before / top:0 paints parchment
   onto parchment — a strip inside that section reveals that section's own
   ground through its transparent half, so there is nothing to see. Cutting the
   tear out of .sheet as a mask, with the section riding up over the hero,
   renders correctly and was still reverted within the hour: "too zoomed in and
   dividers got messed up even more." Moving the menu sheet to solve an edge
   costs more than the edge is worth.

   FIXED 2026-08-17 — the strip carries the same tile .sheet does. It was flat
   --parchment against a textured section, and flat --parchment is not the tone
   of that texture: the tile averages rgb(242,227,202) and the flat fill is
   rgb(236,224,204), so the join showed a ~20px band of duller cream and then a
   hard horizontal line where the paper started. Marcus, 2026-08-17: "fix the
   discoloration between divider."

   IT LINES UP BECAUSE IT IS ANCHORED TO ITS OWN BOTTOM. .sheet paints the tile
   `center top` / repeat-y, so a fresh tile begins at .sheet's top edge. This
   strip's bottom IS that edge, so `center bottom` puts a tile's BOTTOM there and
   the join is the same tile-bottom-meets-tile-top seam that already repeats
   invisibly every ~285px inside .sheet. Both boxes are viewport-wide and both
   size the tile `100% auto`, so they render it at the same scale at every width;
   if either ever stops being full-bleed, this alignment goes with it.

   No sprigs on the strip. The four corner cutouts are `no-repeat` at .sheet's
   own corners and a second set 64px above them would read as a doubled branch.

   --parchment stays as the background-COLOUR under the tile — it is what the
   teeth are cut from in the moment before the image lands, and it is the same
   colour .sheet sits on. Whatever fixes this further must not move .sheet.

   z-index 2 clears the scrim (1) and stays under .hero__inner and .hero__proof
   (--z-raised, 10), so if the proof strip's padding is ever cut the type wins
   rather than disappearing into the teeth.

   SINCE 2026-08-16 IT SITS IN .hero's BOTTOM PADDING, BELOW THE FOLD. `bottom:
   0` is the padding box, so adding `padding-bottom: --hero-tear-h` up on .hero
   moved this whole strip off the first screen without moving it out of the
   photograph. Do not "reclaim" that padding. */
.hero::before {
  content: ""; position: absolute; z-index: 2;
  left: 0; right: 0; bottom: 0;
  height: var(--hero-tear-h);
  background-color: var(--parchment);
  background-image: url("assets/photos/ground-tile-900.webp?v=20260818a");
  background-position: center bottom;
  background-repeat: repeat-y;
  background-size: 100% auto;
  -webkit-mask: url("assets/photos/foot-tear-mask.png") repeat-x center bottom / auto 100%;
          mask: url("assets/photos/foot-tear-mask.png") repeat-x center bottom / auto 100%;
  pointer-events: none;
}

.hero__say { max-width: 40rem; color: var(--color-onphoto); }

/* ⚠️ DO NOT PUT A SCRIM BEHIND THE COPY. A soft radial ellipse was tried here
   on 2026-08-09 to hold the headline over the bright canopy, and it read as
   exactly what it was — a grey smudge floating in the middle of a sunny
   photograph. Marcus killed it on sight.

   Legibility is carried by the type instead. A tight text-shadow does the same
   work and is invisible as an effect: it darkens only the pixels immediately
   under each glyph, so nothing reads as a shape. This is the one shadow on the
   site and it is not a depth cue — tokens.css bans shadows as a way of lifting
   surfaces off a page, which this is not doing. */
.hero__say :is(h1, p) {
  text-shadow: 0 1px 2px oklch(12% 0.03 248 / 0.55),
               0 2px 14px oklch(12% 0.03 248 / 0.40);
}
/* The board carries its own ground, so type on it needs no help and picks up
   a muddy edge if it gets any. */
.hero__act .callboard :is(span) { text-shadow: none; }

/* The one line of script on the whole site. */
.hero__title {
  font-size: clamp(1.9rem, 3.6vw, 3.2rem);
  line-height: 1.25;
  max-width: none;                  /* one line — it stops fighting the sign */
  white-space: nowrap;
  color: var(--color-onphoto);
  margin: 0 0 var(--space-lg);
  /* Pacifico sits high on the baseline; this re-centres the optical block. */
  padding-top: 0.08em;
  text-shadow: 0 2px 18px oklch(16% 0.05 248 / 0.5);
}
.hero__sub {
  font-size: var(--text-md); line-height: 1.6;
  font-weight: 500;                 /* 400 disintegrates over a photograph */
  color: var(--color-onphoto-2);
  max-width: 34ch; margin: 0 auto var(--space-xl);
}
/* ── The call board ───────────────────────────────────────
   The hero's one action, built to Marcus's pick from concepts-hero-cta.html
   on 2026-08-09. It replaced `.btn--outline`, which was a transparent 1px
   rectangle with tracked caps floating on a photograph — the most-generated
   hero button on the web, and the only outlined object on a site whose
   tokens.css bans outlines outright.

   The idea is that the button is made of the same thing as the sign hanging
   above it in the photograph: a slab of hand-cut painted board. That is the
   only genuine logo this business has, so it is the one shape here that no
   template could have produced. */
/* ── The pinned prints ───────────────────────────────────────────────────
   The hero is one photograph of a place. These two are photographs of what
   the place sells, tacked either side of the sign like a corkboard, and they
   are the reason the composition reads as somebody's food truck rather than
   a stock landing page with a headline on a picture.

   Its own grid cell, stacked on .hero__inner's, so neither pushes the other
   around: the prints are positioned against the WHOLE band while the copy
   stays inside the 1180px shell. */
.hero__inner { grid-area: 1 / 1; }
.hero__snaps { grid-area: 1 / 1; }
.hero__proof { grid-area: 2 / 1; }

.hero__snaps {
  position: relative; z-index: 2;
  /* Nothing in here should ever eat a click meant for the call board. The
     prints themselves take pointer events back so their hover still works. */
  pointer-events: none;
}
/* ── PHOTOGRAPHED FRAMES, 2026-08-18 (second pair) ───────────────────────
   Real instant-film cards on transparency, supplied by Marcus: one held by a
   torn strip of masking tape, one by a red drawing pin. They replace the drawn
   version of all three — a warm rectangle with two box-shadows, a clip-path
   tape and a four-stop radial-gradient pin. Each of those was a good drawing
   of a thing; a photograph of the thing beats it, because the paper has grain,
   a dirty corner and a torn edge that no gradient was ever going to invent.

   ⚠️ `--card-w` IS THE DRAWN CARD'S WIDTH AND IT DOES NOT CHANGE. The first
   pair of frames, earlier the same day, had a mat so thick that matching the
   food photograph's size took the print from 20rem to 24rem, and a 24rem print
   crowds the headline, the call board and both doodles — *"i do not like these
   sizes."* Every width below is derived from `--card-w` rather than set, so
   the card lands at exactly the size the drawn one did no matter what the
   artwork around it does.

   ⚠️ THE ELEMENT IS WIDER THAN THE CARD, because the tape hangs off the left
   edge and the pin off the top. That is what the two multipliers and the
   negative margins are: the element is scaled up by the overhang and then
   pulled back by it, so `left`/`right` still position the CARD's edge and not
   the tape's. tools/build-polaroid-frames.py measures the overhang off the
   alpha channel and prints all of these; do not eyeball them.

   The chin is evened up in that script too, not here — *"i do not want one
   side of the square frame to be larger than the others."* */
.snap {
  /* Raised one step off the fold 2026-08-18 — *"move those pics slightly
     higher."* The photographed cards are ~45px shorter than the drawn ones
     they replaced, so they had settled lower in the band than the composition
     was built for. */
  position: absolute; bottom: var(--space-lg);
  margin: 0; pointer-events: auto;
  --card-w: clamp(11rem, 20.5vw, 20rem);
  /* The same two shadows the drawn card had — a wide soft one for the height
     off the board and a tight contact one so it does not float — but as
     `filter`, because `box-shadow` would trace the element's rectangle and
     this element is mostly transparent. drop-shadow reads the alpha, so the
     shadow follows the torn edge, the tape and the pin's head. */
  filter:
    drop-shadow(0 1.5rem 2.75rem oklch(0% 0 0 / 0.42))
    drop-shadow(0 0.15rem 0.4rem oklch(0% 0 0 / 0.3));
  transition: rotate var(--dur-medium) var(--ease-out),
              scale var(--dur-medium) var(--ease-out);
}
/* The frame sits OVER the photograph — ::after rather than a background on
   .snap, which would paint behind its own children. This is what covers the
   photograph's four cut edges, so the 2px bleed below has somewhere to hide:
   the window is torn, not cut, and wanders a pixel either way. */
.snap::after {
  content: ""; position: absolute; inset: 0;
  background: var(--snap-frame) center / contain no-repeat;
}
.snap img {
  position: absolute; object-fit: cover;
  /* Both sources are 765x1020 portrait against a landscape window, so a third
     of the height goes. Biased ABOVE centre: in both of these the food is in
     the upper two-thirds and the lower third is table and packaging. */
  object-position: center 38%;
}
/* Anchored to the viewport edge, not the shell, so they stay in the corners of
   the picture the way they do on a real board. The artwork is square to its
   canvas, so unlike the first pair these are angles, not differences. */
.snap--left  { left:  clamp(0.5rem, 1.6vw, 3.5rem); rotate: -5deg; }
.snap--right { right: clamp(0.5rem, 2.2vw, 4rem);  rotate:  4deg; }
.snap--left:hover  { rotate: -3.2deg; scale: 1.02; }
.snap--right:hover { rotate:  2.4deg; scale: 1.02; }

.snap--left {
  --snap-frame: url("assets/photos/polaroid-taped.webp");
  aspect-ratio: 760 / 670;
  width: calc(var(--card-w) * 1.0386);
  margin-left: calc(var(--card-w) * -0.0368);   /* the tape's overhang */
}
.snap--right {
  --snap-frame: url("assets/photos/polaroid-pinned.webp");
  aspect-ratio: 760 / 676;
  width: calc(var(--card-w) * 1.0035);
  margin-right: calc(var(--card-w) * -0.0026);
}
.snap--left  img { left: 7.09%; top: 15.02%; width: 89.37%; height: 80.67%; }
.snap--right img { left: 3.76%; top: 12.86%; width: 92.40%; height: 82.83%; }

/* The two line drawings. Stroked, never filled — they are meant to read as
   drawn on the photograph, and a fill turns them into stickers. */
.doodle {
  position: absolute;
  fill: none; stroke: oklch(100% 0 0 / 0.5);
  /* Non-scaling stroke: these render at very different widths, and without it
     the skewer's line comes out at half the weight of the sprig's. */
  stroke-width: 1.6; vector-effect: non-scaling-stroke;
  stroke-linecap: round; stroke-linejoin: round;
}
.doodle--skewer { left: clamp(13.5rem, 24vw, 24.5rem); bottom: 8rem; width: 2.7rem; rotate: 12deg; }
.doodle--olive  { right: clamp(13.5rem, 24.5vw, 25rem); bottom: 5rem; width: 7.5rem; rotate: -4deg; }

/* Under 60rem the prints, the headline, the hanging sign and four facts cannot
   all be on screen without one of them winning by force. The photographs are
   the ones with somewhere else to live. */
@media (max-width: 60rem) {
  .hero__snaps { display: none; }
}
@media (prefers-reduced-motion: reduce) {
  .snap, .snap--left:hover, .snap--right:hover { transition: none; scale: 1; }
}

.hero__act { margin: 0; display: grid; justify-items: center; gap: var(--space-sm); }

/* ── The hanging sign ───────────────────────────────────────────────────
   A real photographed sign, supplied 2026-08-14. It replaced a clip-path for
   the hand-cut edge, three gradient layers of grain, two rope elements and an
   inline SVG sprig — about 60 lines of CSS doing an impression of this.

   The ropes are PART OF THE IMAGE and run off its top edge, so the asset is
   taller than the board and the top of it deliberately overlaps the copy
   above. That overlap is the effect; do not add margin to "fix" it. */
.callsign {
  position: relative; display: block;
  /* Twice reduced. 33rem was ~35% of a 1512 laptop and swallowed the headline
     and both prints; 26rem still read heavy against the two polaroids. 21rem
     puts the board at ~22% of that viewport with the number around 32px tall —
     still the loudest single thing in the hero, no longer the whole hero.
     Below ~18rem the painted CALL TO ORDER line starts to close up, so this is
     close to as small as this asset goes.

     Only the cap moved. The vw term stays at 80 so the phone renders exactly
     what it rendered before — below about 420px the viewport is what is in
     force, and nothing was wrong with the sign there. */
  width: min(21rem, 80vw);
  /* NO negative margin, and that is a deliberate removal, not an oversight.
     The first version of this asset hung from two ropes running off its top
     edge — ~45% of the image was rope, which grew the hero stack until the
     headline collided with the painted BEACHSIDE GYROS lettering behind it,
     and -2.25rem was what pulled it back. This asset is the plank alone, so
     there is nothing to pull back. Put the margin back if a roped version
     ever returns. */
  text-decoration: none;
  transition: transform var(--dur-short) var(--ease-out);
}
.callsign__img {
  display: block; width: 100%; height: auto;
  /* drop-shadow, not box-shadow: the asset has an alpha channel and a cut
     edge, and box-shadow would draw a rectangle around a sign that is not
     rectangular. */
  filter: drop-shadow(0 1rem 1.6rem oklch(0% 0 0 / 0.45));
}

/* ── The sign on a phone ─────────────────────────────────────────────────
   *"in the mobile version the cta wooden call button in the hero is too
   large."* At 80vw it took 312 of a 390px screen and read as the hero rather
   than as the thing in it. The note on `.callsign` above says the vw term was
   left at 80 deliberately, because nothing had been wrong with it there — that
   is now superseded for phones only; the rem cap and every width above ~44rem
   are untouched.

   ⚠️ THE FLOOR IS THE PAINTED WORDING, not the tap target. CALL TO ORDER and
   the number are both in the artwork — the asset is 1200x376 and the number's
   caps are 8.3% of its width, the kicker's only 2.3% — so shrinking the board
   shrinks the only two pieces of type on it and nothing reflows to save them.
   `16rem` is set so no phone renders the sign smaller than a 320px screen
   already does today: at 320 the clamp returns the same 256px it always did,
   and 390 comes down 312 → 257, which puts the number at ~21px and the kicker
   at the same ~6px a 320 screen has been shipping all along. Do not lower this
   without measuring the kicker — see the ORDER NOW seal for the same trap.

   The shadow comes down with it. It is a fixed rem, so on a smaller sign it
   grows in proportion, and half of "too large" was a 16/26px shadow under a
   257px board. */
@media (max-width: 44rem) {
  .callsign { width: clamp(16rem, 66vw, 21rem); }
  .callsign__img { filter: drop-shadow(0 0.55rem 1rem oklch(0% 0 0 / 0.42)); }
}
/* Swings very slightly, from the ropes at the top — not from the middle. */
.callsign:hover { transform: rotate(-0.5deg); }
.callsign { transform-origin: 50% 0; }
.callsign:focus-visible { outline: 3px solid var(--color-focus); outline-offset: 6px; }
@media (prefers-reduced-motion: reduce) { .callsign:hover { transform: none; } }

/* config.js set orderOnlineUrl, so the painted number is now wrong. script.js
   hides the image and un-tucks the text, which falls back to the painted-board
   styling that .callboard still carries. */
.callsign__img[hidden] { display: none; }

.callboard {
  position: relative;
  display: inline-grid; gap: 1px; justify-items: center;
  /* 56px+ tall in every state — this is the primary tap target on a phone. */
  padding: clamp(var(--space-md), 4.4vh, 3.4rem)
           clamp(var(--space-xl), 11.5vw, 9.5rem);
  text-decoration: none;
  /* Still darker than the scrim, but nothing like as dark as it was. It went
     out at 15% lightness to guarantee separation from a scrim that was then
     running at 0.94 opacity; with the scrim lightened the board no longer has
     to fight for it, and 15% read as a black hole punched in a sunny
     photograph. 26% is a navy you can see is a navy.

     The floor is roughly 22%: below that it stops reading as painted board
     and starts reading as a hole. The ceiling is whatever still separates
     from the scrim — check both together if either moves. */
  /* Painted timber, not flat navy. Three layers: a broad vertical wash so the
     centre catches more light than the ends, then two sets of very low-contrast
     horizontal streaks at slightly different periods so the grain never repeats
     visibly. Kept under ~4% lightness spread — any more and it stops looking
     like paint on wood and starts looking like a gradient someone applied. */
  /* Weathered painted board.

     ⚠️ THE ANGLES ARE THE WHOLE TRICK AND THEY ARE EASY TO GET BACKWARDS. A
     linear-gradient's angle is the direction the axis POINTS, so 91deg points
     right and bands the fill VERTICALLY. The first attempt used 91deg and
     89.4deg for what was meant to be horizontal grain and produced corduroy.
     Grain that runs left-to-right needs an axis near 0/180deg.

     Three layers: a broad wash across the width so the middle catches more
     light than the ends, then two sets of horizontal streaks at coprime
     periods (7px and 19px) so the grain never visibly tiles. Kept inside a ~5%
     lightness spread — beyond that it stops being paint on wood and becomes a
     gradient someone applied. */
  background:
    repeating-linear-gradient(179.3deg,
      oklch(100% 0 0 / 0.032) 0 1px, transparent 1px 7px),
    repeating-linear-gradient(0.4deg,
      oklch(0% 0 0 / 0.055) 0 1px, transparent 1px 19px),
    radial-gradient(120% 150% at 50% 40%,
      oklch(29% 0.058 254) 0%, oklch(25% 0.053 254) 62%, oklch(21.5% 0.048 254) 100%);
  color: var(--color-onphoto);
  box-shadow: 0 1.1rem 2.2rem oklch(0% 0 0 / 0.4);
  /* THE WHOLE POINT — the edge is cut by hand, not radiused. A border-radius
     is still a rectangle, which is the thing being escaped. Percentages, so
     the cut stays proportional as the board grows with its label. */
  clip-path: polygon(0.6% 6%, 12% 2%, 31% 5%, 55% 1%, 78% 5%, 93% 2%, 99.4% 7%,
                     99% 92%, 86% 97%, 63% 93%, 41% 98%, 19% 94%, 7% 98%, 1% 91%);
  transition: transform var(--dur-short) var(--ease-out),
              background var(--dur-short) var(--ease-out);
}
.callboard__kick {
  font-size: var(--text-xs); font-weight: 700;
  letter-spacing: var(--track-caps); text-transform: uppercase;
  color: var(--color-accent-pale);
}
.callboard__num {
  /* Body face, not display: these are times and they sit in a column that
     people scan down. Gorditas has no tabular figures. */
  font-family: var(--font-body);
  font-weight: 700;
  font-size: clamp(1.5rem, 2.8vw, 2rem); line-height: 1.15;
  font-variant-numeric: tabular-nums;
}
/* Hung on a nail, not lifted off the page — there are no shadows on this site
   and this is not the place to introduce one. */
.callboard:hover { transform: rotate(-0.4deg) scale(1.015); background: oklch(21% 0.052 254); }
/* clip-path crops an outline, so focus has to be drawn OUTSIDE the shape. */
.callboard:focus-visible { outline: 3px solid var(--color-focus); outline-offset: 5px; }
@media (prefers-reduced-motion: reduce) { .callboard:hover { transform: none; } }

/* Quiet, and never a second button: a solid/outline pair in one hero is the
   exact fingerprint this whole change removes. */
.hero__second {
  /* Padded to a 44px target. The rule lives on the inner span, NOT on the
     anchor — put it on the anchor and the padding pushes the underline away
     from the words and it stops reading as a link. */
  display: inline-flex; align-items: center;
  min-height: 44px; padding-inline: var(--space-2xs);
  font-size: var(--text-sm); font-weight: 600; letter-spacing: 0.06em;
  color: var(--color-onphoto-2); text-decoration: none;
  transition: color var(--dur-short) var(--ease-out);
}
.hero__second span {
  border-bottom: 1px solid oklch(100% 0 0 / 0.4);
  padding-bottom: 2px;
  transition: border-color var(--dur-short) var(--ease-out);
}
.hero__second span::after { content: " \2192"; }
.hero__second:hover { color: var(--color-onphoto); }
.hero__second:hover span { border-bottom-color: var(--color-onphoto); }
.hero__status {
  display: inline-flex; align-items: center; gap: var(--space-2xs);
  margin: var(--space-lg) 0 0;
  font-size: var(--text-xs); font-weight: 700;
  letter-spacing: var(--track-caps); text-transform: uppercase;
  color: var(--color-onphoto-2);
}

/* ── Proof strip, on the photograph ───────────────────────
   Four hard facts along the floor of the hero. Not a card grid — flat cells
   that meet, in keeping with the system. It used to be a paper band in the
   section below; carried on the scrim instead, the picture stays full-screen
   and the facts stop costing a scroll. The scrim is at its heaviest down here
   (0.94), so the band needs no fill of its own — a hairline is enough to sit
   it apart from the copy above. */
.hero__proof {
  position: relative; z-index: var(--z-raised);
  /* This used to carry the tear's landing strip in its bottom padding — the
     tear height plus air, ~66+ px on a 1440 laptop — because .hero::before was
     drawn over the last band of this section and the teeth reached into "187
     GOOGLE REVIEWS" without it. **That is no longer this element's job.** The
     tear moved into .hero's own bottom padding on 2026-08-16 and is below the
     fold now, so the strip is plain padding again and the ~66px it was holding
     goes back to the photograph. If you ever move the tear back inside the
     content box, this padding has to come back with it.

     It is not symmetrical: --space-lg at the foot against --space-md at the
     head. `.hero__proof` carries `data-depth="-0.03"`, and depth.js's rule that
     centre reads zero leaves a fold-anchored band translated ~12px DOWN at
     rest. At an even --space-md the second line of facts finished 4px off the
     bottom edge of the screen. The extra 8px is that translate, paid back. */
  padding: var(--space-md) var(--page-gutter) var(--space-lg);
  border-top: var(--rule-hair) solid oklch(100% 0 0 / 0.16);
}
.proof__list {
  max-width: var(--shell); margin: 0 auto; padding: 0;
  list-style: none;
  display: grid; grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: var(--space-md);
  text-align: center;
}
.proof__list li + li { border-left: var(--rule-hair) solid oklch(100% 0 0 / 0.22); }
.proof__list strong {
  display: block;
  /* Figures. Body face — see .callboard__num. */
  font-family: var(--font-body);
  font-size: var(--text-lg);
  font-weight: 700;
  line-height: 1.3;
  color: var(--color-onphoto);
  text-shadow: 0 1px 12px oklch(16% 0.05 248 / 0.55);
}
.proof__list span {
  display: block; margin-top: var(--space-3xs);
  font-size: var(--text-xs); font-weight: 700;
  letter-spacing: var(--track-caps); text-transform: uppercase;
  color: var(--color-onphoto-2);
}
.proof__list span a {
  color: inherit;
  text-decoration: none;
  box-shadow: inset 0 -1px 0 oklch(100% 0 0 / 0.4);
}
.proof__list span a:hover { color: var(--color-onphoto); box-shadow: inset 0 -2px 0 currentColor; }

/* ── Interior page head ────────────────────────────────────────────────────
   The same object as `.mhead` on the menu page, at interior scale. Marcus,
   2026-08-15: bring the remaining pages onto the theme the landing page and
   the menu page settled into.

   What that theme is, in this element: a caps eyebrow saying where you are,
   the title held between two olive sprigs, and a drawn wave under it instead
   of a rule. Before this the interior pages opened with a bare left-aligned
   h1 and three units of the Greek key under it — correct, but it was the one
   part of the site that looked like a stylesheet rather than like printing.

   CENTRED, where the old head was left-aligned. That is not a preference: the
   pages now sit inside the menu's engraved border, and an asymmetric head in a
   symmetric frame reads as a mistake. The body copy below stays left-aligned —
   the menu page does exactly this, centred head over left columns.

   NOT MERGED WITH .mhead. The two share an idiom, not a layout: .mhead is a
   two-column grid with three photographs in the right track and its type is
   sized to a page whose job is to get a price above the fold. Grouping the
   selectors would mean unpicking that on every rule. What IS shared is the
   drawing — see the note on #ic-laurel in any interior page's sprite. */
.phead {
  max-width: var(--shell); margin: 0 auto;
  /* The 6vw floor clears the frame's top rule. The border scales with viewport
     WIDTH (its inner edge sits 3.93vw down) while this padding is otherwise
     driven by viewport HEIGHT, so the two cross over around 1000px and a
     height-only value cuts the eyebrow in half on desktop while looking
     perfectly fine on a phone. Same fix, same reason, as on .mhead. */
  padding: max(clamp(var(--space-lg), 5svh, var(--space-2xl)), 6vw)
           var(--page-gutter) var(--space-lg);
  text-align: center;
}
.phead__where {
  margin: 0 0 var(--space-2xs);
  font-size: var(--text-xs); font-weight: 700;
  letter-spacing: var(--track-caps); text-transform: uppercase;
  color: var(--color-accent);
}
/* Title and sprigs are one line box so the sprigs track the title's size
   rather than being parked at offsets that break at every width. */
.phead__crown {
  display: flex; align-items: center; justify-content: center;
  gap: clamp(var(--space-2xs), 1.8vw, var(--space-md));
}
/* Smaller than the menu's. "Our Menu" is eight characters and can afford
   4.4rem of laurel either side; "That page is off the menu" is twenty-five,
   and at 390px two menu-sized sprigs plus their gaps leave the title 264px to
   set in — about seven characters a line. */
.phead__sprig {
  flex: none;
  width: clamp(1.5rem, 3.4vw, 3rem); height: auto;
  color: var(--color-accent);
}
/* The stem runs bottom-left to top-right, so the sprite as drawn IS the right
   hand sprig and the left one is the mirror. Backwards, both branches lean
   into the title like closing brackets. */
.phead__sprig--flip { transform: scaleX(-1); }
.phead__title {
  font-family: var(--font-display);
  font-weight: var(--weight-display);
  /* Two governors, both needed. The min() caps by height so a wide-but-short
     window does not ask for type it has no room for; the 20ch cap stops the
     longest title on the site ("That page is off the menu") from running the
     full shell as one line at 1920. */
  font-size: clamp(1.9rem, min(5vw, 7.5svh), 3.9rem);
  line-height: 1.05;
  letter-spacing: var(--track-display);
  color: var(--color-accent-2);
  max-width: 20ch;
  margin: 0;
}
/* The wave below replaces it. Left in place on .h-display, which is still the
   section-level heading everywhere else on these pages. */
.phead__title::after { display: none; }
.phead__wave {
  display: block;
  width: clamp(5rem, 9vw, 8rem); height: 12px;
  margin: clamp(var(--space-3xs), 1.2svh, var(--space-xs)) auto
          clamp(var(--space-xs), 2vh, var(--space-md));
  color: var(--color-rule);
}
.phead__lede {
  margin: 0 auto; max-width: 52ch;
  font-size: var(--text-md); line-height: 1.6; color: var(--color-ink-2);
}
.phead__aside {
  margin: var(--space-sm) auto 0; max-width: 46ch;
  font-family: var(--font-body);
  font-weight: 700;
  font-size: var(--text-md);
  color: var(--color-accent);
}
.phead .fold__status {
  display: inline-flex; align-items: center; gap: var(--space-2xs);
  margin-top: var(--space-md);
  padding: var(--space-3xs) var(--space-sm);
  background: var(--color-paper-2);
  color: var(--color-ink-2);
  font-size: var(--text-xs); font-weight: 700;
  letter-spacing: var(--track-caps); text-transform: uppercase;
}
/* Menu-page-only trim. The board below it is 43 rows long and the head is the
   last thing between the reader and a price, so it gives back a little of its
   air. Scoped rather than applied to .phead itself because find.html and
   catering.html open onto short pages where the head IS the content. */
.phead--tight { padding-block: var(--space-xl) var(--space-md); }

.phead__rule { display: none; }      /* the tonal band change is the divider */

/* A section opening straight after a page head does not need its full 7rem
   lead-in on top of the head's own padding — that stacked to ~10rem of air.
   .board is not in this list: it now carries the tighter lead-in itself.

   These were keyed off `.phead__rule +` until 2026-08-15 and so had never
   fired: `.phead__rule` is `display: none` above and appears in no page's
   markup, so the sibling never existed to be matched. Re-keyed to the head
   itself, which is the element actually there. */
.phead + .prose,
.phead + .gallery,
.phead + .qa,
.phead + .here { padding-top: var(--space-lg); }

/* ── Interior pages on the paper ground ────────────────────────────────────
   A single measure of body copy, set hard left under a centred head inside a
   symmetric frame, leaves a column of empty parchment down the right-hand side
   and reads as a layout that has come apart. The menu page does not have this
   problem because its board fills the shell with three columns.

   The TEXT stays left-aligned; only the block is centred. Centred body copy at
   this length is unreadable, and that is not what is being asked for here. */
.paper .prose__measure { margin-inline: auto; }

/* status dot, shared */
.fold__dot {
  width: 7px; height: 7px; border-radius: 50%; flex: none;
  background: currentColor; opacity: 0.55;
}
.fold__status.is-open .fold__dot,
.hero__status.is-open .fold__dot { background: var(--color-open); opacity: 1; }

/* ── Prose ────────────────────────────────────────────── */
.prose { max-width: var(--shell); margin: 0 auto; padding: var(--space-2xl) var(--page-gutter); }
.prose__measure { max-width: var(--measure); }
.prose p { font-size: var(--text-base); line-height: 1.75; }
.prose .h-display,
.slate h2,
.kitchen .h-display { margin-bottom: var(--space-md); }
.prose__sub { font-size: var(--text-lg); margin: var(--space-xl) 0 var(--space-sm); }
/* Seam mark above a .prose__sub. Sits left, not centred, because .prose__sub
   is left-aligned and a centred ornament over left-aligned type reads as
   belonging to the page rather than to the heading under it. Halves the
   heading's top margin, since the mark is now carrying part of that gap. */
.prose__mark {
  display: block; width: 3.4rem; height: auto;
  margin: var(--space-xl) 0 0;
  color: var(--color-accent); opacity: 0.5;
}
.prose__mark + .prose__sub { margin-top: var(--space-2xs); }
/* Centred variant, for a mark that closes a block rather than opening one —
   gallery.html sets it under the grid, over centred copy. Narrower, because
   the amphora is a tall drawing and 3.4rem of it is a lot of pot. */
.prose__mark--mid {
  width: 2rem;
  margin-inline: auto;
  margin-bottom: var(--space-sm);
}

.picks__more {
  margin: var(--space-2xl) 0 0;
  padding-top: var(--space-lg);
  border-top: var(--rule-hair) solid var(--color-rule);
}

/* ── What to order ────────────────────────────────────── */
.picks { padding: var(--space-3xl) var(--page-gutter); }
.picks__inner { max-width: var(--shell); margin: 0 auto; }
.picks__head { margin-bottom: var(--space-xl); }
.picks__note { max-width: 48ch; color: var(--color-muted); margin: var(--space-sm) 0 0; }
.picks__grid {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(17rem, 1fr));
  gap: var(--space-md);
}
/* Flat glazed panel. No border, no shadow. */
.pick { background: var(--color-paper-2); }
.pick__shot { background: var(--color-paper-3); }
.pick__shot img { width: 100%; aspect-ratio: 4 / 3; object-fit: cover; }
.pick__name {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: var(--space-sm);
  font-size: var(--text-md);
  margin: var(--space-md) var(--space-md) var(--space-2xs);
}
.pick__price {
  flex: none;
  font-family: var(--font-body); font-weight: 700;
  font-size: var(--text-base);
  color: var(--color-accent);
  font-variant-numeric: tabular-nums;
}
.pick__note {
  margin: 0 var(--space-md) var(--space-md);
  font-size: var(--text-sm); color: var(--color-muted); line-height: 1.6;
}



/* ── The board · a printed menu card ──────────────────────
   Fourth structure, and the one Marcus specified from a mockup. v1 was three
   white cards on cream (a pricing table). v2 flooded the section with ink. v3
   was a tiled wall of flat colour with a photo plate on each tile — which kept
   losing to the source material rather than to the layout.

   v4 drops the photographs from this section entirely and sets it the way a
   menu is actually set: centred masthead between two runs of the key, three
   columns divided by hairlines, leader dots, a closing plaque of line art.
   Nothing here depends on a photograph being good.

   DELIBERATE DEPARTURE from the "no outlines" rule in tokens.css. Column
   dividers and the plaque's frame are the entire grammar of a printed card;
   without them this is three lists adrift on plaster. The departure is scoped
   to .picks--board and the rules are genuine hairlines in the site's own rule
   token — it is not a licence to start boxing things elsewhere. */
.picks--board { background: var(--color-paper); color: var(--color-ink); }

/* ══ MENU SHEET ══════════════════════════════════════════════════════════
   Marcus's "Concept 3 — Bold & Immersive" blueprint, 2026-08-14. Replaces the
   chalkboard on whitewashed planks that stood here for a few hours.

   ⚠️ ONE SURFACE ACROSS BOTH PAPER BANDS. The parchment here and the parchment
   under "What keeps people coming back" are the same paper. That is the
   direction, not an oversight:

       hero photo → PARCHMENT → navy tzatziki → PARCHMENT → navy footer

   Do not introduce a third major surface between them. The two navy bands are
   the page's colour breaks; everything else is the same sheet of paper.

   Until 2026-08-15 the two bands loaded the literally identical file. Marcus
   then supplied a new parchment cut to several ratios per band — corner sprigs
   here, a branch entering at mid-height on the wall — so they now load
   different files of the same paper at the same tone. The rule above is
   unchanged in substance and this is not licence to keep going.

   The menu itself is TEXT. See the note in index.html for why that matters. */
.sheet {
  position: relative;
  /* The print and the note hang past the wrap on purpose. Without this they
     add a horizontal scrollbar at every width. */
  overflow: hidden;
  padding: clamp(2rem, 3.2vw, 2.75rem) var(--page-gutter)
           clamp(1.5rem, 2.4vw, 2.25rem);
  color: var(--color-ink);

  /* THE GROUND IS SHARED NOW — see `.paper, .sheet, .wall` further down.
     What stood here was a ratio table and three cuts of sheet-*.webp chosen by
     this box's shape, because `cover` only scales by width while the image's
     ratio is at or below the box's, and this box swings from 0.24 at 390px to
     2.61 at 2560px. That machinery is gone with the pictures it was steering:
     the tile repeats to any height and the branches are pinned to the corners,
     so nothing here depends on the box's ratio any more. */

  /* ⚠️ NO MASK ON THIS SECTION. Cutting the hero's tear out of .sheet itself —
     negative margin up over the hero, tear as a mask on the top edge — was
     tried on 2026-08-15 and REVERTED the same day: "too zoomed in and dividers
     got messed up even more." It is not the answer even though the reasoning
     was sound. The tear is a filled strip on .hero::before; see the note there.

     The fault behind that attempt is closed as of 2026-08-17 WITHOUT moving
     this section: the strip now paints this section's own tile, anchored to its
     own bottom so the tile's foot lands on this section's `center top` tile
     head. See the note on .hero::before. What that depends on from HERE is only
     that this section keeps painting the tile `center top` / repeat-y at
     `100% auto` across the full viewport width — change any of those three and
     the seam above re-opens. */

  /* Hairlines in this section's own blue rather than the site's grey rule
     token — on warm parchment, a neutral grey line reads as dirt. */
  --sheet-rule: oklch(46% 0.130 245 / 0.26);
  --sheet-leader: oklch(46% 0.130 245 / 0.42);
}

/* (The tear at this boundary lives on .hero::before, not here — see the note
   beside it. A strip inside .sheet can only ever paint parchment onto
   parchment, because its transparent half reveals this section's own ground
   rather than the photograph above.) */

.sheet__wrap { max-width: var(--shell); margin: 0 auto; }

/* ── Masthead ──────────────────────────────────────────────────────────
   Small on purpose. The blueprint's own note is "do not make the heading
   enormous" — this section's job is twelve prices, and a display line that
   eats a third of the sheet pushes them under the fold. */
.sheet__head {
  text-align: center;
  margin-bottom: clamp(var(--space-lg), 2.8vw, var(--space-2xl));
}
.sheet__title {
  margin: 0;
  font-family: var(--font-display); font-weight: var(--weight-display);
  font-size: clamp(1.75rem, 3.1vw, 2.6rem); line-height: 1.05;
  letter-spacing: 0.015em; text-transform: uppercase;
  color: var(--color-deep);
}
/* "at a glance" — added 2026-08-17 at Marcus's request.
   The obliqued display face, not tracked caps. Three of these column asides
   already speak in that voice further down the section ("Twelve gyros in all,
   $13–14"), and this line is doing the same job: qualifying, not labelling.
   Tracked caps would have been the third uppercase register stacked in one
   header — title, this, then OFF THE SPIT under the icons — and the hierarchy
   goes flat when everything shouts. Lowercase as he wrote it. */
.sheet__sub {
  margin: var(--space-3xs) 0 0;
  font-family: var(--font-display); font-style: italic; font-weight: 400;
  font-size: var(--text-sm); line-height: 1.3;
  color: var(--color-ink-2);
}
.sheet__sprig {
  display: block; width: 2.2rem; height: auto;
  margin: var(--space-2xs) auto 0;
  color: var(--color-accent); opacity: 0.85;
}

/* ── The stage ─────────────────────────────────────────────────────────
   Positioning context for the two loose objects. See index.html for why they
   hang off the columns and not off the sheet. */
.sheet__stage { position: relative; }

/* ── The taped print ───────────────────────────────────────────────────
   Same object the hero already uses twice — warm paper border, thicker at the
   foot, tape torn at both ends. Restrained shadow: this sits on paper, not in
   a room, and the chalkboard's 0.4 drop shadow read as a UI card the moment
   the dark ground went away. */
/* ⚠️ ANCHORED FROM THE TOP AT `100%`, NOT FROM THE BOTTOM. Hung off `bottom`
   these two sat INSIDE the columns — the print covered "Steak $14" and the
   first column's note, the note covered "Dolmades $6" and the third's. `top:
   100%` starts them at the foot of the tallest column and lets them hang into
   the band of air above the call to action, which is what the negative offset
   buys back: a couple of rem of honest overlap, no more.

   They stay clear of the call to action HORIZONTALLY, not vertically — it is
   centred and about 400px wide, so the outer thirds of that band are empty and
   these two live there. Widen the CTA and this needs re-measuring. */
.sheet__print {
  position: absolute; z-index: 2;
  left: -1%; top: calc(100% - 1.5rem);
  /* Was 17.5%/13rem, which read as a thumbnail next to the note and the
     plaque. 22%/16rem is as wide as it goes before the outer third it lives in
     stops being empty and it starts crowding the call to action. */
  width: clamp(11rem, 22%, 16rem);
  margin: 0;
  background: var(--photo-paper);
  padding: 0.45rem 0.45rem 0.95rem;
  rotate: -3.2deg;
  box-shadow:
    0 0.75rem 1.5rem oklch(0% 0 0 / 0.16),
    0 0.1rem 0.3rem oklch(0% 0 0 / 0.11);
}
.sheet__print img { display: block; width: 100%; height: auto; }
/* The tear is cut into the shape rather than faked with a gradient, so the
   tape survives on parchment exactly as it does on the hero's photographs. */
.sheet__tape {
  position: absolute; top: -0.6rem; right: -1.1rem;
  width: 4.75rem; height: 1.5rem;
  background: oklch(89% 0.05 88 / 0.82);
  rotate: 24deg;
  clip-path: polygon(4% 0, 96% 6%, 100% 34%, 97% 96%, 6% 100%, 0 62%, 3% 30%);
  box-shadow: 0 1px 3px oklch(0% 0 0 / 0.2);
}

/* ── The torn note ─────────────────────────────────────────────────────
   Navy paper, cream handwriting and masking tape are all in the pixels — see
   tools/build-note-navy.py. Sized in % OF THE STAGE, not vw: anything pinned
   to this block has to scale with this block, which the chalkboard's note
   learned the hard way when the board changed width and it did not. */
.sheet__note {
  position: absolute; z-index: 3;
  right: 0; top: calc(100% - 1.5rem);
  /* Was 8.5%/6.75rem. Four lines of handwriting at that size were legible only
     if you went looking; 11.5%/9rem lets them be read at a glance. It stays
     narrower than the print because it is the quieter of the two. */
  width: clamp(6.5rem, 11.5%, 9rem); height: auto;
  rotate: 3deg;
  filter: drop-shadow(0 0.5rem 0.9rem oklch(0% 0 0 / 0.2));
}

/* ── Call to action ────────────────────────────────────────────────────
   Not a button. This site has no pill anywhere and the blueprint sets this as
   type between two ornaments. The top margin is also what gives the print and
   the note their landing strip — cut it and they sit on the words. */
.sheet__cta {
  display: flex; align-items: center; justify-content: center;
  gap: clamp(var(--space-sm), 2vw, var(--space-lg));
  margin: clamp(2.25rem, 3.6vw, 3.25rem) 0 0;
}
.sheet__accent {
  width: clamp(2.5rem, 4.5vw, 3.5rem); height: auto; flex: none;
  color: var(--color-accent); opacity: 0.65;
}
.sheet__accent--flip { transform: scaleX(-1); }
.sheet__cta a {
  font-family: var(--font-body); font-weight: 800;
  font-size: var(--text-sm); letter-spacing: var(--track-caps);
  text-transform: uppercase; color: var(--color-deep);
  text-decoration: none;
  border-bottom: 2px solid var(--sheet-rule);
  padding-bottom: 4px;
  transition: color var(--dur-short) var(--ease-out),
              border-color var(--dur-short) var(--ease-out);
}
.sheet__cta a::after { content: " \2192"; }
.sheet__cta a:hover,
.sheet__cta a:focus-visible { color: var(--color-accent); border-bottom-color: var(--color-accent); }
.sheet__mid { color: var(--color-accent); }

/* Closer to the menu than the menu is to the heading — it reads as the sheet's
   last line rather than as a third object floating on the paper. */
/* ⚠️ This margin is also the note's landing strip. The note hangs from
   `top: calc(100% - 1.5rem)` on the stage, so its height is what decides
   whether it clears the plaque — and when the note was widened on 2026-08-15
   its bottom-right corner started overlapping the plaque's top-left meander
   bracket at around 1024px. Enlarge the note again and re-check this at 1024,
   not just at 1440.

   The floor is 4.75rem rather than --space-xl (3rem) because the vw term is
   only ~37-41px through the band where this is tightest, so the floor is what
   is actually in force there. Measured worst case is ~1150px, where the note
   overhangs the plaque's right edge by about 12px; the token left 6px of air
   under it, which is not clearance, it is luck — the note's own drop-shadow
   reaches ~22px below its box and would have landed on the frame. 4.75rem
   buys that 22px back. */
.sheet .commune { margin-top: clamp(4.75rem, 3.6vw, var(--space-2xl)); }

/* ── Phone ─────────────────────────────────────────────────────────────
   ⚠️ THE TWO OBJECTS MUST COME OUT OF FLOW HERE. Left absolute they measure
   from the foot of three COLLAPSED accordions, which is a couple of hundred
   pixels higher than the columns they were placed against — both of them
   landed square on "VIEW THE FULL MENU". The blueprint's own phone stack puts
   them in flow below the accordions anyway, photo left and note right with a
   little overlap, which is what this does. */
@media (max-width: 48rem) {
  /* ⚠️ FLEX, NOT GRID, AND THE REASON IS SPECIFIC. A `minmax(0,1fr) auto`
     grid sizes the auto track from its content's max-content — and the note is
     an <img> carrying width="560". The track claimed most of the row, the 1fr
     column collapsed, and the print rendered FIFTEEN PIXELS WIDE. A flex
     container has a definite width, so a percentage basis means what it says. */
  .sheet__stage { display: flex; flex-wrap: wrap; align-items: flex-end; }
  .breadth { flex: 1 0 100%; }
  .sheet__print {
    position: static; inset: auto;
    flex: 0 0 58%; width: auto;
    margin-top: var(--space-xl);
    rotate: -2.5deg;
  }
  .sheet__note {
    position: static; inset: auto;
    /* Tucked under the print's right edge — the same believable overlap the
       desktop composition has between these two and the columns.

       ⚠️ An explicit width, and min-width:0. As a flex item this <img> keeps
       min-width:auto, which for a replaced element is its INTRINSIC width —
       560px. It ignored a 30% basis, took the whole row and wrapped onto its
       own line, which added 400px to the section on a phone. */
    flex: none; min-width: 0;
    width: 30%; max-width: 7.5rem;
    margin: 0 0 var(--space-sm) auto;
    rotate: 3deg;
  }
  /* Two lines at 390 — let it break rather than shrink the type. The two
     ornaments come off: stacked under a wrapped line they read as a third and
     fourth row of the call to action rather than as its brackets. */
  .sheet__cta { flex-wrap: wrap; text-align: center; }
  .sheet__accent { display: none; }
}

/* ── Masthead ─────────────────────────────────────────────
   key ──── sprig + title ──── key, tagline beneath, all centred. The keys are
   the section mark at this level, so the usual ::after mark is not used here. */
.menuhead {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr);
  align-items: center;
  gap: 0 var(--space-lg);
  text-align: center;
}
.menuhead__key {
  height: 20px;
  background-color: var(--color-accent);
  -webkit-mask: var(--meander) repeat-x right center / var(--meander-size);
          mask: var(--meander) repeat-x right center / var(--meander-size);
  opacity: 0.9;
}
/* Mirrored, so the two runs read outward from the title instead of marching
   through it in one direction. */
.menuhead__key--flip { transform: scaleX(-1); }

/* The key is a geometric, architectural ornament and it needs a face with a
   flat cap-height band to sit against. Gorditas gives it one: an even, solid
   top line the two runs can align to. This used to be set
   in Prata to avoid a script masthead under a script wordmark — that problem
   is gone now the headline face and the logo are the same hand. */
.menuhead__title {
  display: flex; align-items: center; justify-content: center;
  gap: var(--space-sm);
  font-family: var(--font-display);
  font-weight: var(--weight-display);
  font-size: var(--text-display-s);
  line-height: 1.1;
  max-width: none;
}
.menuhead__sprig {
  width: 2.4rem; height: auto; flex: none;
  color: var(--color-accent);
}
.menuhead__line {
  grid-column: 1 / -1;
  margin: var(--space-sm) 0 0;
  font-size: var(--text-xs); font-weight: 700;
  letter-spacing: var(--track-caps); text-transform: uppercase;
  color: var(--color-ink-2);
  /* Without this it broke as "…NOT ALL OF / IT." on a phone. */
  text-wrap: balance;
}

/* ── The three columns ────────────────────────────────────
   Equal thirds. A menu's authority comes from the columns being the same
   measure — three lists at three widths reads as a mistake once the only thing
   marking the split is a hairline.

   Each column is a <details> shipping `open`, which is what lets the same
   markup be three columns here and three accordions on a phone. See the note
   in index.html. */
.breadth {
  /* Declared once and used three times below — the divider's negative margin
     has to be exactly half this or the hairline stops sitting in the middle of
     the gutter, which is the one thing anyone notices about a ruled column. */
  --breadth-gap: clamp(var(--space-lg), 3.2vw, 3.5rem);
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  column-gap: var(--breadth-gap);
}
/* display:block, not flex. <details> with a flex display has patchy support
   for the way engines box the disclosure content, and nothing here needs it —
   the column is a stack, which is what block already gives.

   The bottom padding is LOAD-BEARING, not spacing for its own sake: it is the
   band of clear paper the print and the note overlap into. Without it those two
   were measured sitting on the last line of the first and third columns'
   handwritten notes — box overlap first, then real glyph overlap once the boxes
   were tightened. Take this out and they land on the words again. */
.breadth__col {
  display: block; min-width: 0;
  padding-bottom: clamp(1.5rem, 2.6vw, 2.5rem);
}
/* The divider is pulled back into the middle of the gutter by a negative
   margin that the padding immediately gives back, so the content box still
   starts at the track edge. That matters: padding the columns directly instead
   cost the MIDDLE column twice the inset of the outer two, and a menu whose
   centre column runs visibly short reads as a mistake. */
.breadth__col + .breadth__col {
  border-left: var(--rule-hair) solid var(--sheet-rule, var(--color-rule));
  margin-left: calc(var(--breadth-gap) / -2);
  padding-left: calc(var(--breadth-gap) / 2);
}

/* ── Category head ────────────────────────────────────────
   Icon ABOVE the label and both centred, per the blueprint. The flanking
   dashes this used to carry are gone — with an icon stacked over the label
   they made a five-part ornament out of a two-word heading. */
.breadth__sum {
  display: block;
  list-style: none;          /* Firefox */
  cursor: default;
}
.breadth__sum::-webkit-details-marker { display: none; }   /* Safari */

.breadth__cap {
  display: flex; flex-direction: column; align-items: center;
  gap: var(--space-2xs);
  margin: 0 0 var(--space-md);
  font-family: var(--font-body);
  font-size: var(--text-xs); font-weight: 800;
  letter-spacing: var(--track-caps); text-transform: uppercase;
  line-height: 1.2; text-align: center;
  color: var(--color-deep);
}
/* The print stylesheet still targets these; giving them no content is what
   turns them off rather than deleting rules it depends on. */
.breadth__cap::before, .breadth__cap::after { content: none; }
/* Tracking adds a trailing space after the last letter, which throws the
   optical centre a few pixels right. Claw it back. */
.breadth__cap span { margin-right: calc(var(--track-caps) * -1); }
/* Drawings, not 24px-grid glyphs. The old SVGs read fine at 2rem because they
   were six strokes each; these carry carving lines, griddle speckle and a
   parsley sprig, and at 2rem all of that collapses into a smudge. 4rem was
   still reading as an icon rather than a drawing at desktop widths; 5.5rem is
   where the carving lines and the sprig are actually legible, and it gives the
   three columns a head each instead of three bullets. Below 3rem they stop
   being drawings and become marks. The source files are 256px square, so this
   is still under half their native size. The navy is baked into the file by
   tools/build-menu-icons.py, so `color` no longer does anything here — the
   print sheet never recoloured this icon either, so nothing is lost. */
.breadth__icon {
  width: 5.5rem; height: 5.5rem; flex: none;
  object-fit: contain;
}

/* No rule between rows. The leader dots already carry the eye across, and a
   line under every item turns the columns straight back into a table. */
.breadth .list li { border-top: 0; padding: var(--space-2xs) 0; }
.breadth .list__item { font-weight: 600; color: var(--color-ink); }
.breadth .list__item::after { border-bottom-color: var(--sheet-leader, var(--color-rule)); }
.breadth .list__price {
  font-family: var(--font-body); font-weight: 700;
  font-variant-numeric: tabular-nums;
  font-size: var(--text-base);
  color: var(--color-accent);
}
.breadth .veg { color: var(--color-olive); }

/* The handwritten note under each column. The blueprint sets these as a script
   hand; this site already loads two families and a third for three sentences
   is not a trade worth making, so it is the display face obliqued — the same
   substitution the chalkboard's title used, and it reads as a margin note
   rather than as body copy. */
.breadth__foot {
  display: flex; gap: var(--space-xs);
  margin: 0; padding-top: var(--space-lg);
  font-family: var(--font-display); font-style: italic; font-weight: 400;
  font-size: var(--text-sm); line-height: 1.5; color: var(--color-ink-2);
}
.breadth__arrow {
  width: 1.5rem; height: auto; flex: none;
  margin-top: 0.15em;
  color: var(--color-accent); opacity: 0.5;
}
.breadth__leaf {
  width: 1.6rem; height: auto; flex: none;
  margin-top: 0.2em;
  color: var(--color-accent); opacity: 0.75;
}

/* ── Phone: three accordions ──────────────────────────────
   The blueprint's own mobile stack. Three columns shrunk to a 390px screen is
   the thing it explicitly asks not to happen, and it is right — the prices
   wrap off their names long before the columns get narrow enough to fit.

   script.js closes all but the first and makes them mutually exclusive here.
   Everything below is only the presentation. */
@media (max-width: 48rem) {
  .breadth { display: block; }
  .breadth__col + .breadth__col {
    border-left: 0; margin-left: 0; padding-left: 0;
    border-top: var(--rule-hair) solid var(--sheet-rule, var(--color-rule));
  }
  /* A real target, and a real affordance: 44px+ of row, pointer cursor, and a
     +/− that changes with the state. */
  .breadth__sum {
    display: flex; align-items: center; gap: var(--space-sm);
    padding: var(--space-sm) 0;
    cursor: pointer;
  }
  .breadth__cap {
    flex: 1 1 auto; flex-direction: row; align-items: center;
    justify-content: flex-start; gap: var(--space-sm);
    margin: 0; text-align: left;
  }
  /* Sits inline with the cap text here rather than above it, so it cannot take
     the full 5.5rem without pushing the accordion rows apart. 2.75rem is the
     compromise: still a drawing, still an accordion row. Deliberately NOT
     raised alongside the desktop size — the constraint here is the row, not
     the drawing. */
  .breadth__icon { width: 2.75rem; height: 2.75rem; }
  .breadth__sum::after {
    content: "+";
    flex: none;
    font-family: var(--font-body); font-weight: 400;
    font-size: 1.5rem; line-height: 1;
    color: var(--color-accent);
  }
  .breadth__col[open] .breadth__sum::after { content: "\2212"; }
  .breadth__col[open] .list { padding-bottom: var(--space-2xs); }
  .breadth__foot { padding-top: var(--space-md); padding-bottom: var(--space-sm); }
  /* The clear band under the columns is only there for the two taped objects,
     and on a phone those sit below the accordions in flow rather than on them. */
  .breadth__col { padding-bottom: 0; }
}

/* Closer to the card above than to the plaque below, so it reads as the card's
   own last line rather than as something floating between two objects. */
.picks--board .picks__more {
  margin: var(--space-xl) 0 0;
  padding-top: 0; border-top: 0;
  text-align: center;
}

/* ── Closing plaque ───────────────────────────────────────
   The one framed object on the page, and since 2026-08-15 a painted one:
   Marcus supplied the plaque whole — paper, double rule, meander corners,
   village, sailboat and both sentences in one file. Nothing here draws a
   frame any more, because the artwork already has one.

   It ships in two forms. `.commune__plaque` is the artwork; `.commune__card`
   is the phone reading of it, live type between two ornaments cut from that
   same artwork. Default is the card — the 4.8:1 painting is the thing that
   needs room, so it is the one behind a min-width query. */
.commune {
  display: block;
  margin-top: var(--space-2xl);
  text-align: center;
}
.commune__plaque { display: none; }
.commune__card {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  align-items: center;
  gap: var(--space-lg);
  padding: var(--space-md) var(--space-xl);
  /* The plaque's own paper, sampled off the artwork at rgb(243,230,210) — a
     hair lighter than --parchment. The two ornaments are cut with this cream
     still behind them, so matching it here is what stops each one reading as
     a pale rectangle pasted on the page. */
  background: oklch(93% 0.030 79);
  border: var(--rule-hair) solid var(--color-accent);
}
.commune__art {
  width: clamp(6.5rem, 13vw, 11.5rem); height: auto;
}

@media (min-width: 60rem) {
  /* Not full-bleed. At 100% of the sheet the plaque ran the entire width of
     the menu above it and read as a second section rather than as its closing
     line. 52rem caps it at ~830px — under the artwork's 1520px native width,
     so it never upscales — and the parchment either side is what makes it sit
     ON the sheet instead of replacing it. */
  .commune__plaque {
    display: block;
    width: min(100%, 52rem); height: auto;
    margin-inline: auto;
  }
  .commune__card { display: none; }
}
.commune__kicker {
  margin: 0 0 var(--space-2xs);
  font-size: var(--text-xs); font-weight: 800;
  letter-spacing: var(--track-caps); text-transform: uppercase;
  color: var(--color-accent);
}
.commune__body {
  margin: 0 auto; max-width: 44ch;
  font-size: var(--text-sm); color: var(--color-ink-2); line-height: 1.6;
}

/* width/height 0 rather than display:none — display:none stops <use> resolving
   in some engines. */
.u-sprite { position: absolute; width: 0; height: 0; overflow: hidden; }

/* ── The tzatziki ─────────────────────────────────────────
   Rebuilt 2026-08-09 to Marcus's third mockup. Was a 13rem thumbnail, a
   heading and two prices in a row: correct information, no presence, on the
   one product this truck is actually known by name for.

   Now a full-bleed two-up. The photograph runs off the LEFT EDGE OF THE
   VIEWPORT with no shell to hold it back, which is the cheapest way to make a
   soft phone snap look deliberate — crop it harder and let it bleed. The type
   sits on the dark ground to the right of centre. Because the shell is centred,
   the 50% column boundary IS the shell's midpoint, so the text starts on a line
   the rest of the page already knows about.

   --color-deep-2, NOT --color-deep. This band sits directly under the board,
   and an earlier rule that tried to differentiate the two was silently losing
   the cascade to a later `.tzat { background: var(--color-deep) }` at equal
   specificity — so the two slabs rendered identical and read as one flat
   rectangle half the page tall. That is most of why the board looked cheap. */
.tzat {
  position: relative;
  background: var(--color-deep); color: var(--color-onphoto);
  /* A four-track bleed grid: gutter | food | type | gutter, with the two middle
     tracks adding up to --shell. Every other section on this site centres its
     content on that same 1180px measure, and this band has to as well — a
     free-running two-column split put the food and the type in the left half of
     a wide monitor and left a third of the section as dead navy on the right.
     Now the leftover width is symmetric page margin, which is what the rest of
     the page already does. */
  display: grid;
  grid-template-columns:
    minmax(var(--page-gutter), 1fr)
    minmax(0, calc(var(--shell) * 0.46))
    minmax(0, calc(var(--shell) * 0.54))
    minmax(var(--page-gutter), 1fr);
  /* Room for a torn edge on each side. --tear-h is declared here so the band,
     the two edges and the key tile can never drift out of step.

     ⚠️ IT MATCHES THE HERO'S AND THE FOOTER'S TEAR ON PURPOSE. It ran shallower
     (2rem / 2.9vw / 3.1rem) while the other two ran 4.6vw, which is a third
     less depth for the same device on the same page. Now that all four edges
     cut with the same file, the same depth is what keeps them reading as one
     rip repeated rather than three different ones. It also stops the mask
     repeating: at 66px the 1600x75 source draws 1408px wide, so on a 1440
     laptop it barely tiles at all. Change this and change the other two. */
  --tear-h: clamp(1.5rem, 4.6vw, 4.4rem);
  /* Clear of both torn edges, plus real breathing room. */
  padding-block: calc(var(--tear-h) + var(--space-md));
  align-items: center;
  overflow: hidden;
}

/* NOT a photo panel. The cut-out is a transparent PNG standing directly on the
   band's own ground, so there is no second rectangle and no seam — the whole
   section is one flat block of colour, which is the direction's actual rule.
   That is also why this band moved from --color-deep-2 to --color-deep: with
   nothing behind the food, the darker ground is what makes it read as lit. */
/* Bled off the left edge of the page. Three things matter here and each one
   has already broken once:
     · the cap is on the FIGURE, not the image, so the figure's right edge is
       exactly the edge of the bowl — that is what lets the seal anchor to
       `right: 0` and land against it instead of floating in dead space;
     · align-self: center, NOT the grid default of stretch. Stretched, the
       figure ran the full row height and `bottom: 8%` dropped the seal well
       below the bowl;
     · no vertical padding, so the figure's box IS the image's box and the
       seal's percentages mean what they look like they mean.
   The cap is sized so the food and the four lines of type beside it come out
   at roughly the same height — the photograph is the section's other half,
   not its headline. */
/* No negative margin and no bleed. The cut-out was hanging into the page gutter
   and .tzat clips its overflow, so the pita was being sliced down its left side
   on every wide screen. It sits inside its track, whole. */
.tzat__shot {
  grid-column: 2;
  position: relative; align-self: center;
  margin: 0; padding: 0;
}
.tzat__shot img { display: block; width: 100%; height: auto; }

/* A printed stamp on the photograph.
   THE ONE ROUNDED THING IN THE BUILD, and it does not contradict the zero-radius
   rule in tokens.css: that rule is about tiles and panels not having softened
   corners. This is a hard circle, an ornament in the same class as the sprig —
   nobody will mistake it for a rounded card. Do not generalise from it. */
/* ── The seal ─────────────────────────────────────────────
   THERE IS NO RING. Three attempts at this got rejected on 2026-08-09 and the
   thin outer ring was the common factor every time — Marcus called it, exactly,
   "common AI slop", and he is right: a 1px circle inset from an otherwise empty
   edge is the default badge every generator draws. A hairline around nothing
   is not a design, it is a placeholder for one.

   So the mark is built the way the rest of this site's identity now is: flat
   colour, a hand-cut edge, and nothing outlined. It is CREAM WITH NAVY TYPE,
   inverted from the band it sits on, which makes it read as a paper label
   stuck on the tub rather than as a translucent UI badge — and it matches the
   painted wordmark in the masthead, which is navy on cream with a cut edge.

   Bigger, too. It ran at 6.2rem, which is under 100px, and three lines of caps
   at that size is where the cramping came from. */
.tzat__seal {
  position: absolute; z-index: 1;
  /* Unchanged: the PNG is cropped to the food's own alpha bounds, so the
     figure's right edge IS the edge of the bowl. 30%, not 34% — the mark is
     larger now and the overhang has to stay under .tzat__say's lead padding. */
  right: 0; bottom: 6%; transform: translateX(30%) rotate(-4deg);
  width: 8.6rem; height: 8.6rem; margin: 0;
  display: grid; place-content: center; justify-items: center;
  gap: var(--space-3xs);
  background: var(--color-paper);
  color: var(--color-accent-2);
  text-align: center;
  font-size: var(--text-sm); font-weight: 800;
  /* Half the old tracking. 0.14em is a label idiom for ONE short line; over
     three stacked lines in a disc it was what forced the negative-margin
     correction below. */
  letter-spacing: 0.06em; text-transform: uppercase; line-height: 1.3;
  /* Cut by hand, not drawn by a radius. A circle that is very slightly not a
     circle is the whole difference between pressed and generated. */
  clip-path: polygon(50% 0%, 63% 1%, 74% 6%, 84% 12%, 92% 21%, 97% 32%, 100% 45%,
                     99% 58%, 94% 71%, 86% 81%, 76% 90%, 63% 96%, 50% 100%,
                     37% 97%, 25% 91%, 15% 83%, 7% 72%, 2% 60%, 0% 47%, 2% 34%,
                     8% 22%, 16% 12%, 27% 5%, 38% 1%);
}
.tzat__seal svg { width: 1.9rem; height: auto; color: var(--color-accent); }
/* The tracking is applied to the right of every glyph, including the last one
   on each line, which pushes centred lines visibly left. */
.tzat__seal span { margin-right: -0.06em; }

.tzat__say {
  grid-column: 3;
  display: grid; align-content: center; justify-items: start;
  padding-inline: var(--space-2xl) 0;
}
.tzat__kicker {
  margin: 0; font-size: var(--text-xs); font-weight: 800;
  letter-spacing: var(--track-caps); text-transform: uppercase;
  color: var(--color-accent-pale);
}
.tzat__title {
  margin: var(--space-xs) 0 0; max-width: 15ch;
  font-size: var(--text-display); line-height: 1.16;
  color: var(--color-onphoto);
}
/* Hairline, sprig, hairline — but only trailing, so it reads as a rule drawn
   out from the headline rather than a centred ornament on a left-aligned block. */
.tzat__rule {
  display: flex; align-items: center; gap: var(--space-sm);
  width: 100%; max-width: 20rem; margin: var(--space-md) 0 0;
}
.tzat__rule svg { width: 1.8rem; height: auto; flex: none; color: var(--color-accent-pale); }
.tzat__rule::after {
  content: ""; flex: 1 1 auto; min-width: 1rem;
  height: var(--rule-hair); background: var(--color-accent-pale); opacity: 0.5;
}
.tzat__blurb {
  margin: var(--space-md) 0 0; max-width: 56ch;
  color: var(--color-onphoto-2); font-weight: 450;
}

/* The $3 / $1 price pair and the "Call to order · See the full menu" row that
   used to close this block are gone. Both were repeats — the price list lives
   on the menu, and the call button is in the masthead, on the callbar and in
   the section directly below. Removing them is most of why this band is short
   now: it says one thing, which is the one thing painted on the truck. */

/* ── The torn edge · SHARED ───────────────────────────────
   ⚠️ RENAMED FROM `.tzat__tear` ON 2026-08-17 BECAUSE IT IS NO LONGER THE
   TZATZIKI BAND'S. catering.html's "what to expect" is a navy band now and it
   is torn top and bottom with this same device — one asset, one depth, one set
   of rules, exactly as the note below already required of the four edges on the
   home page. Anything that wants a coloured band bitten out of paper uses
   `.tear` + `--top`/`--bot` and declares `--tear-h` on the band itself.

   The band does not stop on a ruled line, it is torn off. A MASK, not a
   drawing: three vector attempts at this edge all read as vector, because a
   path has one hard boundary and torn paper does not. The shape ships as an
   alpha png and the COLOUR comes from the token here, so if the next section's
   ground changes this is the single value to change with it.

   ⚠️ ONE TEAR ASSET ON THE WHOLE PAGE. This used to load tear-mask.png — a
   loaded-brush drag with grain and bristle streaks breaking up along its edge —
   while the footer and the hero used foot-tear-mask.png, a cleaner rip. Two
   torn edges in one language and one in another, and the odd one out is the one
   Marcus stopped at: "why is this dividing line messed up." Nothing was broken;
   the grain simply reads as damage next to the other two. All four edges now
   cut with the same file. tear-mask.png is parked in _retired/photos/. */
.tear {
  position: absolute; left: 0; right: 0;
  height: var(--tear-h);
  z-index: 2; pointer-events: none;
  /* --parchment, NOT --color-paper. These teeth are the paper above and below
     biting into the navy, so they have to be the colour of that paper. Against
     the parchment sections --color-paper is a good six points lighter and the
     rips read as a pale fringe drawn along the edge rather than as a tear.

     AND THE TILE OVER IT, 2026-08-17, for the same reason .hero::before carries
     it — see that note for the full account. Flat --parchment is a tone the
     textured sections no longer have: the tile averages rgb(242,227,202) and the
     flat fill is rgb(236,224,204), so both of these strips showed a band of
     duller cream and a hard line where the real paper started.

     THE BOTTOM EDGE IS EXACT, THE TOP EDGE IS WITHIN A UNIT. `--bot` meets
     .wall's top, which is where .wall begins a fresh tile, so anchoring that
     strip's tile to its own bottom joins tile-foot to tile-head — the seam that
     already repeats invisibly inside every parchment section. The top strip
     meets .sheet's BOTTOM, which lands mid-tile at a phase CSS cannot read, so
     it restarts the tile at its own top. That costs nothing measurable: the
     tile's row means run 241.0–242.2 in red across all 183 rows, so the worst
     phase break is about one unit against the six the flat fill was showing. */
  background-color: var(--parchment);
  background-image: url("assets/photos/ground-tile-900.webp?v=20260818a");
  background-position: center top;
  background-repeat: repeat-y;
  background-size: 100% auto;
  /* `auto 100%` + repeat-x, NOT `100% 100%`. Stretching 2400px of chips into a
     390px phone squeezed them into a vertical comb. Scaled by height only, the
     grain keeps its real size at every width; the source is flat at both ends
     so the repeat joins invisibly (and at these proportions the tile is wider
     than the viewport anyway, so it rarely gets to repeat at all). */
  -webkit-mask: url("assets/photos/foot-tear-mask.png") repeat-x center bottom / auto 100%;
          mask: url("assets/photos/foot-tear-mask.png") repeat-x center bottom / auto 100%;
}
/* This strip's foot IS .wall's head, and .wall starts a fresh tile there, so
   pulling the tile down to the strip's own foot makes that join exact rather
   than merely close. The base rule's `center top` is the top strip's case.

   `-1px` for the same reason as `--top` below: .tzat closes on a half-pixel
   (160.5 in the viewport at 1400px) and the strip's antialiased foot let a
   1-device-px line of .tzat's navy through right along the join. A pixel of
   overlap puts that edge inside .wall, which is the same paper. It costs one
   pixel of tile phase against .wall's `center top`, which is nothing: the
   tile's row means span 1.2 units across all 183 rows. */
.tear--bot { bottom: -1px; background-position: center bottom; }
/* One asset, mirrored, so the top edge bites down out of the paper above and
   the bottom edge bites up out of the paper below.
   ⚠️ scaleY(-1) FLIPS THE GROUND TOO, so this strip's tile is upside down and
   its `center top` renders against the strip's visual foot. Neither matters:
   the tile is parchment grain with no up, and this edge is the mid-phase one
   either way. Do not "correct" it with a second flip on the background. */
/* `-1px`, NOT `0`, AND ONLY ON THIS ONE. .tzat opens at a fractional y (1874.81
   at 1400px), and a scaleY(-1) layer whose edge lands on a half-pixel gets
   antialiased against what is behind it — which here is .tzat's navy, so the
   join painted a 1-device-px navy hairline right across the torn edge. The
   other three tears carry no transform and show none of it. Overlapping the
   boundary by a pixel puts that antialiased edge inside the paper above instead,
   where it is parchment over parchment. Nothing is lost at the foot: this strip
   is masked to nothing down there and the navy already shows through. */
.tear--top { top: -1px; transform: scaleY(-1); }
/* `.tzat__key` — a single 2rem meander tile on a paper ground, centred on the
   bottom tear — was removed on 2026-08-09. Marcus called it "the random
   square", and at that size, straddling a ragged edge, that is exactly what
   it read as: a boxed glyph with no siblings, too small to be legible as a
   key and too hard-edged to belong to the tear. The tear is the device.
   Do not put a seal back on it. */

/* ── The menu ─────────────────────────────────────────── */
/* Asymmetric on purpose: the last panel in each column carries its own 1.5rem
   bottom margin, so a matching 2rem here would read as 3.5rem of tail. */
.board {
  background: var(--color-paper-2);
  padding: var(--space-lg) var(--page-gutter) var(--space-2xs);
}
.board__head {
  max-width: var(--shell); margin: 0 auto var(--space-xl);
  display: grid; gap: var(--space-sm);
}
.board__note { max-width: 54ch; color: var(--color-muted); margin: 0; }
.board__cols {
  max-width: var(--shell); margin-inline: auto;
  display: grid; grid-template-columns: repeat(auto-fit, minmax(19rem, 1fr));
  gap: var(--space-md);
}
/* MULTI-COLUMN, not grid, and that is the whole point: grid rows stretch every
   panel to the height of the tallest one in its row, so Sandwiches (6 items)
   was being padded out to 957px of dead plaster purely to match Gyros (12
   items) beside it, and Sweets to match Sides. Three rows of that was most of a
   screen of nothing. Multicol has no rows — it packs the groups into balanced
   columns and each panel is exactly as tall as its own list. Reading order
   becomes column-first, which is how a printed menu reads anyway, and the DOM
   order is untouched so screen readers are unaffected.

   THREE columns, measured, not assumed: at 1440 the board is 1503px at three
   and 1985px at two. The panels themselves get slightly taller because the
   descriptions wrap more at 377px, but six indivisible groups pack far better
   into three columns than two — with two, Gyros+Sandwiches+Dinners lands in one
   column and strands ~480px of empty band under Sweets.

   The count is set in two places on purpose. `columns: 2 19rem` handles the
   drop to a single stack at about 40rem, and the third column is held back
   behind its own query rather than being left to fall out of that 19rem
   minimum — on its own, 19rem goes to three at ~1100px and hands each column
   only 273px of measure, under the 280px this menu's longest row ("Spinach Pie
   Platter VEG ⋯ $16") needs. The query waits until the 1180px shell is actually
   full, where three columns are a comfortable 377px. */
.board__cols--two {
  display: block;
  columns: 2 19rem;
  column-gap: var(--space-md);
}
@media (min-width: 75rem) { .board__cols--two { column-count: 3; } }
/* Multicol has no row gap, so the panels carry their own. `avoid` keeps a group
   from being sliced in half across the column break. */
.board__cols--two .grp {
  margin-bottom: var(--space-md);
  -webkit-column-break-inside: avoid;
          break-inside: avoid;
}
/* Narrow panels should not spend 1.5rem a side on plaster — below this the
   column is ~340px and that padding is a seventh of it. The page gutter is
   already holding the panel off the edge of the screen. */
@media (max-width: 54rem) {
  .grp { padding: var(--space-sm); }
  .board__cols--two .grp { margin-bottom: var(--space-sm); }
}

/* Plaster panel on the tile field — reversed from the surrounding band. */
.grp { background: var(--color-surface); padding: var(--space-md); }
/* Badge, then the name, then a rule running out to the panel edge. Set to
   Marcus's menu mockup, 2026-08-09 — it was a plain heading over a 2px accent
   underline, which read as a form fieldset once there were six of them. */
.grp__head {
  display: flex; align-items: center; gap: var(--space-sm);
  margin-bottom: var(--space-2xs);
}
.grp__badge {
  flex: none;
  display: grid; place-items: center;
  width: 2.6rem; height: 2.6rem;
  border-radius: 50%;
  color: var(--color-accent);
  background: var(--color-paper-2);
  box-shadow: 0 0 0 var(--rule-hair) var(--color-accent-pale) inset;
}
.grp__badge svg { width: 1.4rem; height: 1.4rem; }
.grp__title {
  flex: 1 1 auto;
  display: flex; align-items: center; gap: var(--space-sm);
  /* Kept on the display face: these name the sections of the menu and are the
     one place on a dense page that should read as lettering. */
  font-family: var(--font-display);
  font-size: var(--text-lg); font-weight: var(--weight-display); line-height: 1.15;
  border-bottom: 0; padding-bottom: 0;
}
/* The rule takes whatever width the name leaves, so it ends flush with the
   panel however long the name is. */
.grp__title::after {
  content: ""; flex: 1 1 auto; height: var(--rule-hair);
  background: var(--color-rule);
}
.grp__note {
  margin: var(--space-2xs) 0 var(--space-3xs);
  font-size: var(--text-sm); color: var(--color-muted); line-height: 1.55;
}
.grp__foot {
  margin: var(--space-sm) 0 0; padding-top: var(--space-2xs);
  border-top: var(--rule-hair) solid var(--color-rule);
  font-size: var(--text-sm); color: var(--color-muted);
}
/* The two surcharges are not a footnote to the gyro list, they are a thing you
   can order — so they get a plate rather than a rule and a smaller grey. */
.grp__foot--plate {
  display: grid; gap: var(--space-3xs);
  margin-top: var(--space-md); padding: var(--space-sm) var(--space-md);
  border-top: 0;
  background: var(--color-paper-2);
  color: var(--color-ink-2);
}
.board__more {
  max-width: var(--shell); margin: var(--space-xl) auto 0;
  padding-top: var(--space-lg);
  border-top: var(--rule-hair) solid var(--color-rule);
}

/* ══ MENU PAGE HEAD, TABS AND PICKS ═══════════════════════════════════════
   Rebuilt 2026-08-09 to Marcus's blueprint. The head was a two-column split
   with a photograph bled off the right edge; the blueprint is symmetrical and
   centred — a playbill, with the food cut out and floating on painted water.

   ONE DOCUMENTED DEVIATION from tokens.css, which sets every radius to zero
   on the grounds that tiles are square: the blueprint's tabs and cards are
   visibly rounded, and squaring them off turns the tab row into a filing
   cabinet. The radius is defined here, scoped to these three components, and
   is NOT a licence to round anything else on the site.

   Contained here rather than spread through the file so the whole of the menu
   page's own furniture is in one place. --------------------------------- */
/* ⚠️ THE ROUNDING IS GONE, and the deviation documented above with it.
   Marcus, 2026-08-15: "the menu page does not meet the same aesthetic and
   visual vibe" as the landing page. Six 10px-radius white cards on a pale blue
   field is the one composition tokens.css sets every radius to zero to avoid —
   it reads as a dashboard, not as a menu. The blueprint that asked for the
   rounding was drawn before the parchment direction existed and no longer wins.

   The properties are kept, at zero, rather than deleted: .mpicks is dormant
   markup that still references them, and a zero here is a smaller change than
   hunting every use. Do not put a radius back without squaring the site first. */
.mhead, .mfilter, .mpicks {
  --menu-radius: 0;
  --menu-radius-sm: 0;
}

/* ── The menu page's ground ────────────────────────────────
   ONE SHEET BEHIND THE WHOLE PAGE. Everything from the kicker to the "Call it
   in" strip sits on this and paints nothing of its own, so the grain runs
   unbroken from the top of the head to the tear into the footer — the same
   thing that makes the home page's menu band read as paper rather than as a
   cream rectangle.

   ⚠️ THE CUTS ARE CHOSEN THE OPPOSITE WAY ROUND TO .sheet, and it is not an
   oversight. That box is a band a few hundred pixels tall; this one is the
   whole page, and the whole page is enormously taller than any sheet Marcus
   drew. Measured, 2026-08-15, <main> against the three cuts (0.563 tall,
   0.750 portrait, 1.777 wide):

       viewport   390    768   1024   1280   1440   1728   1920   2560
       main      0.090  0.248  0.338  0.529  0.595  0.714  0.794  1.058

   `cover` scales by WIDTH — cropping only the bottom, which this artwork keeps
   empty — exactly while the IMAGE ratio is at or below the BOX ratio. So the
   tall cut only starts behaving at about 1400px and the portrait cut at about
   1800px. sheet-wide NEVER applies here: a 1.777 image needs a 1.777 box, and
   this page does not reach that ratio at 2560. It is deliberately not loaded.

   Below 1400 every cut would be scaled by HEIGHT instead and cropped to a
   vertical strip of its own middle — at 390 that is the sheet drawn 2442px
   wide for a 390px screen, a sixfold blow-up that turns the grain to mush and
   throws every sprig off both sides. So the narrow range does not use `cover`
   at all: the sheet is laid across the full width at its own ratio, top-
   anchored, and dissolved into flat --parchment below the head. The reader
   gets real paper and real sprigs where they actually land, and the board
   below sits on the exact tone the paper is made of.

   That dissolve is the gradient layer, and its stops are in `vw` on purpose —
   the image's height IS a function of the width here (1 / 0.563 = 177.7vw), so
   a vw stop tracks the bottom of the artwork at every screen size where a
   percentage of this very tall box could not. It reaches full parchment at
   172vw, just before the artwork ends at 177.7vw, so there is no hard edge to
   see. Change the ratio of the cut and these two numbers change with it.

   --parchment is the tone sampled off this paper, so the dissolve, a failed
   image and the torn edge into the footer are all the same cream — that last
   one is what makes the footer's teeth match the paper they are torn out of.

   ⚠️ THE SHEET IS NO LONGER USED ON THIS PAGE, and the ratio table above is now
   history rather than instruction — it is kept because the REASONING still
   governs, and because the same trap is one `url()` away for whoever changes
   these next. Marcus, 2026-08-15: the sprigs printed into sheet-*.webp do not
   belong on the menu. Two replacement grounds were commissioned and both are
   wired up below so they can be judged on the live page rather than as flat
   files. They share this block; only the ground differs.

   Both replacements are 0.667, and NEITHER uses `cover` at any width. Against
   the ratios above, 0.667 only clears the box at about 1650px, and below that
   `cover` would scale by height and blow the art up to a vertical strip — six
   times over on a phone. There is no width at which cover buys anything here
   either: whenever it would apply it scales by width, which is exactly what
   `100% auto` already does. So the breakpoints are gone entirely and the art is
   simply laid across the full width, top-anchored, at its own ratio.

   Both sources were pulled onto --parchment in tools/build-menu-bg.py rather
   than --parchment being overridden here, so the page still meets the footer's
   torn edge — which is painted in --parchment — on one cream. */
/* ── THE GROUND, and it is the same one on every page but the menu ───────────
     Marcus, 2026-08-16: "too many different background themes throughout the
     website". There were seven across eight pages — a chart, linen, a ledger,
     awning canvas, album card, a creased sheet, and the menu's border. Six of
     them were commissioned a day earlier to stop the interior pages looking
     identical, and overshot into six unrelated pages. They are gone; this is
     what replaced them. Only menu.html still differs, via `.paper--frame`.

     WHY IT IS FIVE LAYERS AND NOT ONE PICTURE. Every ground before this was a
     whole sheet asked to `cover` a box whose height it could not predict, and
     the ~70 lines of ratio tables that used to sit on `.sheet` and `.wall`
     existed only to manage that. The two jobs separate cleanly:

         the paper       a full-width strip, repeat-y     -> any page length
         the branches    four cutouts pinned to corners   -> any width

     Neither cares about the box's ratio, so there is nothing to re-tune when a
     page grows or a breakpoint moves. That is the actual fix, not the artwork.

     THE BRANCHES RUN OFF THE PAGE EDGE, WHICH IS THE POINT. Marcus, same day:
     "the leaves are not on the sides of the page they are more towards the
     middle, due to incorrect dimensions of the pic". The artwork was fine —
     `build-parchments.py:pad_with_own_paper()` padded each master to hit a
     target ratio, and padding a bordered sheet is a machine for moving its
     border inward. These are cut from the UNPADDED source, where the branches
     already bleed off the edge. See tools/build-sprig-ground.py.

     One `background-size` for all four because all four cutouts are one box
     size (280x600 in the source), so a single width renders every branch at the
     same scale while each keeps its own position inside its box — these were
     painted by hand and the corners are not mirror images of each other.

     ⚠️ THE `?v=` IS LOAD-BEARING, same as on the frame. These files are
     REGENERATED IN PLACE by the builder — same name, new bytes — so a browser
     that has seen them once will keep serving the old ones no matter what the
     stylesheet says. Bump these in the same breath as the stylesheet's own.

   THREE SELECTORS, ONE GROUND. `.paper` is the seven content pages; `.sheet`
   and `.wall` are the home page's two bands, which had grounds of their own and
   were the other half of what Marcus was looking at. Listing them together is
   the point of the change — there is now one place to edit the surface the
   entire site stands on, and no way for one of them to drift off the others. */
.paper,
.sheet,
.wall {
  background-color: var(--parchment);
  background-image:
    url("assets/photos/sprig-tl.webp?v=20260818a"),
    url("assets/photos/sprig-tr.webp?v=20260818a"),
    url("assets/photos/sprig-bl.webp?v=20260818a"),
    url("assets/photos/sprig-br.webp?v=20260818a"),
    url("assets/photos/ground-tile-900.webp?v=20260818a");
  background-position:
    left top, right top, left bottom, right bottom, center top;
  background-repeat:
    no-repeat, no-repeat, no-repeat, no-repeat, repeat-y;
  background-size:
    var(--sprig-w) auto, var(--sprig-w) auto,
    var(--sprig-w) auto, var(--sprig-w) auto, 100% auto;

  /* Caps at 12rem because the cutouts are 280px in the source: past ~192px
     rendered they are being upscaled, and watercolour goes soft before it goes
     wrong. The lower bound never applies on a phone — see the media query. */
  --sprig-w: clamp(4.5rem, 12vw, 12rem);
}

.paper {
  /* Hairlines in this page's own blue rather than the site's grey rule token —
     on warm parchment a neutral grey line reads as dirt. Same pair, same
     values, as the ones .sheet declares for the home page's menu band. */
  --sheet-rule: oklch(46% 0.130 245 / 0.26);
  --sheet-leader: oklch(46% 0.130 245 / 0.42);

  /* ── The surface tokens, re-pointed for warm paper ────────────────────────
     Everything below this line is a REDEFINITION, not a new style. The site's
     components paint themselves with var(--color-paper), var(--color-paper-2)
     and var(--color-rule); re-declaring those three here re-tints every
     descendant at once, so a card on parchment needs no rule of its own.

     This is the whole reason the interior pages could be brought onto the
     menu's ground without a per-component sweep. The alternative — a
     `.paper .qa__jump`, `.paper .fit`, `.paper .plaque` … chain — was drafted
     first and thrown away: twenty-odd overrides that would have to be extended
     by hand every time somebody adds a panel.

     WHY THEY HAD TO CHANGE. The defaults are cool: --color-paper-2 is
     oklch(94.2% 0.012 232), a pale blue-grey, and it was chosen against
     --color-paper, which is very nearly white. Laid on parchment
     (oklch(91.3% 0.031 80), warm) the same panel reads as a grey slab dropped
     on a sheet of paper — the exact fault the note on `.paper .mhead` describes
     the menu page having had before its bands were made transparent.

     The replacements hold the ORIGINAL lightness relationships and only move
     the hue to the parchment's 80 and the chroma up to match its warmth, so
     contrast ratios carry over unchanged:

         token            was                        now
         --color-paper    97.4% 0.006  95   (white)  = --parchment
         --color-paper-2  94.2% 0.012 232   (-3.2)   87.9% 0.038 80   (-3.4)
         --color-paper-3  89.5% 0.020 234   (-7.9)   83.4% 0.048 80   (-7.9)

     --color-rule steps to the same blue hairline the menu board rules use
     rather than to a warm grey: a neutral line on warm paper reads as dirt,
     and this page already had to solve that once for --sheet-rule. */
  --color-paper:   var(--parchment);
  --color-paper-2: oklch(87.9% 0.038 80);
  --color-paper-3: oklch(83.4% 0.048 80);
  --color-rule:    oklch(46% 0.130 245 / 0.26);

  /* The two lifted surfaces — the cards that are meant to come FORWARD off the
     band, like the booking card on catering. Their lift over the ground is kept
     (surface was +1.8 over paper, and still is) but the hue moves onto the
     parchment's, because a card at 99.2% L and 0.003 C on cream does not read
     as bright, it reads as the only thing on the page that is not made of
     paper. Warm white on parchment is a card of good stock on a counter. */
  --color-surface:   oklch(97.8% 0.012 86);
  --color-surface-2: oklch(94.0% 0.026 82);
}

/* NO BRANCHES ON A PHONE, and this is a standing decision rather than a
   shortcut. At 390px the page gutter is 20px, so a branch rendered at the
   clamp's floor still lands under the first line of body copy — the one screen
   where the margin the artwork wants does not exist. The tile carries the page
   alone there, which is exactly what the old `.wall` did below 1180px for the
   same reason.

   Written as a max-width override rather than as a min-width opt-in so that the
   ground on `.paper` stays complete when read on its own; a reader who finds
   only the desktop half of a split rule tends to assume the other half is
   missing rather than deliberate. */
@media (max-width: 48rem) {
  .paper,
  .sheet,
  .wall {
    background-image: url("assets/photos/ground-tile-900.webp?v=20260818a");
    background-position: center top;
    background-repeat: repeat-y;
    background-size: 100% auto;
  }
}

/* The master is 1086 across, so 1024 is the last cut that is not an upscale of
   an upscale. The branches are NOT re-cut here: they are 280px and rendered at
   192px at most, so the desktop cut would be the same pixels at a larger file. */
@media (min-width: 56.25rem) {
  .paper,
  .sheet,
  .wall {
    background-image:
      url("assets/photos/sprig-tl.webp?v=20260818a"),
      url("assets/photos/sprig-tr.webp?v=20260818a"),
      url("assets/photos/sprig-bl.webp?v=20260818a"),
      url("assets/photos/sprig-br.webp?v=20260818a"),
      url("assets/photos/ground-tile-1024.webp?v=20260818a");
  }
}

/* ── Ground A: the chart ─────────────────────────────────────────────────────
   Two layers. The art is top-anchored and drawn once; underneath it a one-row
   slice of the same sheet repeats down the rest of the page.

   The tile exists because the chart's double margin rules run the full height
   of the sheet. Dissolving the art into flat cream the way the wash does would
   cut those rules off a fraction of the way down and read as a rendering fault
   rather than as a design. Tiling a strip of the chart's own empty paper lets
   the frame run the true height of the page, off the paper it came from, with
   no CSS approximation of the rules to drift out of alignment.

   Alignment is free rather than lucky: both layers are sized `100% auto`, so
   both are scaled by the same factor and the tile's rules land on the art's at
   every viewport width. Nothing here needs adjusting if the page gets wider.

   The join is invisible because the art's foot was cross-faded into the tile in
   the build script — the portrait sheet is vignetted at its bottom edge, and
   butting it straight against the tile drew a dark line the full page width.

   ONE SHEET AT EVERY WIDTH. Two earlier masters put artwork in the upper field
   and both collided with the page — measured live at 1400, the title occupies
   x 0.08-0.47 / y 0.12-0.34, the three prints x 0.66-0.92 / y 0.13-0.33, and the
   tab row crosses the full width at y 0.40-0.44. There is no clear space up
   there, so no picture could have worked and fading one only made the collision
   fainter. This master is a BORDER instead: engraved rules down both sides and
   across the top, corner diamonds, empty field.

   That is also why the breakpoints collapsed from three to one. The chart pair
   needed a portrait cut for phones and a landscape cut for desktop, because a
   compass has to sit somewhere and where it sits depends on the layout. A border
   has no such preference — the rules simply run down whatever shape the page is
   — so one artwork now serves 390px and 2560px alike, and the ground no longer
   has to change when the head's layout does.

   The bands clear the text at every width, which is worth stating because it is
   not obvious: the ornament ends at about 4.5% in from each edge, and the page
   gutter is `clamp(1.25rem, 4vw, 4.5rem)`. At 390 that is 17px of band against a
   20px gutter; at 1400 it is 63px of band against content starting at 110px.
   Widen the ornament in a future master and check this again — an 8% band, which
   is what the brief asked for, would have run under the text below about 1400px.

   ⚠️ THE `?v=` ON THESE IMAGES IS NOT DECORATION — KEEP IT AND BUMP IT.
   Unlike every other image on the site, these are REGENERATED IN PLACE by
   tools/build-menu-bg.py: same filename, new bytes. A browser caches by URL, so
   bumping the stylesheet's own `?v=` refetches the CSS and then serves the OLD
   picture from cache, and the page looks exactly as it did before you changed
   anything. Marcus reviewed two rounds of veil adjustment on 2026-08-15 and saw
   neither of them, because of precisely this.

   Bump these in the same breath as the stylesheet's. If you ever move these
   assets to content-hashed filenames, this whole class of fault disappears and
   the query strings can go. */
.paper--frame {
  background-image:
    url("assets/photos/menu-bg-frame-900.webp?v=20260818a"),
    url("assets/photos/menu-bg-frame-tile-900.webp?v=20260818a");
  background-size: 100% auto, 100% auto;
  background-position: center top, center top;
  background-repeat: no-repeat, repeat-y;
}
/* The master is 1536 across, so this is the last honest step up. Beyond that the
   browser scales past 1:1 — acceptable on ruled linework, and cheaper than a
   third cut nobody could see the benefit of. */
@media (min-width: 56.25rem) {
  .paper--frame {
    background-image:
      url("assets/photos/menu-bg-frame-1536.webp?v=20260818a"),
      url("assets/photos/menu-bg-frame-tile-1536.webp?v=20260818a");
  }
}

/* ── The six interior grounds were HERE, and they are gone ─────────────────
   A chart, linen, a ledger, awning canvas, album card and a creased sheet —
   one per interior page, commissioned 2026-08-15 and removed 2026-08-16 when
   Marcus said "too many different background themes throughout the website".
   All six pages now take the shared sprigged ground on `.paper` above.

   Ground B, the wash, went in the same pass: it was composed for a centred
   menu head that was retired days earlier and no page had referenced it since.

   Their .webp cuts are still on disk and unreferenced. Nothing was deleted —
   this repo has no version control, so an `rm` here is final. See the retiring
   note in tools/bg-prompts.md before removing any of them. */

/* ── The bands stand ON the sheet and paint nothing ────────────────────────
   Each of these was its own flat fill — --color-paper for the menu head and
   tabs, --color-paper-2 for the board, the call strip and every band on
   catering — which is what made those pages read as a stack of grey-blue slabs
   rather than as one printed sheet.

   It also breaks the border outright. The frame is a background IMAGE on the
   element that carries the ground, so an opaque child stacked on top of it
   hides the side rules for its full height — on catering the border appeared to
   stop dead after the first section, which is what sent me here.

   The hand-written list this replaces named .mhead, .mfilter, .board and .cta
   and had already missed one: `.deliv`, which is menu.html's fifth direct child
   and fills with --color-paper. It has never been seen wrong because the
   section ships `hidden` and only JS unhides it, when delivery apps are
   configured. That is a fault waiting for a config change, not a fault — which
   is exactly the kind a list maintained by hand produces.

   STRUCTURAL, not a list. A direct child of the element carrying the ground is
   by definition a full-width band; a card is always nested deeper, inside that
   band's own row or inner wrapper. So this selects the bands and leaves the
   cards their tint, on every page, including pages that do not exist yet. It
   reproduces the four-name list it replaced exactly — .mhead, .mfilter, .board,
   .cta are precisely menu.html's direct children — and picks up .deliv, which
   the list had missed.

   Cards inside these bands are unaffected and still paint --color-paper-2. If a
   band ever genuinely needs a fill of its own, give it one and say why; this is
   a default, not a prohibition.

   ⚠️ `:is(...)` HERE IS LOAD-BEARING, NOT TIDINESS. The first draft of this
   rule was `.paper > *`, which does not work: the universal selector
   contributes NOTHING to specificity, so `.paper > *` weighs exactly one class
   — the same as `.expect` — and every band declared later in this file won it
   on source order. The frame came back everywhere except the one band that had
   been declared last, which is a very convincing way to look fixed. `:is()`
   takes the specificity of its most specific argument, so a list of element
   types adds a type selector and the rule lands one notch above a bare class.

   The element list is the set of things HTML gives you to be a band with. It is
   not a list of components and does not grow when a component is added. */
.paper > :is(section, div, header, nav, article, aside),
.paper--frame > :is(section, div, header, nav, article, aside) {
  background-color: transparent;
}

/* ── The groups, off the cards and onto the paper ──────────
   THE SINGLE BIGGEST THING THAT WAS WRONG WITH THIS PAGE. The six courses were
   --color-surface panels — white fills with their own 1.5rem of inset padding —
   sitting on a pale blue field. Six of them in a grid is a card deck, and a card
   deck is the one shape the home page never makes: there, the menu is TEXT on
   the sheet, and the only thing separating one course from the next is a rule.

   So the fill goes and the columns get a rule between them instead, which is how
   .breadth sets the same content on the home page and how a printed menu has
   always been set. Nothing about the multicol layout changes — read the note on
   .board__cols--two, all of it still applies. */
.paper .grp {
  background: transparent;
  padding: 0;
  /* Was --space-md, sized to sit between two card edges. With no cards the
     courses need a real beat between them or the last price of one course and
     the badge of the next read as the same list. Pulled back from --space-2xl
     to --space-xl on 2026-08-16: 72px was more air than the beat needs once
     the badge and the display-face title are doing the separating anyway, and
     it is charged to every column, so it was 72px of page height for a gap
     nobody was asking to be bigger. */
  margin-bottom: var(--space-xl);
}
/* The gap has to carry the rule AND keep type off it on both sides, so it is
   roughly double the old card gutter — at three columns this is ~24px of air
   either side of a hairline, which is a printed menu's own proportion. */
.paper .board__cols--two {
  column-gap: clamp(2rem, 4.4vw, 3.75rem);
  column-rule: var(--rule-hair) solid var(--sheet-rule);
}
/* One stack, no columns, so there is no rule to hold type off — but the
   courses still need their beat. */
@media (max-width: 54rem) {
  .paper .grp { padding: 0; margin-bottom: var(--space-xl); }
}

/* The badge was a pale blue disc, which on parchment reads as a sticker someone
   put on the paper. Ink on paper instead: the glyph alone, held at the accent,
   with the hairline ring dropped. */
.paper .grp__badge {
  width: 1.9rem; height: 1.9rem;
  background: transparent;
  box-shadow: none;
}
.paper .grp__badge svg { width: 1.55rem; height: 1.55rem; }

/* Every hairline on the page moves off the site's neutral grey and onto the
   section blue. Grey rules were readable on the white cards; on warm paper they
   go muddy, which is exactly the note .sheet carries on the home page. */
.paper .grp__title::after { background: var(--sheet-rule); }
.paper .list li + li { border-top-color: var(--sheet-rule); }
.paper .list__item::after { border-bottom-color: var(--sheet-leader); }
.paper .grp__foot { border-top-color: var(--sheet-rule); }

/* "Extra meat $2 / Extra feta $1.50" was a --color-paper-2 plate — the same
   pale blue as the old field, so on parchment it would be the only cool
   rectangle left on the page. It is still not a footnote (see the note on
   .grp__foot--plate), so it keeps its own box; the box is now ruled rather
   than filled. */
.paper .grp__foot--plate {
  background: transparent;
  padding: var(--space-sm) 0 0;
  margin-top: var(--space-sm);
  border-top: var(--rule-hair) solid var(--sheet-rule);
}

/* ── The tabs, off the segmented control ───────────────────
   Seven bordered, rounded, white-filled boxes with the live one flooded navy is
   a filter control off any admin panel, and it was the second thing making this
   page look like a different site. The row keeps its job — see the long note on
   .mfilter__inner, the scrolling and the flex sizing are untouched — but it is
   now set as a printed index: names ruled off the paper, the live one weighted
   and keeled rather than filled.

   The keel does the work the navy fill used to do, which is why it was already
   there: it marked the live tab a second time for anyone who cannot separate the
   fill from the row. With the fill gone it is promoted from backup to primary
   and thickened to earn it. Weight and colour move with it, so the live tab is
   marked three ways over. */
.paper .mfilter__tab {
  border: 0;
  border-bottom: var(--rule-hair) solid var(--sheet-rule);
  border-radius: 0;
  background: transparent;
  color: var(--color-ink-2);
  padding: var(--space-xs) var(--space-sm) calc(var(--space-xs) + 6px);
}
.paper .mfilter__tab:hover {
  background: transparent;
  color: var(--color-accent);
  border-bottom-color: var(--color-accent);
}
.paper .mfilter__tab.is-on {
  background: transparent;
  color: var(--color-accent-2);
  border-bottom-color: transparent;
}
.paper .mfilter__tab.is-on .mfilter__ic { color: var(--color-accent); }
/* Full-width and 3px: this is now the only mark on the live tab, so it runs the
   tab's own width like a rule under a heading instead of sitting under it as a
   1.9rem tick. */
.paper .mfilter__tab.is-on::after {
  left: 0; translate: none;
  width: 100%; height: 3px;
  bottom: 0;
  background: var(--color-accent);
  opacity: 1;
}

/* ── The wash, from painted sea to paper shadow ────────────
   The watercolour was RIGHT on the old ground and is wrong on this one. Its
   whole argument (see the note on .mhead__wash) is that the plate floats on
   painted water — which reads as sea against a near-white field, and reads as a
   blue bruise spilled across a sheet of cream paper. Nothing else on the
   parchment is cool; the home page's own menu band has no blue on it anywhere.

   The device survives, recoloured and moved, because what the plate actually
   needs on paper is not water but CONTACT — a cut-out this large with nothing
   under it reads as a sticker someone floated over the sheet. So the same four
   ellipses, the same turbulence and the same torn edges now sit low and warm
   and do the job a shadow does. Kept as the SVG rather than swapped for a
   radial-gradient: the point of the filter is that the edges tear like wet
   pigment, and a gradient fades like a gradient. Paper wants the torn edge.

   Sampled off --parchment and pushed down in lightness, so it darkens the sheet
   it is on instead of tinting it. */
.paper .mhead__wash {
  --wash-1: oklch(74% 0.036 76 / 0.50);
  --wash-2: oklch(67% 0.042 72 / 0.34);
  --wash-3: oklch(80% 0.028 78 / 0.40);
  /* Low and inside the gutter. A shadow that bleeds 112vw is a sky, not a
     shadow — the old width existed to stop the sea stopping dead at the text
     column, and there is no sea any more. */
  width: min(96%, 60rem);
  top: 58%; height: 42%;
}

/* ── Menu head ──────────────────────────────────────────── */
/* ── Menu head ────────────────────────────────────────────
   THE WHOLE HEAD IS SIZED AGAINST VIEWPORT HEIGHT, not just width, and that
   is the point of it: Marcus landed on this page in a normal Safari window on
   2026-08-09 and the plate ran off the bottom of the screen. A head that has
   to be scrolled to be seen is not a head.

   So every vertical measure below — the top padding, the title, the sprigs,
   the rule, the two gaps and the plate itself — carries a `vh` term inside
   its clamp. On a tall window they all sit at their comfortable maximum; on a
   short one the whole block scales down together and stays in proportion,
   rather than one part collapsing while the photograph stays 560px tall.

   `svh`, deliberately, NOT `vh` or `dvh`. `svh` is the viewport with the
   mobile browser chrome SHOWN, which is exactly the height the visitor has at
   the moment they land — and unlike `dvh` it does not change as the URL bar
   retracts, so nothing resizes under the reader mid-scroll. */
/* THE `6vw` FLOOR ON THE TOP PADDING IS CLEARANCE FOR THE GROUND'S TOP BORDER,
   not taste. `.paper--frame`'s sheet carries an engraved rule across its top,
   and that rule's inner edge lands at 5.9% of the image's height — which, since
   the sheet is drawn `100% auto` at a 1.5 ratio, is 3.93% of the VIEWPORT WIDTH.
   The rest of this padding is driven by viewport HEIGHT, so the two scale on
   different axes and cross over around 1000px: below that the border sits above
   the eyebrow, above it the border cuts straight through "INDIAN HARBOUR BEACH,
   FLORIDA". Measured at 1400 before the fix, the rule crossed the eyebrow's
   baseline exactly.

   6vw against a 3.93vw border leaves about 2vw of air at every width. If the
   ground is ever swapped for one with a different top band, re-measure the inner
   edge and move this number with it. */
.mhead {
  position: relative;
  overflow: hidden;
  padding: max(clamp(var(--space-sm), 3svh, var(--space-xl)), 6vw) var(--page-gutter) 0;
  background: var(--color-paper);
  text-align: center;
}
.mhead__say {
  position: relative; z-index: var(--z-raised);
  max-width: 46rem; margin-inline: auto;
}
.mhead__where {
  margin: 0 0 var(--space-2xs);
  font-size: var(--text-xs); font-weight: 700;
  letter-spacing: var(--track-caps); text-transform: uppercase;
  color: var(--color-accent);
}

/* Title and laurels are one line box, so the sprigs track the title's size
   instead of being parked at fixed offsets that break at every width. */
.mhead__crown {
  display: flex; align-items: center; justify-content: center;
  gap: clamp(var(--space-2xs), 1.8vw, var(--space-lg));
}
/* The stem runs bottom-left to top-right, so the sprite as drawn IS the right
   hand sprig and the left one is the mirror — not the other way round. Get
   this backwards and both branches lean into the title like closing brackets. */
.mhead__sprig {
  flex: none;
  width: clamp(2.2rem, min(4.4vw, 6svh), 4.4rem); height: auto;
  color: var(--color-accent);
}
.mhead__sprig--flip { transform: scaleX(-1); }
/* Slanted, because at this size "Our Menu" is a brand moment — it is the first
   lettering below the wordmark and should echo it rather than dodge it. The old
   build set this in a serif specifically to avoid a script clashing with the
   script wordmark; matching the logo outright is the better answer. */
.mhead__title {
  font-family: var(--font-display);
  font-weight: var(--weight-display);
  /* The min() is the height governor: on a wide-but-short window 6.8vw would
     ask for 125px of type in a viewport that has no room for it. */
  font-size: clamp(2.2rem, min(6.2vw, 9svh), 4.8rem);
  font-weight: 400; line-height: 1.0;
  letter-spacing: var(--track-display);
  color: var(--color-accent-2);
  margin: 0;
}
.mhead__title::after { display: none; }
.mhead__wave {
  display: block;
  width: clamp(6rem, 11vw, 9.5rem); height: 12px;
  margin: clamp(var(--space-3xs), 1.2svh, var(--space-xs)) auto
          clamp(var(--space-2xs), 1.8svh, var(--space-sm));
  color: var(--color-rule);
}
/* Two beats, the second stepped right — an aside pencilled under the copy,
   not a third sentence of it. */
.mhead__aside {
  display: grid; justify-items: center;
  margin: clamp(var(--space-2xs), 2.2svh, var(--space-md)) 0 0;
  font-weight: 500;
  font-size: var(--text-md); line-height: 1.5;
  color: var(--color-accent);
}
.mhead__aside span + span { margin-left: clamp(1rem, 4vw, 3.5rem); }

/* ⛔ DEAD FROM HERE TO THE END OF .mhead__cut — 2026-08-15.
   `.mhead__wash` and `.mhead__cut` match NOTHING. The single cut-out photograph
   they styled was replaced by the three `.mshot` prints above; no page on the
   site has either class in its markup any more. Also dead for the same reason:
   `.paper .mhead__wash` and the `.mhead__cut` line in the max-height: 46rem
   block further down. (`.paper--wash .mhead__wash` was on this list too until
   2026-08-16, when the ground it belonged to was deleted outright — see the
   tombstone where the six interior grounds used to be.)

   NOT DELETED, on purpose. This repo has no version control, so a deletion is
   final, and Marcus is still reviewing the head — if the prints go back to being
   one photograph these rules are the fastest route there. They also carry the
   reasoning for two decisions that were argued at length (the wash as contact
   shadow rather than sea; width-driven sizing rather than height-driven), and
   that reasoning outlives the rules. Delete the lot once the head is signed off.

   ── The plate ────────────────────────────────────────────
   The cut-out sits above the wash, and the wash is allowed past the gutter on
   both sides — a watercolour that stops dead at the text column reads as a
   rectangle someone blurred. */
.mhead__plate {
  position: relative;
  margin-top: clamp(var(--space-3xs), 1.5svh, var(--space-md));
}
/* Sits high — starting ABOVE the top of the plate and running out around its
   waist. Level with the food it reads as a shadow; above it, it reads as the
   sea the truck is parked beside, which is the whole point of the device. */
.mhead__wash {
  position: absolute; z-index: var(--z-base);
  left: 50%; translate: -50% 0;
  width: 112vw; max-width: none;
  top: -8%; height: 78%;
  /* Aegean, thinned. Kept as properties so the wash recolours with the
     palette instead of pinning four hexes into the markup. */
  --wash-1: oklch(80% 0.070 236 / 0.85);
  --wash-2: oklch(72% 0.092 240 / 0.60);
  --wash-3: oklch(87% 0.048 234 / 0.72);
  /* Turbulence tears the top and bottom edges but leaves the left and right
     as clean vertical cuts against the viewport, which is the one place a
     watercolour must not have a straight edge. The mask does that end. */
  -webkit-mask: linear-gradient(to right, transparent, #000 14%, #000 86%, transparent);
          mask: linear-gradient(to right, transparent, #000 14%, #000 86%, transparent);
}
/* SIZED BY HEIGHT, NOT WIDTH — the inversion is deliberate and load-bearing.
   Given `width: min(100%, 54rem)` the plate was ~570px tall on any desktop
   window, which is most of a laptop viewport before the tabs are even
   reached. Height-first means the photograph takes a fixed share of whatever
   screen it lands on and the head fits by construction.

   ⚠️ IT IS STILL WIDTH-DRIVEN — the height budget is expressed AS a width.
   `52svh` is `34svh × 1.5217`, the plate's own 1400∶920 ratio, and `51.75rem`
   is the 34rem cap through the same multiplier. So the picture never exceeds
   about a third of the viewport's height, while `height: auto` keeps the
   intrinsic ratio at every width. Change the budget by changing these numbers.

   The budget was HALF the viewport until 2026-08-15. Half was still too much:
   measured at 768x1024, the width cap bound before the height one ever did and
   the plate rendered 688x452, which put the tab row at y=833 and every price
   below the fold — the same fault the two-column head fixes on desktop, just
   out of reach of it. A third leaves the tabs visible on a tablet. Phones are
   untouched by the change: at 390 the 100% term still wins by a wide margin.

   Do NOT "simplify" this to `height: min(50svh, 34rem); max-width: 100%`.
   That was tried and it SQUASHES the plate: when max-width clamps the used
   width the browser keeps the specified height, so at 390px it rendered
   350×422 — a 1.52 photograph crushed to 0.83. `aspect-ratio` does not
   rescue it either (height stays definite, so the ratio resolves width and
   the clamp then overrides it), and `object-fit: contain` only fixes the
   painting while leaving ~190px of empty box shoving the tab row down.
   Width-driven has no such failure mode: one axis is auto, always. */
.mhead__cut {
  position: relative; z-index: var(--z-raised);
  display: block;
  width: min(100%, 52svh, 51.75rem); height: auto;
  margin: 0 auto -2px;              /* -2px kills the seam against .mfilter */
}

/* ── Three prints where the cut-out was ───────────────────
   The idiom is `.sheet__print`'s, deliberately: this site already knows how to
   put a photograph on parchment and it does it as a paper print — white border,
   weighted at the foot the way a real print's white margin is, a shadow with
   both a tight and a broad term, and a few degrees off square. Reusing it means
   the menu head and the home page's sheet are speaking the same language rather
   than two dialects of "photo on paper".

   NOT extracted into a shared class with .sheet__print. They differ in every
   number — size, rotation, shadow depth, overlap — and share only a pattern.
   A common base carrying four overrides is harder to read than two honest
   blocks, and the home page's print is pinned into an absolute layout this one
   knows nothing about. */
.mhead__plate {
  display: flex;
  align-items: center; justify-content: center;
  /* Capped on the stacked layout, where the plate would otherwise run the full
     text measure and the three prints would be bigger than the single cut-out
     they replaced — which would undo the fold fix rather than help it. */
  max-width: min(100%, 28rem);
  margin-inline: auto;
}
.mshot {
  margin: 0;
  flex: 0 1 33%;
  background: var(--photo-paper);
  padding: 0.4rem 0.4rem 0.85rem;
  box-shadow:
    0 0.6rem 1.2rem oklch(0% 0 0 / 0.15),
    0 0.08rem 0.25rem oklch(0% 0 0 / 0.11);
}
.mshot img { display: block; width: 100%; height: auto; }
/* Middle one on top and pulled over both neighbours, so the three read as a
   handful dropped on the sheet rather than as a row of thumbnails. z-index
   works here without `position` because these are flex items. */
.mshot:nth-child(1) { rotate: -4deg;   z-index: 1; }
.mshot:nth-child(2) { rotate: 1.5deg;  z-index: 3; margin-inline: -4.5%; }
.mshot:nth-child(3) { rotate: 3.5deg;  z-index: 2; }
/* Straightened for anyone who has asked the OS for less motion and decoration.
   The rotation is styling rather than animation, but it is the same instinct —
   and three squared prints still read as prints. */
@media (prefers-reduced-motion: reduce) {
  .mshot { rotate: none; }
}

/* ── The head goes back to two columns on desktop ──────────
   Marcus, 2026-08-15: "i do not like the idea of a user arriving on the menu
   page and being greeted with the picture of the gyro and fries and nothing
   else and needing to scroll down to view menu".

   He is right, and it is measurable. On a 1440x900 laptop the centred head ran
   to y=880, the tabs sat at y=946 and the first price — Lamb Classic $13 — was
   at y=1210. The fold is at 900. So the menu page showed a title, a photograph
   and NOT ONE MENU ITEM until you had scrolled a screen and a half.

   ⚠️ THIS REVERSES THE BLUEPRINT the centred head was built to, and the note in
   menu.html still describes that blueprint as a symmetrical playbill. Both are
   kept: the playbill is still what this is BELOW 64rem, where there is no room
   for two columns and the stack is the only sane order. The reversal is desktop
   only, and it is not a rejection of the composition — it is the fold winning an
   argument the composition could not have known it was having, because the
   blueprint was drawn against the head alone and not against what sits under it.

   Everything vertical in the block above still carries its `svh` term and still
   works; the two columns simply mean the head's height is now the TALLER of the
   two rather than the SUM of them, which is where the ~600px comes from.

   48rem AND NOT 64rem. 64 was the first guess and it left the commonest tablet
   portrait — 768x1024 — on the stacked layout with the tabs at y=833 and no
   price on screen: the exact complaint, unfixed, on a real device. Measured at
   768 the two columns fit with room to spare — 707px of content, 288 to the
   plate track, 38 to the gap, 380 to the text, and the title with both sprigs
   measures about 303 of that 380. The narrow band still stacked below 48rem is
   covered instead by the tightened plate budget above. */
@media (min-width: 48rem) {
  .mhead {
    display: grid;
    /* The plate is the second track and it is capped, so the text column takes
       every pixel the window adds. Widen the window and the photograph does not
       grow with it — the reason it was too big was that it always did. */
    grid-template-columns: minmax(0, 1fr) minmax(0, clamp(18rem, 27vw, 26rem));
    align-items: center;
    gap: clamp(2rem, 5vw, 4.5rem);
    /* Aligned to the shell rather than to the 46rem measure the centred head
       used, so the text starts on the board's left column edge and the plate
       ends on its right — the head reads as the top of the same sheet of ruled
       paper instead of as a narrower thing floated over it. */
    max-width: calc(var(--shell) + 2 * var(--page-gutter));
    margin-inline: auto;
    /* Same 6vw border clearance as the base rule — this shorthand would
       otherwise override it and put the top rule back through the eyebrow. */
    padding-block: max(clamp(1.5rem, 4svh, 3.5rem), 6vw)
                   clamp(1.25rem, 3svh, 2.75rem);
    text-align: left;
  }
  .mhead__say { max-width: none; margin-inline: 0; }
  .mhead__crown { justify-content: flex-start; }
  .mhead__wave { margin-inline: 0 auto; }
  .mhead__aside { justify-items: start; }
  /* The stepped second beat is an indent against a centred axis. Left-aligned
     it just looks like a broken line, so the two beats stack flush. */
  .mhead__aside span + span { margin-left: 0; }

  /* The grid track does the capping in two columns, so the stacked cap and its
     centring both come off — otherwise the prints sit 28rem wide in the middle
     of a 26rem track and lose their alignment to the board's right edge. */
  .mhead__plate { margin-top: 0; max-width: none; margin-inline: 0; }
}

/* ── Menu filter ──────────────────────────────────────────
   Six courses and 43 rows: the tabs are the difference between reading the
   board and scanning for the one thing you came for. Shipped hidden — see the
   note in menu.html — and they filter in place rather than jumping, because
   an anchor jump on a multicol board lands you mid-column. */
.mfilter { padding: 0 var(--page-gutter); background: var(--color-paper); }
.mfilter__inner {
  max-width: var(--shell); margin: 0 auto;
  display: flex; gap: var(--space-2xs);
  /* Height-aware for the same reason the head is: the tab row is the last
     thing that has to clear the fold. */
  padding: clamp(var(--space-2xs), 2svh, var(--space-md)) 0 var(--space-sm);
  /* THE ROW SCROLLS AT EVERY WIDTH, not only on a phone.

     Seven tabs with `white-space: nowrap` measure about 1185px side by side,
     and a flex item cannot shrink below its own min-content — so anywhere the
     row had less than that it pushed straight through the page instead. It was
     doing exactly that from roughly 830px to 1190px: measured 2026-08-11, an
     iPad at 1024 landscape gave menu.html a body 1186px wide inside a 1024px
     window, so the WHOLE PAGE scrolled sideways and every band on it ran short
     of the right edge. The 52rem rule further down turned the row into a
     scroller for phones and the gap above it was never covered.

     `overflow-x: auto` here costs nothing when the row does fit — a scroll
     container with no overflow has no scrollbar and the tabs still grow to
     fill via `flex: 1 1 auto`. When it does not fit, the tabs scroll inside
     their own band instead of dragging the document with them. Width-agnostic,
     so it cannot open up again the day an eighth category is added. */
  overflow-x: auto;
  scrollbar-width: none;                  /* the tabs are the affordance */
  scroll-padding-inline: var(--page-gutter);
}
.mfilter__inner::-webkit-scrollbar { display: none; }
/* Grow to fill the row but keep their natural proportions, so "Dinners &
   Platters" stays the widest tab and "Gyros" the narrowest — seven equal
   boxes would read as a segmented control, which this is not. */
.mfilter__tab {
  position: relative;
  flex: 1 1 auto;
  appearance: none; cursor: pointer;
  display: flex; align-items: center; justify-content: center;
  gap: var(--space-2xs);
  padding: var(--space-xs) var(--space-sm) calc(var(--space-xs) + 5px);
  border: var(--rule-hair) solid var(--color-accent-pale);
  border-radius: var(--menu-radius-sm);
  background: var(--color-surface);
  color: var(--color-ink-2);
  font: inherit;
  font-size: var(--text-xs); font-weight: 700;
  /* One notch under --track-caps. At the full 0.14em "Dinners & Platters" and
     "Sweets & Drinks" no longer fit their tabs on one line, and a two-line tab
     in a row of one-line tabs bows the whole row. */
  letter-spacing: 0.1em; text-transform: uppercase;
  line-height: 1.1; white-space: nowrap;
  transition: background var(--dur-short) var(--ease-out),
              color var(--dur-short) var(--ease-out),
              border-color var(--dur-short) var(--ease-out);
}
.mfilter__ic {
  flex: none;
  width: 1.2rem; height: 1.2rem;
  color: var(--color-accent);
  transition: color var(--dur-short) var(--ease-out);
}
/* The sprig is drawn 68×46. Squared off with the rest it letterboxes down to
   a few grey pixels, so it keeps its own ratio. */
.mfilter__ic--wide { width: 1.7rem; height: 1.15rem; }
.mfilter__tab:hover {
  background: var(--color-paper-2); color: var(--color-accent);
  border-color: var(--color-accent);
}
.mfilter__tab.is-on {
  background: var(--color-accent); color: var(--color-accent-ink);
  border-color: var(--color-accent);
}
.mfilter__tab.is-on .mfilter__ic { color: currentColor; }
/* The keel. Marks the live tab a second time, so the row still reads when the
   navy fill is the thing a colour-blind visitor cannot separate. */
.mfilter__tab.is-on::after {
  content: ""; position: absolute;
  left: 50%; translate: -50% 0; bottom: 6px;
  width: 1.9rem; height: 2px;
  background: currentColor; opacity: 0.85;
}

/* ── Three to start with ──────────────────────────────────
   ⚠️ DORMANT since 2026-08-14 — nothing on the site uses these rules.

   The section was three photographed cards under the menu's filter tabs. The
   photographs were the scraped 600px food set, pulled for being soft at
   display size; without them the cards were three buttons duplicating three
   of the five tabs directly above them, so the section came out with the
   photos. Kept, not deleted, because the repo is not under version control
   and this is the only copy — bring it back by restoring the markup from
   _retired/, or delete this block outright once that is clearly not wanted.

   Photographed cards under the tabs. They are buttons, not links — see the
   note in menu.html — so everything type-ish inside them has to be re-set,
   because a <button> inherits nothing worth having. */
.mpicks { padding: 0 var(--page-gutter) var(--space-md); background: var(--color-paper); }
.mpicks__inner {
  max-width: var(--shell); margin: 0 auto;
  display: grid; grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: var(--space-md);
}
.mpick {
  appearance: none; cursor: pointer; text-align: left;
  display: flex; flex-direction: column;
  padding: 0; overflow: hidden;
  border: var(--rule-hair) solid var(--color-rule);
  border-radius: var(--menu-radius);
  background: var(--color-surface);
  font: inherit; color: inherit;
  transition: border-color var(--dur-short) var(--ease-out),
              background var(--dur-short) var(--ease-out);
}
/* The photo rounds its own top corners rather than leaning on the card's
   overflow:hidden. A <button> in Chromium does not reliably clip descendants
   to its border-radius — the card reads rounded while the photograph inside
   it keeps square corners. Minus the 1px border so the curve sits on the
   frame instead of a pixel outside it. */
.mpick__shot {
  display: block; aspect-ratio: 16 / 10; overflow: hidden;
  border-radius: calc(var(--menu-radius) - 1px) calc(var(--menu-radius) - 1px) 0 0;
  background: var(--color-paper-2);
}
.mpick__shot img {
  width: 100%; height: 100%; object-fit: cover; display: block;
  transition: scale var(--dur-medium) var(--ease-out);
}
/* greek-salad.jpg is a square frame with the salad on the left and a takeout
   lid and a dressing cup on the right. Cropped from the centre the packaging
   wins; held to the left it is a Greek salad. */
.mpick__shot--left img { object-position: 18% center; }
.mpick__say {
  display: grid; gap: var(--space-3xs);
  padding: var(--space-md);
}
.mpick__name {
  font-family: var(--font-body); font-weight: 700;
  font-size: var(--text-md); line-height: 1.15;
  color: var(--color-ink);
}
.mpick__note { font-size: var(--text-sm); line-height: 1.5; color: var(--color-muted); }
.mpick__from {
  margin-top: var(--space-3xs);
  font-size: var(--text-xs); font-weight: 700;
  letter-spacing: var(--track-caps); text-transform: uppercase;
  color: var(--color-accent);
}
.mpick:hover { border-color: var(--color-accent-pale); background: var(--color-paper); }
.mpick:hover .mpick__shot img { scale: 1.04; }
@media (prefers-reduced-motion: reduce) {
  .mpick:hover .mpick__shot img { scale: 1; }
}

/* ── Call it in ───────────────────────────────────────────
   The badge is the mockup's device: the phone is the whole point of the band,
   so it gets a mark rather than sitting inside the sentence. */
.cta__badge {
  flex: none;
  display: grid; place-items: center;
  width: 3.4rem; height: 3.4rem;
  border-radius: 50%;
  background: var(--color-accent);
  color: var(--color-accent-ink);
}
.cta__badge svg { width: 1.6rem; height: 1.6rem; }
.cta__say { display: flex; align-items: center; gap: var(--space-md); }

.list { list-style: none; margin: 0; padding: 0; }
/* name .......... price, with any description on its own line beneath. */
.list li {
  display: flex; flex-wrap: wrap; align-items: baseline;
  gap: 0 var(--space-sm);
  /* 8px, not 12. There are 43 rows on menu.html and this is multiplied by every
     one of them — the four pixels back were the single cheapest thing on the
     page. Still a comfortable 44px row, well over the 24px touch minimum once
     the line box is counted. */
  padding: var(--space-2xs) 0;
}
.list li + li { border-top: var(--rule-hair) solid var(--color-rule-soft); }
/* Menu page only — .board appears on no other page, and the home page's own
   copy of these rows (.breadth) keeps the roomier setting because there are
   only a dozen of them there.

   Second pass at the same 43 rows, 2026-08-16, asked for after the page still
   scrolled long. The padding above took the box; this takes the line box. A
   row is one short line of type — "Chicken Swarma ⋯ $13" — and the body's 1.65
   is a measure set for paragraphs, so it was spending 11px of leading on a line
   with nothing above or below it inside the box. 1.4 keeps a wrapped two-line
   name readable (the descriptions set their own 1.5 and are untouched) and the
   row lands at ~36px, still well over the 24px touch minimum. Together with the
   6px padding that is ~8px a row, and the board is as tall as its tallest
   column — 16 rows of it — so it comes off the page height sixteen times.

   The 6px is not a token on purpose: it sits between --space-3xs (4px, which
   closes the row onto its own rule) and --space-2xs (8px, what this was). */
.board .list li { padding: 0.375rem 0; line-height: 1.4; }
.list__item { flex: 0 1 auto; min-width: 0; font-weight: 600; }
.list__price {
  flex: none; margin-left: auto; white-space: nowrap;
  font-weight: 700; color: var(--color-accent);
  font-variant-numeric: tabular-nums;
}
.list__desc {
  flex: 1 0 100%; margin-top: 3px;
  font-size: var(--text-sm); color: var(--color-muted); line-height: 1.5;
}
/* Not a boxed stamp — olive small caps, the way a menu marks a dish. */
.veg {
  font-style: normal;
  font-size: var(--text-xs); font-weight: 700;
  letter-spacing: 0.12em; text-transform: uppercase;
  color: var(--color-olive);
  margin-left: var(--space-2xs);
}

/* ── The deep band — story / catering brief ───────────── */
.kitchen, .slate {
  background: var(--color-deep); color: var(--color-onphoto);
  padding: var(--space-3xl) var(--page-gutter);
}
.kitchen__inner, .slate__inner { max-width: var(--shell); margin-inline: auto; }
.kitchen .h-display, .slate h2 { color: var(--color-onphoto); }
.kitchen__inner > p, .slate p { color: var(--color-onphoto-2); max-width: var(--measure); }
.kitchen__inner > p, .slate p, .foot__blurb, .foot__hours, .foot address { font-weight: 450; }
.kitchen .tlink, .slate .tlink {
  color: var(--color-onphoto); box-shadow: inset 0 -1px 0 oklch(100% 0 0 / 0.5);
}
.kitchen .tlink:hover, .slate .tlink:hover { color: #fff; box-shadow: inset 0 -2px 0 #fff; }
.kitchen__quote {
  /* A sentence someone said, not a sign. The display face at 34ch of running
     text is unreadable — the body face at 500 keeps it a quote. */
  font-family: var(--font-body);
  font-weight: 500;
  font-size: var(--text-lg); line-height: 1.5;
  color: var(--color-onphoto);
  max-width: 34ch;
  margin: var(--space-lg) 0 var(--space-md);
  padding-top: var(--space-md);
  border-top: var(--rule-hair) solid oklch(100% 0 0 / 0.3);
}

/* ══════════════════════════════════════════════════════════════════════════
   CATERING PAGE — rebuilt 2026-08-10 to Marcus's picture-free mockup.

   WHAT CHANGED AND WHY IT MUST NOT DRIFT BACK:
   Every photograph came off this page. It ran a split hero with the truck
   bleeding off the right edge and three round food plates in the formats
   band; the mockup that replaced it is navy line art and flat tinted blocks
   on plaster, and one photograph anywhere in that would read as a banner
   pasted onto a different page. The library also has no exterior shot and no
   catering shot at all (assets/photos/README.md), so the images that were
   here were three listing photos of food standing in for an event — which is
   most of why the page never looked like catering.

   The grammar of the page is now four things and only four:
     · flat tinted blocks (--color-paper-2) on plaster (--color-paper)
     · navy line art, stroked, never filled except the seal's spit
     · caps labels in the body face, display only for the format names and
       the one closing line
     · the Greek key at section scale — short flanking runs, never a band
   ══════════════════════════════════════════════════════════════════════════ */

/* ── Catering hero ────────────────────────────────────────
   Split, not banded: the argument sits on paper at the left and the
   photograph runs off the right edge of the page. Built to Marcus's mockup
   2026-08-09 and KEPT on 2026-08-10 when everything below it went
   picture-free. It is the page's only photograph on purpose — the sections
   under it are line art and flat tinted blocks, and this is the one place a
   customer gets to see who is actually going to show up. */
.chero {
  position: relative;
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  align-items: center;
  background: var(--color-paper);
  overflow: hidden;                    /* the shot bleeds; nothing else may */
}
/* Right-aligned inside the left half and capped at half the shell, so the copy
   starts exactly on the page's left gutter line without a full-bleed grid and
   its four tracks. The right column is then free to run to the viewport edge. */
.chero__say {
  position: relative; z-index: var(--z-raised);
  justify-self: end;
  width: 100%; max-width: calc(var(--shell) / 2);
  padding: var(--space-2xl) var(--space-lg) var(--space-2xl) var(--page-gutter);
}
.chero__title {
  font-family: var(--font-display);
  font-weight: var(--weight-display);
  font-size: var(--text-display);
  line-height: 1.02;
  margin: 0;
}
/* The one line of script above the fold, and it finishes the headline's
   sentence rather than repeating it. */
.chero__script {
  font-family: var(--font-display);
  font-weight: var(--weight-display);
  font-size: var(--text-display-s);
  line-height: 1.05;
  color: var(--color-accent);
  margin: var(--space-2xs) 0 0;
}
/* A short run of the key, used as a rule. Three tiles, not a full band — at
   page width it is a border, at paragraph width it is punctuation. */
.chero__key {
  width: 4.6rem; height: 12px; border: 0; margin: var(--space-md) 0;
  background-color: var(--color-accent);
  -webkit-mask: var(--meander) repeat-x left center / 15px 12px;
          mask: var(--meander) repeat-x left center / 15px 12px;
}
.chero__lede {
  max-width: 34ch;
  font-size: var(--text-base); line-height: 1.7;
  color: var(--color-ink-2);
  margin: 0 0 var(--space-lg);
}
.chero__act { margin: 0; }

.chero__shot { position: relative; margin: 0; height: 100%; min-height: clamp(20rem, 46vh, 30rem); }
.chero__shot img { width: 100%; height: 100%; object-fit: cover; object-position: center 42%; }
/* Dissolves into the paper instead of butting against it with a hard seam.
   The mockup blends the same way; a straight edge here reads as a stock banner. */
/* The three stops were the literal value of --color-paper, written out longhand
   because a bare token cannot take an alpha in the `/` form. They are now
   `color-mix` against the token itself, which is the same colour when nothing
   overrides it and — the point — follows the ground when something does. On
   parchment the hardcoded version faded the photograph into near-white and drew
   a pale band down the middle of the page. */
.chero__shot::after {
  content: ""; position: absolute; inset: 0;
  background: linear-gradient(to right,
    var(--color-paper) 0%,
    color-mix(in oklab, var(--color-paper) 72%, transparent) 18%,
    color-mix(in oklab, var(--color-paper) 0%, transparent) 46%);
}

/* ── Shared section head ──────────────────────────────────
   The centred caps line with a run of the key flanking each side. Used by the
   promises band and the service-area band; the hero above sets its own type
   because it is the only left-aligned block on the page. */
.expect__kicker,
.area__title {
  display: flex; align-items: center; justify-content: center;
  flex-wrap: wrap; gap: var(--space-sm);
  margin: 0;
  font-family: var(--font-body);
  font-size: var(--text-sm); font-weight: 800;
  letter-spacing: var(--track-caps); text-transform: uppercase;
  color: var(--color-accent);
}
/* The section-scale key. tokens.css allows exactly two scales — the
   full-width page band and this ~75px segment — and this is the segment,
   halved and mirrored so it flanks a line instead of underlining it. */
.keyflank {
  flex: none; width: 2.6rem; height: 11px;
  background-color: var(--color-accent-pale);
  -webkit-mask: var(--meander) repeat-x right center / 14px 11px;
          mask: var(--meander) repeat-x right center / 14px 11px;
}
.keyflank--flip { transform: scaleX(-1); }
/* The heads are flex rows holding two ornament spans; the site-wide meander
   ::after would land inside as a third child and break the centring. */
.expect__kicker::after,
.area__title::after,
.fit__title::after,
.promise__title::after,
.ready__title::after,
.area__head::after { content: none; }

/* ── The three ways people book us ────────────────────────
   Three separate tinted cards on plaster, not one band split by hairlines.
   The band version was built on 2026-08-09 around three round food plates,
   and hairlines were the only thing holding it together once the photographs
   came out — three columns of centred type on one flat ground with a 1px line
   between them is a table, not a set of cards. Separate blocks with plaster
   showing between them is the same separation done the way the tokens ask
   for it: ground colour and space, no rules. */
.fits {
  max-width: var(--shell); margin: 0 auto;
  padding: var(--space-lg) var(--page-gutter) var(--space-2xl);
  display: grid; grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: var(--space-md);
}
/* ⚠️ NO FILL — 2026-08-17. This card was a tinted --color-paper-2 slab, and so
   were five other blocks on this page. That is the LAST page still speaking the
   card language the rest of the site gave up on 2026-08-15, when Marcus called
   it twice in one day: "the white review block on the left here" and "this block
   does not match the rest of the feeling and vibe of the page." Both times the
   answer was the same — take the fill out, let the paper show, and draw a
   printed frame instead. catering.html was built on 2026-08-10, before either
   ruling, and never got the pass.

   The check that settles it: faq, areas, gallery and find are all `.paper`
   pages too, and NOT ONE of them paints a band. Separation on this ground is
   space and hairlines. This page was the odd one out, not the standard.

   The frame is `.framed` in the markup — Marcus's engraved border, the same one
   the home plaque and the catering reminder carry. Do not put a tint back
   "so the cards read as cards"; they read as cards because of the frame. */
.fit {
  display: grid; align-content: start; justify-items: center;
  background: none;
  /* Trimmed with the fill: `.framed` draws a ~21px engraved border outside this,
     so the old --space-xl top was being paid twice. */
  padding: var(--space-md) var(--space-sm) var(--space-sm);
  text-align: center;
}
.fit__ic { width: 2.4rem; height: 2.4rem; color: var(--color-accent); }
/* Script, and the only place on the page it carries a heading. These are the
   three names a customer is choosing between, so they are the one thing that
   should read as lettering rather than as a label. */
.fit__title {
  margin: var(--space-sm) 0 0;
  font-family: var(--font-display); font-weight: var(--weight-display);
  font-size: var(--text-xl); line-height: 1.12;
  color: var(--color-ink);
  text-wrap: balance;
}
.fit__for {
  margin: var(--space-2xs) 0 0;
  font-family: var(--font-body);
  font-size: var(--text-xs); font-weight: 800;
  letter-spacing: var(--track-caps); text-transform: uppercase;
  color: var(--color-accent);
}
.fit__say {
  margin: var(--space-sm) 0 0; max-width: 32ch;
  font-size: var(--text-sm); line-height: 1.65;
  color: var(--color-ink-2);
  text-wrap: pretty;
}
.fit .checks { margin-top: var(--space-lg); }

/* ── Checklists ───────────────────────────────────────────
   Shared by the format cards and the "what we need" column.

   The mark is a ring with a tick in it, which is an OUTLINED circle, and the
   tokens ban outlines. It is allowed here for one reason: every other mark on
   this page is stroked line art at the same 1.4–1.6px weight, so the ring
   reads as a sibling of the truck and the umbrella rather than as a border.
   The ban is about boxing CONTENT — cards, tags, buttons — and it still holds
   for all of those. Do not take this as a licence to outline a panel.

   Both halves are drawn, not typed: a ✓ character sets at a different weight
   in every fallback font and drifts off the first line's baseline. */
.checks {
  list-style: none; padding: 0; margin: 0;
  display: grid; gap: var(--space-2xs);
  text-align: left;
}
.checks li {
  position: relative;
  padding-left: 1.75rem;
  font-size: var(--text-sm); line-height: 1.5;
  color: var(--color-ink-2);
}
.checks li::before {
  content: ""; position: absolute;
  left: 0; top: 0.16em;
  width: 1.05rem; height: 1.05rem; border-radius: 50%;
  box-shadow: inset 0 0 0 1.4px var(--color-accent);
}
.checks li::after {
  content: ""; position: absolute;
  left: 0.29rem; top: 0.44em;
  width: 0.46rem; height: 0.24rem;
  border-left: 1.6px solid var(--color-accent);
  border-bottom: 1.6px solid var(--color-accent);
  transform: rotate(-45deg);
}
/* The booking column's list is a form the customer is reading to prepare, not
   a set of examples, so it sets at reading size. */
.checks--lg li {
  padding-left: 2.1rem;
  font-size: var(--text-base); line-height: 1.6;
  margin-bottom: var(--space-3xs);
}
.checks--lg li::before { width: 1.2rem; height: 1.2rem; top: 0.14em; }
.checks--lg li::after { left: 0.34rem; top: 0.46em; width: 0.54rem; height: 0.28rem; }

/* ── What to expect ───────────────────────────────────────
   ONE tinted band, four columns on hairlines — the device the formats row
   gave up when it became three cards. It earns the hairlines here because
   these four are one statement read across, not four things to choose
   between: nothing in the row is clickable and no column stands alone. */
/* ⚠️ THIS IS THE PAGE'S ONE COLOUR BREAK — 2026-08-17. See the long note on the
   markup in catering.html for why. Short version: Marcus called the page bland
   between the hero and the form, and it was cream from the masthead to the
   footer with no photograph anywhere in between. This band supplies both, in
   the shape `.tzat` already uses on the home page.

   ⚠️ FOUR TRACKS, gutter | food | type | gutter, and the two middle ones add up
   to --shell. Copied deliberately from `.tzat`, whose own note records why: a
   free-running two-column split pushes the content into the left half of a wide
   monitor and leaves a third of the band as dead navy. Here the leftover width
   is symmetric page margin, which is what every other section already does.

   --tear-h MATCHES the home page's four edges. All five torn edges on this site
   now cut with one asset at one depth; that is what keeps them reading as one
   rip repeated rather than as several different ones. Change this and change
   `.tzat` and `.foot::before` with it. */
/* ⚠️ `.paper > .expect`, NOT `.expect`, AND THAT IS THE WHOLE REASON THIS BAND
   IS NAVY. There is a structural rule further up this file —
   `.paper > :is(section, div, header, …) { background-color: transparent }` —
   that strips the fill off every direct child of a parchment page. It weighs
   (0,1,1); a bare `.expect` weighs (0,1,0) and loses, silently, which renders as
   a band whose type has gone light-on-cream and vanished. That rule's own note
   says: "If a band ever genuinely needs a fill of its own, give it one and say
   why; this is a default, not a prohibition."

   This is the why: it is the page's only colour break, and a page that runs one
   cream from the masthead to the footer is the thing Marcus called bland. It is
   the single exception on this ground — do not read it as licence to tint the
   other bands, which is the fault that was just taken OUT of this page. */
.paper > .expect {
  position: relative;
  background: var(--color-deep); color: var(--color-onphoto);
  display: grid;
  grid-template-columns:
    minmax(var(--page-gutter), 1fr)
    minmax(0, calc(var(--shell) * 0.40))
    minmax(0, calc(var(--shell) * 0.60))
    minmax(var(--page-gutter), 1fr);
  /* ── The band's own gold ──────────────────────────────────────────────
     Sampled straight off Marcus's mockup: sRGB 244,215,160. It is scoped to
     this band on purpose and is NOT a new global token — the site's palette is
     navy, cream and terracotta, and a fourth hue loose in tokens.css would end
     up on things nobody drew it on. It exists because on navy the blue
     `--color-accent` the four marks used to take is a shade of the ground:
     technically visible, doing nothing. */
  --expect-gold: oklch(89% 0.078 83);
  /* ⚠️ THE TWO DIVIDERS ARE WAVES, NOT RULES, and that is in the mockup — zoom
     in on it before "fixing" them back to hairlines. One tile of a shallow
     wave, repeated along the axis, masked so the colour still comes from a
     token. Same device as `--meander` and the `.keyflank` it draws: a mask, not
     a background-image, because a background-image would hardcode the ink.

     They are pseudo-elements rather than borders because a mask applies to an
     element AND its children — masking `.promise` itself would put the wave
     through the paragraph. */
  --wave-x: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='5' viewBox='0 0 12 5'%3E%3Cpath d='M0 2.5q3-2.2 6 0t6 0' fill='none' stroke='black' stroke-width='1' stroke-linecap='round'/%3E%3C/svg%3E");
  --wave-y: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='5' height='12' viewBox='0 0 5 12'%3E%3Cpath d='M2.5 0q-2.2 3 0 6t0 6' fill='none' stroke='black' stroke-width='1' stroke-linecap='round'/%3E%3C/svg%3E");
  --wave-ink: oklch(100% 0 0 / 0.34);
  --tear-h: clamp(1.5rem, 4.6vw, 4.4rem);
  padding-block: calc(var(--tear-h) + var(--space-lg));
  gap: clamp(var(--space-lg), 3vw, var(--space-2xl));
  align-items: center;
  overflow: hidden;
}
/* ── The pinned-up print ─────────────────────────────────────────────────
   Built to Marcus's mockup, 2026-08-18. It replaces a bare cropped rectangle
   capped at 24rem tall — a photograph with a background in it standing on navy
   with nothing to say why it was a rectangle.

   Now it is an OBJECT on the band: real torn paper taped at two opposite
   corners, the truck's postmark hanging off its bottom-left, and the label
   across its foot. Three layers, and the stacking is the whole trick —
   postmark UNDER the frame, photograph under the frame's paper, label over
   everything. That is also the order they are in the markup, so no z-index is
   needed beyond lifting the caption.

   ⚠️ THE APERTURE IS A ROTATED QUAD. The frame was shot at a tilt and its
   window tilts with it, so the photograph is placed at the window's centre, at
   its size, at its angle. tools/build-expect-scene.py measures all four off the
   alpha channel and prints them; re-run it if the artwork is ever recut. */
.expect__shot {
  grid-column: 2; margin: 0;
  position: relative;
  align-self: center;
}
.expect__frame {
  display: block; position: relative;
  aspect-ratio: 900 / 1032;
}
/* Over the photograph, not behind it — a background on `.expect__frame` would
   paint behind its own child and the paper would never cover the photograph's
   cut edges, which is what the 1.5% of bleed below is hiding under. */
.expect__frame::after {
  content: ""; position: absolute; inset: 0; z-index: 1;
  background: url("assets/photos/expect-frame.webp") center / contain no-repeat;
}
.expect__win {
  position: absolute; z-index: 0; display: block; overflow: hidden;
  translate: -50% -50%;
  left: 51.73%; top: 49.73%; width: 79.28%; height: 84.79%;
  rotate: -4.53deg;
}
.expect__photo {
  display: block; width: 100%; height: 100%;
  /* Zoomed past the window, to the mockup's crop. Uncropped, the top of this
     photograph is blue table and the handle of a spatula; the aperture above
     cuts off whatever the zoom pushes out. */
  scale: 1.26;
  object-fit: cover; object-position: center 82%;
}
/* Hangs off the bottom-left, mostly outside the frame, the way a postmark sits
   half off the stamp it cancels. `.expect` clips, so the overhang costs nothing
   at any width. Opacity, not a paler file: it is a watermark on the navy and at
   full strength it argues with the label two inches away — which is the one
   piece of lettering in the picture. */
.expect__stamp {
  position: absolute; z-index: 0;
  left: -9%; bottom: 10%;
  width: 26%; aspect-ratio: 400 / 303;
  background: url("assets/photos/expect-postmark.webp") center / contain no-repeat;
  opacity: 0.75;
}
/* Straddles the frame's foot and runs past its left edge, so the print reads as
   something labelled after the fact rather than a card with a title bar. It
   leans a degree off the frame's own tilt on purpose: two objects at exactly
   the same angle read as one printed graphic. */
.expect__cap {
  position: absolute; z-index: 2;
  left: 27%; bottom: 4%;
  width: 55%;
  rotate: -3.4deg;
  filter: drop-shadow(0 0.35rem 0.7rem oklch(0% 0 0 / 0.45));
}
.expect__cap img { display: block; width: 100%; height: auto; }
.expect__say { grid-column: 3; min-width: 0; }
.expect__kicker { margin-bottom: var(--space-lg); color: var(--color-onphoto); }
/* Outside the two key flanks, to the mockup: wave, key, WHAT TO EXPECT, key,
   wave — and all four marks in the CREAM the words are set in, not the pale
   blue the flanks take everywhere else. On this ground a blue ornament is a
   lighter shade of the band; the mockup has them all at one weight, reading as
   one drawn line of type. Scoped to this kicker so `.req__kicker` on the same
   page, which sits on parchment, keeps the blue that is right there. */
.expect__kicker .keyflank {
  width: 0.9rem;                      /* ONE meander unit a side, per the mockup */
  background-color: var(--color-onphoto);
}
.expect__wave {
  flex: none; width: 2.4rem; height: 1.1rem;
  color: var(--color-onphoto);
  fill: none; stroke: currentColor;
  stroke-width: 1.5; stroke-linecap: round;
}
.expect__wave--flip { transform: scaleX(-1); }
/* Two up rather than four across. Beside a photograph the four columns fell to
   ~170px each, which is two words a line under a caps heading. */
.expect__row {
  position: relative;
  display: grid; grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--space-lg) 0;
}
/* The mark on the crossing of the two hairlines. It carries a patch of the
   band's own navy so the horizontal rule BREAKS for it instead of running
   underneath — which is the difference between an ornament placed on a cross
   and a sticker dropped on a line. */
.expect__cross {
  position: absolute; z-index: 1;
  left: 50%; top: 50%; translate: -50% -50%;
  display: grid; place-items: center;
  width: 3.4rem; height: 1.6rem;
  background: var(--color-deep);
  color: var(--color-onphoto);
}
.expect__cross svg {
  width: 2.1rem; height: 2.1rem;
  fill: none; stroke: currentColor; stroke-width: 1.7;
}
.promise { position: relative; padding-inline: var(--space-md); text-align: center; }
/* Between the columns only. A rule on every column would box the band, and
   the band is meant to read as one object. */
/* ⚠️ THE DIVIDERS ARE A 2x2 PROBLEM NOW, not a row one. In a four-across row
   `+ .promise` put a rule between every pair, which was right. In a 2x2 the
   same selector draws one down the middle AND one between items 3 and 4 while
   leaving a gap between rows — a broken lattice. The columns take a left rule
   on the odd-indexed items only, and the rows take a top rule from the third
   item on, which is a real cross. */
.promise:nth-child(even)::before {
  content: ""; position: absolute; left: 0; top: 0; bottom: 0; width: 5px;
  background-color: var(--wave-ink);
  -webkit-mask: var(--wave-y) repeat-y center top / 5px 12px;
          mask: var(--wave-y) repeat-y center top / 5px 12px;
}
.promise:nth-child(n + 3) { padding-top: var(--space-lg); }
.promise:nth-child(n + 3)::after {
  content: ""; position: absolute; left: 0; right: 0; top: 0; height: 5px;
  background-color: var(--wave-ink);
  -webkit-mask: var(--wave-x) repeat-x left center / 12px 5px;
          mask: var(--wave-x) repeat-x left center / 12px 5px;
}
/* ⚠️ A RING, AND IT IS AN EXCEPTION. `tokens.css` bans outlines, and the note
   on `.tzat__seal` calls a thin circle round a mark "common AI slop" — that
   ruling stands for BADGES, where the ring IS the object. This one is drawn in
   Marcus's mockup for the band, where four ringed marks are what turn a row of
   loose icons into a set. Do not copy it onto anything he has not drawn it on.

   A span rather than a border on the <svg>: the truck is drawn 32x24, so a
   border on the icon itself would come out as an ellipse round that one. */
.promise__mark {
  display: grid; place-items: center;
  width: 3.8rem; height: 3.8rem; margin: 0 auto;
  border: var(--rule-hair) solid var(--expect-gold);
  border-radius: 50%;
}
/* ⚠️ THREE SIZES, AND THAT IS DELIBERATE — it is what makes them look like one
   size. Sampled off the mockup's own rings: the spit fills 79% of the circle,
   the clock and the calendar 63%, the truck 60%. A vertical mark set to the
   width of a horizontal one always reads as the smaller of the two, so a single
   number here would leave the spit looking shrunken and the truck crowding its
   ring. Measured as glyph-height over ring-diameter, not guessed. */
.promise__ic { width: 2.4rem; height: 2.4rem; color: var(--expect-gold); }
.promise__ic--tall { width: 3rem; height: 3rem; }
/* The truck is drawn in a 32×24 box, not 24×24 — a vehicle squeezed into a
   square either shrinks below the others or distorts. */
.promise__ic--wide { width: 2.3rem; height: 1.73rem; }
.promise__title {
  margin: var(--space-xs) 0 var(--space-2xs);
  font-family: var(--font-body);
  font-size: var(--text-xs); font-weight: 800;
  letter-spacing: var(--track-caps); text-transform: uppercase;
  /* --color-onphoto, not --color-ink. This band went navy on 2026-08-17 and
     these four headings were the last thing in it still painted for cream —
     navy on navy, i.e. gone. The paragraphs under them moved at the same time. */
  color: var(--color-onphoto);
}
.promise p {
  margin: 0; max-width: 26ch; margin-inline: auto;
  font-size: var(--text-sm); line-height: 1.6;
  color: var(--color-onphoto-2);
  text-wrap: pretty;
}

/* ── What we need from you ────────────────────────────────
   ⚠️ THIS USED TO BE A SECTION OF ITS OWN — `.ask`, two columns with the seal on
   the seam — and on 2026-08-17 it was folded into the request form. Marcus asked
   for the layout restructured; measured, `.ask` was 628px on a desktop and
   1,184px on a phone, 13-15% of the page, and every word of it was about the
   section immediately below it. Its left column listed the date, the headcount,
   the ready-by hour and a name and number: the form's own fields, in prose,
   directly above the form. Its right column was a phone button and a link
   reading "Or send the details" pointing at #request — the form, again. The page
   previewed the form, signposted the form, then showed the form.

   What survives is the checklist, because it does one thing the fields cannot:
   it tells somebody what to go and find out BEFORE they start typing. It is a
   framed card at the head of the form now. The phone button was already
   duplicated by the form's own "Needed sooner than tomorrow?" line and the
   masthead, the footer and the call bar; the urgency sentence moved into the
   form's lede verbatim.

   DELETED WITH IT: `.ask`, `.ask__inner`, `.ask__col`, `.ask__col--need`,
   `.ask__col--book`, `.ask__lede`, `.ask__act`, `.ask__aside`, `.ask__second`
   and `.ask__act--second`. `.btn--wide` is kept — menu.html still uses it. */
.ready {
  position: relative;
  max-width: 44rem; margin: 0 auto;
  padding: var(--space-lg) var(--space-md) var(--space-md);
  text-align: center;
}
.ready__title {
  margin: 0;
  font-family: var(--font-body);
  font-size: var(--text-sm); font-weight: 800;
  letter-spacing: var(--track-caps); text-transform: uppercase;
  color: var(--color-accent);
}
/* A short run of the key as a rule under the head. */
.ready__key {
  display: block;
  width: 2.6rem; height: 11px;
  margin: var(--space-2xs) auto var(--space-md);
  background-color: var(--color-accent-pale);
  -webkit-mask: var(--meander) repeat-x left center / 14px 11px;
          mask: var(--meander) repeat-x left center / 14px 11px;
}
/* The list is left-ragged inside a centred column: centred list items with a
   mark on the left is the one thing that always looks broken. inline-grid keeps
   the BLOCK centred while its rows stay flush left. */
/* ⚠️ TWO COLUMNS, and that is what makes this an aside rather than a second
   section. Stacked one-per-line these four sentences ran 418px inside the form
   — which handed most of the deleted section's height straight back and made
   the fold look like a rename rather than a restructure. Two up, they sit in
   two rows and the card reads as a note at the head of the form.
   `inline-grid` + `justify-self: center` keeps the BLOCK centred in a centred
   card while its rows stay flush left; centred list items with a mark down the
   left is the one arrangement that always looks broken. */
.ready .checks {
  display: inline-grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--space-sm) var(--space-lg);
  justify-self: center;
  text-align: left;
  max-width: 40rem;
}
.ready__close {
  display: flex; align-items: center; justify-content: center;
  gap: var(--space-2xs);
  margin: var(--space-md) 0 0;
  font-family: var(--font-body);
  font-weight: 700;
  font-size: var(--text-md); line-height: 1.2;
  color: var(--color-accent);
}
.ready__swoop { flex: none; width: 2.4rem; height: 1.5rem; }

/* The page's one primary action, so it takes a full-width tile rather than
   sitting as a small chip in the middle of an empty panel. */
.btn--wide { width: min(100%, 22rem); }

/* ── The seal ─────────────────────────────────────────────
   A THICK RING OF TWENTY GREEK-KEY UNITS. Read the note on .tzat__seal before
   touching this: three seal concepts were rejected on 2026-08-09 and a thin
   outer ring was the common factor every time — "common AI slop", and right,
   because a 1px circle inset from an empty edge is the default badge every
   generator draws. A band of the site's own ornament is a drawn object. A
   hairline around nothing is not.

   ⚠️ IT LAPS THE FRAME'S CORNER NOW, not a seam. It used to be centred on the
   join between two panels, and there is no join any more — so it takes the
   position the home page's postmark takes on the plaque: over the top-right
   corner, cancelling it. The disc under it stays --color-paper (which `.paper`
   re-points to --parchment) so the seal reads as one mark laid ON the card
   rather than as a hole punched through it. */
.ready__seal {
  position: absolute; z-index: var(--z-raised);
  top: 0; right: 0; transform: translate(38%, -38%);
  display: grid; place-items: center;
  width: clamp(4.4rem, 8vw, 6.4rem); aspect-ratio: 1;
  border-radius: 50%;
  background: var(--color-paper);
}
.ready__seal > svg { width: 100%; height: 100%; overflow: visible; }
.ready__seal-ring { color: var(--color-accent); }
.ready__seal-mark { color: var(--color-accent-2); overflow: visible; }

/* ── Where we deliver ─────────────────────────────────────
   Map, towns, contact — three columns on hairlines, the same device as the
   promises band and for the same reason: one statement read across. */
/* Tint gone — see `.fit`. Three columns on hairlines, same as the promises. */
.area {
  background: none;
  padding: var(--space-xl) var(--page-gutter) var(--space-2xl);
}
.area__inner {
  max-width: var(--shell); margin: 0 auto;
  display: grid; grid-template-columns: auto minmax(0, 1fr) auto;
  /* Tightened with the map above: at --space-xl the three columns sat 48px
     apart AND had the band's own gutters outside them, which read as four gaps
     rather than one row. */
  align-items: center; gap: clamp(var(--space-md), 2.6vw, var(--space-lg));
}
/* ⚠️ 7.2rem -> a clamp topping out at 14rem, 2026-08-17. At 115px the
   silhouette was a stamp marooned in a 300px column with the towns centred
   beside it and the contact stack stranded at the far right — three things at
   arm's length with nothing holding them together, which is most of what was
   left of Marcus's "too bland" on this page once the navy band went in above it.
   Scaled up it anchors the left of the band and the composition closes.
   It is an inline SVG silhouette, so this costs nothing and never softens. */
.area__map {
  position: relative; flex: none;
  width: clamp(7.2rem, 12vw, 14rem);
}
.area__fl { display: block; width: 100%; height: auto; fill: var(--color-accent-pale); }
/* Brevard is the mid-east coast. The pin is positioned to that point on the
   silhouette, not centred — a centred pin on a state outline says nothing. */
/* Translated so the pin's POINT sits on Brevard — the mid-east coast — rather
   than its bounding box. A pin whose box is positioned reads about 10px low. */
.area__pin {
  position: absolute; left: 94%; top: 43%;
  transform: translate(-50%, -88%);
  width: 1.7rem; height: 1.7rem;
  color: var(--color-accent);
}
.area__say {
  text-align: center;
  padding-inline: var(--space-xl);
  border-inline: var(--rule-hair) solid var(--color-accent-pale);
}
.area__towns {
  margin: var(--space-sm) auto 0; max-width: 44ch;
  font-size: var(--text-base); line-height: 1.9;
  color: var(--color-ink);
  text-wrap: pretty;
}
/* The separators are ornament, not punctuation — they step back so the town
   names carry the line. */
.area__town { white-space: nowrap; }
.area__dot { color: var(--color-muted); margin-inline: 0.45rem; }
.area__note {
  margin: var(--space-sm) auto 0; max-width: 46ch;
  font-size: var(--text-sm); line-height: 1.6;
  color: var(--color-ink-2);
}
.area__ask { text-align: left; }
.area__head {
  margin: 0;
  font-family: var(--font-body);
  font-size: var(--text-sm); font-weight: 800;
  letter-spacing: var(--track-caps); text-transform: uppercase;
  color: var(--color-accent);
}
.area__contact {
  list-style: none; padding: 0;
  margin: var(--space-sm) 0 0;
  display: grid; gap: var(--space-2xs);
}
.area__contact li {
  display: flex; align-items: center; gap: var(--space-2xs);
  font-size: var(--text-sm);
}
.area__contact svg { flex: none; width: 1.1rem; height: 1.1rem; color: var(--color-accent); }
.area__contact a { color: var(--color-ink); text-decoration: none; }
.area__contact a:hover { color: var(--color-accent); text-decoration: underline; }

/* ══ Catering request form ═══════════════════════════════════════════════
   Added 2026-08-11.

   THE DEPTH PROBLEM, AND HOW IT IS SOLVED WITHOUT OUTLINES. Form controls are
   the one component every design system solves with a 1px border, and this
   one bans borders (tokens.css, top). A bordered input here would be the first
   outlined object on the site and would drag the whole ceramic direction back
   toward the neo-brutalist build it replaced.

   So the fields are TILES, the same way everything else here separates: the
   band sits on the glazed-tile ground (--color-paper-2) and each control is a
   near-white slab (--color-surface) laid on it. Two flat blocks meeting, no
   line between them — which is exactly the plaster-and-tile logic, and reads
   as a field because it is inset in luminance, not because it is ringed.

   The ONE line in the component is the 2px bottom rule, and it is not a
   border in the decorative sense: it is the focus and error channel. It is
   transparent at rest, accent on focus, terracotta when the field is wrong.
   A state channel that only draws when there is a state to show is not the
   thing the ban is aimed at.

   No radius (tiles are square), no shadow, no lift on focus — the ground
   colour and that one rule carry every state. */
/* ⚠️ THE BAND LOSES ITS TINT, THE FIELDS KEEP THEIRS. Everything else on this
   page gave its fill back to the paper (see `.fit`), and this section does too —
   but the controls inside it do NOT. `.field input/select/textarea` stay on
   --color-surface, because a text field with no fill on textured parchment
   stops looking like something you can type in, and the same rule carries the
   16px iOS floor and the focus channel. A form is the one place on this site
   where a filled box is doing a job rather than decorating. */
/* ── CONDENSED AND SPLIT IN TWO, 2026-08-18 ──────────────────────────────
   *"the form section on the catering page requires too long of a scroll,
   reduce that, condense and restructure."* It was 1789px at 1440 — 38% of the
   whole page — as one 928px column running intro (291) → checklist card (312)
   → form (866), each block waiting for the one above to finish.

   Nothing was cut to fix it. The two blocks that are ABOUT the form move
   alongside the form instead of in front of it, which is what they were always
   for: the intro says what happens when you send it and the checklist says
   what to go and find out before you start typing. Neither is a step in the
   sequence, so neither should have been costing a screen of scroll.

   ⚠️ IT SITS ON --shell NOW, not 58rem. Every other section on this page is
   already shell-wide (`.fits`, `.area`, `.expect`); this one was narrow because
   a form wants a narrow measure, and that is still true — the FORM column is
   ~46rem, which is close to the 58rem it had before once the aside takes its
   24rem. The section stops being the one indented block on the page. */
.req {
  background: none;
  /* One step down from the page's --space-3xl sections. This block is the last
     thing on the page before the delivery band and it was the longest. */
  padding: var(--space-2xl) var(--page-gutter);
}
.req__inner {
  max-width: var(--shell); margin: 0 auto;
  display: grid; gap: var(--space-xl);
}
.req__say { text-align: center; }

/* 62rem, not the 44rem the fields stack at: below this the aside column cannot
   hold the checklist at a readable measure, and a 24rem form is worse than a
   long one. */
@media (min-width: 62rem) {
  .req__inner {
    grid-template-columns: minmax(0, 24rem) minmax(0, 1fr);
    align-items: start;
    column-gap: var(--space-2xl);
    row-gap: var(--space-lg);
  }
  /* The form takes the whole right-hand column, so the two blocks on the left
     stack against it however tall they end up. */
  .req__form { grid-column: 2; grid-row: 1 / span 2; }
  .req__say, .ready { grid-column: 1; }

  /* Everything in the aside stops being centred. A centred column beside a
     left-aligned form reads as two unrelated things; ranged left, it reads as
     the form's own margin note. */
  .req__say { text-align: left; }
  .req__kicker { justify-content: start; }
  .req__lede, .req__rush { margin-inline: 0; }
  .req__rush { justify-content: start; }
  /* max-width only — the padding is holding the text off `.framed`'s engraved
     border-image, and the seal still laps the corner into a 4.5rem gutter. */
  .ready { max-width: none; margin: 0; text-align: left; }
  .ready__key { margin-inline: 0; }
  .ready__close { justify-content: start; }
  /* One column: the two-up arrangement exists to stop four sentences running
     418px inside the form, and in a 24rem aside there is no width to give it
     and no height to save. */
  .ready .checks { grid-template-columns: minmax(0, 1fr); justify-self: start; }
}
.req__kicker {
  display: flex; align-items: center; justify-content: center;
  gap: var(--space-sm);
  margin: 0 0 var(--space-sm);
  font-family: var(--font-body);
  font-size: var(--text-xs); font-weight: 700;
  letter-spacing: var(--track-caps); text-transform: uppercase;
  color: var(--color-accent);
}
.req__title {
  margin: 0;
  font-family: var(--font-display); font-weight: var(--weight-display);
  font-size: var(--text-display-s); line-height: 1.06;
  letter-spacing: var(--track-display);
  color: var(--color-ink);
}
.req__lede {
  margin: var(--space-sm) auto 0; max-width: 46ch;
  font-size: var(--text-base); line-height: 1.6;
  color: var(--color-ink-2);
}
/* The escape hatch, set at the top rather than buried under the button. Anyone
   who needs food today should never reach the date picker. */
/* ⚠️ HELD TO THE LEDE'S MEASURE. This line picked up two clauses from the
   deleted booking panel on 2026-08-17 — "and we will confirm the same day" and
   "Or stop by the truck and ask us in person" — and at full width it then ran
   WIDER than the lede above it, which reads as the small print being the more
   important of the two. `align-items: start` because it is two lines now and a
   centred icon floats between them. */
.req__rush {
  display: flex; align-items: start; justify-content: center;
  gap: var(--space-2xs);
  max-width: 44ch; margin-inline: auto;
  margin-block: var(--space-md) 0;
  font-size: var(--text-sm); line-height: 1.5; color: var(--color-muted);
  text-wrap: pretty;
}
.req__rush svg { flex: none; width: 1.05rem; height: 1.05rem; color: var(--color-accent); }
.req__rush a { color: var(--color-accent); font-weight: 600; }

.req__form { margin: 0; }
/* Twelve tracks, so a field can take a half (6) or the full row without a
   second grid. Half-fields stack automatically on a phone — see the narrow
   block at the foot of this file. */
.req__grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--space-md);
}
.field--full { grid-column: 1 / -1; }
.field--half { grid-column: span 1; }

.field { margin: 0; display: grid; gap: var(--space-3xs); }
.field label,
.field legend {
  font-family: var(--font-body);
  font-size: var(--text-xs); font-weight: 700;
  letter-spacing: 0.08em; text-transform: uppercase;
  color: var(--color-ink-2);
}
/* Terracotta, and it stays visible. A required marker in the same grey as the
   label is decoration; this one is the only warm mark in the component and it
   is doing work. */
.field__req { color: var(--color-terra); margin-left: 0.15em; }

/* :not([type="radio"]) is load-bearing, not tidiness. `appearance: none` below
   erases a radio's dot entirely, and the collect/deliver pair sits inside a
   .field like everything else — without this exclusion the two most important
   controls on the form render as two unmarked lines of text and the customer
   cannot see which one is selected. Caught in a 390px screenshot, 2026-08-11.
   Any control type added to this component needs the same check. */
.field input:not([type="radio"]),
.field select,
.field textarea {
  width: 100%;
  min-height: 52px;                       /* same tap target as .btn */
  padding: var(--space-2xs) var(--space-sm);
  border: 0;
  border-bottom: var(--rule-thin) solid transparent;
  border-radius: 0;
  background: var(--color-surface);
  color: var(--color-ink);
  font-family: var(--font-body);
  /* 16px FLOOR, and it is not a style choice. iOS Safari zooms the whole page
     when a focused field is under 16px, and it does not zoom back out — the
     customer is left on a sideways-scrolling page halfway through the form.
     --text-base is 17px, so this is comfortably clear of it. Never set a form
     control on this site below 1rem. */
  font-size: var(--text-base);
  line-height: 1.4;
  transition: border-color var(--dur-short) var(--ease-out),
              background var(--dur-short) var(--ease-out);
  -webkit-appearance: none; appearance: none;
}
/* 6rem, down from 8. It is the last field and the tallest thing in the form, it
   is optional, and it resizes — so the height it opens at is a hint about how
   much to write, not a limit on it. Four lines still says "a sentence or two",
   which is what this field wants. */
.field textarea { min-height: 6rem; padding-top: var(--space-xs); resize: vertical; }

/* The native arrow is drawn in the OS's own idiom and is the one piece of
   chrome in this band that would look borrowed. Replaced with the site's own
   chevron as a mask, so it takes its colour from a token like every other
   mark here. */
.field select {
  padding-right: var(--space-xl);
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='14' height='9'%3E%3Cpath d='M1 1l6 6 6-6' fill='none' stroke='black' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right var(--space-sm) center;
  background-size: 0.875rem 0.5625rem;
}
/* Placeholder-as-prompt only. Never used to replace a label — a field whose
   only name is its placeholder loses that name the moment you type in it. */
.field ::placeholder { color: var(--color-muted); opacity: 1; }

/* `.field` and `.req__grid` are both `display: grid`, and a display value beats
   the UA stylesheet's `[hidden] { display: none }` — so the delivery address
   row stayed on screen after script.js hid it, asking for an address from
   someone collecting at the window. Same trap for the grid when the form is
   replaced by the thank-you. Scoped to this component rather than set as a
   global `[hidden] { display: none !important }`, which would be a blunt
   instrument reaching into the lightbox and the menu filters too. */
.field[hidden], .req__grid[hidden], .req__send[hidden] { display: none; }

.field input:focus-visible,
.field select:focus-visible,
.field textarea:focus-visible {
  outline: none;                          /* replaced, not removed */
  border-bottom-color: var(--color-accent);
  background: var(--color-surface-2);
}
.field.is-bad input,
.field.is-bad select,
.field.is-bad textarea { border-bottom-color: var(--color-terra); }

.field__hint {
  font-size: var(--text-xs); line-height: 1.45;
  color: var(--color-muted);
}
/* Terracotta text on the tile ground, and it carries a word — never a bare
   red rule, which tells someone something is wrong but not what. */
.field__err {
  font-size: var(--text-xs); font-weight: 600; line-height: 1.45;
  color: var(--color-terra);
}

/* The collect/deliver pair. A fieldset with the browser's default border and
   padding stripped, because that border is the outlined box this whole system
   avoids. */
.field--choice {
  border: 0; padding: 0; margin: 0;
  display: grid; gap: var(--space-2xs);
}
.field--choice legend { padding: 0; margin-bottom: var(--space-3xs); }
.choice {
  display: flex; align-items: center; gap: var(--space-2xs);
  min-height: 44px;                       /* tap target, on the row not the dot */
  background: var(--color-surface);
  padding: 0 var(--space-sm);
  cursor: pointer;
}
.choice input {
  flex: none; width: 1.15rem; height: 1.15rem;
  accent-color: var(--color-accent);
  cursor: pointer;
}
.choice label {
  font-size: var(--text-base); font-weight: 500;
  letter-spacing: 0; text-transform: none;
  color: var(--color-ink); cursor: pointer;
}

/* Off-screen, not display:none. A bot reading the DOM sees a normal field; a
   screen reader does not reach it because the wrapper is aria-hidden and the
   input is tabindex="-1". */
.req__trap {
  position: absolute; width: 1px; height: 1px;
  margin: -1px; padding: 0; overflow: hidden;
  clip-path: inset(50%); white-space: nowrap;
}

.req__send {
  margin-top: var(--space-lg);
  display: grid; justify-items: center; gap: var(--space-xs);
}
.req__send .btn { width: min(100%, 22rem); }
.req__send .btn[disabled] { opacity: 0.6; cursor: default; }
.req__fine {
  margin: 0; text-align: center;
  font-size: var(--text-xs); color: var(--color-muted);
}

/* One live region, three states. Each is a flat tinted block — the same
   separation device as everything else — rather than an alert box with an
   icon and a ring. */
.req__status {
  margin: var(--space-md) 0 0;
  padding: var(--space-sm) var(--space-md);
  font-size: var(--text-base); line-height: 1.55;
  text-align: center;
}
.req__status--good {
  background: var(--color-accent);
  color: var(--color-accent-ink);
  font-weight: 600;
}
.req__status--bad {
  background: var(--color-surface);
  color: var(--color-terra);
  font-weight: 600;
}
.req__status--busy { background: var(--color-surface); color: var(--color-ink-2); }

/* ══ Delivery — floating button + popover ════════════════════════════════
   REPLACED THE FULL-WIDTH BAND, 2026-08-17. The band shipped on 2026-08-11 as
   a `.deliv` section on home and menu; Marcus cut it the day the real URLs
   landed — *"I don't like the current way … it should be a small
   call-to-action button that unlocks the three options … I don't want it as
   its own section on the homepage."* Both sections are gone from the markup.
   Do not reinstate them: a headline, a lede and three tiles gave DoorDash,
   Uber Eats and Grubhub more of the page than the phone number, which inverts
   what this business actually wants — see the commission note in the README.

   The `.dapp` tiles survive because they were already right. Everything else
   here is new.

   INJECTED BY script.js ON EVERY PAGE, not written into any HTML file. That is
   why there is no markup to find: eight pages would have meant eight copies of
   the same block, and the whole thing must still vanish when no URL is set.

   No shadow anywhere in this block ON PURPOSE — --shadow-1/2/3 are all `none`
   in tokens.css, so a floating panel has to be separated by a rule and a fill,
   not by elevation. Do not "fix" the flatness with a drop shadow. */
/* One source of truth for the seal's diameter. Resize it here and the button,
   the disc, the shine's clip and the panel's max-height all follow.

   `--seal-claim` (the seal plus its gutter plus a little air) used to sit here
   too, for the hero proof strip to reserve a corner with. Both the reservation
   and the raise that replaced it are gone — see the note further down — so the
   variable went with them rather than sitting here computing a number nothing
   reads. */
:root { --seal-size: 100px; }
@media (max-width: 22.5rem) { :root { --seal-size: 84px; } }
/* A phone held sideways is ~390px tall. The seal and its panel are competing
   for that, so the seal gives some back — height, not width, is the scarce
   axis here, which is why this is a height query. */
@media (max-height: 30rem) { :root { --seal-size: 76px; } }

.dorder {
  position: fixed; z-index: var(--z-bar);
  right: var(--page-gutter); bottom: var(--page-gutter);
  display: flex; flex-direction: column; align-items: flex-end;
  gap: var(--space-2xs);
  /* ── ALWAYS PRESENT ──
     It was held below the fold and revealed on scroll, like .callbar. Marcus
     reversed that on 2026-08-17: *"i want that pop up present at all times."*
     There is deliberately no `is-up` state any more and script.js no longer
     watches the scroll for it — do not put either back.

     The reason it was gated is still real and is handled further down instead:
     at the iPad widths the seal sat on the fourth cell of the hero proof strip
     ("750 E Eau Gallie Blvd"). See the `.hero__proof` padding rule below. */
}

/* ── The button · Marcus's ORDER NOW seal ────────────────────────────────
   REPLACED the navy pill with an icon and the word "Delivery", 2026-08-17,
   when Marcus supplied the artwork. The seal is a round painted badge — olive
   branches, the scooter with the truck's name on its box, waves and a sun —
   so it carries its own meaning and needs no chrome around it. No background,
   no border, no padding: the disc IS the button.

   ⚠️ THE WORDING IS PAINTED INTO THE ART. "ORDER NOW" is pixels, not type, so
   it cannot reflow, cannot be translated and is invisible to a screen reader.
   The accessible name comes from `aria-label` on the button in script.js —
   if you swap this file, keep that label.

   SIZED DOWN 120px → 100px, 2026-08-17: *"the order now pop up AND button are
   too large, scale them down."* The painted caps are ~10px tall at 100px, so
   treat that as the floor — below it the wording in the art stops being
   readable and the seal becomes decoration with no call to action in it. */
/* ⚠️ THIS BLOCK BREAKS THE FLAT RULE ON PURPOSE. `--shadow-1/2/3` are all
   `none` in tokens.css and the rest of the site has no elevation anywhere.
   Marcus asked for the seal to "show up as a button and make a bit more of a
   shine" on 2026-08-17, which a flat sticker cannot do — with no lift and no
   highlight it reads as a decal printed on the page rather than a control.

   So the exception is scoped to this one element and no further: a ring, a
   lift, and a gloss. Do NOT generalise any of it into tokens.css, and do not
   copy it onto other buttons to "match" — the flat rule still governs
   everything else on the site. */
.dorder__btn {
  display: block; width: var(--seal-size); height: var(--seal-size);
  padding: 0; border: none; background: none;
  border-radius: 50%;
  cursor: pointer;
  /* drop-shadow, not box-shadow: the artwork is a transparent PNG-shaped disc,
     and box-shadow would draw a square shadow behind a round picture. */
  filter: drop-shadow(0 3px 10px oklch(23% 0.032 250 / 0.34));
  transition: transform var(--dur-short) var(--ease-out),
              filter var(--dur-short) var(--ease-out);
}
/* The disc that clips the gloss. The seal's own painted border is its edge, so
   this ring sits just outside it and reads as the rim of a physical button. */
.dorder__disc {
  position: relative; display: block;
  width: 100%; height: 100%;
  border-radius: 50%; overflow: hidden;
  box-shadow: 0 0 0 3px var(--color-surface), 0 0 0 5px var(--color-accent);
}
.dorder__btn img { display: block; width: 100%; height: 100%; }

/* ── The shine ──
   A specular band swept across the face. It is a pseudo-element on the disc so
   it is clipped to the circle, and it is skewed rather than rotated so the
   highlight reads as a bar of light rather than a spinning wedge. */
.dorder__disc::after {
  content: ""; position: absolute; top: -60%; bottom: -60%;
  left: -40%; width: 34%;
  background: linear-gradient(90deg,
    oklch(100% 0 0 / 0) 0%,
    oklch(100% 0 0 / 0.62) 50%,
    oklch(100% 0 0 / 0) 100%);
  transform: skewX(-18deg);
  pointer-events: none;
  /* Long pause, quick sweep. 6s with the travel packed into the first ~13%
     means it glints about every six seconds rather than shimmering
     constantly, which on a fixed element in the corner of every page would
     be maddening rather than inviting. */
  animation: dorder-shine 6s var(--ease-out) 1.5s infinite;
}
@keyframes dorder-shine {
  0%   { left: -40%; }
  13%  { left: 106%; }
  100% { left: 106%; }
}

/* The disc is round, so a rectangular focus ring around it looks broken. */
.dorder__btn:focus-visible {
  outline: 2px solid var(--color-focus); outline-offset: 4px;
}
/* Lifts and brightens together — the shadow grows as the button rises, which
   is what makes it read as picked up rather than just scaled. */
.dorder__btn:hover {
  transform: translateY(-2px) scale(1.05);
  filter: drop-shadow(0 8px 18px oklch(23% 0.032 250 / 0.42));
}
.dorder__btn:active { transform: translateY(0) scale(0.98); }
.dorder__btn[aria-expanded="true"] {
  transform: scale(0.97);
  filter: drop-shadow(0 2px 7px oklch(23% 0.032 250 / 0.3));
}

/* ── The panel ───────────────────────────────────────────────────────────
   `hidden` does the hiding, so there is no `display` here to fight it. The
   width is set by the buttons: they are 280px of artwork plus the padding. */
.dorder__pop {
  /* 20rem → 16.5rem and the padding one step down, 2026-08-17, with the seal:
     *"the order now pop up AND button are too large."* The three brand marks
     are the panel's whole width, so narrowing the panel is what shrinks them —
     there is no size to set on `.dapp img`, and setting one would squash the
     marks. See the note on `.dapp`. */
  width: min(16.5rem, calc(100vw - (var(--page-gutter) * 2)));
  padding: var(--space-sm);
  /* ── The ground ──
     WAS FLAT --color-surface, a near-white card. Changed 2026-08-17 — *"add a
     background to that pop up so that its not just white bland."*

     It is the site's own parchment now: --parchment underneath and
     ground-tile-900.webp over it, which is the same pair .paper, .sheet and
     .wall lay down. That is the point — the panel is a torn-off piece of the
     same paper the interior pages are printed on, not a white UI card
     borrowed from somewhere else.

     ⚠️ `background-size: 100% auto` here is NOT the same picture as it is on a
     section. The tile is 900x183 and sections are hundreds of px wide, so it
     renders near 1:1 there; this panel is 264px, so the same declaration
     downscales it ~3.4x into a fine, dense grain. That is why it is kept —
     a coarse paper texture inside a 264px box reads as blotches, and cutting
     the tile to a small canvas would have been a new asset for no gain. If
     the panel ever gets much wider, look at this again.

     The colour under it still matters: the tile has transparency, and
     --parchment is what the sections put behind it. A white fill here would
     wash the grain out to nothing. */
  background-color: var(--parchment);
  background-image: url("assets/photos/ground-tile-900.webp?v=20260818a");
  background-position: center top;
  background-repeat: repeat-y;
  background-size: 100% auto;
  border: var(--rule-thin) solid var(--color-accent);
  /* ⚠️ A LANDSCAPE PHONE IS 390px TALL AND THE PANEL IS ~290px BEFORE THE SEAL
     BELOW IT. Measured at 844x390 on all eight pages: the panel opened off the
     top of the screen every time. The cap makes it scroll instead of
     overflowing, so it fits at any viewport height including the short ones
     nobody thinks to test. Keep it — it is the difference between a control
     that always works and one that works in portrait. */
  max-height: calc(100dvh - var(--seal-size) - var(--page-gutter) * 2 - 1.5rem);
  overflow-y: auto;
  overscroll-behavior: contain;
}
.dorder__pop[hidden] { display: none; }
.dorder__head {
  margin: 0 0 var(--space-2xs);
  font-family: var(--font-body);
  font-size: var(--text-xs); font-weight: 700;
  letter-spacing: var(--track-caps); text-transform: uppercase;
  color: var(--color-accent);
}
/* The hand-off line. It used to be printed on each tile, which is the better
   place for it — but these are the platforms' OWN buttons now and nothing may
   be overprinted on a brand mark, so it moves up here where it is still read
   before any of the three is tapped, not down into the fine print. */
.dorder__note {
  margin: 0 0 var(--space-xs);
  font-size: var(--text-xs); line-height: 1.45; color: var(--color-ink-2);
}
.dorder__list {
  list-style: none; margin: 0; padding: 0;
  display: grid; gap: var(--space-2xs);
}
/* Every file out of tools/build-delivery-assets.py is the same 840x175
   canvas, so ONE width lands all three on one height with their own
   proportions intact.

   ⚠️ NEVER SET A HEIGHT, and never force a common aspect ratio. The three
   pills are genuinely different shapes (DoorDash 4.61, Uber Eats 4.80,
   Grubhub 4.39) and squashing a brand mark is the exact thing the build
   script exists to prevent. A WIDTH with `height: auto` is always safe —
   that scales, it does not distort.

   Scaled down 2026-08-17 — *"the three icons can be scaled down"* — after the
   panel had already come in from 20rem to 16.5rem. 80% of the content box,
   centred, so they read as a cluster of three buttons inside the panel rather
   than three bars filling it edge to edge. */
.dapp {
  display: block;
  width: 80%; margin-inline: auto;
  /* ⚠️ THIS PADDING IS THE TAP TARGET AND IT IS LOAD-BEARING. At 80% the
     artwork is ~186px wide, and 186 ÷ 4.8 is a 39px-tall picture — under the
     44px minimum for a touch target. The anchor is what gets tapped, so it
     keeps the height the image gave up. Shrink the marks further and this
     number has to grow to match. */
  padding-block: 3px;
}
.dapp img {
  display: block; width: 100%; height: auto;
  transition: transform var(--dur-short) var(--ease-out);
}
.dapp:hover img { transform: scale(1.02); }
.dapp:focus-visible { outline: 2px solid var(--color-focus); outline-offset: 3px; }
.dorder__fine {
  margin: var(--space-xs) 0 0;
  font-size: var(--text-xs); line-height: 1.5; color: var(--color-muted);
}
.dorder__fine a { color: var(--color-accent); font-weight: 600; white-space: nowrap; }

/* Below 40rem the sticky .callbar occupies the bottom of the screen and body
   already carries 4.5rem of padding for it. The FAB is FIXED, so that padding
   does nothing for it — it has to be lifted by hand or it lands on top of
   "Call to order". */
@media (max-width: 40rem) {
  .dorder { bottom: calc(4.5rem + var(--space-2xs)); }
}

/* ── The seal vs. the hero proof strip: NOTHING IS DONE ABOUT IT ──────────
   Because the seal is on screen at all times it permanently owns the bottom
   right of the viewport, and the last cell of the proof strip wants the same
   pixels. MEASURED at 14 widths: the glyphs of "750 E Eau Gallie Blvd /
   Indian Harbour Beach" are covered at 430, 600, 768, 834 and 1440. It is not
   a clean range because it depends on where the hero's bottom edge lands
   against the viewport height.

   ⚠️ THIS IS OPEN, NOT SOLVED, AND MARCUS HAS BEEN TOLD. Re-measured after
   the raise was removed on 2026-08-17, at eleven real device sizes: the seal
   covers address glyphs at 390x844, 768x1024, 834x1112, 1024x768, 1280x800
   and (by 28px) 1440x900. It is clear at 430x932, 600x900, 1512x982,
   1920x1080 and 2560x1440. Do not read the three rejected fixes below as
   "nothing can be done" — read them as "these three cost more than they
   save". The seal not moving is the fixed requirement; everything else is
   still on the table.

   ⚠️ THREE WAYS WERE TRIED AND ALL THREE WERE WRONG — do not reach for any of
   them again.

   Padding on `.hero__proof` does nothing: the band is full width but the list
   inside is `max-width: 1180px` with auto margins, so band padding only
   re-centres it and its right edge does not move (measured at 1440: list edge
   1310, seal edge 1262, still covered).

   Padding on `.proof__list` DOES pull the cells clear — and costs more than it
   saves. Taking ~190px out of a 1180px list leaves 247px a cell, and both
   "Open from 10:30am" and "750 E Eau Gallie Blvd" then wrap to two lines at
   every width from 1280 to 2560, which they never did before. The strip is
   four facts on one line each by design.

   ⚠️ AND A THIRD, WHICH SHIPPED FOR A FEW HOURS AND WAS THE WORST OF THEM.
   Moving the SEAL instead of the strip: script.js set `--seal-raise` to lift
   it above the band while the band was on screen. Reversed 2026-08-17 — *"it
   should appear at the same screen position at all times … currently it
   disappears from hero as you scroll and then reappears."* The lift was
   `vh - strip.top`, which grows with every pixel scrolled: 92px → 858px at
   1440x900, so the seal climbed off the top of the screen and snapped back
   once the hero cleared. See the long note in script.js.

   So NOTHING is reserved, the strip is untouched, and THE SEAL IS FIXED IN THE
   CORNER AND NEVER MOVES. The address collision is a known, accepted overlap
   on the hero at some widths — the button is the more important object and it
   has to be findable in the same place at all times. If it is ever solved,
   solve it in the strip. */
.dorder { bottom: var(--page-gutter); }
@media (max-width: 40rem) {
  .dorder { bottom: calc(4.5rem + var(--space-2xs)); }
}

@media (prefers-reduced-motion: reduce) {
  .dorder, .dorder__btn, .dapp img { transition: none; }
  .dorder__btn:hover, .dapp:hover img { transform: none; }
  /* The sweep is decorative motion on a fixed element that is now on screen at
     all times — exactly the thing this query exists to stop. The ring and the
     lift stay; only the travelling highlight goes. */
  .dorder__disc::after { animation: none; opacity: 0; }
}

/* The step down to 84px on the narrowest phones is driven by --seal-size on
   :root above, so the proof strip's reservation shrinks with it. 84px still
   clears the 44px tap target by a wide margin. */

/* No-JS: the button is built by script.js, so there is nothing to hide here —
   the whole control simply never exists, exactly like the band before it. The
   phone number is in the masthead and the footer of every page regardless. */

/* The split hero stops splitting: below this the copy column is under 300px and
   the headline breaks after "Bring the", while the call button runs out of room
   for its own phone number. Copy first, then the photograph as a band — the
   left-to-right reading order of the mockup, turned top-to-bottom. */
@media (max-width: 52rem) {
  .chero { grid-template-columns: minmax(0, 1fr); }
  .chero__say {
    justify-self: stretch; max-width: none;
    padding: var(--space-xl) var(--page-gutter) var(--space-lg);
  }
  .chero__lede { max-width: 46ch; }
  .chero__shot { min-height: 0; aspect-ratio: 16 / 10; }
  /* The fade turns with the layout — a left-edge fade under a stacked column
     would lighten the wrong side of the picture. */
  .chero__shot::after {
    background: linear-gradient(to bottom,
      oklch(97.4% 0.006 95 / 1) 0%,
      oklch(97.4% 0.006 95 / 0.6) 9%,
      oklch(97.4% 0.006 95 / 0) 32%);
  }
}

/* The menu head is centred at every width, so it needs no stacking rule — only
   air taken back out of it, and a tab row that stops trying to fill a line it
   can no longer fill. */
@media (max-width: 64rem) {
  /* Three photographed cards run out of measure well before the viewport
     does — two up, then the third full width beneath, turned on its side. */
  .mpicks__inner { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .mpicks__inner > :last-child {
    grid-column: 1 / -1;
    flex-direction: row; align-items: stretch;
  }
  /* The photo is taken OUT OF FLOW here, and it has to be. Left in flow at
     38% of a full-width card it is ~450px of 1:1 photograph, and since
     nothing else in the card is that tall the card grows to meet it — a
     500px-tall band holding two lines of text. Absolute inside a stretched
     flex item inverts that: the copy sets the height, the photo fills it. */
  .mpicks__inner > :last-child .mpick__shot {
    position: relative; flex: 0 0 38%; aspect-ratio: auto;
    /* On its side the rounded pair moves from top to left. */
    border-radius: calc(var(--menu-radius) - 1px) 0 0 calc(var(--menu-radius) - 1px);
  }
  .mpicks__inner > :last-child .mpick__shot img { position: absolute; inset: 0; }
  .mpicks__inner > :last-child .mpick__say { flex: 1 1 auto; align-content: center; }
}

@media (max-width: 52rem) {
  /* The masthead wraps to three rows on a phone and eats ~175px before the
     head even starts, so the head gives some of that back. */
  .mhead__aside { margin-top: var(--space-2xs); font-size: var(--text-base); }
  /* A seven-tab row becomes a scroller rather than four ragged lines. Growing
     to fill is what the wide layout wants and exactly what a scroller must
     not do, so the flex-grow comes back off here. */
  .mfilter__inner {
    flex-wrap: nowrap; overflow-x: auto; scrollbar-width: none;
    /* The gutter is on .mfilter, so the scroller's own end needs padding or
       the last tab butts against the viewport edge mid-scroll. */
    scroll-padding-inline: var(--page-gutter);
  }
  .mfilter__inner::-webkit-scrollbar { display: none; }
  .mfilter__tab { flex: 0 0 auto; white-space: nowrap; }
}

/* One up. Everything the 64rem rule did to the third card comes back off —
   it is an ordinary vertical card again. */
/* Short windows — a 1280×720 laptop, a 1440×700 half-height window, a phone
   held sideways. The clamps above already scale the head, but at this height
   the leading and the tab row's own padding are the last places left to take
   the final ~50px from, and both cost nothing legible. */
@media (max-height: 46rem) {
  .mhead__aside { font-size: var(--text-base); }
  .mfilter__inner { padding-block: var(--space-2xs); }
  /* 30svh and 30rem, put through the same ×1.5217 as the base rule. Was 44svh
     — which stopped being a reduction the moment the base budget dropped from
     50svh to 34svh on 2026-08-15, and quietly became a 10svh ENLARGEMENT on
     exactly the short windows this block exists to protect. */
  .mhead__cut { width: min(100%, 45.65svh, 45.65rem); }
}

@media (max-width: 40rem) {
  .mpicks__inner { grid-template-columns: minmax(0, 1fr); }
  .mpicks__inner > :last-child { grid-column: auto; flex-direction: column; }
  .mpicks__inner > :last-child .mpick__shot {
    position: static; flex: none; aspect-ratio: 16 / 10;
    border-radius: calc(var(--menu-radius) - 1px) calc(var(--menu-radius) - 1px) 0 0;
  }
  .mpicks__inner > :last-child .mpick__shot img { position: static; }
}

/* ── Catering · narrow ────────────────────────────────────
   Four promises across a tablet gives each column about eighteen characters,
   which breaks "appreciation" in half. Two by two instead, and the hairlines
   turn with them: the rule between columns 1|2 and 3|4 stays vertical, the
   one between the rows becomes horizontal. */
@media (max-width: 64rem) {
  .expect__row { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .promise { padding-block: var(--space-md); }
  .promise + .promise { border-left: 0; }
  .promise:nth-child(even) { border-left: var(--rule-hair) solid var(--color-accent-pale); }
  .promise:nth-child(n + 3) { border-top: var(--rule-hair) solid var(--color-accent-pale); }

  /* The map column is the first thing to go: a 6.6rem silhouette beside two
     squeezed text columns is a decoration taking a third of the row. The
     towns and the phone number are the content. */
  .area__inner { grid-template-columns: minmax(0, 1fr) auto; }
  .area__map { display: none; }
  .area__say { border-inline-start: 0; padding-inline-start: 0; }
}

/* Three format cards run out of measure well before the viewport does. Two up,
   then the third full width beneath — and the third keeps its own centring
   rather than stretching its paragraph across the whole row. */
@media (max-width: 60rem) {
  .fits { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .fits > :last-child { grid-column: 1 / -1; }

  /* The seal is hung off the card's top-right corner and translated out of the
     box on both axes, so at narrow widths it drifts toward the page gutter and
     starts overlapping the frame's rule rather than lapping its corner. Pulled
     back inside the card's own edge here; it is still a mark laid ON the card. */
  .ready__seal { transform: translate(10%, -30%); }
}

@media (max-width: 44rem) {
  .fits { grid-template-columns: minmax(0, 1fr); max-width: 30rem; }
  .fits > :last-child { grid-column: auto; }

  .expect__row { grid-template-columns: minmax(0, 1fr); max-width: 26rem; margin-inline: auto; }
  /* One column has no crossing to mark, and the wave would land in the middle
     of the second promise's paragraph. */
  .expect__cross { display: none; }
  /* One column: no vertical seam left to draw, and the horizontal wave has to
     start one item earlier because every item now follows another. */
  .promise:nth-child(even)::before { content: none; }
  .promise:nth-child(n + 2) { padding-top: var(--space-lg); }
  .promise:nth-child(n + 2)::after {
    content: ""; position: absolute; left: 0; right: 0; top: 0; height: 5px;
    background-color: var(--wave-ink);
    -webkit-mask: var(--wave-x) repeat-x left center / 12px 5px;
            mask: var(--wave-x) repeat-x left center / 12px 5px;
  }

  /* The contact column drops under the towns rather than beside them, and
     centres with everything else once it is no longer a side column. */
  .area__inner { grid-template-columns: minmax(0, 1fr); gap: var(--space-lg); }
  .area__say { border-inline: 0; padding-inline: 0; }
  .area__ask { text-align: center; }
  .area__contact li { justify-content: center; }

  .ready { padding: var(--space-md) var(--space-sm) var(--space-sm); }
  /* Two columns of ~14rem is two words a line. Back to one. */
  .ready .checks { grid-template-columns: minmax(0, 1fr); gap: var(--space-2xs); }
  /* Below this the card is narrow enough that any overhang lands on type. */
  .ready__seal { position: static; transform: none; margin: 0 auto var(--space-sm); }
}

/* ── Request form · narrow ────────────────────────────────
   Two half-fields side by side stop working well before the viewport runs
   out. Measured on the binding pair, "Of those, how many vegetarian" over a
   number input: the LABEL is what breaks first, not the control — it wraps to
   three lines at about 190px of track, which is where a two-up grid lands at
   ~44rem once the page gutter comes off. One column from here down.

   The date and time controls have a second, harder floor: iOS renders
   <input type="date"> with its own internal picker text at a fixed size, and
   below roughly 150px of inner width it clips the year. One column clears
   that with room to spare at 320. */
@media (max-width: 44rem) {
  .req { padding-block: var(--space-2xl); }
  /* A phone cannot have the two-column restructure, so the only thing left to
     take is air: the three blocks still stack, one step closer together. */
  .req__inner { gap: var(--space-lg); }
  .req__grid { grid-template-columns: minmax(0, 1fr); gap: var(--space-sm); }
  .field--half { grid-column: 1 / -1; }
  /* Full-width on a phone. A 22rem button centred in a 320px column leaves
     two dead margins and a smaller target than the thumb wants. */
  .req__send .btn { width: 100%; }
  .req__rush { align-items: flex-start; text-align: left; }

  /* The three-across `.deliv__apps` rule that used to live here went with the
     band on 2026-08-17. The tiles are stacked at EVERY width now — they sit in
     a 19rem popover, not a 52rem section — so there is nothing width-specific
     left to say about them. */
}

/* ── Why people come back · and where to find us ──────────
   Built to Marcus's second mockup (2026-08-09). Replaces the WHERE / WHEN /
   ORDER AHEAD panels — three equal tinted boxes, one of which was a seven-row
   timetable. It answered "when are you open" competently and "why would I go"
   not at all, which is the wrong job for the last screen before the footer.

   Continues the printed-card grammar the menu section above now uses:
   ornamented centred head, hairline frames, line art, no photographic
   furniture beyond the one portrait that carries the whole argument.

   SECOND SCOPED DEPARTURE from the NO-OUTLINES rule in tokens.css, same
   reasoning as the menu card: the plaque and the quote card are framed printed
   objects and the frame is the form. Everywhere else on the site, separation
   is still a change of ground colour. */
.here {
  position: relative;
  padding: var(--space-3xl) var(--page-gutter);
  overflow: hidden;                      /* the olive branch bleeds off-page */
}
/* ⚠️ BOTTOM PADDING CUT ON THE INDEX ONLY, and the shorthand above is why this
   is a second rule rather than an edit to it: `.here` also ships on find.html,
   where the block is the last thing before the footer and wants the full
   --space-3xl under it.

   Here it is not last — the catering reminder follows it on the same sheet of
   parchment, and 7rem of bottom padding plus that section's own top padding
   put 184px of empty paper between the two. Marcus, 2026-08-16: "not too much
   space". The two blocks are on ONE background (see the .wall note), so the
   gap read as a hole in the middle of a page rather than as a boundary
   between two sections. */
.here--scene { padding-bottom: clamp(var(--space-lg), 4vw, var(--space-2xl)); }
.here__inner {
  position: relative;
  max-width: var(--shell); margin-inline: auto;
  display: grid;
  /* ⚠️ NARROW COLUMN FIRST, and it is load-bearing. The proof column holds a
     480px photograph that cannot be enlarged (see .voice) plus four short
     lines, so it CANNOT fill a column as tall as the plaque — at 1.5fr it left
     a 360px hole beside it, and stretching the frame to match only moved the
     hole inside the box. Giving the plaque the wide column instead makes its
     address, hours and asides wrap less, which shortens it toward what the
     proof column can actually reach. Do not swap these back to put the big
     column on the left. */
  /* ⚠️ THE 1fr/1.5fr SPLIT IS GONE AND SO IS THE REASON FOR IT — 2026-08-17.
     The note above is the ruling it replaces, and it was correct for what this
     column USED to hold: a 480px photograph that cannot be enlarged plus four
     short lines, which could not fill a column as tall as the plaque no matter
     what track it was given. The blueprint changes the contents, not just the
     look: the proof column is now a photograph AND a six-line note side by side
     inside one piece of paper, so it has the copy to stand at the plaque's
     height on its own. Measured off the blueprint the two are 550 and 522 wide,
     which is this 1.06/1 — near enough to equal that the pair reads as two
     objects laid on a table rather than as a main column and a sidebar.

     THREE TRACKS, NOT TWO. The middle one is `.here__spine` — the hairline and
     the little column that stand between them. It is `auto`, so it costs only
     its own width and both cards keep their share of the rest. */
  /* 1.18 -> 1.32 on 2026-08-17, Marcus: "make the paper asset on the left a bit
     larger". The note is ratio-locked (see .voice), so WIDTH is the only handle
     on its size — a wider track is a bigger sheet of paper in both directions,
     and because the sheet's padding is a percentage of its own width, the
     margins inside grow with it rather than staying put while the paper spreads
     out from under them. The plaque pays for it and can afford to: at ~469 its
     address line still sets on one line with ~55px to spare. */
  grid-template-columns: minmax(0, 1.32fr) auto minmax(0, 1fr);
  /* Split gaps. One `gap: --space-2xl` gave the row under the header the same
     72px the two columns need horizontally, which is far too much under a rule
     that is already doing the separating. */
  column-gap: clamp(var(--space-md), 2.4vw, var(--space-xl));
  row-gap: clamp(var(--space-lg), 2.6vw, var(--space-xl));
  /* ⚠️ `start`, AND IT WAS `stretch` FOR A GOOD REASON THAT NO LONGER HOLDS.
     The old rule matched the two columns' heights because both drew the SAME
     printed frame, and two identical frames at two different heights reads as
     one of them being broken; the surplus became matting inside a pair of equal
     panels.

     They are not the same object any more. The right-hand one is a framed
     plaque and the left-hand one is a torn note pinned to a board, and a note
     does not have to be as tall as the thing beside it — board showing under it
     is what a board is for. Stretching it was actively wrong: it is a
     PHOTOGRAPH of paper scaled `100% 100%`, so forcing it to the plaque's 641px
     pulled a 1.36 image into a 0.92 box, i.e. 48% of vertical stretch, and the
     one object in that photograph with a shape everybody knows is the pin. It
     went visibly oval. At `start` the note comes out 589x434 against a file at
     1200x885 — 1.357 against 1.356, which is as close to untouched as this gets
     without measuring anything.

     Do not put `stretch` back to close the gap under the note. It costs the pin
     to buy parchment nobody was looking at. */
  align-items: start;
}

/* ── The header, spanning both columns ─────────────────────────────────────
   ⚠️ IT SPANS ON PURPOSE. Marcus, 2026-08-16: the section "feels a bit awkward
   with the layout and blankness to it".

   Measured, the layout half of that was structural: this header used to sit
   INSIDE the left column, which put "What keeps people coming back" and the
   plaque's own "Beachside Gyros" side by side at the same altitude — two <h2>s
   of similar authority competing for the same role, with the section headline
   confined to 56% of the width. Spanning it makes one header over two
   supporting columns, which is what the content actually is.

   The blankness half was arithmetic: the old stack ran 323px tall to deliver a
   sprig, a three-word kicker, a five-word headline, A SECOND SPRIG, and a
   six-word caps line. Two ornaments doing one job. The second sprig
   (`.here__flourish`) is deleted — do not put it back; the crest above the
   kicker is the one that survives. */
.here__head {
  grid-column: 1 / -1;
  text-align: center;
  padding-bottom: clamp(var(--space-md), 2.2vw, var(--space-lg));
  border-bottom: var(--rule-hair) solid var(--color-accent-pale);
}

/* ── Left · the proof ──────────────────────────────────── */
/* Grid, so the card inside can be told to match the plaque's height. */
/* ⚠️ CENTRED IN THE ROW, and it is the only honest answer left. Marcus,
   2026-08-17: "spacing a bit weird" — and it was: the note came out 428 tall
   against the plaque's 641, so 213px of blank parchment hung under it in an L
   around the corner of the section.

   The two fixes that do not work, both checked with a tape measure first:
   STRETCHING the note pulls a 1.356 photograph into a 0.92 box and the pin goes
   visibly oval (see .here__inner). GROWING the note does nothing — its height
   is set by the writing column at 357px, and the photograph beside it is capped
   at 320 by the source file, so the picture can never be the taller of the two
   and can never drive the card's height. Tightening the plaque's own spacing
   recovers about 44px of the 213.

   So the surplus cannot be removed; it can only be placed. Centred, it splits
   to ~85 above and below and reads as a note sitting on a board. Pinned to the
   top it read as a hole. */
.here__say { display: grid; align-self: center; }
.here__crest { width: 2.6rem; height: auto; color: var(--color-accent); }

/* ── The spine between the two cards ───────────────────────────────────────
   Its own grid track, not a border on either card. A border belongs to the box
   that draws it and would sit hard against that box's edge; this has to float
   in the gutter, centred between two objects of different widths, and it has to
   be able to vanish when they stack.

   The little column is #ic-column, which is the SAME drawing the plaque used to
   carry on its internal rule. That rule is gone — the blueprint runs the facts
   straight under the name — so the mark moved out here rather than being
   deleted. One ornament, still doing one job, now doing it between the pair
   instead of inside one of them. */
.here__spine {
  display: flex; flex-direction: column; align-items: center;
  gap: var(--space-sm);
  margin: 0;
  /* ⚠️ REQUIRED, because .here__inner aligns its items to `start`. Without it
     this item is only as tall as the little column drawing and the hairlines
     collapse to nothing — the spine renders as a 24px stub floating beside two
     600px cards. `stretch` is scoped to this one item so the note above keeps
     its natural height. */
  align-self: stretch;
}
.here__spine::before,
.here__spine::after {
  content: "";
  flex: 1 1 auto; min-height: 1rem;
  width: var(--rule-hair);
  background: var(--color-accent); opacity: 0.32;
}
.here__spine svg {
  width: 1.5rem; height: 1.5rem; flex: none;
  color: var(--color-accent);
}

/* ── The engraved frame ────────────────────────────────────────────────────
   Marcus's supplied border, 2026-08-17, on both the plaque and the catering
   reminder. It replaces the pair of interrupted hairlines those two drew for
   themselves, and it is the THIRD scoped departure from the no-outlines rule in
   tokens.css — same argument as the menu board and the plaque before it: a
   printed plaque's frame IS the object, not a box drawn round one.

   ⚠️ `border-image`, AND THE SLICE IS TIED TO THE BUILT FILE. tools/
   build-here-assets.py scales the master so the ornate stepped corner lands on
   exactly 48px; that is what `48` means here, and it is measured in the FILE's
   pixels while `border-width` below is in CSS pixels. Change one and the
   corners either clip or float. Rebuild the asset, do not retune the slice.

   Corners keep their shape and only the straight runs stretch, which is the
   whole reason this is border-image rather than four positioned cutouts: the
   middle of every edge is two parallel rules, and stretching a straight line
   horizontally does not change its weight.

   ⚠️ THE COLOUR IS IN THE FILE — rgb(0,52,120), the artwork's own ink, which is
   within a hair of --color-accent-2. border-image takes an image, not a mask,
   so unlike the postmark and the tear this one cannot be painted with a token.
   If the palette ever moves, the frame has to be re-inked in the builder.

   `border-color: transparent` is the fallback and not decoration: if the webp
   404s, a browser paints the border in currentColor and the card gets a 24px
   navy slab round it. */
.framed {
  border: solid clamp(0.8rem, 1.5vw, 1.45rem);
  border-color: transparent;
  border-image: url("assets/photos/frame-engraved.webp?v=20260818j") 48 stretch;
}

/* Flanking hairlines are ::before/::after on a flex container, so they become
   flex items and shrink with the line instead of overrunning it. */
.here__kicker {
  display: flex; align-items: center; justify-content: center;
  gap: var(--space-sm);
  margin: var(--space-xs) 0 var(--space-sm);
  font-size: var(--text-xs); font-weight: 800;
  letter-spacing: var(--track-caps); text-transform: uppercase;
  color: var(--color-accent);
}
.here__kicker::before,
.here__kicker::after {
  content: "";
  flex: 0 1 4.5rem; min-width: 0.75rem;
  height: var(--rule-hair);
  background: var(--color-accent); opacity: 0.45;
}
/* Caps tracking adds a trailing space that reads as an off-centre gap. */
.here__kicker span { margin-right: calc(var(--track-caps) * -1); }

/* The display face, slanted, like the menu card's masthead. Every page
   masthead now speaks in the truck's lettering; the body face carries
   everything underneath it. */
.here__title {
  font-family: var(--font-display);
  font-weight: var(--weight-display);
  font-size: var(--text-display);
  line-height: 1.04;
  /* 15ch -> 24ch. At 15ch the headline inked 574px inside a 665px column; now
     that it spans the full 1180 shell, 15ch would have left ~300px of empty
     paper on each side of it — the same "blankness" one level up. */
  max-width: 24ch; margin-inline: auto;
  text-wrap: balance;
}
/* One row, not a 34ch block. The three claims inked 205px and were centred in a
   665px column set to wrap at 34ch, so they broke onto two lines with ~70% of
   each line empty — the single most visible hole in the old header. The
   periods already separate the three claims, so no separator glyph is added
   and THE WORDING IS UNCHANGED. */
.here__sub {
  display: flex; flex-wrap: wrap;
  align-items: center; justify-content: center;
  gap: 0 var(--space-md);
  margin: var(--space-md) auto 0; max-width: none;
  font-size: var(--text-xs); font-weight: 700;
  letter-spacing: var(--track-caps); text-transform: uppercase;
  line-height: 1.7; color: var(--color-accent-2);
}
.here__sub span { display: inline-block; }

/* ── The quote card ────────────────────────────────────────
   The mockup fills this with an invented five-star review signed by a customer
   who does not exist. What sits here instead is the one piece of social proof
   that is real and independently checkable, plus the portrait: the 4.9 score
   and a link out to the real Google listing. */
/* ⚠️ NO FILL, AND THE FRAME IS .plaque's FRAME. Marcus, 2026-08-15: "the white
   review block on the left here" does not match the rest of the page.

   He is right and the evidence was next to it. This block used to be a solid
   --color-surface panel with a grey --color-rule border: a web card dropped on
   the parchment, covering the paper rather than printed on it. .plaque — the
   "Here for good" block sitting directly beside it in the same section, which
   he did NOT flag — has no fill at all and draws an interrupted hairline in
   --color-accent-pale instead. Two blocks, same row, two different materials.

   Now they are the same device: paper showing through, horizontals inset from
   the corners and verticals inset from the ends, so the frame reads as printed
   rules rather than as a box. And a grey rule on warm parchment reads as dirt —
   the menu sheet's note says so — so nothing here is --color-rule any more.

   The pseudo-elements are absolutely positioned, which is what keeps them out
   of this grid: an abspos child of a grid container is not a grid item, so the
   two-column layout is untouched. */
/* ⚠️ REBUILT 2026-08-17 AS A PINNED NOTE. Everything above this line describes
   the interrupted-hairline frame it used to draw, and that frame is gone from
   here — .plaque still carries the argument for it, now in Marcus's supplied
   engraving (see .framed). This block is a photograph of torn paper with a pin
   already in it, and the proof is laid on top of it.

   THE PAPER IS A STRETCHED BACKGROUND, `100% 100%`, WHICH IS THE ONE FIT THAT
   WORKS HERE. `cover` crops, and cropping a deckled edge cuts off the very
   thing the picture is for. `contain` letterboxes and would float the paper
   inside its own box.

   ⚠️ WHICH MEANS THIS CARD'S RATIO IS LOAD-BEARING AND `.here__inner` KNOWS IT.
   The file is 1200x885, i.e. 1.356. Left to its own height this box lands on
   589x434 at 1400 — 1.357 — so nothing is stretched at all and the pin stays
   round. Anything that forces the card taller distorts the picture in exactly
   the place a reader can see it; see the alignment note on .here__inner for the
   48% that cost when the card was matched to the plaque. The phone breakpoint
   drops the image entirely rather than stretch it, for the same reason.

   The alternative to all of this needed the pin cut off the paper and the paper
   inpainted underneath it, and there is no source for those pixels. See the
   note in tools/build-here-assets.py on why the supplied pin-on-its-own (6.png)
   is NOT used.

   ⚠️ THE PADDING IS MEASURED OFF THE FILE, NOT CHOSEN. The paper's own edge sits
   about 5% in from the left, 4.4% from the right, 7.1% from the top and 6.6%
   from the bottom of the image, and the pin's head occupies 9.8-13.5% across by
   4.4-12.9% down. Anything less than these values puts type on the deckle or
   under the pin. Re-measure before tightening them. */
.voice {
  position: relative;
  /* Was --space-2xl (72px) to clear the header stack that used to sit directly
     above it inside this column. The header spans the section now and closes
     with its own rule, so that margin became a second gap on top of the grid's
     row-gap. */
  margin: 0;
  background: url("assets/photos/note-paper.webp?v=20260818j") center / 100% 100% no-repeat;
  /* ⚠️ PERCENTAGES, NOT rem/vw CLAMPS, AND THE REASON IS THE 38rem CAP. What
     this padding has to clear is the deckle, and the deckle is a fixed FRACTION
     of the picture. A vw-based clamp tracks the viewport, and this card does
     not — between 641px and 1088px it is pinned at 38rem while the viewport
     keeps growing, so the padding kept shrinking against a card that had
     stopped. At 768 it came out at 17px against a 27px torn edge and the last
     line of the note was printed across the rip. Percentage padding resolves
     against the containing block's WIDTH, including the vertical values, which
     is exactly the box the deckle is a fraction of.

     ⚠️ AND THE NUMBER THAT MATTERS IS THE WORST ROW, NOT THE MEDIAN. Marcus,
     2026-08-17: "i do not like how little space there is between the image of
     maria and the edge of that card, it looks wrong." He was right and the first
     pass had measured the wrong statistic — the deckle's left inset is 4.9% at
     the MEDIAN but it wanders, and across the vertical band the photograph
     actually occupies it reaches 6.4%. Against a 7% padding that left four
     pixels of paper beside the picture at the tightest row. A torn edge is not a
     straight line and cannot be cleared by its average.

     These are the p100 insets over the photograph's own band plus ~4% of air:

         edge    median   worst    padding
         left      6.6%   10.3%     14%
         right     6.2%    9.8%     12.9%
         top       7.1%   10.6%      7%     (never binds — see below)
         bottom    6.8%    9.9%      7%     (never binds — see below)

     ⚠️ MEASURE THE PAPER, NOT THE FEATHER. The first attempt at this read 6.4%
     for the left edge and still left 3px of clearance at the tightest row,
     because it thresholded on brightness > 185 — which catches the soft feather
     and the shadow either side of the tear, not the sheet. Re-measured against
     the rendered page with a strict paper test (r>=242, g>=232, b>=218) the same
     edge reaches 10.3%. The strict number is the one a reader sees. The check is
     worth keeping: screenshot the card, walk in from the box's left edge row by
     row over the photograph's own band, and take the deepest.

     Top and bottom are deliberately under their worst case. The card is
     ratio-locked and its contents are centred, so the real gap above and below
     the writing is far larger than the padding and the deckle is nowhere near
     it. If `align-content` ever stops being `center`, these two need the same
     treatment the sides just got. Re-derive all of it from the file, not by eye,
     if the artwork is recut. */
  padding: 7% 12.9% 7% 14%;
  display: grid;
  /* 0.8fr -> 1fr. ⚠️ THE PHOTOGRAPH IS 480x480 AND THAT IS THE WHOLE FILE —
     the 574x1020 portrait it was cropped from is NOT in this repo. At 0.8fr it
     rendered 251px, i.e. ~1.9x, which is right for a 2x screen; 1fr takes it
     to ~273px (~1.75x) and that is about as far as it goes. Anything past
     ~300px is under 1.6x and starts to look soft on a phone. If a bigger
     portrait is ever wanted here, the ORIGINAL has to be re-supplied first. */
  /* TWO COLUMNS NOW — photograph beside the writing, which is what lets this
     card stand at the plaque's height.
     FRACTIONS, NOT `auto` PLUS A WIDTH CLAMP ON THE PICTURE. A viewport-based
     clamp on the photograph is measuring the wrong box: this card is ~48% of
     the shell at desktop and ~100% of it once the two stack, so the same vw
     value that fits at 1400 leaves a 174px stamp in a 608px card at 1024.
     Tracks scale with the card, which is the thing the photo has to look right
     inside. Blueprint proportion is 253 picture to 240 writing. */
  /* ⚠️ THE PHOTOGRAPH PAYS FOR THE MARGIN, and it has to be the one that pays.
     Clearing the deckle properly costs ~55px of measure a side, and the writing
     column cannot give it up: "across 187 Google reviews" wants 216px and had
     221, so a single step narrower breaks it mid-phrase the way the plaque's
     hours line broke. The picture has no such floor — it is capped from above by
     the source file, never from below — so the tracks tip from 1.14/1 to 0.95/1
     and the photograph comes in to ~212. That is smaller than the blueprint's
     253, and it is the right thing to trade: Marcus asked for a bigger sheet of
     paper and air around the picture, not a bigger picture. */
  grid-template-columns: minmax(0, 0.95fr) minmax(0, 1fr);
  gap: clamp(var(--space-xs), 1.2vw, var(--space-sm));
  /* ⚠️ `center`, NOT `start`, AND IT IS THE SQUARE PHOTOGRAPH'S FAULT. The
     blueprint's picture is portrait, 253x340, so it stands almost the full
     height of the writing beside it. Ours is square and capped, so it comes out
     around 230 against a 380-tall column — and pinned to the top of that
     column, the whole 150px difference pooled as one blank rectangle under the
     photograph, in the bottom-left corner of a piece of paper, which is exactly
     where the eye goes. Centred, the same surplus splits above and below and
     reads as margin. The real fix is a portrait crop and there is no file for
     one; see the note on .voice__shot. */
  align-items: center; align-content: center;
  text-align: left;
  /* ⚠️ THE FILE'S OWN RATIO, DECLARED. This is what lets the sheet be sized
     rather than merely filled, and it is the fix for both halves of Marcus's
     note on 2026-08-17 — bigger paper, less on it.

     Before this the box was purely content-driven: take text out and the paper
     shrank to match, so "remove some of the text" would have made the sheet
     SMALLER, which is the opposite of what was asked. With the ratio declared,
     height comes from width, so the paper keeps its true proportion at whatever
     size the track gives it and the freed space becomes margin instead of
     disappearing.

     It is a floor, not a cap: a grid container's automatic minimum size still
     wins, so if the writing ever outgrows the ratio the box grows and the
     picture stretches from there. That is the old failure mode, so if this card
     is given much more copy, widen the track before trusting the ratio. */
  aspect-ratio: 1200 / 885;
}
/* `maria-window.jpg` is a 9:16 phone portrait in a square slot, so cover keeps
   only ~56% of its height. 40% pulls the window up onto Maria and off the
   counter and card reader along the bottom — at plain `center` the tray takes
   the lower third of the frame. Re-check this if the photograph is replaced;
   the value is tied to where she stands in THIS one. */
/* The source is now pre-cropped SQUARE and tight on Maria (480x480 out of the
   supplied 574x1020 phone portrait), so there is nothing left for
   object-position to do — a second crop on top of the first only re-introduces
   the counter. Marcus asked for it closer on 2026-08-09; the zoom lives in the
   file, which also stops the browser throwing away two thirds of the pixels. */
.voice__shot {
  display: block; width: 100%;
  /* ⚠️ CAPPED AT 320px AND THE CAP IS ABOUT THE FILE, NOT THE LAYOUT. The
     source is 480x480 and it is the ONLY copy — the 574x1020 portrait it was
     cut from is not in this repo. 320 CSS px is a 1.33x upscale on a 2x
     screen, which is the most this file takes before a face goes soft. Let it
     fill the column and it renders ~373px and starts to mush. Re-supply the
     original before asking for a bigger one. */
  /* ⚠️ AND THE BLUEPRINT DRAWS IT PORTRAIT, 253x340, WHICH IS NOT AVAILABLE.
     Cropping this square to that shape means taking 355 of its 480 columns and
     throwing away a quarter of the width — and the width is where her arms are.
     The picture is Maria with her arms spread across the service window; a
     portrait crop of it is Maria with her elbows cut off. It stays square and
     the note beside it carries the extra height instead. This is the same
     constraint as the cap above, and the same answer: re-supply the 574x1020
     original and both stop being true. */
  max-width: 320px;
  aspect-ratio: 1 / 1; object-fit: cover;
}
.voice__say { margin: 0; width: 100%; }
.voice__stars { display: flex; gap: 0.2rem; margin: 0; }
.voice__stars svg { width: 1.2rem; height: 1.2rem; color: var(--color-accent); }
.voice__score {
  margin: var(--space-xs) 0 0;
  font-family: var(--font-body); font-weight: 500;
  /* Stepped up from --text-md. The 4.9 is the strongest and only independently
     checkable proof on this page and it was set smaller than the body copy
     around it; the taller panel is the room to let it carry. */
  font-size: var(--text-lg);
  line-height: 1.1; color: var(--color-ink);
}
.voice__score strong { font-weight: 400; font-size: 2.1em; }
/* ⚠️ --text-sm, NOT INHERITED. At body size this line wants 216px and the
   writing column only reaches that above ~1250px, so from 641 to 1250 it broke
   as "across 187 Google / reviews" — the same mid-phrase break the plaque's
   hours line had. Stepped down it wants ~178 and sets whole at every width the
   card exists at. It also reads better: the 4.9 is the claim and this is the
   footnote saying where it came from, and it was set at the same size as the
   body copy. */
.voice__from {
  margin: var(--space-2xs) 0 0;
  font-size: var(--text-sm); line-height: 1.5;
  color: var(--color-ink-2);
}
.voice__who {
  margin: var(--space-md) 0 0; padding-top: var(--space-sm);
  /* Accent, not --color-rule: a neutral grey hairline on warm parchment reads
     as dirt. Same reasoning as --sheet-rule on the menu. */
  border-top: var(--rule-hair) solid var(--color-accent-pale);
  font-size: var(--text-xs); font-weight: 800;
  letter-spacing: var(--track-caps); text-transform: uppercase;
  color: var(--color-accent);
}

/* ── The lines off the board ───────────────────────────────────────────────
   ⚠️ THESE ARE THE MENU CARD'S OWN NOTES AND THEY ARE NOT SET AS A QUOTATION.
   The blueprint draws this block with a large opening and closing quote mark
   under the heading "MARIA, AT THE WINDOW", which makes six lines of existing
   site copy into six sentences a named, real, living person is on record as
   having said. The words themselves are lifted verbatim from `.breadth__foot`
   on the menu card two sections up — they are the truck's voice, written for
   this site, and nobody has confirmed Maria speaks them.

   So the block ships with everything the blueprint gives it EXCEPT the quote
   glyphs: same six lines, same rule down the left, same obliqued display face
   the menu card sets those very lines in, which is what ties the two blocks
   together and is a better argument than punctuation was. `.voice__who` above
   goes on captioning the photograph, which is all it has ever claimed to do.

   TO RESTORE THE BLUEPRINT EXACTLY, once Marcus confirms she says them: wrap
   the <p> in a <blockquote> and add the two glyphs back as ::before/::after
   here. That is the whole change — the layout does not move. */
/* ⚠️ THREE PARAGRAPHS, NOT SIX <br>-SEPARATED LINES, AND THAT IS A ROBUSTNESS
   FIX RATHER THAN A STYLE ONE. The blueprint draws six short lines and the
   obvious build is six sentences with <br> between them. Measured, the longest
   of them — "Made the same as everything else." — wants 242px at --text-sm, and
   this column only reaches that above about 1350px: everywhere between there
   and the 68rem stacking breakpoint it broke, leaving the word "else." alone on
   a line, which reads as a bug rather than as a line break.

   These are three notes on the menu card, not six, and each is two sentences.
   Set as three paragraphs they wrap where they were always going to wrap, land
   on six lines at desktop anyway, and cannot produce an orphan. It is also
   closer to the source: this is exactly how `.breadth__foot` sets them. */
.voice__lines {
  margin: var(--space-md) 0 0;
  padding-left: var(--space-sm);
  border-left: var(--rule-hair) solid var(--color-accent-pale);
  /* The display face obliqued at 400, exactly as the menu card sets these same
     sentences. See the note on `.breadth__foot` for why the site does not load
     a third family for a margin hand. */
  font-family: var(--font-display); font-style: italic; font-weight: 400;
  font-size: var(--text-sm); line-height: 1.6;
  color: var(--color-ink-2);
}
.voice__lines p { margin: 0; }
.voice__lines p + p { margin-top: 0.55em; }

/* ── The plaque ────────────────────────────────────────────
   Two crossing pairs of hairlines instead of one border, which leaves each
   corner open the way a printed plaque's does. The mockup notches its corners;
   an open corner is the same gesture and needs no mask, so it survives print
   and forced-colours intact. */
/* ⚠️ THE INTERRUPTED HAIRLINES ARE GONE — 2026-08-17. The paragraph above is
   the argument for them and it is unchanged in substance: this is a printed
   plaque and the frame is the form. Marcus supplied the actual engraving on
   2026-08-17, so the device that stood in for it steps aside. `.framed` carries
   it now, on this and on the catering reminder, and the corners are cut into
   the artwork rather than left open by construction.

   The padding shrank with them: the old top value cleared a chapel drawing
   hung inside the box (see below), and the frame supplies its own margin. */
.plaque {
  position: relative;
  /* ⚠️ THE SIDES ARE 1rem AND THAT IS MEASURED, NOT TIDINESS. `.framed` draws a
     21px engraved border outside this padding, so the visual inset is ~37px a
     side; the old 2rem was sized against the 1px rule the frame replaced and
     was being paid twice.

     What forced it: the note beside this grew on 2026-08-17 and this column went
     to ~469px, and at 2rem the hours line — "Mon-Sat 10:30am - 7:30pm · Sun
     closed", which wants 340px — had 323 and broke, leaving the word "closed"
     alone on a line. That is one of the three facts somebody drove here to
     read. Dropping the sides buys 32px of measure and the line sets whole with
     slack, WITHOUT taking anything back off the paper.
     Re-measure the hours line before narrowing this column again; it is the
     longest unbreakable string in the box and SCHEDULE in script.js can change
     its length. */
  padding: var(--space-lg) var(--space-sm) var(--space-md);
  text-align: center;
}

/* ⚠️ #il-chapel IS NOT HUNG IN HERE ANY MORE, and this is a swap rather than a
   deletion. The blueprint puts a postal cancellation across the top-right
   corner, which is the same gesture — a mark bleeding off the corner of a
   printed thing — done in the object's own language: the plaque is addressed
   like a piece of mail and the stamp is what mail carries. Two corner marks in
   one box would be one too many, so the drawing came out as the stamp went in.

   #il-chapel is now a symbol with no <use> pointing at it on either page. That
   is deliberate rather than an oversight: it is a few hundred bytes of inline
   sprite, it is the only copy of that drawing, and leaving it means putting the
   chapel back is one <svg> tag rather than a redraw. If the sprite is ever
   audited for dead symbols, this is the one with a reason. */
.plaque__stamp,
.voice__stamp {
  position: absolute; margin: 0;
  pointer-events: none;
  /* ⚠️ A MASK, PAINTED WITH A TOKEN — same rule as every other flat mark on
     this site, and the master is the reason it matters here: its ink is
     rgb(48,96,176), which is neither accent nor accent-2, and a cancellation
     told what colour it is drifts off the palette the moment the palette moves.
     tools/build-here-assets.py throws the colour away and keeps the alpha. */
  background: var(--color-accent);
  -webkit-mask: url("assets/photos/postmark-mask.png?v=20260818j") no-repeat center / contain;
          mask: url("assets/photos/postmark-mask.png?v=20260818j") no-repeat center / contain;
}
/* Top-right, over the frame's corner, cancelling it the way a stamp cancels a
   corner it does not respect. It is BEHIND the type: .plaque__kicker and its
   two siblings are `position: relative` (see below) and so paint over it. */
.plaque__stamp {
  top: calc(var(--space-md) * -1); right: calc(var(--space-sm) * -1);
  width: clamp(6.5rem, 12vw, 10.5rem); aspect-ratio: 400 / 297;
  opacity: 0.38;
}
/* The second impression, on the note. Rotated and set fainter and larger than
   the plaque's so the pair reads as two strikes of one stamp rather than as the
   same file used twice — which is what it is, because only one cancellation was
   supplied and the blueprint draws two different ones. If a second stamp is
   ever cut, this is the rule that takes it. */
.voice__stamp {
  left: 2%; bottom: 3%;
  width: clamp(6rem, 11vw, 9.5rem); aspect-ratio: 400 / 297;
  opacity: 0.2;
  transform: rotate(-13deg);
}
/* Held clear of the crest above so the two never collide as the column narrows. */
.plaque__kicker,
.plaque__name,
.plaque__sub { position: relative; max-width: 24ch; margin-inline: auto; }
.plaque__kicker {
  margin-top: 0; margin-bottom: var(--space-xs);
  font-size: var(--text-xs); font-weight: 800;
  letter-spacing: var(--track-caps); text-transform: uppercase;
  color: var(--color-ink-2);
}
.plaque__name {
  font-family: var(--font-display);
  font-weight: var(--weight-display);
  font-size: var(--text-xl); line-height: 1.1;
}
.plaque__sub {
  max-width: none;
  margin: var(--space-3xs) auto 0;
  font-size: var(--text-xs); font-weight: 800;
  letter-spacing: var(--track-caps); text-transform: uppercase;
  color: var(--color-accent);
}
/* `.plaque__rule` — a hairline broken by #ic-column, drawn between the name and
   the facts — was deleted on 2026-08-17 along with its markup. The drawing was
   not: it is the mark on `.here__spine` now, standing between the two cards
   instead of inside one. The blueprint runs the address straight under the
   name, and with the engraved frame around the box a second internal rule was
   one horizontal too many. */
.plaque__facts {
  list-style: none; margin: var(--space-md) 0 0; padding: 0;
  display: grid; gap: var(--space-sm);
  text-align: left;
}
.plaque__facts li {
  display: grid; grid-template-columns: auto minmax(0, 1fr);
  gap: var(--space-sm); align-items: start;
}
.plaque__ic { width: 1.5rem; height: 1.5rem; flex: none; margin-top: 0.15em; color: var(--color-accent); }
/* Serif for the three things people read in order to act on them — the address
   they drive to, the hours they plan around, the number they dial. */
.plaque__facts address,
.plaque__facts b {
  display: block;
  font-style: normal; font-weight: 500;
  font-family: var(--font-body); font-size: var(--text-base);
  line-height: 1.45; color: var(--color-ink);
}
.plaque__tel {
  font-family: var(--font-body); font-weight: 600;
  font-size: var(--text-md);
  color: var(--color-accent); text-decoration: none;
  box-shadow: inset 0 -1px 0 var(--color-accent-pale);
  transition: box-shadow var(--dur-short) var(--ease-out);
}
.plaque__tel:hover { box-shadow: inset 0 -2px 0 var(--color-accent); }
.plaque__aside {
  display: block; margin-top: var(--space-3xs);
  font-size: var(--text-sm); line-height: 1.5; color: var(--color-muted);
}
.plaque__go {
  display: flex; width: 100%;
  margin-top: var(--space-md);
  gap: var(--space-2xs);
}
.plaque__go svg { width: 1.15rem; height: 1.15rem; flex: none; }
.plaque__foot {
  margin: var(--space-sm) 0 0;
  font-size: var(--text-sm); line-height: 1.5; color: var(--color-muted);
}

.here__olive {
  display: block;
  width: clamp(12rem, 22vw, 19rem); height: auto;
  margin: var(--space-md) calc(var(--page-gutter) * -0.6) 0 auto;
  color: var(--color-accent); opacity: 0.3;
}

/* ── The wall ────────────────────────────────────────────────────────────
   ONE background for the three things standing on it — "what keeps people
   coming back", "feeding a crowd?" and the row of links. See the note in
   index.html for why it is on a wrapper and not on each of them: a background
   per section is a background per section, and no amount of matching the
   images makes the seam between them disappear.

   ⚠️ STILL ONE PARCHMENT PER BAND. Marcus, 2026-08-14: "I do NOT want a new
   major background for every section." The page's rhythm is

       hero photo -> PARCHMENT (menu) -> navy tzatziki -> PARCHMENT (wall) -> navy footer

   and it is unchanged. On 2026-08-15 Marcus supplied a second parchment cut to
   four ratios and asked for it HERE specifically, so the two bands no longer
   load the identical file — but they are the same paper, the same palette and
   the same olive-branch motif, sampled within a few points of each other
   (241,232,217 on the sheet, 241,228,211 here, and the white wash below closes
   most of that). This is a variant, not a second major background. Do not read
   it as licence to give the tzatziki band or the footer artwork of their own.

   THE ARTWORK LIVES IN THE MARGINS AND THE MIDDLE IS DELIBERATELY EMPTY, which
   is what makes it usable behind copy and also means `cover` is the only
   correct fit. `contain` would letterbox it; `100% 100%` would stretch the
   olive branches. */

/* THE GROUND IS SHARED NOW — see `.paper, .sheet, .wall` further up.
   Four cuts of wall-*.webp and a ratio table used to stand here, picking a
   file by THIS BOX's shape: its height comes from the text inside it, so it
   stays ~1530-1630px tall while its width runs 390 -> 2560, and `cover` only
   scales by width while the image's ratio sits under the box's. Above that it
   scaled by height and ate the left and right margins — which is where the
   entire illustration lived. Two landscape pieces were lost that way.

   All of it is gone, and so is the fault it was managing. The branches are no
   longer inside a picture that has to fit; they are four cutouts pinned to the
   four corners, and the paper behind them tiles. Nothing here has a ratio to
   get wrong.

   It also fixes what Marcus actually complained about. These masters were run
   through `build-parchments.py:pad_with_own_paper()` to hit those target
   ratios, and padding a bordered sheet moves its border inward — which is why
   the branches sat ~13% in from the page edge instead of on it. The cutouts
   come from the unpadded source. */
/* Both sections are transparent now — the wall shows through them. */
.here--scene { background: none; }

/* ⚠️ NO WASH LAYER ON THE WALL, and putting one back breaks something two files
   away. There used to be a 16%-white gradient over the artwork, added to lift
   the old parchment.webp onto the menu sheet's tone. It is actively wrong now:
   the tzatziki band's bottom tear bites into the top of this section and is
   filled with flat --parchment, so a wash that lightens the wall by four points
   and does not lighten the token leaves the teeth showing as a darker fringe.
   One token, no washes, both bands match. Carried over from the block this
   replaced — the shared ground did not make it stop being true. */

/* Still hidden, and still for the original reason: a branch already lands in
   this corner. It used to come from the wall artwork's own right-hand margin;
   it is now the bottom-right cutout. Two olive illustrations stacked in one
   corner reads as a mistake, and the drawn one is the weaker of the two. */
.here--scene .here__olive { display: none; }

/* ⚠️ AND NOW IT IS HIDDEN ON find.html TOO — 2026-08-16, with the header moved
   out of the left column. This branch existed for ONE reason, stated in its own
   note above: "it fills the dead space the shorter column leaves". There is no
   shorter column any more. The plaque holds the WIDE track and the proof card
   the narrow one, and they are matched to the same height on purpose.

   Left in, it was actively harmful rather than merely redundant: in flow under
   the plaque it added ~160px to the side column, which the proof card then
   stretched to match — so a decorative branch was inflating the panel beside it
   into exactly the half-empty box this rebuild removed (find.html measured
   voice 831 vs plaque 669). Deleting the markup is the tidier fix; it is only
   hidden because the same block ships on two pages and the SVG symbol it uses
   is shared. */
.here__olive { display: none; }

/* ── Cross-page CTA ───────────────────────────────────── */
.cta { padding: var(--space-2xl) var(--page-gutter); background: var(--color-paper-2); }

/* ── "Feeding a crowd?" ─────────────────────────────────────────────────
   Marcus's blueprint, 2026-08-14: a ruled card standing on plaster.

   The artwork's own torn bottom edge was CROPPED OFF at import. The footer
   directly below already carries a torn top edge, and two rips stacked an inch
   apart read as a rendering fault rather than as paper. One tear, at the
   boundary, and it belongs to the footer. */
.cta--crowd {
  padding: clamp(var(--space-lg), 3.5vw, var(--space-2xl)) var(--page-gutter);
  background: none;
}

/* The card. Two rules rather than one: an outer border on the box and an inner
   one drawn 6px in by a pseudo-element. A single heavier line reads as a
   table cell; two thin ones read as something printed. */
.crowd {
  position: relative;
  /* 47rem -> 60rem. The card is two columns now, and at the old width the
     promise column had ~19rem to hold a heading plus two lines of prose —
     every one of the three wrapped to four lines and the "tighter card" this
     was set to be turned back into a tall one. */
  max-width: 60rem; margin: 0 auto;
  /* ⚠️ THIS IS A REMINDER, NOT A SECTION. Marcus, 2026-08-16, on the version
     that carried three promise rows in here: *"still crappy, lame — should
     simply be a small catering reminder, not too much space not too little,
     not too minimalist."*

     That version was the overcorrection. The block before it was empty (a lede
     and a tag row saying the same four occasions twice), so it got filled with
     the three promises off catering.html — which fixed the emptiness and
     turned a closing note into a 400px feature block competing with the
     "what keeps people coming back" section directly above it. THE PROMISES
     BELONG ON catering.html AND ARE STILL THERE; they were never needed here.

     What is left is one row: title, one line, one button. Do not grow it back.
     If this block ever needs more to say, that is a sign the catering PAGE
     needs the work, not this reminder. */
  /* Tighter than it was, because `.framed` now draws a 23px engraved border
     that is itself margin — the old padding was sized against a 1px rule. */
  padding: clamp(var(--space-sm), 2vw, var(--space-lg))
           clamp(var(--space-sm), 2.2vw, var(--space-lg));
  /* ⚠️ NOT centred any more, and that is deliberate rather than incidental.
     Marcus, 2026-08-16: the wall reads bland. Every block standing on it — the
     kicker, the title, the sub, the plaque, and this card — was centred on the
     same axis with the same pale-blue hairline, so there were six symmetrical
     things in a column and nothing for the eye to travel along. This is now
     the one block with a left axis. Re-centring it puts the flatness back. */
  /* `auto` on the button column, not a fraction: the button sizes to its own
     label and the sentence takes the rest. A 1fr/1fr split gave the button a
     half-card of empty paper to sit in, which is the "too much space" note. */
  /* FOUR COLUMNS as of 2026-08-17, and the two new ones are both ornament:
     Marcus's blueprint opens the row with an olive branch and closes it with
     the engraved plate, with the button held between them behind a hairline.
     The rule above still holds — this is one row, one line of copy, one button.
     It gained two drawings and a label, not a second idea. */
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto auto auto;
  gap: clamp(var(--space-sm), 2vw, var(--space-md));
  align-items: center;
  text-align: left;
  /* ⚠️ NO FILL. It was oklch(99% 0.004 92 / 0.62) — translucent on paper, on the
     theory that the plaster would still show through. It does not: 62% of near
     white over parchment is a pale slab, and Marcus, 2026-08-15: "this block
     does not match the rest of the feeling and vibe of the page."

     The page has exactly one card language and .plaque next door is it — rules
     drawn on the paper, nothing filled. This block keeps its double rule, which
     is the printed-frame device and was never the problem; only the fill goes.
     Do not put a tint back "so it reads as a card". It reads as a card because
     of the rules. */
  /* The hand-drawn double rule this used to carry is replaced by the supplied
     engraving — see `.framed`, which the markup now also carries. Same device,
     same reasoning, real corners. */
  background: none;
}
.crowd > * { position: relative; z-index: 1; }

/* The two drawings that open and close the row. Both are ornament and both are
   `aria-hidden` in the markup: the sentence beside them already says catering,
   and a screen reader does not need to be told twice in pictures. */
.crowd__leaf {
  width: clamp(2.6rem, 4.6vw, 4rem); height: auto;
  color: var(--color-accent); opacity: 0.5;
}
.crowd__plate {
  width: clamp(5.5rem, 9.5vw, 8.5rem); height: auto;
}
/* The hairline between the sentence and the button, which is what stops the
   four columns reading as a toolbar. Its own element rather than a border on
   the button, so it can be dropped at the stacking breakpoint without touching
   the button's own box. */
.crowd__split {
  align-self: stretch; width: var(--rule-hair);
  min-height: 2.5rem; margin: 0;
  background: var(--color-accent); opacity: 0.28;
}

/* ── The one outlined button on the site ───────────────────────────────────
   ⚠️ READ THE TOMBSTONE ON `.btn--outline` UP AT THE TOP OF THIS FILE BEFORE
   TOUCHING THIS. An outlined button was deleted on 2026-08-09 because Marcus
   called it out on sight as reading like generic AI, and the rule was removed
   rather than left unreferenced precisely so it could not quietly come back.

   This is not that rule coming back quietly. It is drawn this way in Marcus's
   own blueprint of 2026-08-17, and the two cases are different in the ways that
   made the first one fail: that one was a transparent tile with a 1px ring
   floating ON A PHOTOGRAPH and it was the only outlined object on the site.
   This one stands on parchment inside a supplied engraved frame, on a page
   that now carries three documented outline exceptions, and it is doing a job —
   the blueprint sets the plaque's "Get directions" SOLID and this one hollow
   because one is the primary action on the page and the other is a footnote
   pointing at another page. A second solid navy slab here would compete with
   it.

   DELIBERATELY SCOPED TO `.crowd`. There is no `.btn--outline` modifier and
   there should not be one — if a second block ever wants this, that is the
   moment to decide whether the site has a secondary button, not a reason to
   promote this by default. */
.crowd .btn {
  background: none;
  color: var(--color-accent-2);
  box-shadow: inset 0 0 0 var(--rule-hair) var(--color-accent);
  transition: background var(--dur-short) var(--ease-out),
              color var(--dur-short) var(--ease-out);
}
.crowd .btn:hover {
  background: var(--color-accent);
  color: var(--color-accent-ink);
}
.crowd .btn::after { content: " \203A"; }

/* The eyebrow, new 2026-08-17. It carries the words that used to be printed on
   the button — the button now says what it does ("Learn more") and the label
   says where it goes, which is the right way round and costs no height: it sits
   on a line the title's own leading was already paying for. */
.crowd__eyebrow {
  margin: 0 0 var(--space-3xs);
  font-size: var(--text-xs); font-weight: 800;
  letter-spacing: var(--track-caps); text-transform: uppercase;
  color: var(--color-accent);
}

/* ⚠️ THE INLINE SPRIG IS GONE — 2026-08-17, and `.crowd__sprig` with it. It was
   here because the block opened on its heading and one ornament had to ride
   beside it without costing a line; the blueprint opens the row with a branch
   in its own column instead (`.crowd__leaf`), so the heading is just a heading
   now and this is a plain block box rather than a flex row.

   ⚠️ AND THE CLASS IS DOUBLED, LIKE `.crowd__lede` AND `.crowd__act` BELOW IT.
   `.cta h2` further down this file is (0,1,1) and this rule was (0,1,0), so
   every font-size written here was silently losing to --text-xl — 31.2px at
   1400 against the 22.4 this asks for. That is the third rule in this block to
   fall into the same trap; if you add a fourth, double it before you measure
   anything. It was found by measuring the heading's ink (398px) against its
   column (277px) and getting a number that made no sense for the size written
   here. */
.crowd .crowd__title {
  margin: 0;
  font-family: var(--font-display); font-weight: var(--weight-display);
  /* A step down from the 2.25rem cap. This heading closes the page; at the old
     size it matched "What keeps people coming back" above it and read as a
     second major section rather than as a note under one.
     STEPPED DOWN AGAIN 2026-08-17. The row grew two ornament columns and a
     hairline, which took ~200px off the text column, and at the old size
     "We bring the gyros to you" broke over two lines inside a block whose one
     rule is that it is ONE ROW. */
  font-size: clamp(1.1rem, 1.75vw, 1.4rem); line-height: 1.15;
  color: var(--color-ink);
}
.crowd .crowd__lede {
  /* `auto` -> 0 on the inline margins: `margin-inline: auto` inside a
     left-aligned column re-centres the paragraph's BOX against the heading
     above it, which reads as a wobble in the left edge even though the text
     itself is flush left. */
  margin: var(--space-2xs) 0 0; max-width: 52ch;
  font-size: var(--text-base); line-height: 1.55; color: var(--color-ink-2);
}
/* Doubled class deliberately. `.cta p` further down is (0,1,1) and this was
   (0,1,0), so the button's margin was silently coming from `.cta p`'s
   --space-2xs rather than from the rule written here. Same trap `.crowd__lede`
   is doubled up to dodge. ZERO, not a gap: the button is a grid item in its
   own column now, so the column gap is the only spacing it needs and a top
   margin on top of that pushes it off the sentence's centre line. */
.crowd .crowd__act { margin: 0; }

/* Stack under ~46rem, where the sentence and the button stop sharing a row.
   `justify-items: start` keeps the button at its label width instead of
   stretching it across the card — a full-bleed button turns the reminder back
   into a section, which is the thing this block was cut down to stop being. */
@media (max-width: 46rem) {
  .crowd {
    grid-template-columns: minmax(0, 1fr);
    gap: var(--space-md);
    justify-items: start;
  }
  .crowd .crowd__lede { max-width: none; }
  /* THE TWO DRAWINGS AND THE HAIRLINE GO, they do not stack. Stacked they
     become three more rows in a block whose whole point is being one row, and
     the hairline in particular turns into a 40px vertical tick floating above
     the button with nothing on either side of it. The label, the line and the
     button are the block; the ornament was always the desktop row's. */
  .crowd__leaf,
  .crowd__plate,
  .crowd__split { display: none; }
}
/* On the menu page the board itself is already paper-2, so the closing band
   was dissolving into the bottom of the menu. Reversed to plaster, which is
   the same trick the panels use against the board. */
.board + .cta { background: var(--color-surface); }
.cta__inner {
  max-width: var(--shell); margin: 0 auto;
  display: grid; grid-template-columns: minmax(0, 1fr) auto;
  align-items: center; gap: var(--space-xl);
}
.cta h2 { font-size: var(--text-xl); max-width: 18ch; }
.cta p { margin: var(--space-2xs) 0 0; max-width: 48ch; color: var(--color-ink-2); }
.cta__act { display: flex; flex-wrap: wrap; gap: var(--space-sm); }

/* The closing strip, to the mockup: a column ornament at each end with a
   hairline running out toward the page edge, the line and the button between
   them. Scoped to the modifier — menu.html still uses the plain .cta. */
.cta--strip .cta__inner {
  grid-template-columns: minmax(0, 1fr) auto auto minmax(0, 1fr);
  gap: var(--space-lg);
  justify-items: center;
}
.cta--strip .cta__say { text-align: center; }
/* Title case, NOT the mockup's all-caps. A leaning display face set in caps
   turns into a shout — that is written into tokens.css and it is why this
   build stopped shouting. Inherits the display face from the h1,h2 rule. */
.cta--strip .cta__say h2 {
  font-size: var(--text-lg);
  line-height: 1.15; max-width: none;
}
.cta--strip .cta__say p {
  margin-top: var(--space-2xs); max-width: none;
  font-size: var(--text-xs); font-weight: 700;
  letter-spacing: var(--track-caps); text-transform: uppercase;
  color: var(--color-accent);
}
.cta__orn { display: flex; align-items: center; gap: var(--space-sm); width: 100%; }
.cta__orn::before {
  content: ""; flex: 1 1 auto; min-width: 1rem;
  height: var(--rule-hair); background: var(--color-accent); opacity: 0.4;
}
.cta__orn svg { width: 1.6rem; height: 1.6rem; flex: none; color: var(--color-accent); }
/* Mirrored so the hairline always runs toward the outside of the page. */
.cta__orn--end { flex-direction: row-reverse; }

/* ── Gallery ──────────────────────────────────────────────
   LIVE. Used by gallery.html, driven by gallery() in script.js — the three
   move together and are useless separately.

   A uniform crop grid, NOT masonry.
   Masonry was tried to preserve each photo's true aspect ratio, but eight
   mixed-ratio images balanced badly: one hung below the others and left a
   large dead area. A span grid was tried before that and left holes.
   Cropping here is acceptable precisely because the lightbox shows the full,
   uncropped image — the grid is an index, not the presentation. Eight items
   fill 4, 2 or 1 columns exactly, and 3 columns leaves a tidy short row. */
.gallery {
  max-width: var(--shell); margin: 0 auto;
  padding: var(--space-xl) var(--page-gutter) var(--space-3xl);
  display: grid;
  /* Fixed 3 across, not auto-fit: six photos then divide exactly at every
     breakpoint (3x2 / 2x3), so the last row is never short. */
  grid-template-columns: repeat(3, minmax(0, 1fr));
  /* Was --space-2xs, when the photographs butted almost edge to edge against a
     near-white page and read as one block of pixels. On parchment each is now a
     print with a paper border of its own, and prints need air between them or
     the borders merge into a grid of white lines. */
  gap: clamp(var(--space-xs), 1.8vw, var(--space-lg));
}
/* ── Each photograph as a print ────────────────────────────────────────────
   The site's one idiom for a photograph laid on paper — warm paper border
   weighted at the foot, two-term shadow — carried over from the hero's pinned
   prints, the home page's taped print and the menu head's three. Before this
   the gallery was the only place a photograph sat directly on the ground, and
   once the ground became parchment that showed immediately.

   NOT ROTATED, where the other three are. One print at a jaunty angle is a
   photograph somebody put down; six of them in a grid is a scrapbook, and the
   grid exists here precisely because this page is an index you scan. The
   border and the shadow carry the idiom on their own. */
.shot {
  position: relative; display: block; width: 100%;
  overflow: hidden; background: var(--photo-paper);
  border: 0; cursor: pointer;
  padding: 0.5rem 0.5rem 1.15rem;
  box-shadow:
    0 0.6rem 1.2rem oklch(0% 0 0 / 0.14),
    0 0.08rem 0.25rem oklch(0% 0 0 / 0.10);
  transition: box-shadow var(--dur-short) var(--ease-out);
}
/* Lifting the print off the page on hover is the one motion that suits the
   object — a deeper shadow, not a translate. (See the banned-patterns list at
   the top of this file: hover lifts that move the element are out.) */
.shot:hover, .shot:focus-visible {
  box-shadow:
    0 1rem 2rem oklch(0% 0 0 / 0.20),
    0 0.1rem 0.3rem oklch(0% 0 0 / 0.12);
}
/* SQUARE, not the 4:3 this used to be. Five of the six photographs Marcus
   supplied are 765x1020 portrait, and a 4:3 cell takes 574px of their 1020 —
   it was cutting the food in half to keep the table. A square cell crops 25%
   off a portrait and about 44% off the sides of the one landscape frame, which
   is the least bad split available and still shows every dish whole. The
   lightbox has the uncropped file either way; the grid is an index. */
.shot img {
  width: 100%; height: 100%;
  aspect-ratio: 1 / 1; object-fit: cover;
  /* ── Where the square crop is taken from ──
     The grid crops every photograph square while the lightbox shows it whole,
     so a frame that is well composed at 4:3 can still land badly at 1:1. Six
     of the eight are centred subjects and take the default.

     A CUSTOM PROPERTY WITH A DEFAULT, not a per-photo `object-position` rule.
     The next photograph that needs re-centring sets one variable on its own
     `.shot` instead of adding another selector to this file — and the six that
     do not need it never mention crops at all. */
  object-position: var(--shot-focus, 50% 50%);
  transition: opacity var(--dur-short) var(--ease-out);
}
/* ⚠️ IT ONLY MOVES THE AXIS THAT IS BEING CROPPED, which is the whole reason
   two of the eight are not on this list. `cover` on a square trims the LONG
   edge only: a portrait's crop slides up and down, a landscape's slides left
   and right, and there is no third option. A landscape frame whose subject
   sits too HIGH cannot be fixed here at any value — it needs a cut box, and
   that is what tools/build-gallery-squares.py is for. Read the note at the
   top of that script before adding a photo to either mechanism.

   The one entry below was measured by rendering the actual crop at five
   positions and looking, not estimated. The platter is portrait and its
   clamshell lid takes the top third, so a centred crop spends a third of the
   tile on an empty lid and cuts the food off at the bottom; 45% pulls the
   window up until the container reads as a whole object with its bottom rim
   inside the frame. */
.shot--focus-platter { --shot-focus: 50% 45%; }
.shot:hover img, .shot:focus-visible img { opacity: 0.86; }
/* Inset to the print's own padding, so the caption's scrim stops at the edge
   of the PHOTOGRAPH rather than running out over the paper border. */
.shot__cap {
  position: absolute; left: 0.5rem; right: 0.5rem; bottom: 1.15rem;
  padding: var(--space-lg) var(--space-sm) var(--space-sm);
  font-size: var(--text-sm); color: var(--color-onphoto); text-align: left;
  background: linear-gradient(to top, var(--color-scrim), transparent);
  opacity: 0; transition: opacity var(--dur-short) var(--ease-out);
}
.shot:hover .shot__cap, .shot:focus-visible .shot__cap { opacity: 1; }

/* ── Lightbox ─────────────────────────────────────────────────────────────
   A POPUP, NOT A TAKEOVER. Rebuilt 2026-08-17 — *"it should not take up the
   entire screen … it should only appear slightly larger than how it appeared
   without clicking it."* It was `inset: 0` filled opaque navy, so the page
   vanished the moment you tapped a photograph.

   `.lightbox` is now only the scrim and `.lightbox__panel` is the viewer. */
body.is-locked { overflow: hidden; }
.lightbox {
  position: fixed; inset: 0; z-index: var(--z-skip);
  display: grid; place-items: center;
  padding: var(--space-md);
  /* Translucent, so the gallery stays visible behind and the panel reads as
     lifted off the page rather than as a different screen. */
  background: oklch(19% 0.055 248 / 0.74);
}
.lightbox[hidden] { display: none; }

/* The panel is the site's print idiom at viewing size — the same warm paper
   the .shot tiles sit on, so the photograph looks picked up off the page
   rather than loaded into a viewer. */
.lightbox__panel {
  display: grid; grid-template-rows: auto auto auto;
  gap: var(--space-2xs);
  width: max-content; max-width: 100%;
  padding: var(--space-xs) var(--space-sm) var(--space-sm);
  background-color: var(--parchment);
  background-image: url("assets/photos/ground-tile-900.webp?v=20260818a");
  background-position: center top;
  background-repeat: repeat-y;
  background-size: 100% auto;
  border: var(--rule-thin) solid var(--color-accent);
}
.lightbox__bar {
  display: flex; align-items: center; justify-content: space-between;
  gap: var(--space-sm);
  color: var(--color-muted);
  font-size: var(--text-sm); letter-spacing: 0.08em;
}
.lightbox__stage { display: grid; place-items: center; min-height: 0; }
/* ── HOW BIG "SLIGHTLY LARGER" IS ──
   The grid tile is 328px at a 1280 viewport. 30rem is 480px, so the photograph
   comes up about half again as big — enough to read the food, not enough to
   become the screen. Both dimensions are capped because the set is a mix of
   portrait and landscape and only capping the width would let a 3:4 run down
   past the fold.

   The vh/vw halves of the min() are the small-screen escape: on a phone 30rem
   is wider than the viewport, and 78vh leaves room for the bar and the
   caption under it. */
.lightbox__stage img {
  max-width: min(30rem, 82vw);
  max-height: min(30rem, 62vh);
  width: auto; height: auto; object-fit: contain;
}
.lightbox__foot {
  display: flex; align-items: center; justify-content: center;
  gap: var(--space-sm); min-height: 2.5rem;
  max-width: min(30rem, 82vw);
  margin-inline: auto;
  color: var(--color-ink-2);
  font-family: var(--font-body); font-size: var(--text-sm); text-align: center;
  text-wrap: balance;
}
/* On a phone the caption has ~150px between two 44px buttons and wraps to four
   lines, which makes the panel taller than the photograph in it. Below 30rem
   it takes its own row above them instead — `order: -1` rather than reordering
   the markup, because Prev/Next belong either side of the caption for anyone
   reading the DOM or tabbing through. */
@media (max-width: 30rem) {
  .lightbox__foot { flex-wrap: wrap; row-gap: var(--space-2xs); }
  .lightbox__foot [data-lightbox-cap] { order: -1; flex: 1 0 100%; }
}
.lightbox__btn {
  appearance: none; border: 0;
  background: var(--color-accent); color: var(--color-accent-ink);
  font: inherit; font-family: var(--font-body);
  font-size: var(--text-xs); font-weight: 700;
  letter-spacing: var(--track-caps); text-transform: uppercase;
  padding: var(--space-2xs) var(--space-xs);
  /* flex: none or the two buttons give up width to a long caption and the
     labels wrap to "PREV" over an arrow. */
  flex: none;
  min-height: 44px; cursor: pointer;
  transition: background var(--dur-short) var(--ease-out);
}
.lightbox__btn:hover { background: var(--color-accent-2); }
.lightbox__btn:focus-visible { outline: 2px solid var(--color-focus); outline-offset: 3px; }

/* ── Reviews ──────────────────────────────────────────────
   4.9 from 187 is the strongest asset this business has. It gets the left
   half of the band at full display size rather than being a line of body
   copy — and it fills a band that was previously half empty. */
.praise {
  display: grid; grid-template-columns: auto minmax(0, 1fr);
  gap: var(--space-3xl); align-items: center;
}
.praise__score { text-align: center; }
.praise__say { max-width: 46ch; }
.praise__say h2 { margin-bottom: var(--space-md); }
/* Serif figures — script numerals are hard to scan at a glance. */
.rating__score {
  /* Body face at 800. A display figure this large reads as a price on a sign,
     and this is a rating people are meant to trust. */
  font-family: var(--font-body); font-weight: 800;
  font-size: clamp(4rem, 9vw, 7rem); line-height: 0.86;
  color: var(--color-onphoto); margin: 0;
}
.rating__stars {
  color: var(--color-accent-pale); font-size: var(--text-lg);
  letter-spacing: 0.18em; margin: var(--space-2xs) 0 var(--space-3xs);
}
.rating__count {
  font-size: var(--text-xs); font-weight: 700;
  letter-spacing: var(--track-caps); text-transform: uppercase;
  color: var(--color-onphoto-2); margin: 0;
}

/* ── Footer ───────────────────────────────────────────── */
/* ── The footer ──────────────────────────────────────────────────────────
   Marcus's artwork, 2026-08-14, split into two layers on import.

   ⚠️ WHY TWO FILES AND NOT THE ONE IMAGE HE SUPPLIED. The original has a torn
   paper edge along its top AND a line-drawn chapel in its bottom-right corner,
   and a single background can only anchor to one of them. `top` keeps the tear
   and crops the chapel off; `bottom` keeps the chapel and eats the tear;
   `cover` picks one depending on how tall the footer happens to render, which
   is the worst of the three because it changes with the content.

   Split, each layer anchors to the edge it belongs to and both survive at any
   footer height. tools/ has no importer for this — see assets/photos/README.

   The tear is transparent above the rip, not cream, so whatever section
   precedes the footer shows through it. Hard-coding cream there would have
   broken the moment the footer followed anything but plaster. */
.foot {
  position: relative;
  color: var(--color-onphoto-2);
  /* ⚠️ THE FLOOR PADDING IS THE COASTLINE'S ROOM, NOT WHITESPACE. The drawing
     below is pinned flush to the bottom and its drawn band is 11.9vw deep at
     100% width; this clears it so the legal line and the Hatwebs credit stand
     above the horizon rather than on it. NOT capped: the band is a fraction of
     the viewport width, so a ceiling on the padding is a width above which the
     coastline starts creeping back into the address. 13rem was tried and it
     binds at 1920, which is not a hypothetical screen. Cut this and the address
     goes back to having a coastline drawn through it. */
  padding: var(--space-lg) 0 calc(11.9vw + var(--space-sm));
  background-color: var(--foot-navy);
  /* Marcus's coastline, supplied 2026-08-15, replacing the 560px palm-and-
     church vignette that stood in the bottom-right corner (retired to
     _retired/photos/foot-scene.webp). This one is a 3:1 panorama drawn to run
     the footer's whole floor, so it is sized by WIDTH and pinned to the bottom
     — the top of the piece is empty sky and is the part that gets cut.

     100% wide, height auto: 480px tall at 1440 in a 530px footer, so it clears
     the top of the section; 640px at 1920, where the extra is sky; and 130px on
     a 390px phone, where it reads as a horizon line along the floor. No
     min-width fight, no ratio ladder — unlike the two parchments this artwork
     is content to be cropped from the top, which is why one file does.

     ⚠️ THE FILE'S OWN GROUND IS NEVER USED, WHATEVER IT IS. The version that
     shipped arrives already composited on rgb(3,23,53) — a navy a shade off
     --foot-navy, which painted as-is would seam against the footer exactly the
     way the torn edge above it did an hour earlier. tools/build-foot-scene.py
     measures the field, un-blends it out and ships line on alpha, so the navy
     here is always the token and the two cannot disagree. It also carries the
     gain: un-blended, this drawing's median coverage is 0.09, and 0.09 is what
     "the footer is still looking too faint" looks like. Read that script before
     pointing it at a fourth version.

     ⚠️ FLUSH TO THE FLOOR, WITH THE FOOTER'S PADDING HOLDING THE TYPE OFF IT.
     The drawing keeps its whole subject — village, horizon, sailboat, ridge,
     palm — in the bottom third of the file, so pinned flush that band occupies
     the bottom 11.9vw of this section (tools/build-foot-scene.py prints the
     figure). The floor padding below is that plus air, and the legal block
     stands on top of the coastline rather than in it.

     IT WAS LIFTED 6.5rem FOR ABOUT AN HOUR AND THAT WAS WRONG. A lifted
     coastline floats: there is a strip of bare navy under the waterline and the
     drawing does not belong to the bottom of the page. Marcus said so twice —
     "not sitting correctly on the bottom", then "footer image is messed up."
     Fading it out under the type is also spent: the ramp deep enough to protect
     the address swallows the only third of the picture with anything in it,
     which is how "still looking too faint" happened. Pin it, and move the
     type. */
  background-image: url("assets/photos/foot-scene-wide.webp");
  background-position: center bottom;
  background-size: 100% auto;
  background-repeat: no-repeat;
}

/* At 100% the panorama is only 130px tall in a 390px column and its drawn band
   is 46px of that — a hairline. 200% makes it 260px with a 93px band, enough to
   read as a coastline, cropped left and right at the cost of the village and
   the palm. Those are the parts a 390px column has no room for anyway. The
   floor padding doubles with it, because the band does. */
@media (max-width: 48rem) {
  .foot {
    background-size: 200% auto;
    padding-bottom: calc(23.8vw + var(--space-sm));
  }
}

/* ⚠️ THE TEAR IS A ::before ABOVE THE FOOTER, NOT A BACKGROUND LAYER INSIDE IT,
   and that is the whole reason it works.

   As a background layer it was defeated by its own element: background-color
   paints the full box, so the transparent half of the strip showed navy rather
   than the section above, and the result was a straight navy edge at the top of
   the footer with a ragged line stranded 45px below it — two edges where the
   design has one.

   Sitting outside the box with bottom:100%, the strip's opaque navy half meets
   the footer's navy exactly, and its ragged transparent half lets whatever is
   above show through. One torn edge, at the boundary, over any background.

   ⚠️ A MASK, NOT A PICTURE, AND IT MUST STAY ONE. This loaded foot-tear.webp
   directly until 2026-08-15, i.e. it was a photograph OF a torn navy edge, and
   Marcus called the result: "I do not like the blue discoloration." Two causes,
   one cure. The navy painted into that file is oklch(24.3% 0.073 258) while
   --foot-navy under it is 21.5% — three points of lightness, meeting edge to
   edge across the full width of the page — and lossy WebP had additionally
   speckled the flat field up to rgb(141,149,177) with a paler halo on every
   tooth. Painting the token through the shape's alpha kills both at once and
   makes the drift structurally impossible: the tear is no longer told what
   colour it is. tools/build-tear-mask.py cuts the mask; foot-tear.webp stays in
   assets/photos as the only copy of the drawn edge. */
.foot::before {
  content: ""; position: absolute; z-index: 1;
  left: 0; right: 0; bottom: 100%;
  height: clamp(1.5rem, 4.6vw, 4.4rem);
  background: var(--foot-navy);
  /* `auto 100%` + repeat-x, NOT the `100% 100%` this used to stretch with —
     same lesson as .tear. Squeezing 1600px of chips into a 390px phone
     combed them into vertical teeth; scaled by height only, the grain keeps its
     real size at every width. The source is flat at both ends so the repeat
     joins invisibly, and at desktop widths it barely gets to repeat at all. */
  -webkit-mask: url("assets/photos/foot-tear-mask.png") repeat-x center bottom / auto 100%;
          mask: url("assets/photos/foot-tear-mask.png") repeat-x center bottom / auto 100%;
  pointer-events: none;
}

/* THE FOURTH TRACK IS GONE, AND ITS REASON WENT WITH IT.
   There used to be an empty fourth grid column here, plus a matching
   padding-right on the legal line, to hold the footer's three real columns
   clear of a 560px palm-and-church drawing that stood against the right edge —
   on a 1512 laptop the two overlapped by about 250px, right where the Pages
   links land. The 2026-08-15 coastline is full-width and pinned to the floor,
   so there is no corner to keep clear any more: reserving a quarter of the row
   for it would only squeeze the three columns for nothing. If a corner
   vignette ever comes back, this is the block to restore. */
/* NO BAND HERE. There was one — a pale "closing bookend" to the masthead's
   opening band — and Marcus cut it on 2026-08-09, right after cutting the
   second masthead run for the same reason. Which is what the note at the top
   of this file prescribed all along: deep navy meeting plaster is already
   maximum contrast, and the footer needs no ornament to separate it. The
   meander is now a masthead device and nothing else. Do not restore it. */
.foot__top {
  max-width: var(--shell); margin: 0 auto var(--space-xl);
  padding: var(--space-2xl) var(--page-gutter) 0;
  display: grid; grid-template-columns: 1.4fr 1fr 1fr; gap: var(--space-xl); align-items: start;
}
.foot__brand {
  /* The brand name set as the brand. */
  font-family: var(--font-display);
  font-weight: var(--weight-display);
  font-size: var(--text-lg);
  color: var(--color-onphoto); text-decoration: none; line-height: 1.1;
}
.foot__blurb {
  margin: var(--space-sm) 0 var(--space-md);
  max-width: 34ch; font-size: var(--text-sm); line-height: 1.65;
}
.foot__head {
  font-family: var(--font-body); font-weight: 700;
  font-size: var(--text-xs); letter-spacing: var(--track-caps); text-transform: uppercase;
  color: var(--color-accent-pale);
  margin: 0 0 var(--space-sm);
  padding-bottom: var(--space-2xs);
  border-bottom: var(--rule-hair) solid oklch(100% 0 0 / 0.22);
}
/* ── "Try one of these instead" · 404 ──────────────────────────────────────
   A page-ground version of what `.foot__list` does on navy. See the note in
   404.html for why it exists: that class was being used here and painted every
   link near-white on near-white.

   `width: fit-content` + auto margins centres the BLOCK under the centred head
   while leaving each line set left, which is what a list of seven unequal
   descriptions needs. Centring the text itself puts the links on a ragged
   diagonal and there is nothing to scan down. */
.gonext {
  list-style: none;
  width: fit-content;
  max-width: 100%;
  margin: 0 auto;
  padding: 0;
  font-size: var(--text-base);
  line-height: 1.9;
  color: var(--color-ink-2);
}
/* The site's one link treatment — a rule under the word that thickens — rather
   than an underline that is always at full weight. Same object as .tlink. */
.gonext a {
  color: var(--color-accent);
  font-weight: 700;
  text-decoration: none;
  box-shadow: inset 0 -1px 0 var(--color-accent);
  transition: box-shadow var(--dur-short) var(--ease-out);
}
.gonext a:hover,
.gonext a:focus-visible { box-shadow: inset 0 -2px 0 currentColor; }

/* ── The seven marks ──
   ⚠️ ABSOLUTE, AND NEITHER FLEX NOR GRID. Both were tried on 2026-08-17 and
   both are wrong here, for two different reasons:

   · GRID (`1.5rem minmax(0,1fr)`) destroyed the list. `.gonext` above is
     `width: fit-content`, which resolves against the grid's MIN-content — one
     word — so every line broke after every word and the page rendered as a
     column of single words. It looked like a layout bug because it was one.
   · FLEX splits the line. Each `li` is an `<a>` followed by a bare text node,
     so flex promotes that text to its own anonymous item and the link and its
     description become two boxes that wrap independently.

   Taking the mark out of flow leaves the text exactly as it was — one inline
   run, sized by its own content, so `fit-content` still measures the longest
   line and nothing about the list's shape changes.

   `top` in em, not px: it tracks the first line's cap-height when the type
   scales at the breakpoints. These lines wrap to two on a phone and the mark
   has to stay on the first one, not float to the middle of the sentence. */
.gonext li { position: relative; padding-left: 2.15rem; }
.gonext__mark {
  position: absolute; left: 0; top: 0.42em;
  /* Width only. ic-truck is a 32x24 viewBox against six 24x24s — fixing the
     WIDTH is what lands all seven labels on one edge; fixing the height would
     make the truck the odd one out by scaling it down to match. */
  width: 1.5rem; height: auto;
  color: var(--color-accent); opacity: 0.55;
}

.foot__list { list-style: none; margin: 0; padding: 0; font-size: var(--text-sm); }
.foot__list li { margin-bottom: var(--space-2xs); line-height: 1.5; }
.foot__list a, .foot__tel {
  color: var(--color-onphoto); text-decoration: none;
  transition: color var(--dur-short) var(--ease-out);
}
.foot__list a:hover, .foot__tel:hover { color: var(--color-accent-pale); }
.foot address { font-style: normal; font-size: var(--text-sm); line-height: 1.65; margin-bottom: var(--space-sm); }
.foot__hours { font-size: var(--text-sm); line-height: 1.75; }
.foot__hours strong { color: var(--color-onphoto); font-weight: 700; }
.foot__legal {
  max-width: var(--shell); margin: 0 auto;
  padding: var(--space-md) var(--page-gutter) 0;
  border-top: var(--rule-hair) solid oklch(100% 0 0 / 0.18);
  font-size: var(--text-sm); line-height: 1.9;
  color: oklch(74% 0.014 232);
}
.foot__legal p { margin: 0; }
.foot__name { font-weight: 600; color: var(--color-onphoto); }
.foot__sep { padding: 0 var(--space-2xs); color: oklch(100% 0 0 / 0.3); }

/* ── Agency credit ────────────────────────────────────────
   Marcus's byline. It is a credit, not an advert: it sits below the legal
   line, at the smallest size on the site, at 70% — and only comes up to full
   on hover. If it ever competes with the client's own name in that footer it
   has been made too loud. */
.foot__by {
  max-width: var(--shell); margin: 0 auto;
  padding: var(--space-sm) var(--page-gutter) 0;
  text-align: center;
}
.byline {
  display: inline-flex; align-items: center; gap: 0.55em;
  /* Padding is for the finger, not the look — the label alone is a 16px-tall
     target, well under the 24px minimum. */
  padding: var(--space-2xs) var(--space-2xs);
  font-size: var(--text-xs); font-weight: 600; letter-spacing: 0.05em;
  color: var(--color-onphoto-2); text-decoration: none;
  opacity: 0.7;
  transition: opacity var(--dur-short) var(--ease-out);
}
/* The fedora alone. The full lockup carries a "DIGITAL PARTNER" wordmark that
   is illegible under ~60px, and this runs at 18. Height-driven so the mark
   scales with the type rather than to a pixel count. */
.byline__mark { display: block; width: auto; height: 1.15rem; }
.byline:hover { opacity: 1; }
.byline:focus-visible { outline: 2px solid var(--color-focus); outline-offset: 3px; opacity: 1; }

/* ── Sticky call bar · narrow viewports ───────────────── */
.callbar {
  position: fixed; z-index: var(--z-bar); inset: auto 0 0 0;
  display: none; align-items: center; justify-content: space-between;
  gap: var(--space-sm);
  padding: var(--space-2xs) var(--space-sm);
  padding-bottom: max(var(--space-2xs), env(safe-area-inset-bottom));
  background: var(--color-paper);
  border-top: var(--rule-thin) solid var(--color-accent);
  transform: translateY(100%);
  transition: transform var(--dur-short) var(--ease-out);
}
.callbar.is-up { transform: translateY(0); }
.callbar__note { font-size: var(--text-sm); font-weight: 600; color: var(--color-ink-2); line-height: 1.3; }
.callbar__btn {
  flex: none; white-space: nowrap;
  min-height: 46px; display: inline-flex; align-items: center;
  padding: 0 var(--space-md);
  background: var(--color-accent); color: var(--color-accent-ink);
  font-size: var(--text-sm); font-weight: 700;
  letter-spacing: var(--track-caps); text-transform: uppercase;
  text-decoration: none;
  transition: background var(--dur-short) var(--ease-out);
}
.callbar__btn:hover { background: var(--color-accent-2); }


/* ═══════════════════════════════════════════════════════════════════════
   ORNAMENT
   Craft, not decoration. Everything here is either a material quality
   (plaster grain, glaze lattice) or classical menu typography (leader dots,
   double rule, drop cap). Nothing is a motif applied for its own sake —
   that is the line between "ceramic" and "souvenir".
   ═══════════════════════════════════════════════════════════════════════ */

/* ── Plaster grain ────────────────────────────────────────
   A fixed 3% noise film over the whole page. Two jobs: it gives the flat
   grounds the tooth of limewashed plaster, and it lays a common grain over
   photographs of very different quality, which pulls the scraped listing
   images and the real phone shots into one surface. */
body::after {
  content: "";
  position: fixed; inset: 0; z-index: var(--z-overlay);
  pointer-events: none;
  opacity: 0.02;   /* pulled back — noise reads stronger on the dark bands */
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='140' height='140'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='140' height='140' filter='url(%23n)'/%3E%3C/svg%3E");
}


/* ── Leader dots ──────────────────────────────────────────
   The oldest ornament in menu setting, and a functional one — it carries the
   eye from a dish to its price across a wide column. The dots grow to fill
   whatever gap is left, so they work at every width. */
.list__item {
  display: flex; align-items: baseline;
  gap: 0 0.45em;
  /* basis 0, NOT auto. A flex line breaks on the items' base sizes, so with
     `auto` the browser compares the dish's full natural width against the
     column and drops the price to a line of its own — never giving min-width:0
     the chance to shrink it. At 0 the pair always shares one line and the name
     wraps to a second line instead, which is what a printed menu does. Grows to
     fill either way, so nothing changes on a wide column. */
  flex: 1 1 0;
}
.list__item::after {
  content: "";
  flex: 1 1 auto;
  min-width: 1.25em;
  align-self: baseline;
  border-bottom: 1px dotted var(--color-rule);
  transform: translateY(-0.3em);
}
/* No leader where there is no price to lead to. */
.list li:not(:has(.list__price)) .list__item::after { display: none; }

/* ── Double rule ──────────────────────────────────────────
   Two lines under a section name, the way a printed menu or a tile border
   band is set. A single 2px rule read like a UI divider. */
.grp__title {
  border-bottom: 4px double var(--color-accent);
  padding-bottom: var(--space-xs);
}

/* ── Drop cap ─────────────────────────────────────────────
   On the story only. */

/* ── Lozenge, for the footer's inline separators ────────── */
.foot__sep { font-size: 0.7em; vertical-align: 0.15em; }

/* ── Responsive ───────────────────────────────────────── */

/* Breakpoints here are measured, not guessed. The binding row is "Spinach Pie
   Platter  VEG ⋯ $16", which needs 280px of column measure before the price
   drops to a line of its own and reads as a stray $16. With a 64px gutter that
   puts three columns at 1052px and two at 678px; both are rounded up for slack.
   The 68rem block comes BEFORE the 44rem one so the single stack still wins. */
@media (max-width: 68rem) {
  .breadth { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  /* The spit banners across the top — it is the thing she actually sells, so it
     leads rather than sharing a row. The rule belongs on ITS bottom edge, not on
     the tops of the two columns below: as two separate border-tops the line came
     out broken in the middle by the column gutter. */
  .breadth__col:nth-child(1) {
    grid-column: 1 / -1;
    padding-bottom: var(--space-xl);
    border-bottom: var(--rule-hair) solid var(--color-rule);
  }
  /* Its four rows run two-up on the same grid as the columns beneath. Left full
     width they dragged a leader nearly 900px from the dish to its price, which
     stops reading as a menu row; this also lines the halves up with the two
     columns below them. */
  .breadth__col:nth-child(1) .list {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    column-gap: calc(var(--space-lg) * 2);
  }
  .breadth__col:nth-child(2) { border-left: 0; margin-left: 0; padding-left: 0; }
  .breadth__col:nth-child(2),
  .breadth__col:nth-child(3) { padding-top: var(--space-xl); }

  /* Same measured logic as the columns above: "Indian Harbour Beach, FL 32937"
     set in the body face wants ~255px, so the plaque needs ~360px of content,
     and the
     1/2.5 track stops supplying that at about 1060px. Stacking one step early
     is cheaper than a three-line address. */
  .here__inner { grid-template-columns: minmax(0, 1fr); gap: var(--space-xl); }
  /* The spine has nothing left to stand between once the cards stack, and a
     vertical hairline in a vertical stack reads as a stray mark. */
  .here__spine { display: none; }
  /* Full bleed leaves both cards sparse — a 1180px-wide plaque holding three
     short rows, and a quote card whose text ends halfway. Matched widths so the
     two stacked cards read as a pair. */
  /* ⚠️ `width: 100%` is required, not tidying. `margin-inline: auto` on a grid
     item switches it from stretch to shrink-to-fit, and once the photo and the
     rating inside are both capped at 320 this card shrank to 368 while the
     plaque below it still filled 460 — two stacked cards at two different
     widths, which is the opposite of the "read as a pair" this rule exists for. */
  /* ⚠️ THE CAP IS ON THE WRAPPERS, NOT ON THE CARDS, AND MOVING IT BACK BREAKS
     THE NOTE'S PADDING. `.voice` pads itself in PERCENTAGES so the padding
     tracks the deckle (see the note there), and percentage padding resolves
     against the CONTAINING BLOCK's width — not the element's own. With the cap
     on `.voice`, the card was 608 while `.here__say` around it was the full 944,
     so 14% resolved to 132px instead of 85 and the two columns inside collapsed:
     at 1024 the photograph came out 157px and "across 187 Google reviews" broke
     in half. Capping the wrapper instead makes containing block and card the
     same box at every width, which is the condition those percentages were
     written against.
     `.here__side` is capped alongside it purely so the pair still matches. */
  .here__say, .here__side { width: 100%; max-width: 38rem; margin-inline: auto; }
  .voice, .plaque { width: 100%; }
  /* Photo-beside-writing is the BASE layout now, not something restored here,
     so this only widens the gap for the wider card. */
  .voice { gap: var(--space-lg); align-items: center; }
  /* NOTE the 320 cap on .voice__shot is deliberately NOT lifted here. In this
     two-column form the track only gives the photo ~264px, so the cap never
     binds on a tablet — but it does bind between ~390 and 640px, where the
     card is one column and the picture would otherwise render up to 412px off
     a 480px file. */
  .voice__say { max-width: none; }
}

@media (max-width: 60rem) {
  /* Higher specificity than the base rule, so it needs restating here. */
  .cta--strip .cta__inner { grid-template-columns: minmax(0, 1fr); }
  .cta__orn { display: none; }        /* two ornaments and no rail to hang on */
  /* Measured, not chosen: the text track needs ~26rem before "Home of Maria's
     famous tzatziki" drops to four script lines, and the 0.54 share of the
     shell stops supplying that at about 960px once the seam padding comes out.
     The four-track bleed grid collapses to one column here, so both children
     have to be released from their explicit tracks or they stay in tracks 2
     and 3 of a one-track grid. */
  .tzat { grid-template-columns: minmax(0, 1fr); }
  .tzat__shot, .tzat__say { grid-column: 1; }
  .tzat__shot { padding: 0 var(--page-gutter); }

  /* ⚠️ catering's navy band collapses HERE TOO, and for the identical reason —
     it is the same four-track bleed grid. Without this the tracks keep their
     0.40/0.60 shares of a 390px screen and the photograph renders 127px wide:
     a thumbnail of a tray, which is the one thing the picture is there to stop
     the page being. Both children have to be released from their explicit
     tracks or they stay in columns 2 and 3 of a one-column grid. */
  .paper > .expect { grid-template-columns: minmax(0, 1fr); }
  .expect__shot, .expect__say { grid-column: 1; }
  /* ⚠️ A WIDTH CAP, not the `max-height: 20rem` on the <img> that used to be
     here. The print is a portrait object built out of a ratio now, so its size
     comes from the box: released to the full column it renders 860px of paper
     on a 900px screen, and capping the photograph's height instead would only
     crop the window while the frame around it stayed enormous. */
  /* ⚠️ CAPPED WITH PADDING, AND IT MUST STAY THAT WAY. Two other spellings of
     this cap made the photograph DISAPPEAR on every phone and tablet — it
     measured 72x0, exactly its own padding — and a third overflowed 320px by
     34px. Both failures have the same root: the frame is a box built out of
     `aspect-ratio`, so it has NO intrinsic width, and anything that puts the
     figure into shrink-to-fit (`margin-inline: auto`, `justify-self`, a
     percentage `max-width` in a `minmax(0, 1fr)` track) leaves it sizing itself
     from a child that cannot answer.

     Padding keeps the figure STRETCHED, so its width stays definite and the
     frame inherits one. The `max()` is the centring: the gutter until the
     column passes 24rem, then whatever it takes to hold the frame at 24rem. */
  .expect__shot {
    padding-inline: max(var(--page-gutter), calc((100% - 24rem) / 2));
  }
  /* The postmark hangs off the frame's left edge, and on a 390px screen the
     frame starts 20px in — so -9% put a third of the stamp past the left edge
     of the phone. It is clipped, not scrolling, but a mark cut off by the
     window reads as a mistake rather than as a stamp running off the paper. */
  .expect__stamp { left: -2%; }
  .expect__say { padding-inline: var(--page-gutter); }
  /* Stacked there is no seam to straddle, so it tucks back inside the cut-out's
     own box instead of hanging off the right edge of the page. */
  .tzat__seal { right: var(--page-gutter); bottom: 0; transform: none; }
  .tzat__say {
    padding: var(--space-lg) var(--page-gutter) 0;
  }
  .tzat__title { max-width: 18ch; }
  /* Two-up. The row gap is deliberately tighter than the column gap: the band
     is inside the hero now and every row it grows is a row the photograph
     loses, so the two lines sit close and the dividers do the separating. */
  .proof__list { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--space-md); }
  .proof__list li:nth-child(odd) { border-left: 0; }
  .proof__list li:nth-child(3) { border-left: 0; }
  .hero__proof { padding-block: var(--space-sm) var(--space-md); }
  .gallery { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .board__cols { grid-template-columns: minmax(0, 1fr); }
  .cta__inner { grid-template-columns: minmax(0, 1fr); gap: var(--space-lg); }
  .praise { grid-template-columns: minmax(0, 1fr); gap: var(--space-lg); }
  .praise__score { text-align: left; }
  .foot__top { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: var(--space-lg); }
  .foot__col:first-child { grid-column: 1 / -1; }
  /* The floor of this box is now the top of the proof band rather than the
     bottom of the screen, so it gives back most of its lower air. */
  .hero__inner { padding-block: var(--space-2xl) var(--space-lg); }
}

/* 678px is where two columns run out of measure, so the stack starts at 44rem
   rather than sharing the 40rem block below — between 640 and 678 a two-up
   layout was still nominally in force and orphaning prices. */
@media (max-width: 44rem) {
  /* A PHONE NEEDS ITS OWN SCRIM, and this is not laziness about one number.
     On desktop the copy sits in the lower third where the ramp is already
     heavy. On a phone the same copy wraps to roughly twice the lines, so the
     headline ends up ~78% of the way UP the hero — where the desktop ramp has
     faded to almost nothing — and it lands on the white canopy. Measured, it
     was 2.53:1 there against a 3.0 bar.

     It can afford to be flatter and stronger here because a phone only ever
     sees ~33% of the photograph's width anyway: at that crop the picture is
     a texture, not a view, so holding it costs far less than it would at
     1440. Re-measure both widths if either ramp changes. */
  .hero::after {
    background: linear-gradient(to top,
      oklch(16% 0.052 248 / 0.80) 0%,
      oklch(16% 0.052 248 / 0.76) 40%,
      oklch(16% 0.052 248 / 0.70) 70%,
      oklch(16% 0.052 248 / 0.40) 88%,
      oklch(16% 0.052 248 / 0.10) 100%);
  }
  /* One column. Every divider becomes a horizontal rule so the card still reads
     as a card and not as three unrelated lists. */
  .breadth { grid-template-columns: minmax(0, 1fr); margin-top: var(--space-xl); }
  .breadth__col,
  .breadth__col:nth-child(1),
  .breadth__col:nth-child(2),
  .breadth__col:nth-child(3) {
    padding: var(--space-lg) 0; margin-left: 0; border-left: 0;
  }
  .breadth__col:nth-child(1) { padding-top: 0; border-top: 0; border-bottom: 0; }
  .breadth__col:nth-child(1) .list { display: block; }   /* undo the two-up above */
  .breadth__col + .breadth__col { border-top: var(--rule-hair) solid var(--color-rule); }
  /* Nothing sits beside the footnote now, so there is no floor to flush it to
     and the extra padding just opens a hole above it. */
  .breadth__foot { padding-top: var(--space-md); }

  /* The title wraps to two or three lines here, and a sprig sitting beside a
     wrapped block reads as debris left at the margin. Centred above it instead.
     The key runs are down to a couple of tiles by this width, so they go. */
  /* Measured: the one-row bar needs ~650px for two link groups either side of a
     script wordmark, so it holds all the way down to here rather than dropping
     to the stack at the first tablet breakpoint. Flex-wrap rather than a
     one-column grid, so the wordmark takes its own row and BOTH nav groups
     share the row under it — two rows, not three. */
  .mast__bar { display: flex; flex-wrap: wrap; justify-content: center; gap: var(--space-2xs) var(--space-md); }
  .mast__name { flex: 1 0 100%; }
  .mast__nav--start ul, .mast__nav--end ul { justify-content: center; }

  .menuhead { grid-template-columns: minmax(0, 1fr); }
  .menuhead__key { display: none; }
  .menuhead__title { flex-direction: column; gap: var(--space-2xs); }

  /* The plaque's art stacks over and under the copy rather than shrinking to
     two illegible thumbnails either side of a 20ch measure. */
  .commune__card {
    grid-template-columns: minmax(0, 1fr);
    justify-items: center;
    padding: var(--space-lg) var(--space-md);
    gap: var(--space-md);
  }
  .commune__art { width: min(13rem, 60%); }
}

@media (max-width: 40rem) {
  .callbar { display: flex; }
  body { padding-bottom: 4.5rem; }             /* room for the bar */
  .gallery { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  /* ── The caption moves onto the print's foot ─────────────────────────────
     Touch has no hover, so the caption is always shown here — and at this width
     a tile is about 125px across, which is not enough room to lay a sentence
     over the photograph. It was filling the frame corner to corner and burying
     the dish it was describing.

     A print has a paper margin under the picture and that is where a caption
     belongs, so it goes there: in flow, ink on paper, no scrim. The foot grows
     to hold however many lines the sentence needs instead of the sentence
     fighting a fixed box. Above 40rem it goes back to a scrim on hover, where
     there is room for it and the grid stays even. */
  /* `align-items: start` and `height: auto` are both required, and the caption
     silently vanishes without either. The grid stretches every `.shot` to the
     tallest in its row; `.shot img` is `height: 100%`, so the photograph then
     eats the whole print including the foot, and `.shot`'s `overflow: hidden`
     clips the caption out of existence. Sizing the image from its own
     aspect-ratio instead leaves the foot to the text. */
  .gallery { align-items: start; }
  .shot { padding-bottom: 0.5rem; }
  .shot img { height: auto; }
  .shot__cap {
    /* `display: block` is not decoration. `.shot__cap` is a <span>, and in the
       base rule it is blockified for free by `position: absolute`. Taking it out
       of absolute positioning here hands it back its inline display, and
       `text-align` set on an inline box does nothing at all — it is a property
       that aligns inline content INSIDE a block container. The caption went on
       rendering flush left, one line short of the change it was supposed to
       have, which is a very quiet way to fail. */
    position: static;
    display: block;
    opacity: 1;
    padding: var(--space-2xs) var(--space-3xs) 0;
    background: none;
    color: var(--color-ink-2);
    font-size: var(--text-xs); line-height: 1.35;
    text-align: center;
  }
  /* Clears the sticky call bar. It stays on the SCRIM rather than the panel:
     the scrim centres the panel in whatever box it is left with, so padding
     here lifts the popup off the bar instead of padding it from inside. */
  .lightbox { padding-bottom: 5rem; }
  .foot__top { grid-template-columns: minmax(0, 1fr); gap: var(--space-lg); }
  .grp { padding: var(--space-md); }
  .plaque { padding: var(--space-lg) var(--space-md); }
  /* Below this the text column runs under ~15ch and the score line breaks.

     ⚠️ AND THE TORN PAPER STOPS BEING AN IMAGE HERE. The base rule stretches
     note-paper.webp `100% 100%`, which costs about 13% of vertical stretch at
     desktop — fine on a deckled edge. On a phone the card is one column and
     runs roughly 350 wide by 680 tall, i.e. 0.51 against the file's 1.36: that
     is not a stretch any more, it is the torn edge pulled into vertical streaks
     with the pin squashed into a lozenge. What replaces it is the paper's own
     colour, sampled off the middle of the file at rgb(244,237,225) — same
     stock, no edge. The edge is the right thing to give up at a width where it
     would be four pixels of the screen.

     NOT --color-surface. That token is near-white and a near-white panel on
     parchment is the exact fault Marcus called out on 2026-08-15 ("the white
     review block on the left"). This is warm paper on warmer paper. */
  .voice {
    grid-template-columns: minmax(0, 1fr);
    /* Restores the desktop pairing: photo and rating share one centred column
       so the picture's edge and the rating's rule line up. Without this the
       stretch set at 68rem leaves them on different widths. */
    justify-items: center;
    background: oklch(94.6% 0.017 84);
    padding: var(--space-lg) var(--space-md);
    /* ⚠️ AND THE RATIO COMES OFF WITH THE PICTURE. There is no photograph of
       paper to keep in proportion here, so a declared 1.356 would just be a
       rule forcing a flat cream panel to be 260px taller than its contents. */
    aspect-ratio: auto;
  }
  .voice__say { max-width: 320px; }
  /* No torn edge to cancel, and at this size the stamp lands across the type. */
  .voice__stamp { display: none; }
  /* The file is a tight square now, so a 3:2 letterbox of it cuts Maria's head
     off. It stays square on a phone; the card is narrow enough there that a
     square photo is not too tall. */
  .voice__shot { aspect-ratio: 1 / 1; }
  .cta { padding-block: var(--space-xl); }
  .cta__act .btn { width: 100%; }
  .picks { padding-block: var(--space-2xl); }

  /* The hero is one screen on a phone as well, and a phone has roughly half
     the height to spend. Each line of the block steps down rather than any of
     it collapsing — the headline, the promise, the action and the four facts
     all still make the first view. */
  .hero__title { margin-bottom: var(--space-md); }
  .hero__sub { margin-bottom: var(--space-lg); }
  .hero__status { margin-top: var(--space-md); }
  /* A 163px column will not hold "750 E Eau Gallie Blvd" at 1.5rem, and four
     cells each wrapping to two lines makes the band taller than the headline
     block above it. Both lines step down so every cell stays one line. */
  .proof__list strong { font-size: var(--text-base); }
  /* ⚠️ The label was 0.6875rem/11px with 0.05em of tracking, on the claim above
     that it kept every cell to one line. MEASURED 2026-08-17 at 320/360/375/
     390/430/600 — that claim is only true from 375px up. At 360 "Indian Harbour
     Beach" wraps anyway, and at 320 (col = 128px) three of the four cells wrap
     at EVERY size tested, 11px included. So below 375 the 11px was buying
     nothing and costing legibility on the one strip that carries the rating,
     the price, the hours and the town — over a photograph, in tracked caps.

     12px holds one line at 375 too, but ONLY at 0.02em: at 0.03em and 0.05em
     that width wraps. Hence the tighter tracking, which is measured, not taste.
     Costs 3px of band height at 390 and 6px at 320. */
  .proof__list span { font-size: var(--text-xs); letter-spacing: 0.02em; }
}

/* ── Touch targets ────────────────────────────────────────
   Measured on a 390px screen, 2026-08-11. Every one of these is a real link a
   customer taps with a thumb, and every one was under the 44px both Apple and
   Google publish as the minimum:

     footer page links (Home / Menu / Catering / Find Us)  16px
     footer phone number                                   16px
     footer brand link                                     29px
     the phone number on the find-us plaque                24px
     phone and email on the catering contact list          23px
     masthead nav links                                    36px

   The footer four were the worst of it — 16px tall, stacked 8px apart, which
   is a column of near-misses on the one piece of secondary navigation a phone
   user has once they are past the fold.

   min-height on an inline-flex, not padding. Padding on an inline element
   grows the box without moving the line, so the extra area overlaps the link
   above it and the top half of a 44px target belongs to the wrong link — the
   fix that measures correctly and still mis-taps.

   THE MASTHEAD PAYS FOR ITSELF. Its links go 36 → 44, which would push a bar
   that is already 151px tall on a phone to 159. The bar's own block padding
   comes down by the same amount to cover it, so the sticky header ends up a
   couple of pixels SHORTER than before while its targets get bigger. Do not
   restore the padding without also reconsidering the min-height. */
@media (max-width: 44rem) {
  .mast__bar { padding-block: var(--space-3xs) var(--space-3xs); }
  .mast__nav a,
  .foot__list a, .foot__tel, .foot__brand,
  .plaque__tel, .area__contact a {
    display: inline-flex; align-items: center;
    min-height: 44px;
  }
  /* The rows are 44px tall now, so the gap between them is doing nothing but
     stretching the footer — the targets already separate themselves. */
  .foot__list li { margin-bottom: 0; }
  /* Its own line-height was holding it at 16px inside a taller box. */
  .foot__list a, .foot__tel { line-height: 1.2; }
}

/* ── The masthead is TWO rows on a phone, not three ───────
   The 44rem block above says, in its own comment, that the wordmark takes a
   row and "BOTH nav groups share the row under it — two rows, not three."
   That is not what it was doing. Measured at 390px on 2026-08-11 the bar came
   out in three bands — Menu/Catering, then the wordmark, then Find Us/phone —
   because `.mast__name` is the MIDDLE child in source order, and a
   `flex: 1 0 100%` item in the middle of a wrap container splits its siblings
   onto opposite sides of itself. The rule did what it said; the source order
   undid it.

   `order: -1` pulls the wordmark to the front of the flex line so the two nav
   groups land together underneath, as intended. Source order is untouched, so
   the tab order and the screen-reader reading are unchanged — the nav still
   comes before the logo in the DOM.

   That alone is not enough to fit them: the two groups measure 388px side by
   side and a 390 screen only offers 350 between the gutters. The masthead
   phone number is what goes, and only below 40rem — which is exactly the
   width at which `.callbar` pins "Call to order" to the bottom of the screen
   for the whole visit. The number is not removed from a phone, it is moved to
   the thumb. Above 40rem the callbar is gone and the number stays in the bar.

   Net effect at 390: 156px of sticky header down to 100px, and the 56px it
   gives back is the top of the hero. */
@media (max-width: 40rem) {
  .mast__name { order: -1; }
  .mast__nav--end li:has(.mast__tel) { display: none; }
}

/* ── The hero headline stops being one line ───────────────
   THIS WAS THE MOBILE BUG. `.hero__title` is set `white-space: nowrap` at base
   — a desktop device, so the headline clears the truck's own painted sign
   instead of stacking on top of it — and the release to `normal` was sitting
   in the 24rem block below. 24rem is 384px. Every current phone is wider than
   that, so the release never fired on any of them.

   The headline lost its first word on 2026-08-16 — "True Authentic Greek Food"
   became "Authentic Greek Food", because true and authentic were doing the same
   job. That is ~5 characters narrower than everything measured below, so the
   30rem breakpoint now carries MORE slack than it was set with, not less. The
   measurement that set it is kept because it is the one that has to be redone
   if the string ever grows again.

   Measured, 2026-08-11: "True Authentic Greek Food" on one line is 427px, and
   with the two page gutters it needs 467px of viewport. On a 390 (iPhone 14),
   412 (Pixel) or 430 (iPhone Pro Max) screen the hero therefore laid out 458px
   wide inside a 390px window — the headline ran off the right edge with the
   last word cut, and it dragged the proof band out with it, because a grid
   item's min-content width is what sizes an `auto` track.

   30rem = 480px, which clears the measured 467 with a little slack for a
   longer headline. It is a max-width query on the LINE-BREAKING of one string,
   so if that string is ever rewritten, re-measure: a longer one needs a wider
   breakpoint. The size step-down still happens at 40rem below; only the wrap
   moved. */
@media (max-width: 30rem) {
  .hero__title { white-space: normal; }
  /* Belt and braces on the same failure mode. Both are grid items in `.hero`,
     where the implicit track is `auto` and therefore floored at min-content —
     so any future nowrap string, long word or wide table inside them would
     push the whole page sideways again rather than being contained. */
  .hero__inner, .hero__proof { min-width: 0; }
}

/* At 320 the longest rows ran out of room and dropped their price to a line of
   its own, which reads as a stray $13 rather than a menu row. Four small claw-
   backs — pad, gap, leader minimum, price size — buy back the ~30px needed. */
@media (max-width: 24rem) {
  .breadth .list li { gap: 0 var(--space-2xs); }
  .breadth .list__item::after { min-width: 0.5em; }
  .breadth .list__price { font-size: var(--text-base); }
  .breadth .veg { font-size: 0.6875rem; letter-spacing: 0.06em; }
  .tzat__seal { width: 5.6rem; height: 5.6rem; font-size: 0.6875rem; }
  .tzat__seal svg { width: 1.1rem; }
  .kitchen, .slate { padding-block: var(--space-2xl); }
  .phead { padding-top: var(--space-xl); }
  .mast__nav ul { gap: var(--space-3xs) var(--space-2xs); }
  .mast__nav a { padding: var(--space-3xs) var(--space-2xs); letter-spacing: 0.08em; }
  .hero__facts { gap: var(--space-sm) var(--space-lg); }
  /* Stays two-up even at 320. One column is four rows, and four rows of facts
     inside a one-screen hero costs more height than the whole headline block. */
  .proof__list { gap: var(--space-sm) var(--space-md); }
  .proof__list strong { font-size: var(--text-base); }
  /* Only the size step is left here. The nowrap release moved up to 30rem on
     2026-08-11 — see the block above the 24rem query for why. */
  .hero__title { font-size: clamp(1.75rem, 8vw, 2.4rem); }
  /* A LANDSCAPE photo in a portrait viewport is the opposite problem to the
     one this line used to solve: the binding axis flips, so only ~42% of the
     frame's WIDTH survives and the vertical bias stops mattering. The painted
     sign and the service window sit left of centre, around 36% across, so the
     crop is pulled that way — centred, it lands on the neighbouring lot. */
  .hero__shot img { object-position: 36% center; }
}

/* ── Short screens ────────────────────────────────────────
   Width is not the constraint here — height is. A 720-tall laptop and a phone
   held in landscape both have to carry the same four parts of the hero, so the
   block gives up its air rather than giving up a part. Last of the queries on
   purpose: it has to outrank the width tiers above, which set the same
   properties for tall phones.

   ⚠️ WIDTH IS IN HERE AFTER ALL, AND IT IS NOT A SECOND THOUGHT — IT IS THE
   HALF THAT WAS MISSING. Marcus hit the headline sitting ON the painted sign in
   a 1400x800 Safari window on 2026-08-16, in a band this query did not reach.
   The arithmetic, measured across seven viewports:

     the sign's bottom edge  ≈  74 + 0.076 × VIEWPORT WIDTH
       (`cover` on a 1360x785 file in a box wider than 1.73 is WIDTH-bound, so
        the painted board grows with the window and nothing can shrink it —
        crop it out and you lose the only real brand mark this business owns)
     the headline's top      ≈  VIEWPORT HEIGHT − 686
       (the copy is bottom-anchored above a fixed-height proof band, and the
        display face is clamped out at these widths, so it tracks height 1:1)

   Set them equal and the collision line is H < 784 + 0.076W: 891px tall at
   1400 wide, 906 at 1600, 930 at 1920, 979 at 2560. A window can therefore be
   900 tall — nowhere near "short" — and still land the headline on the sign,
   which is exactly what Marcus saw. No single query says that, so it is two,
   OR'd: the laptop band, and a wide-screen band that reaches higher because a
   wider window paints a bigger sign. Measured clearances after this fires:
   134px at 1400x800, 19 → comfortable at 1600x900, and −6 → clear at 1920x900.

   A genuinely tall window is deliberately untouched — 1920x1080 keeps 180px of
   clearance and the un-compacted hero is the better composition when there is
   room for it. The wide tier's ceiling is 62rem rather than 60 to cover a 2560
   window, whose line sits at 979; the cost is that a 1440-or-wider window
   between 960 and 992 tall gets compacted when it did not strictly need it.
   That is the cheap direction to be wrong in.

   NOT FIXED HERE, AND NOT A REGRESSION: a phone in LANDSCAPE (844x390) still
   lands the headline across the sign. 390px of height cannot hold the sign, a
   headline, a standfirst, a hanging board and four facts, and the 46rem block
   below is the existing answer to it — the scrim thickens so the copy is held
   over the lettering rather than fighting it. */
@media (max-height: 56rem),
       (min-width: 90rem) and (max-height: 62rem) {
  .hero__inner { padding-block: var(--space-md) var(--space-sm); }
  .hero__title { margin-bottom: var(--space-sm); }
  .hero__sub { margin-bottom: var(--space-md); font-size: var(--text-base); }
  .hero__status { margin-top: var(--space-sm); }
  /* The foot does NOT compact with the rest. --space-sm here left the second
     line of facts 4px off the bottom of the screen once depth.js's ~12px rest
     translate was applied — the claw-back has to come from the copy's air, not
     from the one band the design promises will be in the first view. */
  .hero__proof { padding-block: var(--space-xs) var(--space-md); }
}
/* The scrim change is NOT in the query above and must not be folded into it.
   It is for a genuinely short screen — a phone in landscape — and at 46rem it
   would otherwise start darkening 982-tall laptops that have no need of it. */
@media (max-height: 46rem) {
  /* The base scrim is bottom-weighted for a 700px hero. In half that height the
     copy rides up into the thin part of the gradient and lands on the painted
     sign unheld, so the hold moves up with it. The top stays open — the sky is
     still the best thing in this photograph. */
  .hero::after {
    background: linear-gradient(to top,
      oklch(16% 0.052 248 / 0.94) 0%,
      oklch(16% 0.052 248 / 0.9) 42%,
      oklch(16% 0.052 248 / 0.7) 68%,
      oklch(16% 0.052 248 / 0.3) 88%,
      oklch(16% 0.052 248 / 0.06) 100%);
  }
}

/* ── Motion / print ───────────────────────────────────── */
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after {
    animation-duration: 0.001ms !important;
    transition-duration: 120ms !important;
  }
}

@media print {
  /* .hero__status is "Closed — back tomorrow at 10:30am". It is true for about
     an hour and it is a lie on paper, so it never prints. */
  .mast__nav, .callbar, .lightbox, .cta, .foot__top, .foot__by,
  .hero__act, .hero__status { display: none; }
  body { background: var(--color-print-paper); color: var(--color-print-ink); }
  .mast { position: static; }
  .mast::before { display: none; }
  /* The wordmark stands down on screen because the photograph below it carries
     the same painted sign — and `.hero__shot` does not print. On paper the
     masthead is the only place the name appears, so it comes back. */
  .mast--defer-brand .mast__name { opacity: 1; visibility: visible; }
  .board, .grp, .picks, .pick, .voice { background: var(--color-print-paper); }
  /* The card is the one part of this page that genuinely wants to be printed,
     so it keeps its whole structure — dividers, leaders, plaque frame, line art
     — and only drops to ink on white. Rules stay translucent so they do not
     print heavier than the type they separate. */
  .menuhead__key,
  .breadth__cap::before, .breadth__cap::after { background-color: var(--color-print-ink); }
  .menuhead__title, .menuhead__sprig, .breadth__cap,
  .breadth .list__price, .breadth .veg, .commune__kicker,
  .menuhead__line, .breadth__foot, .commune__body { color: var(--color-print-ink); }
  .breadth__leaf { color: var(--color-print-ink); opacity: 0.55; }
  .breadth__col + .breadth__col { border-left-color: oklch(0% 0 0 / 0.3); }
  .breadth .list__item::after { border-bottom-color: oklch(0% 0 0 / 0.4); }
  /* The plaque prints as its live-type card, ruled, ink on white. The painted
     version and its two ornaments are watercolour — the same reason the taped
     objects below come out. A page box is narrower than 60rem, so the card is
     already the one in the layout; these two lines only make that explicit and
     close the empty ornament tracks the columns would otherwise leave. */
  .commune__plaque, .commune__art { display: none; }
  .commune__card {
    grid-template-columns: minmax(0, 1fr);
    background: none;
    border-color: oklch(0% 0 0 / 0.45);
  }
  .picks--board .picks__more { display: none; }

  /* The parchment is a texture, and a texture prints as a sheet of toner over
     every word on it. Both parchment sections drop to plain paper; the type,
     the leaders and the hairlines above already print as ink on white.
     The two taped objects go for the same reason the tzatziki photograph does
     — they are atmosphere, and atmosphere is what makes a printout expensive. */
  /* The same reset the two home-page grounds get, for the engraved border that
     leads the menu page and the five interior pages. Browsers do not print
     background images unless the reader turns them on — but if they do, this is
     an ornamental frame reprinted on every sheet, and the pages underneath it
     are a menu, a price list and a booking form. Off. */
  .paper { background-image: none; background-color: var(--color-print-paper); }
  .paper--frame { background-image: none; }
  .sheet, .wall { background-image: none; background-color: var(--color-print-paper); }
  .sheet__print, .sheet__note, .sheet__accent, .sheet__sprig { display: none; }
  .sheet__title, .sheet__cta a { color: var(--color-print-ink); }
  .sheet__cta a { border-bottom-color: oklch(0% 0 0 / 0.4); }
  /* Accordions are a screen affordance; on paper every category is open. */
  .breadth__col[open] .breadth__sum::after, .breadth__sum::after { content: none; }

  /* The plaque prints for the same reason the menu card does — an address, a
     schedule and a phone number are exactly what someone puts on the fridge.
     The Get-directions button is the one thing paper cannot honour, so it goes. */
  .here__kicker::before, .here__kicker::after,
  .here__spine::before, .here__spine::after { background: var(--color-print-ink); opacity: 0.45; }
  .here__crest, .here__spine svg,
  .plaque__ic, .voice__stars svg,
  .here__olive { color: var(--color-print-ink); }
  .here__kicker, .here__sub, .here__title, .plaque__kicker, .plaque__sub,
  .plaque__name, .plaque__tel, .plaque__aside, .plaque__foot,
  .voice__score, .voice__from, .voice__who { color: var(--color-print-ink); }
  /* ⚠️ THE THREE SUPPLIED PICTURES DO NOT PRINT — 2026-08-17. Backgrounds and
     border-images are dropped by most print paths anyway, and the two that are
     NOT dropped are the ones that would ruin the page: a full-bleed torn-paper
     photograph behind the proof and a 24px engraved frame round the plaque
     would between them soak a cartridge to deliver an address. The frame falls
     back to a plain hairline and the note to nothing, which is what this block
     already did before the artwork arrived. The stamps just go. */
  .framed { border-image: none; border-width: var(--rule-hair);
            border-color: oklch(0% 0 0 / 0.45); }
  .voice { background: none; border: var(--rule-hair) solid oklch(0% 0 0 / 0.45); }
  .plaque__stamp, .voice__stamp, .crowd__plate, .crowd__leaf { display: none; }
  .voice__lines { border-left-color: oklch(0% 0 0 / 0.3); color: var(--color-print-ink); }
  .voice__who { border-color: oklch(0% 0 0 / 0.3); }
  .crowd__eyebrow { color: var(--color-print-ink); }
  .plaque__go { display: none; }
  /* Was never handled while this was a dark band, and a dark band prints as a
     solid rectangle of toner. Photograph and stamp go — a full-bleed half-page
     photo is the single most expensive thing on a printed page — and the two
     prices stay, because those are the reason anyone prints this at all. */
  .tzat {
    background: var(--color-print-paper); color: var(--color-print-ink);
    grid-template-columns: minmax(0, 1fr);
  }
  .tzat__shot, .tear { display: none; }
  .tzat { padding-block: 0; }
  .tzat__say { max-width: none; padding: var(--space-lg) 0; }
  .tzat__kicker, .tzat__title, .tzat__blurb,
  .tzat__rule svg { color: var(--color-print-ink); }
  .tzat__rule::after { background: var(--color-print-ink); opacity: 0.45; }

  .hero, .kitchen, .slate, .foot { background: var(--color-print-paper); color: var(--color-print-ink); }
  /* The two torn edges go with the colour they were torn out of. On paper the
     hero's strip would print a band of parchment across white and the footer's
     a band of navy, which is two ragged bars and no tear — the sections they
     divide are both white here. Listed alongside .tear above for the
     same reason. */
  .hero::after, .hero__shot, .hero::before, .foot::before { display: none; }
  .hero__title, .hero__sub,
  .foot__legal, .foot__name { color: var(--color-print-ink); }
  /* A screen-height hero would burn most of a sheet on white space, and the
     proof strip is white type carried by a scrim that does not print. */
  /* The bottom padding is a landing strip for a tear that `display: none`s two
     rules above — on paper it is just a band of white under the facts. */
  .hero { min-height: 0; padding-bottom: 0; }
  .hero__inner { padding-block: var(--space-lg); }
  /* Both of these carry a glow to hold them off the photograph. There is no
     photograph on paper, so the glow is just a smudge around the letters. */
  .hero__title, .proof__list strong { text-shadow: none; }
  .proof__list strong { color: var(--color-print-ink); }
  .proof__list span { color: var(--color-print-ink); }
  /* Hairlines stay light so they do not print heavier than the type between. */
  .hero__proof { border-top-color: var(--color-rule); }
  .proof__list li + li { border-left-color: var(--color-rule); }
  /* Catering page. Same stance as the rest of the sheet: ink on white, no
     photographs, and no ornament that only exists to hold a layout together.
     With the plates gone every card is a single column of text, so the row
     goes two-up — three text columns on a 7in sheet is a 2in measure. */
  .mfilter, .mpicks, .mhead__plate, .mhead__sprig, .mhead__wave,
  .phead__sprig, .phead__wave,
  .grp__badge, .cta__badge { display: none; }
  .mhead { padding: 0; background: var(--color-print-paper); }
  .mhead__say { max-width: none; padding-inline: 0; }
  .mhead__title, .mhead__aside, .mhead__where, .grp__title { color: var(--color-print-ink); }
  /* The interior pages' head is the menu head's twin and gets the same
     treatment: laurels and wave off, type to ink, left-aligned. Centred display
     type over a left-aligned page is a screen composition — on paper it just
     wastes the top of the sheet. */
  .phead { padding: 0 0 var(--space-md); text-align: left; }
  .phead__title, .phead__aside, .phead__where { color: var(--color-print-ink); }
  .phead__lede, .phead__aside { margin-inline: 0; }
  .phead__crown { justify-content: flex-start; }
  /* Catering. The seal and the Florida silhouette are the two marks here that
     exist only to be looked at, and a sheet someone has printed to hold while
     they call is not the place for either. */
  .chero__shot, .ready__seal, .area__map { display: none; }
  .chero { grid-template-columns: minmax(0, 1fr); }
  .chero__say { justify-self: stretch; max-width: none; padding-inline: 0; }
  .fits { padding-inline: 0; }
  .fit { padding: 0 0 var(--space-md); background: var(--color-print-paper); break-inside: avoid; }
  .expect, .area { padding-inline: 0; background: var(--color-print-paper); }
  /* Both carry a patch of the band's navy so they can sit ON something, and on
     a printed sheet there is nothing under them to sit on — the cross would be
     a navy smudge in the middle of the promises and the postmark a grey blur
     behind the photograph. Same reasoning as `.ready__seal` above. */
  .expect__cross, .expect__stamp { display: none; }
  .promise { break-inside: avoid; }
  .ready { padding: 0; break-inside: avoid; background: var(--color-print-paper); }
  .area__inner { grid-template-columns: minmax(0, 1fr) auto; }
  .area__say { border-inline-start: 0; padding-inline-start: 0; }
  .board__cols { grid-template-columns: 1fr 1fr; }
  /* .board__cols--two is multicol on screen, not grid, so grid-template-columns
     no longer reaches it — set the count the way the element is actually laid
     out or the whole 43-item menu prints as one long single file. */
  .board__cols--two { column-count: 2; }
  /* Do not let a group be torn across a sheet. */
  .grp { break-inside: avoid; }
}

/* ══════════════════════════════════════════════════════════════════════════
   DEPTH · scroll motion and dimensional photography
   ═══════════════════════════════════════════════════════════════════════════
   Paired with depth.js. Added 2026-08-12 on Marcus's request for "3d scroll
   animations" — with his own constraint that it must NOT be real 3D: "with
   pictures we could make it look like it." So every effect below is a flat
   photograph pretending, and the page ships no geometry and no WebGL.

   THE NO-FOUC CONTRACT: every resting state here is guarded behind
   `.has-depth`, which depth.js sets on <html> as its first act. With JS off,
   broken, or still parsing, nothing is hidden and nothing is moved. Do not
   write a bare `[data-reveal] { opacity: 0 }` — that is how a menu ends up
   invisible to the one customer whose browser threw. */

:root {
  --dur-long:    620ms;
  /* Stagger step. Deliberately small: at 90ms a six-item grid takes over half
     a second to finish arriving, which reads as a slow website rather than as
     choreography. */
  --reveal-step:  55ms;
  --ease-settle: cubic-bezier(0.16, 1, 0.3, 1);
}

/* ── Reveals ─────────────────────────────────────────────────────────────
   Rise-and-settle. The distance is small on purpose — 18px, not 60px. Big
   travel looks like a template; small travel looks like the page has weight. */
.has-depth [data-reveal] {
  opacity: 0;
  transform: translate3d(0, 18px, 0);
  transition:
    opacity   var(--dur-long) var(--ease-settle),
    transform var(--dur-long) var(--ease-settle);
  transition-delay: calc(var(--reveal-i, 0) * var(--reveal-step));
}
.has-depth [data-reveal="left"]  { transform: translate3d(-24px, 0, 0); }
.has-depth [data-reveal="right"] { transform: translate3d(24px, 0, 0); }
.has-depth [data-reveal="scale"] { transform: scale(0.94); }

/* The dimensional one: the element arrives lying slightly away from you and
   rotates upright into the page plane. This is the effect that reads as "3D"
   without a single polygon. Needs perspective from an ancestor .scene. */
.has-depth [data-reveal="tilt"] {
  transform: perspective(1100px) rotateX(7deg) translate3d(0, 26px, 0);
  transform-origin: 50% 100%;
}

.has-depth [data-reveal].is-in {
  opacity: 1;
  transform: none;
}

/* ── Parallax layers ─────────────────────────────────────────────────────
   depth.js writes --depth-y / --depth-x. Uses the standalone `translate`
   property rather than `transform`, so a layer can be parallaxed AND revealed
   AND tilted without the three of them overwriting one another. */
.has-depth [data-depth] {
  translate: var(--depth-x, 0px) var(--depth-y, 0px);
}

/* ── Perspective scenes ──────────────────────────────────────────────────
   A .scene establishes the vanishing point its children turn inside. One
   scene per composition, not one per card: sharing a vanishing point is what
   makes a grid read as objects on a table rather than six unrelated tilts. */
.scene { perspective: 1200px; perspective-origin: 50% 50%; }

.has-depth [data-tilt] {
  transform: rotateX(var(--tilt-x, 0deg)) rotateY(var(--tilt-y, 0deg));
  transform-style: preserve-3d;
  transition: transform var(--dur-medium) var(--ease-out);
}
/* While the pointer is inside, drop the easing to near-zero so the face tracks
   the cursor instead of chasing it. The transition above is then only doing
   the return-to-rest, which is where the ease actually belongs. */
.has-depth .scene:hover [data-tilt] { transition-duration: 90ms; }

/* Lift, so the tilt has a shadow to sell it — without one the rotation reads
   as a skew bug rather than as an object being turned.

   OPT-IN, AND THE TWO KINDS ARE NOT INTERCHANGEABLE. box-shadow paints the
   element's BOX, so on a transparent cut-out it draws a hard rectangle in mid
   air around the food. Cut-outs must use drop-shadow, which follows the alpha
   channel and traces the actual pita. Getting this backwards is the single
   most visible way to break this section. */
.has-depth [data-tilt][data-lift="box"] {
  box-shadow: 0 2px 10px oklch(16% 0.052 248 / 0.16);
  transition: transform var(--dur-medium) var(--ease-out),
              box-shadow var(--dur-medium) var(--ease-out);
}
.has-depth .scene:hover [data-tilt][data-lift="box"] {
  box-shadow: 0 22px 44px oklch(16% 0.052 248 / 0.30);
}
.has-depth [data-tilt][data-lift="drop"] {
  filter: drop-shadow(0 8px 14px oklch(16% 0.052 248 / 0.22));
  transition: transform var(--dur-medium) var(--ease-out),
              filter    var(--dur-medium) var(--ease-out);
}
.has-depth .scene:hover [data-tilt][data-lift="drop"] {
  filter: drop-shadow(0 26px 34px oklch(16% 0.052 248 / 0.34));
}

/* ── Hero · the photograph gains depth without losing its crop ───────────
   THE TRANSFORM ORIGIN IS THE WHOLE TRICK: `50% 0%` pins the top edge, so
   every pixel of growth is spent on the empty asphalt along the bottom. The
   hand-painted sign — the only real brand mark this business owns — never
   moves and never crops.

   THIS LAYER IS SCALED, NOT TRANSLATED, AND THAT IS A CORRECTNESS REQUIREMENT
   RATHER THAN A TASTE ONE. Because the origin is top-pinned, all of the scale
   slack sits at the BOTTOM of the frame — so a downward translate lifts the
   image's top edge off the figure and shows the ground colour through it.
   Measured at 26px on a 375 phone and 45px on a tablet before this changed.
   Scale has no such failure mode: from a top-pinned origin it only ever grows
   downward, so the frame stays covered at every value. If you want travel
   here, you cannot have it — move the copy layers instead. */
.has-depth .hero__shot img {
  transform-origin: 50% 0%;
  scale: var(--depth-scale, 1);
  will-change: scale;
}

/* The copy floats forward: it leaves at a different rate than the photograph
   behind it, which is the single strongest depth cue on the page because it
   is the one the eye checks first. */
.has-depth .hero__say,
.has-depth .hero__proof { will-change: translate; }

/* ── Cleanup ─────────────────────────────────────────────────────────────
   will-change is a promise to the compositor, and an unkept one costs memory
   on exactly the cheap phones this site is read on. Released the moment the
   element has finished arriving. */
.has-depth [data-reveal].is-in { will-change: auto; }

/* ── The off switch ──────────────────────────────────────────────────────
   Both the media query and the runtime class, because a visitor can flip
   Reduce Motion mid-session and should not have to reload to be believed. */
@media (prefers-reduced-motion: reduce) {
  .has-depth [data-reveal],
  .has-depth [data-reveal].is-in {
    opacity: 1; transform: none; transition: none;
  }
  .has-depth [data-depth] { translate: none; }
  .has-depth [data-tilt]  { transform: none; transition: none; }
  .has-depth .hero__shot img { scale: 1; }
}
.depth-off [data-reveal] { opacity: 1 !important; transform: none !important; }
.depth-off [data-depth]  { translate: none !important; }
.depth-off [data-tilt]   { transform: none !important; }
.depth-off .hero__shot img { scale: 1 !important; }

/* Motion is screen behaviour. Paper gets none of it. */
@media print {
  [data-reveal] { opacity: 1 !important; transform: none !important; }
  [data-depth]  { translate: none !important; }
  .hero__shot img { scale: 1 !important; }
}

/* ══ QUESTIONS · faq.html and the town list on areas.html ════════════════
   Deliberately NOT an accordion.

   Two reasons, and the second is the real one. A <details> that starts closed
   hides its answer from anyone skimming, and this is the page most likely to be
   read by an assistant answering "is there anything vegetarian at Beachside
   Gyros" — so every answer stays open and in the DOM. And an accordion is the
   template shape for this page; a left-rail group name against a column of
   questions is the editorial one, and it matches the two-column measure the
   menu board and the catering brief already use.

   Shared by faq.html (questions) and areas.html (towns) because the two pages
   are structurally the same object: a labelled group, then a run of
   heading-plus-paragraph. One component, not two that drift. */
.qa {
  max-width: var(--shell); margin: 0 auto;
  padding: 0 var(--page-gutter) var(--space-2xl);
}

/* The jump strip. Four anchors, set as caps chips on the tile field so they
   read as navigation rather than as the page's first sentence. */
.qa__jump {
  max-width: var(--shell); margin: 0 auto;
  padding: 0 var(--page-gutter) var(--space-md);
}
.qa__jump ul {
  display: flex; flex-wrap: wrap; gap: var(--space-2xs);
  list-style: none; padding: 0; margin: 0;
}
.qa__jump a {
  display: inline-block;
  padding: var(--space-2xs) var(--space-sm);
  background: var(--color-paper-2);
  color: var(--color-ink-2); text-decoration: none;
  font-family: var(--font-body);
  font-size: var(--text-xs); font-weight: 700;
  letter-spacing: var(--track-caps); text-transform: uppercase;
  transition: background var(--dur-short) var(--ease-out),
              color var(--dur-short) var(--ease-out);
}
.qa__jump a:hover,
.qa__jump a:focus-visible { background: var(--color-accent); color: var(--color-accent-ink); }

/* The group. The rule is on the TOP of each group rather than the bottom of the
   one before, so the last group does not leave a line hanging under it with
   nothing beneath. */
.qa__group {
  display: grid;
  grid-template-columns: minmax(0, 12rem) minmax(0, 1fr);
  gap: var(--space-md) var(--space-xl);
  align-items: start;
  padding-block: var(--space-xl);
  border-top: var(--rule-hair) solid var(--color-rule);
}

/* The questions sit in their own grid inside column 2, two abreast.

   They used to be SIBLINGS of the group name in the group's own grid, which
   cost twice: each question was forced to its own row, so the page ran as long
   as fifteen stacked answers; and because .qa__a caps at the measure, every
   answer left roughly 260px of empty column beside it. It also needed each
   child pinned to an explicit grid-column, because auto-placement otherwise
   filled row 1 (name, Q1), row 2 (Q2, Q3) and dropped every other question
   into the narrow rail meant for the group name.

   One wrapper element fixes the height, fills the dead column, and deletes the
   pinning hack — the rail is a rail because it is a column with one thing in
   it, not because three rules agree to pretend. */
.qa__list {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--space-lg) var(--space-xl);
  align-items: start;
}
/* Display face, but at --text-lg rather than a display size: this is a label
   for the column beside it, not a headline competing with the h1 above. */
.qa__groupname {
  margin: 0;
  font-family: var(--font-display); font-weight: 400;
  font-size: var(--text-lg); line-height: 1.15;
  color: var(--color-accent);
  text-wrap: balance;
}
/* The mark above a group name. areas.html only — the FAQ's groups get theirs
   inline instead, because there the name is a small caps label rather than a
   display-face heading and a mark on its own line above a 12px label reads as
   a mark with a caption under it.

   ⚠️ It is a child of the h2, not a sibling. See the note in areas.html: the
   group is a grid and a sibling <svg> eats the heading's cell.

   Paler than the heading it introduces. At full accent it competed with the
   name for the eye, and an ornament that wins that fight has stopped being an
   ornament. */
.qa__groupmark {
  display: block; width: 1.7rem; height: auto;
  margin: 0 0 var(--space-2xs);
  color: var(--color-accent); opacity: 0.55;
}

/* Each Q&A. The rhythm is intentionally uneven — a tight gap between a question
   and its own answer, a wide one before the next question — so the pairs group
   by eye without needing a box drawn round them. The wide half is the grid's
   row-gap; only the tight half is set here. */
.qa__q {
  margin: 0 0 var(--space-3xs);
  font-family: var(--font-body); font-weight: 700;
  font-size: var(--text-md); line-height: 1.3;
  color: var(--color-ink);
  text-wrap: balance;
}
.qa__a {
  margin: 0; max-width: var(--measure);
  font-size: var(--text-base); line-height: 1.7;
  color: var(--color-ink-2);
}
.qa__a a { color: var(--color-accent); text-decoration-thickness: 1px; text-underline-offset: 2px; }
.qa__a em { font-style: normal; font-weight: 700; color: var(--color-ink); }

/* The "and here is the page that answers it properly" line. Caps and small, so
   it reads as an exit rather than as a second sentence of the answer. */
.qa__more { margin: var(--space-2xs) 0 0; }
.qa__more a {
  font-family: var(--font-body);
  font-size: var(--text-xs); font-weight: 700;
  letter-spacing: var(--track-caps); text-transform: uppercase;
  color: var(--color-accent); text-decoration: none;
  box-shadow: inset 0 -1px 0 currentColor;
}
.qa__more a:hover { box-shadow: inset 0 -2px 0 currentColor; }

/* ── Drop-down rows (faq.html only) ──────────────────────────────────────
   Native <details>/<summary>, no JavaScript. It opens on Enter and Space,
   reports its own expanded state to a screen reader, and works with JS off —
   all of which a div-and-click-handler accordion has to reimplement badly.

   Scoped to .qa--drops so areas.html, which shares this component, keeps its
   towns open and two-up: a list of places you scan is not a set of questions
   you pick one of. */
.qa--drops .qa__group {
  grid-template-columns: minmax(0, 1fr);
  gap: var(--space-sm);
  padding-block: var(--space-lg);
}

/* ── TWO COLUMNS OF GROUPS, 2026-08-18 ───────────────────────────────────
   *"condense the faq page cuz it requires too long of a scroll."* Fifteen
   questions in four groups ran 1341px at 1440 — half the page — as one column
   of 58px rows down the middle of a 1180px shell, with roughly 500px of empty
   measure either side of every question.

   Nothing is cut and nothing is hidden: the four groups sit two abreast, so
   the rows use the width the page already had. 1341 → ~755.

   Rows, not `columns: 2` — multicol balances unbreakable blocks
   unpredictably, and a jump link has to land on a group that is where the
   reader expects it. Row order stays the DOM order the jump strip lists.

   `align-items: start` because the pairs are different lengths and a stretched
   group would centre its rows in dead space. `row-gap: 0` because each group
   already opens on a hairline — the rule IS the separator, and a gap on top of
   it reads as a second one. */
@media (min-width: 62rem) {
  .qa--drops {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    column-gap: var(--space-2xl);
    row-gap: 0;
    align-items: start;
  }
}
.qa--drops .qa__groupname {
  font-size: var(--text-sm); font-family: var(--font-body); font-weight: 700;
  letter-spacing: var(--track-caps); text-transform: uppercase;
  color: var(--color-ink-2);
  /* Inline-flex so the mark and the caps sit on one baseline row. `gap` rather
     than a margin on the mark, because --track-caps adds a trailing space to
     the last letter of tracked caps and a margin would stack on top of it. */
  display: inline-flex; align-items: center; gap: 0.5rem;
}
/* The mark before a category label. 1.15em, so it scales with the label
   instead of holding a fixed px size that drifts at the breakpoints, and pale
   enough to stay behind the words it introduces.

   `flex: none` is load-bearing: ic-truck is a 32x24 viewBox against three
   24x24s, and without it the flex row would shrink the wide one to fit and the
   truck would sit smaller than its neighbours. */
.qa__namemark {
  flex: none; width: auto; height: 1.15em;
  color: var(--color-accent); opacity: 0.6;
}
.qa__list--rows { grid-template-columns: minmax(0, 1fr); gap: 0; }

/* The row. A hairline between rows and nothing else — no card, no box. */
.qa__list--rows .qa__item { border-top: var(--rule-hair) solid var(--color-rule); }
.qa__list--rows .qa__item:first-child { border-top: 0; }

.qa__sum {
  display: flex; align-items: baseline; gap: var(--space-sm);
  padding: var(--space-sm) 0;
  cursor: pointer;
  list-style: none;                     /* Firefox */
  transition: color var(--dur-short) var(--ease-out);
}
.qa__sum::-webkit-details-marker { display: none; }   /* Safari */
.qa__sum .qa__q { margin: 0; flex: 1; }
.qa__sum:hover .qa__q,
[open] > .qa__sum .qa__q { color: var(--color-accent); }
.qa__sum:focus-visible {
  outline: 2px solid var(--color-accent); outline-offset: 3px;
}

/* The indicator is drawn, not typed: a + built from two 1px bars, the vertical
   one rotating away on open. A "+"/"−" glyph pair jumps because the two
   characters have different widths in this face. */
.qa__sum::after {
  content: ""; flex: none;
  position: relative; width: 0.75rem; height: 0.75rem;
  align-self: center;
  background:
    linear-gradient(var(--color-accent), var(--color-accent)) center/100% 2px no-repeat,
    linear-gradient(var(--color-accent), var(--color-accent)) center/2px 100% no-repeat;
  transition: transform var(--dur-medium) var(--ease-out), opacity var(--dur-short) var(--ease-out);
  opacity: 0.7;
}
.qa__sum:hover::after { opacity: 1; }
/* Rotating 90deg turns the upright bar onto the horizontal one, so the plus
   reads as a minus without swapping anything out. */
[open] > .qa__sum::after { transform: rotate(90deg); }

.qa__body { padding: 0 0 var(--space-md); max-width: var(--measure); }

/* details does not animate its own open/close, and animating height would need
   a fixed value. Fading the body in covers the snap without pretending to. */
[open] > .qa__body { animation: qa-open var(--dur-medium) var(--ease-out) both; }
@keyframes qa-open {
  from { opacity: 0; transform: translate3d(0, -4px, 0); }
  to   { opacity: 1; transform: none; }
}
@media (prefers-reduced-motion: reduce) {
  [open] > .qa__body { animation: none; }
  .qa__sum::after { transition: none; }
}

/* Written from SCHEDULE at runtime. Ships with the sentence already in it, so
   it is never an empty line with JS off. */
.qa__live {
  margin: var(--space-2xs) 0 0;
  font-family: var(--font-body);
  font-size: var(--text-xs); font-weight: 700;
  letter-spacing: var(--track-caps); text-transform: uppercase;
  color: var(--color-ink-2);
}

/* The rail cannot hold a 12rem column and a readable measure on a tablet, so it
   folds to one column and the group name becomes a heading over its own list —
   which is what it is anyway. The two-up questions survive the fold: with the
   rail gone they have the full shell to share, which is wider each than they
   had beside the rail. */
@media (max-width: 60rem) {
  .qa__group { grid-template-columns: minmax(0, 1fr); gap: var(--space-md); }
  .qa__groupname { font-size: var(--text-xl); }
}

/* Below this the two columns would be about 300px each, which is narrower than
   a phrase like "(321) 344-3733" wants to sit on one line. One column. */
@media (max-width: 48rem) {
  .qa__list { grid-template-columns: minmax(0, 1fr); gap: var(--space-lg); }
}

/* ── Current page in the masthead ────────────────────────────────────────
   `markCurrentNav()` in script.js sets BOTH `.is-here` and aria-current, but
   only `.is-here` was ever styled — so find.html, which hard-codes
   aria-current in the markup, showed no current-page marker at all until the
   JS ran, and none whatsoever if it did not. Styling the attribute rather than
   the class fixes it at the source and makes the class redundant rather than
   load-bearing. Kept as one selector list so the two can never diverge. */
.mast__nav a[aria-current="page"] {
  background: var(--color-accent);
  color: var(--color-accent-ink);
}
.mast__nav a[aria-current="page"]:hover {
  background: var(--color-accent);
  color: var(--color-accent-ink);
}
/* ...except the phone number, which is never "the current page" and must not
   pick this up if someone ever marks it. */
.mast__nav .mast__tel[aria-current="page"] { background: none; color: var(--color-accent); }

/* ── The rest of the site · index.html only ──────────────────────────────
   One centred row of chips under the catering strip. Same chip as .qa__jump —
   deliberately, because it is the same object (a short run of links that is
   not the masthead) and a second visual language for it would be noise. The
   only differences are that it centres and that it sits on the plaster rather
   than following a page head. */
/* Inside the plaster section since 2026-08-14: it used to sit between that
   section and the footer on flat paper, which put a band of unstyled off-white
   between two finished surfaces and read as something half-built. */
.wall .morerow { padding-bottom: clamp(var(--space-xl), 4vw, var(--space-3xl)); }
.wall .morerow a {
  /* The default chip is a paper-2 tile, which on plaster reads as three grey
     patches stuck to a wall. On this surface the link is the ink. */
  background: transparent;
  color: var(--color-accent);
  box-shadow: inset 0 -1px 0 oklch(46% 0.13 245 / 0.4);
}
.wall .morerow a:hover,
.wall .morerow a:focus-visible {
  background: var(--color-accent); color: var(--color-accent-ink);
  box-shadow: none;
}

.morerow {
  max-width: var(--shell); margin: 0 auto;
  padding: var(--space-xl) var(--page-gutter) var(--space-2xl);
}
.morerow ul {
  display: flex; flex-wrap: wrap; justify-content: center;
  gap: var(--space-2xs);
  list-style: none; padding: 0; margin: 0;
}
.morerow a {
  display: inline-block;
  padding: var(--space-2xs) var(--space-sm);
  background: var(--color-paper-2);
  color: var(--color-ink-2); text-decoration: none;
  font-family: var(--font-body);
  font-size: var(--text-xs); font-weight: 700;
  letter-spacing: var(--track-caps); text-transform: uppercase;
  transition: background var(--dur-short) var(--ease-out),
              color var(--dur-short) var(--ease-out);
}
.morerow a:hover,
.morerow a:focus-visible { background: var(--color-accent); color: var(--color-accent-ink); }

/* The gallery's 7rem tail is right when the grid is the last thing on the page
   before the footer. gallery.html follows it with a paragraph, and 7rem plus
   the prose block's own 4.5rem stacked into ~11.5rem of blank page between a
   photograph and one sentence. The :has() rule is the nicety; the adjacent
   rule below it does most of the work and needs no :has() support at all. */
.gallery + .prose { padding-top: 0; }
.gallery:has(+ .prose) { padding-bottom: var(--space-xl); }

/* ── Utilities ───────────────────────────────────────────────────────────
   Last in the file on purpose. These have to beat component rules on source
   order, because they are only ever applied ON TOP of a component. */

/* Visually hidden, still read aloud, still indexed. Used for the call sign's
   words, which are painted into the image and so invisible to everything that
   is not a pair of eyes.

   ⚠️ This lived above .callboard once and silently did nothing: the element
   carries BOTH classes, .callboard came later, and its clip-path and padding
   won. The fallback text rendered as a ghost board under the sign. */
.is-tucked {
  position: absolute; width: 1px; height: 1px;
  padding: 0; margin: -1px; border: 0;
  overflow: hidden; clip-path: inset(50%); white-space: nowrap;
}
