/* Storefront design system.
 *
 * Every rule here derives from the theme's CSS custom properties emitted in the
 * storefront layout <head> (--primary, --primary-text, --bg, --accent, --radius,
 * --font-heading, --font-body), so it works for ANY operator colour scheme. The
 * tonal ladder below is the key trick: color-mix() computes washes, tints,
 * hairlines and deep shades from the shop's own colours, giving a full palette
 * from two or three inputs instead of raw-primary-on-flat-white.
 */

body.storefront-body {
  /* ── Tonal ladder ─────────────────────────────────────────────────────── */
  --primary-soft: color-mix(in srgb, var(--primary) 6%, var(--bg));
  --primary-tint: color-mix(in srgb, var(--primary) 13%, var(--bg));
  --primary-line: color-mix(in srgb, var(--primary) 28%, transparent);
  --primary-deep: color-mix(in srgb, var(--primary) 78%, #121212);
  /* Panels sit near-white regardless of page background so menus stay readable
     over dark themes and background images alike. */
  /* --theme-panel (StorefrontTheme#panel_colour) is the page colour lifted
     toward white on a light page, so panel and page share a temperature. */
  --panel: var(--theme-panel, color-mix(in srgb, #ffffff 94%, var(--primary)));
  /* 🔴 --theme-ink comes from StorefrontTheme#body_ink, which checks this
     against the page background and swaps to white/near-black when the
     operator's background would make it unreadable (#498 — an allergen notice
     at 1.04:1 on a dark theme). The color-mix stays as the fallback so any
     surface rendering this sheet without the theme block still gets ink. */
  --ink: var(--theme-ink, color-mix(in srgb, #191b20 94%, var(--primary)));
  --ink-soft: color-mix(in srgb, var(--ink) 55%, #9a9a9a);
  --hairline: color-mix(in srgb, var(--ink) 10%, transparent);
  /* Accents can be light (e.g. lime); pulling them 30% toward ink keeps them
     readable on panels while preserving the hue. */
  /* --accent-ink comes from StorefrontTheme#accent_ink (AA on the panel),
     emitted in the layout's :root and inherited — not re-derived here. */
  /* Corner radius for boxes: follows the button shape but clamped, so "pill"
     buttons don't balloon cards into ellipses. */
  --radius-panel: min(var(--radius), 1rem);
  --radius-row: min(var(--radius), 0.5rem);

  /* 🔴 Inherit the derived ink, don't leave body text on DaisyUI's base token.
     Deriving --ink (#498) fixed every element that names it — the allergen
     notice went 1.04:1 → 17.40:1 on a near-black theme — while body copy stayed
     on `oklch(0.21 …)` from the DaisyUI base and was still dark-on-dark.
     Measured, not assumed: computed body colour was that token on BOTH a white
     and a #1A1A1A background. On a light theme this is a near-black for a
     near-black, so nothing visibly changes; on a dark one it is the difference
     between readable and invisible. */
  color: var(--ink);
}

/* 🔴 Panels carry their OWN ink. --panel is always near-white, but --ink is
   worked out for the PAGE background (StorefrontTheme#body_ink) — on a dark
   page it is white, and every category name, tile title and chip inside a
   panel went white-on-white. Found the first time a shop's page background
   was dark wood (Monty's, design lab 2026-09-24); any dark background had
   it. The derived shades are re-declared here too: a custom property that
   references var(--ink) is resolved where it's DECLARED (on body), so
   changing --ink lower down wouldn't move them. */
body.storefront-body :is(.menu-panel, .card, .sf-sidebar-panel, .sf-catchips a, .sf-tile, .sf-board-tile,
                         .sf-config .modal-box, .sf-page-head, .sf-config-footer, .sf-scrollhint__more, .sf-btn--panel) {
  --ink: color-mix(in srgb, #191b20 94%, var(--primary));
  --ink-soft: color-mix(in srgb, var(--ink) 55%, #9a9a9a);
  --hairline: color-mix(in srgb, var(--ink) 10%, transparent);
  /* Washes are mixed toward the panel, not the page — on a dark page the
     page-based tint is a dark brown, and the active row went dark-on-dark. */
  --primary-soft: color-mix(in srgb, var(--primary) 6%, var(--panel));
  --primary-tint: color-mix(in srgb, var(--primary) 13%, var(--panel));
}

/* ── Utility bar (slim chrome above the masthead) ─────────────────────────── */
.storefront-header .sf-shoplink { font-size: 1rem; }
/* On the menu page the masthead names the shop, so the bar's copy starts
   hidden and fades in once the masthead scrolls behind it (condensed_header
   controller adds .is-visible). JS-off leaves it visible — better a shop named
   twice than not at all. */
@media (scripting: enabled) {
  .sf-shoplink--condensed { opacity: 0; visibility: hidden; transition: opacity 0.18s ease; }
}
.sf-shoplink--condensed.is-visible { opacity: 1; visibility: visible; }
@media (prefers-reduced-motion: reduce) {
  .sf-shoplink--condensed { transition: none; }
}

/* ── Masthead — the shop's fascia board ───────────────────────────────────── */
.sf-masthead {
  /* The gradient shades AWAY from the text colour, never toward it.
     It used to run primary → --primary-deep (a fixed darkening), which on a
     mid-tone brand meant the right-hand end — where the right-aligned fact
     rail sits, ~83% across — was the darkest part of the band. Measured on
     production: dark ink was 4.65 on the primary but 3.20 under the facts,
     i.e. the ink chosen for readability was picked against a colour the text
     doesn't actually sit on. --fascia-motif is the ink's opposite (see
     StorefrontTheme#fascia_motif), so mixing toward it can only raise
     contrast across the band. */
  /* --fascia is the primary unless the logo needed the band to move (a tint,
     a shade, or the logo's own background) — StorefrontTheme#fascia_colour. */
  background: linear-gradient(130deg, var(--fascia, var(--primary)) 20%,
    var(--fascia-end, color-mix(in srgb, var(--fascia, var(--primary)) 86%, var(--fascia-motif, #121212))));
  color: var(--fascia-ink, var(--primary-text));
  position: relative;
  overflow: hidden;
  /* So the motif can sit at z-index -1 (behind the content, in front of the
     band) without escaping the section. */
  isolation: isolate;
}
.sf-masthead--hero { min-height: 16rem; display: flex; align-items: flex-end; }
/* 🔴 Shallower on a phone. 16rem is 30% of a 844px screen, and with the offers
   message and the category board beneath it the first thing a customer could
   ORDER sat right on the fold ("the menu is almost entirely off-screen",
   Graeme, 2026-09-08). The band still holds the lockup, tagline and facts. */
@media (max-width: 639px) { .sf-masthead--hero { min-height: 12rem; } }
.sf-masthead--hero .sf-masthead-inner { position: relative; z-index: 1; }
.sf-masthead-photo {
  position: absolute; inset: 0;
  background-size: cover; background-position: center;
}
/* The shop's own mark, blown up and bled off the trailing edge so it reads as
   texture rather than a second copy of the logo.

   Drawn as a SILHOUETTE, not the logo's own colours: a brand's logo is usually
   in its brand colour, which is usually the primary — so painting it over the
   primary band left it invisible. Masking and filling makes it read as a tonal
   shape whatever the logo is coloured.

   --fascia-motif is deliberately the OPPOSITE of the band's ink (see
   StorefrontTheme#fascia_motif) so the watermark always pushes the band away
   from the text colour. Filling with the ink colour instead measurably broke
   the fact rail's contrast on a mid-tone band.

   Only rendered for logos with real transparency; masking one with a solid
   background would produce a plain rectangle (Account#logo_suits_motif?). */
.sf-masthead-motif {
  position: absolute;
  inset-block: -35%;
  inset-inline-end: -6%;
  width: min(40rem, 62%);
  z-index: -1;
  pointer-events: none;
  background-color: var(--fascia-motif, #000000);
  opacity: 0.1;
  -webkit-mask-repeat: no-repeat;
          mask-repeat: no-repeat;
  -webkit-mask-position: center right;
          mask-position: center right;
  -webkit-mask-size: contain;
          mask-size: contain;
}
@media (max-width: 1023px) {
  /* Hidden on a phone: the band is too short for a watermark to read as
     texture, and it sat directly behind the real logo — double vision. */
  .sf-masthead-motif { display: none; }
}
/* ── Brand patterns across the masthead (StorefrontTheme::PATTERNS) ────────
   Drawn from the theme's own colours, layered over the band's colour. Not on
   a photo masthead — the photo is the texture there. */
/* 🔴 Bold patterns are a TRIM, not a backdrop. Full-width navy-and-white
   stripes behind a detailed logo were "still hard to read" even with the
   logo and name on solid chips: "strong patterns like that need to be used
   carefully" (Graeme, design lab 2026-09-24). So the band stays solid and
   the pattern runs along its bottom edge, like a shop awning — the brand
   cue without the fight. */
body.pattern-stripes .sf-masthead:not(.sf-masthead--hero),
body.pattern-gingham .sf-masthead:not(.sf-masthead--hero) {
  padding-bottom: 1.75rem; /* room for the trim */
}
body.pattern-stripes .sf-masthead:not(.sf-masthead--hero)::after,
body.pattern-gingham .sf-masthead:not(.sf-masthead--hero)::after {
  content: ""; position: absolute; left: 0; right: 0; bottom: 0; height: 1.75rem; z-index: 0;
}
body.pattern-stripes .sf-masthead:not(.sf-masthead--hero)::after {
  /* In the brand's pattern colour on the PAGE colour — the flyer's green on
     its own cream, not always primary-on-white (Dr Foster). */
  background: repeating-linear-gradient(90deg, var(--pattern, var(--primary)) 0 1.4rem, var(--bg, #ffffff) 1.4rem 2.8rem);
}
body.pattern-gingham .sf-masthead:not(.sf-masthead--hero)::after {
  background:
    repeating-linear-gradient(90deg, color-mix(in srgb, var(--pattern, var(--primary)) 55%, transparent) 0 0.55rem, transparent 0.55rem 1.1rem),
    repeating-linear-gradient(0deg, color-mix(in srgb, var(--pattern, var(--primary)) 55%, transparent) 0 0.55rem, transparent 0.55rem 1.1rem),
    var(--bg, #ffffff);
}
body.pattern-pinstripe .sf-masthead:not(.sf-masthead--hero) {
  background-image: repeating-linear-gradient(90deg,
    transparent 0 0.9rem, color-mix(in srgb, var(--fascia-motif) 9%, transparent) 0.9rem 1.05rem);
}
body.pattern-gingham .sf-masthead:not(.sf-masthead--hero) {
  background:
    repeating-linear-gradient(90deg, color-mix(in srgb, var(--primary) 55%, transparent) 0 1.2rem, transparent 1.2rem 2.4rem),
    repeating-linear-gradient(0deg, color-mix(in srgb, var(--primary) 55%, transparent) 0 1.2rem, transparent 1.2rem 2.4rem),
    #ffffff;
}
body.pattern-dots .sf-masthead:not(.sf-masthead--hero) {
  background-image: radial-gradient(color-mix(in srgb, var(--fascia-motif) 12%, transparent) 18%, transparent 20%);
  background-size: 1.1rem 1.1rem;
}

/* 🔴 A photo masthead's text sits on the SCRIM, not on the band, so it can't
   take --fascia-ink (worked out against the band colour — Theme::LogoStaging
   may have made that white or lime, and the name then printed dark on a dark
   photo: design lab, 2026-09-24). The scrim is always dark — the brand's deep
   shade pulled well toward black, so even a lime brand's scrim is dark — and
   the text over it is always white. */
.sf-masthead--hero {
  --scrim: color-mix(in srgb, var(--primary-deep) 35%, #0b0b0b);
  --fascia-ink: #ffffff;
  color: #ffffff;
}
/* The bar above a textured statement joins it, rather than a pale strip of
   brand colour sitting on dark wood (Deli-H). */
body.storefront-body:has(.sf-masthead--textured) .storefront-header {
  background-color: color-mix(in srgb, var(--primary-deep) 30%, #141414);
  color: #ffffff;
}
.sf-masthead-scrim {
  position: absolute; inset: 0;
  background:
    /* 🔴 A second wash from the trailing edge. The vertical gradient alone is
       12–35% up where the fact rail sits at lg+ (plain right-aligned lines, no
       pill), so "Collection by 10am" was near-white type over a photograph's
       own highlights. A scrim, not a text-shadow: shadows fake contrast and
       fail on a light background. */
    linear-gradient(
      to left,
      color-mix(in srgb, var(--scrim, var(--primary-deep)) 62%, transparent) 0%,
      color-mix(in srgb, var(--scrim, var(--primary-deep)) 20%, transparent) 38%,
      transparent 62%
    ),
    linear-gradient(
      to top,
      color-mix(in srgb, var(--scrim, var(--primary-deep)) 90%, transparent) 0%,
      color-mix(in srgb, var(--scrim, var(--primary-deep)) 55%, transparent) 55%,
      color-mix(in srgb, var(--scrim, var(--primary-deep)) 35%, transparent) 100%
    );
}
/* 🔴 The masthead and the menu beneath it are two containers that MUST agree
   about width, and until 2026-09-11 they did not. The masthead is 72rem;
   classic lays its content out at 72rem too, but tabbed, scroll and billboard
   all use 56rem. Measured at a 1440px viewport that put the logo plate 128px
   to the LEFT of the menu and the opening-hours facts 128px to the RIGHT of
   it — both edges at once, which is what makes it read as broken rather than
   as a deliberately wide header. Classic always lined up, which is why the
   report ("the logo's left hand edge should line up with the shop below, same
   on the right") was hard to reproduce.

   One value, declared where the layout is known and read by both, rather than
   two numbers that happen to match today. spec/system/storefront_masthead_edges_spec.rb
   measures every layout and fails if they part company again. */
.storefront-body { --sf-content-max: 72rem; }
.storefront-body[data-layout="tabbed"],
.storefront-body[data-layout="scroll"],
.storefront-body[data-layout="billboard"] { --sf-content-max: 56rem; }

.sf-masthead-inner {
  width: 100%; max-width: var(--sf-content-max, 72rem); margin-inline: auto;
  padding: 1.6rem 1rem 1.5rem;
  display: flex; flex-direction: column; align-items: flex-start; gap: 1rem;
}
.sf-masthead-brand { display: flex; flex-direction: column; align-items: flex-start; gap: 0.6rem; min-width: 0; }
@media (min-width: 1024px) {
  /* Brand left, facts right — the band stays shallow and the right half isn't
     dead space. */
  .sf-masthead-inner {
    padding: 2rem 1rem 1.9rem;
    flex-direction: row; align-items: center; justify-content: space-between; gap: 2.5rem;
  }
  .sf-masthead--hero .sf-masthead-inner { padding-top: 3.75rem; }
}
body.header-centre .sf-masthead-inner { flex-direction: column; align-items: center; justify-content: flex-start; text-align: center; }
body.header-centre .sf-masthead-brand { align-items: center; }
body.header-centre .sf-masthead-lockup { flex-direction: column; align-items: center; }
body.header-centre .sf-masthead-facts { align-items: center; text-align: center; }

/* Logo above the name on a phone, side by side once there's room. */
.sf-masthead-lockup {
  display: flex; flex-direction: column; align-items: flex-start; gap: 0.75rem;
  color: inherit; text-decoration: none; min-width: 0;
}
@media (min-width: 640px) {
  .sf-masthead-lockup { flex-direction: row; align-items: center; gap: 1.15rem; }
}

/* 🔴 On a phone the logo stacks above the name, and a left-aligned stack is a
   small logo stranded in the corner over a big name (West, and eight more of
   32 lab shops, Graeme 2026-09-25). So on a phone the masthead is centred
   whatever the header alignment; "left" takes effect from 640px, where logo
   and name sit side by side. */
@media (max-width: 639.98px) {
  .sf-masthead-inner, .sf-masthead-brand, .sf-masthead-lockup { align-items: center; text-align: center; }
  .sf-masthead-facts { justify-content: center; }
}

.sf-shopname {
  font-family: var(--font-display, var(--font-heading));
  font-size: clamp(1.75rem, 4vw, 2.6rem);
  font-weight: 700;
  line-height: 1.04;
  letter-spacing: -0.015em;
  text-wrap: balance;
}
/* A wordmark logo already spells the name, so showing it twice is noise — but
   the heading stays in the DOM for search engines and screen readers. */
.sf-shopname--hidden {
  position: absolute; width: 1px; height: 1px;
  margin: -1px; padding: 0; border: 0;
  overflow: hidden; white-space: nowrap; clip-path: inset(50%);
}

/* One height cap cannot serve every logo: at max-height 3.5rem a wide wordmark
   renders ~300px across while a square roundel renders ~68px — the same rule
   flattering one and shrinking the other to nothing. Size by what the logo
   actually IS; Theme::LogoProfiler measures it at upload and the partial emits
   the class as [data-logo]. Unprofiled logos keep the old conservative cap. */
.sf-masthead-logo { display: block; width: auto; height: auto; max-width: min(70vw, 20rem); max-height: 3.5rem; }
[data-logo="wordmark"] .sf-masthead-logo { max-height: 4.25rem; max-width: min(78vw, 24rem); }
[data-logo="combo"]    .sf-masthead-logo { max-height: 4.75rem; max-width: min(72vw, 20rem); }
[data-logo="emblem"]   .sf-masthead-logo { max-height: 5rem;    max-width: 5rem; }
[data-logo="tall"]     .sf-masthead-logo { max-height: 5.5rem;  max-width: 8rem; }
@media (min-width: 1024px) {
  .sf-masthead-logo { max-height: 4.25rem; }
  [data-logo="wordmark"] .sf-masthead-logo { max-height: 5rem; }
  [data-logo="combo"]    .sf-masthead-logo { max-height: 5.75rem; }
  [data-logo="emblem"]   .sf-masthead-logo { max-height: 7rem; max-width: 7rem; }
  [data-logo="tall"]     .sf-masthead-logo { max-height: 6.5rem; max-width: 10rem; }
}

/* When the logo carries the name alone (the heading beside it is hidden), it
   IS the brand block, and gets the room the name would have had. At the
   emblem cap a detailed crest (Honywood's) or a symbol with small caps under
   it (Margo's) became an unreadable smudge (design lab, 2026-09-24). */
.sf-masthead-lockup:has(.sf-shopname--hidden) .sf-masthead-logo { max-height: 7rem; max-width: min(84vw, 26rem); }
@media (min-width: 640px) {
  .sf-masthead-lockup:has(.sf-shopname--hidden) .sf-masthead-logo { max-height: 9.5rem; max-width: 32rem; }
  /* Over a photo the band is taller (16rem), and a logo at the banner's size
     looked lost in it (Graeme, 2026-09-24) — it fills most of the height. */
  .sf-masthead--hero .sf-masthead-lockup:has(.sf-shopname--hidden) .sf-masthead-logo { max-height: 12rem; max-width: 36rem; }
}

/* The sign-board plate is a last resort: Theme::LogoStaging moves the band
   rather than boxing the logo, and only a logo it couldn't measure (or an
   operator's explicit choice) gets a plate. --plate is white or near-black,
   whichever the logo's inks read on; no plate → body.logo-no-plate. */
.sf-logoplate {
  display: inline-flex; align-items: center; justify-content: center;
  background: var(--plate, #ffffff);
  padding: 0.55rem 0.8rem;
  border-radius: min(var(--radius), 0.9rem);
  box-shadow: 0 2px 10px -6px rgb(0 0 0 / 0.35);
}
/* A roundel needs even padding all round, or the plate reads as an off-centre
   rectangle behind a circle. */
[data-logo="emblem"] .sf-logoplate { padding: 0.6rem; border-radius: min(var(--radius), 1.1rem); }
body.logo-no-plate .sf-logoplate { background: transparent; padding: 0; box-shadow: none; }
.storefront-header .sf-logoplate { padding: 0.2rem 0.45rem; border-radius: min(var(--radius), 0.45rem); box-shadow: none; }
body.logo-no-plate .storefront-header .sf-logoplate { padding: 0; }

/* --fascia-ink is the theme's text colour, or white/near-black when that
   colour only clears AA for large text — the tagline and facts are small, so
   they can't inherit the heading's contrast allowance. Set in the layout. */
.sf-tagline {
  font-size: 1rem;
  color: var(--fascia-ink, var(--primary-text));
  max-width: 46ch;
  text-wrap: balance;
}

/* Wrapping pills on a phone; a right-aligned column of plain lines on desktop,
   so the band reads as a fascia with a notice board rather than a row of tags. */
.sf-masthead-facts {
  display: flex; flex-wrap: wrap; gap: 0.45rem;
  list-style: none; margin: 0; padding: 0;
  color: var(--fascia-ink, var(--primary-text));
}
.sf-fact {
  display: inline-flex; align-items: center; gap: 0.4rem;
  font-size: 0.8rem; font-weight: 600; letter-spacing: 0.01em;
  padding: 0.3rem 0.7rem;
  border-radius: 999px;
  background: color-mix(in srgb, currentColor 14%, transparent);
  border: 1px solid color-mix(in srgb, currentColor 26%, transparent);
}
/* Only the closed state gets a marker — it's the one a customer needs to
   notice. Dotting "open" too just adds a stray bullet to the list. */
/* A tappable fact (a phone number): the fact's own colour, underlined. */
.sf-fact-link { color: inherit; text-decoration: underline; text-underline-offset: 2px; }
.sf-fact--closed::before {
  content: ""; flex: none;
  width: 0.45rem; height: 0.45rem; border-radius: 999px;
  background: currentColor;
}
@media (min-width: 1024px) {
  .sf-masthead-facts {
    flex: none;
    flex-direction: column; align-items: flex-end; gap: 0.3rem;
    text-align: right;
  }
  .sf-fact {
    padding: 0; border: 0; background: none;
    font-size: 0.9rem; font-weight: 500;
  }
  /* A centred header has no side to pin a notice board to: stacked, five
     catering facts ran a column down the middle of the band (Dr Foster).
     One wrapping line, dot-separated, under the logo. */
  body.header-centre .sf-masthead-facts {
    flex-direction: row; flex-wrap: wrap; justify-content: center;
    column-gap: 0; row-gap: 0.3rem; max-width: 60rem;
  }
  body.header-centre .sf-masthead-facts > .sf-fact:not(:last-child)::after {
    content: "·"; margin-inline: 0.65rem; opacity: 0.55;
  }
}

/* DaisyUI's .label never wraps, so side-by-side fields ran their captions
   into each other at checkout ("Company or organisation (optional)" over
   "Your PO number", Graeme 2026-09-24). A caption wraps like any text. */
body.storefront-body form .label { white-space: normal; }
body.storefront-body form .label-text { overflow-wrap: anywhere; }
/* A caption beside its field (the configurator's "How many people?"). */
.sf-config .form-control:has(> input[type="number"]) { gap: 0.25rem; }

/* ── Scroll layout: the basket card beside the menu ──────────────────────────
   The menu column is 56rem, centred (so the masthead lines up with it); the
   card is 18rem and wants a 1.5rem gap and 1rem of window edge. On a wide
   window the card sits beside the centred menu — pinned to the window's edge,
   it left a gulf between the two (Graeme, 2026-09-24). Below 97rem there
   isn't room for both centred, so the menu is nudged left just enough:
     menu right = (100vw - pad)/2 + 28rem  ≤  100vw - 1rem - 18rem - 1.5rem
     ⇒ pad ≥ 97rem - 100vw;   card right = 50vw - 28rem - 1.5rem - 18rem
   (the menu panel sits 1rem inside its column, so the visible gap is 2.5rem) */
@media (min-width: 1024px) {
  /* …and never more than the card's own zone, or a narrow window shrank the
     menu and reopened the gap on the other side. */
  .sf-scroll-with-basket { padding-right: clamp(0px, calc(97rem - 100vw), 20.5rem); }
  .sf-scroll-basket { right: max(1rem, calc(50vw - 47.5rem)); }
}

/* 🔴 "+ A different address" at checkout reveals the new-address fields.
   The view said "CSS-only via :has()", but no rule existed: a customer with
   saved addresses who picked it got nothing to type into (found writing the
   postcode-estimate spec, 2026-09-25). */
[data-controller~="address-lookup"]:has(input[name="order[saved_address_id]"][value=""]:checked) [data-checkout-target="newAddress"] {
  display: block;
}

/* ── Menu panels — a printed-menu section, not floating cards ─────────────── */
.menu-panel {
  background: var(--panel);
  color: var(--ink);
  border-radius: var(--radius-panel);
  overflow: hidden;
}
/* .card treatments (cards-flat / cards-outlined body classes) target .card, so
   panels carry both classes; shadowed default lives here. */
.menu-panel.card { box-shadow: 0 1px 2px rgb(0 0 0 / 0.05), 0 6px 18px -8px rgb(0 0 0 / 0.14); }
body.cards-flat .menu-panel.card { box-shadow: none; }
body.cards-outlined .menu-panel.card { box-shadow: none; border: 1px solid var(--primary-line); }

/* The category photo strip over a menu panel (shared/_menu_section). */
.menu-panel-banner {
  display: block; width: 100%; aspect-ratio: 1200 / 340; object-fit: cover;
  border-radius: var(--radius-panel) var(--radius-panel) 0 0;
}
.menu-panel-head { padding: 1.1rem 1.25rem 0.85rem; border-bottom: 2px solid var(--primary-line); }
.menu-panel-eyebrow {
  font-size: 0.68rem; font-weight: 700;
  letter-spacing: 0.14em; text-transform: uppercase;
  color: var(--accent-ink);
  margin-bottom: 0.15rem;
}
.menu-panel-title {
  font-family: var(--font-heading);
  font-size: 1.45rem; font-weight: 700; line-height: 1.15;
  letter-spacing: -0.01em;
}
.menu-panel-desc { font-size: 0.88rem; color: var(--ink-soft); margin-top: 0.3rem; max-width: 60ch; }

.menu-rows { list-style: none; margin: 0; padding: 0; }
.menu-row {
  display: flex; align-items: center; gap: 1rem;
  padding: 0.85rem 1.25rem;
}
.menu-row + .menu-row { border-top: 1px solid var(--hairline); }
.menu-row:hover { background: var(--primary-soft); }
.menu-row-text { flex: 1; min-width: 0; }
.menu-row-name { font-weight: 600; line-height: 1.3; }
.menu-row-desc {
  font-size: 0.85rem; color: var(--ink-soft); line-height: 1.35; margin-top: 0.1rem;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
}
.menu-row-price {
  font-variant-numeric: tabular-nums;
  font-weight: 700; font-size: 0.95rem;
  color: var(--accent-ink);
  white-space: nowrap;
}
.menu-row-price .from { font-weight: 500; font-size: 0.78rem; color: var(--ink-soft); }

/* Compact density — tighter rows, one-line descriptions. */
body.menu-compact .menu-row { padding-block: 0.5rem; }
body.menu-compact .menu-row-desc { -webkit-line-clamp: 1; }
body.menu-compact .menu-panel-head { padding-block: 0.85rem 0.6rem; }

/* ── Category navigation (Classic sidebar) ───────────────────────────────── */
.sf-eyebrow {
  font-size: 0.68rem; font-weight: 700;
  letter-spacing: 0.14em; text-transform: uppercase;
  color: var(--ink-soft);
  margin-bottom: 0.55rem;
}
.sf-catnav ul { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 2px; }
.sf-catnav a {
  display: flex; align-items: center; gap: 0.65rem;
  padding: 0.55rem 0.75rem;
  border-left: 3px solid transparent;
  border-radius: 0 var(--radius-row) var(--radius-row) 0;
  font-size: 0.92rem;
  color: var(--ink);
  transition: background 0.12s ease;
}
.sf-catnav a:hover { background: var(--primary-soft); }
.sf-catnav a.active {
  border-left-color: var(--primary);
  background: var(--primary-tint);
  font-weight: 650;
}
/* Sidebar sits on the page background (which may be an image) — give the nav a
   panel of its own so it always reads. */
.sf-sidebar-panel {
  background: var(--panel);
  color: var(--ink);
  border-radius: var(--radius-panel);
  padding: 1rem 0.85rem;
}
body.cards-outlined .sf-sidebar-panel { border: 1px solid var(--primary-line); }
.menu-panel.card, .sf-sidebar-panel { backdrop-filter: none; }

/* Fallback category thumb — the category's initial set in the heading face, so
   shops with no photography still get visual rhythm. */
.sf-thumb {
  /* 🔴 2.25rem rendered at about 32 device pixels — a photograph of lunch,
     shown too small to tell what it is. The sidebar row is the one place a
     Classic shop's category photography appears at all, so it gets a size a
     picture can survive. */
  width: 3rem; height: 3rem; flex-shrink: 0;
  border-radius: var(--radius-row);
  object-fit: cover;
}
/* A category still waiting for its photo, beside others that have one. */
.sf-thumb--empty { display: inline-block; background: var(--primary-tint); }
/* Text-only category list (no category has a photo yet): rows sized for text
   rather than for a 3rem thumbnail. */
.sf-catnav--text a { min-height: 0; padding-block: 0.6rem; }
.sf-thumb--initial {
  display: inline-flex; align-items: center; justify-content: center;
  background: var(--primary-tint);
  color: var(--primary-deep);
  font-family: var(--font-heading);
  font-weight: 700; font-size: 1.25rem;
}

/* ── Mobile category chips (Classic below lg) ─────────────────────────────── */
.sf-catchips {
  display: flex; gap: 0.45rem; overflow-x: auto;
  padding: 0.6rem 1rem;
  scrollbar-width: none;
  -webkit-overflow-scrolling: touch;
}
.sf-catchips::-webkit-scrollbar { display: none; }
.sf-catchips a {
  flex-shrink: 0;
  font-size: 0.85rem; font-weight: 600;
  padding: 0.42rem 0.95rem;
  border-radius: 999px;
  background: var(--panel);
  color: var(--ink);
  border: 1px solid var(--hairline);
  white-space: nowrap;
}
.sf-catchips a.active {
  background: var(--primary);
  color: var(--primary-text);
  border-color: var(--primary);
}

/* Variants: tab bar (Tabbed layout) and mini anchor nav (Scroll layout) share
   the chip vocabulary at different sizes. */
.sf-catchips--tabs { padding-inline: 0; }
.sf-catchips--tabs a { font-size: 0.9rem; padding: 0.5rem 1.1rem; }
.sf-catchips--mini { padding-inline: 0; }
.sf-catchips--mini a { font-size: 0.78rem; padding: 0.3rem 0.75rem; }

/* 🔴 ONE offset for every layout's category bar, and it tracks the header.
   All three hardcoded Tailwind's `top-12` (48px) against a header that is 50px
   — and taller still on iOS, where the header carries
   `pt-[env(safe-area-inset-top)]`. A hardcoded offset can never be right on a
   device with a notch, which is precisely where the shells run. */
body.storefront-body { --sf-header-h: calc(3.125rem + env(safe-area-inset-top)); }
/* 🔴 ...and the header IS that tall, rather than hoping its content adds up
   to it. It measured 48px against the bars' 50px offset, leaving a 2px slit
   the page scrolled through under every sticky bar (Graeme, 2026-09-24).
   box-sizing: border-box, so the safe-area padding is inside the height. */
.storefront-header { box-sizing: border-box; min-height: var(--sf-header-h); display: flex; align-items: center; }
.storefront-header > div { width: 100%; }

.sf-stickybar {
  top: var(--sf-header-h);
  /* 🔴 OPAQUE, and the blur is only a fallback for browsers that ignore it.
     This was 86% + backdrop-blur, which reads fine over a photograph and fails
     over the thing it actually sits on: a white menu card carrying black text.
     14% of black text is a legible grey ghost, so headings scrolled visibly
     THROUGH the bar (Graeme, 2026-09-08). A sticky bar's whole job is to stay
     readable over whatever passes beneath it, so it cannot be translucent. */
  background: var(--bg);
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
  /* Reads as the menu's own header rather than a band floating over the page. */
  border-bottom: 1px solid var(--hairline);
}

/* ── Category grid tiles (Classic "browse all") ───────────────────────────── */
.sf-tile {
  display: block; overflow: hidden;
  background: var(--panel);
  color: var(--ink);
  border-radius: var(--radius-panel);
  transition: transform 0.15s ease, box-shadow 0.15s ease;
}
.sf-tile:hover { transform: translateY(-2px); box-shadow: 0 10px 24px -10px rgb(0 0 0 / 0.25); }
.sf-tile-image { aspect-ratio: 4 / 3; background: var(--primary-tint); overflow: hidden; }
.sf-tile-initial {
  width: 100%; height: 100%;
  display: flex; align-items: center; justify-content: center;
  font-family: var(--font-heading);
  font-weight: 700; font-size: 2.6rem;
  color: var(--primary-deep);
  background:
    radial-gradient(circle at 20% 15%, color-mix(in srgb, var(--primary) 18%, transparent), transparent 55%),
    var(--primary-tint);
}

/* ── Menu-board teaser (a category with no photo) ────────────────────────────
   The first few items, set like a chalkboard list on the brand tint. Replaced
   the giant-initial placeholder — see shared/_category_teaser. */
.sf-teaser {
  width: 100%; height: 100%;
  display: flex; flex-direction: column; justify-content: center; gap: 0.3rem;
  padding: 1rem 1.1rem;
  /* The brand colour itself, solid. A pale tint (and before it a hatched
     one) read as "unfinished" in every design-lab review; a grid of photos
     and confident colour blocks reads as deliberate, like a magazine page.
     --primary-ink is the small-text-safe ink on the primary. */
  background: var(--primary);
  color: var(--primary-ink, var(--primary-text));
  font-family: var(--font-heading);
  overflow: hidden;
}
.sf-teaser-item {
  font-size: 0.98rem; font-weight: 600; line-height: 1.2;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
/* Menu-board lines, not a dashed list — the "— A — B + 3 more" look read as a
   truncated array rather than a menu (design-lab review). */
.sf-teaser-item + .sf-teaser-item { border-top: 1px solid color-mix(in srgb, currentColor 22%, transparent); padding-top: 0.3rem; }
/* Full ink, no opacity: faded "+3 more" failed contrast on the tint. */
.sf-teaser-more { font-family: var(--font-body); font-size: 0.8rem; font-weight: 600; margin-top: 0.2rem; }
/* No items worth listing: the category's own name, large, is the tile. */
.sf-teaser-name { font-family: var(--font-display, var(--font-heading)); font-size: 1.5rem; font-weight: 700; line-height: 1.1; }
@media (max-width: 639px) {
  .sf-teaser { padding: 0.75rem; gap: 0.2rem; }
  .sf-teaser-item { font-size: 0.85rem; }
}
.sf-tile--wide .sf-tile-image { aspect-ratio: 16 / 5; }
.sf-teaser--board { padding: 0.5rem 0.6rem; gap: 0.1rem; }
.sf-teaser--board .sf-teaser-item { font-size: 0.72rem; }
.sf-teaser--board .sf-teaser-item::before { content: none; }

/* The lead tile without a photo: the brand colour itself, not a grey plate.
   --primary-ink is the small-text-safe ink ON the primary (StorefrontTheme
   #primary_ink), because the teaser lines are body-size text. */
.sf-tile--typeset {
  background: var(--primary);
  color: var(--primary-ink, var(--primary-text));
}
.sf-lead-typeset { padding: 1.6rem 1.75rem 1.5rem; }
.sf-lead-name { font-family: var(--font-display, var(--font-heading)); font-size: clamp(1.6rem, 3.4vw, 2.3rem); font-weight: 700; line-height: 1.05; }
.sf-lead-desc { margin-top: 0.35rem; opacity: 0.9; max-width: 60ch; }
.sf-tile--typeset .sf-teaser--lead {
  background: none; color: inherit; padding: 0; margin-top: 0.9rem;
  flex-direction: row; flex-wrap: wrap; gap: 0.35rem 1.1rem; height: auto;
}
.sf-tile--typeset .sf-teaser--lead .sf-teaser-item { font-size: 1.02rem; }
.sf-tile--typeset .sf-teaser--lead .sf-teaser-item + .sf-teaser-item { border-top: 0; padding-top: 0; }

/* ── Storefront footer ────────────────────────────────────────────────────
   🔴 This was `text-base-content/40` on the page background, with no ground of
   its own. Over a background photograph it all but vanished — "depending on
   the design, the 'powered by pekkish' footer can almost disappear" (Graeme,
   2026-09-08) — and the shop's own Offers and Opening-hours links went with
   it, which is worse: they are the footer's real job. An opaque band and a
   themed soft ink, for the same reason the sticky category bar is opaque.

   `margin-top: auto` in the body's column pins it to the bottom of the
   viewport on a short menu, instead of stranding it halfway down the page. */
body.storefront-body { display: flex; flex-direction: column; }
/* 🔴 A flex item's automatic minimum size is its CONTENT, so making the body a
   column turned every horizontally-scrolling rail into a page-wide overflow:
   Classic's mobile category bar measured 956px inside a 390px phone, and the
   whole page scrolled sideways. `min-width: 0` lets an item be narrower than
   what is inside it, which is exactly what a rail with `overflow-x: auto`
   wants.

   `width: 100%` for the other half of it: a flex item with an AUTO margin in
   the cross axis does not stretch, it is sized to fit-content — so Classic's
   root, which is `max-w-6xl mx-auto`, sized itself to the widest thing inside
   it and ignored the 390px it was sitting in. min-width alone fixed nothing,
   which is how the cause was found.

   Deliberately ONLY these two. This selector has form: it used to also set
   `position: relative; z-index: 1` here, which silently clobbered the header's
   `sticky` on every shop with a background image. Nothing about position or
   stacking belongs in it. */
body.storefront-body > * { min-width: 0; width: 100%; }
.sf-footer {
  margin-top: auto;
  padding: 1.5rem 1rem calc(1.5rem + env(safe-area-inset-bottom));
  text-align: center;
  font-size: 0.75rem;
  color: var(--ink-soft);
  background: var(--bg);
  border-top: 1px solid var(--hairline);
}
.sf-footer a { color: inherit; }
.sf-footer a:hover { color: var(--ink); }

/* ── Billboard: the board ─────────────────────────────────────────────────
   Billboard's navigation and its photography are the same object. A rail on a
   phone, where three stacked rows of tiles would push the food off the screen
   (which is exactly what the old hero did); an even grid from sm up. */
.sf-board {
  display: grid;
  grid-auto-flow: column;
  /* 🔴 ONE ROW, whatever the category count. `minmax(min, 1fr)` shares the
     width between four tiles and holds the minimum for twelve, so a big menu
     scrolls sideways instead of stacking rows of photographs between the shop
     and its food ("what happens if there are more than the screen is wide?",
     Graeme, 2026-09-08). The board's height is then a constant. */
  grid-auto-columns: minmax(8.5rem, 1fr);
  gap: 0.75rem;
  overflow-x: auto;
  scroll-snap-type: x proximity;
  scrollbar-width: none;
  -webkit-overflow-scrolling: touch;
  /* Room for the tiles' lift on hover, and for the shadow under them. */
  padding-block: 0.15rem 0.35rem;
}
.sf-board::-webkit-scrollbar { display: none; }
@media (max-width: 639px) { .sf-board { grid-auto-columns: minmax(44%, 1fr); } }

.sf-board-tile {
  display: flex; flex-direction: column;
  overflow: hidden;
  border-radius: var(--radius-panel);
  background: var(--panel);
  color: var(--ink);
  scroll-snap-align: start;
  box-shadow: 0 1px 2px rgb(0 0 0 / 0.05), 0 6px 18px -8px rgb(0 0 0 / 0.14);
  transition: transform 0.15s ease, box-shadow 0.15s ease;
}
body.cards-flat .sf-board-tile { box-shadow: none; }
body.cards-outlined .sf-board-tile { box-shadow: none; border: 1px solid var(--primary-line); }
.sf-board-tile:hover { transform: translateY(-2px); box-shadow: 0 10px 24px -10px rgb(0 0 0 / 0.25); }

.sf-board-photo {
  display: block; position: relative;
  aspect-ratio: 3 / 2;
  background: var(--primary-tint);
  overflow: hidden;
}
.sf-board-img { display: block; width: 100%; height: 100%; object-fit: cover; }
/* The shared initial plate, scaled for a tile this size. */
.sf-board-photo .sf-tile-initial { font-size: 1.9rem; }

.sf-board-label { display: block; padding: 0.55rem 0.7rem 0.6rem; }
.sf-board-name {
  display: block;
  font-family: var(--font-heading);
  font-weight: 700; font-size: 0.95rem; line-height: 1.2;
}

/* The board, condensed. Revealed by condensed_nav_controller once the board
   has scrolled past the header, and never on screen at the same time as it —
   two rows of the same category names at the top of the page read as a
   mistake. FIXED, not sticky: it comes and goes while the reader is scrolling,
   and a sticky bar entering and leaving the flow would jump the menu under
   their thumb. */
.sf-condensednav {
  position: fixed;
  top: var(--sf-header-h);
  left: 0; right: 0;
  z-index: 40;
  background: var(--bg);
  border-bottom: 1px solid var(--hairline);
  padding-block: 0.4rem;
  transform: translateY(-110%);
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
  transition: transform 0.18s ease, opacity 0.18s ease, visibility 0.18s;
}
.sf-condensednav.is-visible {
  transform: none; opacity: 1; visibility: visible; pointer-events: auto;
}
@media (prefers-reduced-motion: reduce) { .sf-condensednav { transition: none; } }

/* Same names, same face, same corner as the tiles they stand in for — the two
   sets read as one thing seen twice, not two navigations. */
.sf-catchips--board { padding-inline: 0; padding-block: 0.15rem; }
.sf-catchips--board a {
  font-family: var(--font-heading);
  font-weight: 700;
  font-size: 0.82rem;
  padding: 0.3rem 0.8rem;
  border-radius: var(--radius-row);
  background: var(--panel);
  border: 1px solid var(--hairline);
}

/* ── Configurator modal (Customise) ───────────────────────────────────────── */
.sf-config .modal-box {
  background: var(--panel);
  color: var(--ink);
  border-radius: var(--radius-panel);
  font-family: var(--font-body);
}
/* A page's heading and intro on a plate. Every page but the menu opens with
   an <h1> and a line under it, and on a shop with a background photograph
   (Billboard) they sat straight on the picture and could not be read —
   offers, find-us, the checkout's "Your order", the sign-up form's labels
   (customer walk, 2026-09-18). The account pages had already solved it with
   the same panel; this is that treatment as one class. */
.sf-page-head {
  background: var(--panel);
  border-radius: var(--radius-panel);
  padding: 1rem 1.25rem;
  margin-bottom: 1.5rem;
}

/* The configurator rendered INLINE, where a category's only item is a
   build-your-own (#375). The modal supplies .modal-box padding; on the page the
   panel has to supply its own, and the title sits under the panel head rather
   than under a dialog's close button. */
.sf-config--inline { padding: 1rem 1.25rem 1.25rem; }
.sf-config--inline .sf-config-group:first-of-type { border-top: 0; padding-top: 0; margin-top: 0.75rem; }

.sf-config-title {
  font-family: var(--font-heading);
  font-size: 1.5rem; font-weight: 700; line-height: 1.15;
  letter-spacing: -0.01em;
}
.sf-config-desc { font-size: 0.88rem; color: var(--ink-soft); margin-top: 0.25rem; }
.sf-config-group { margin-top: 1.1rem; padding-top: 1rem; border-top: 1px solid var(--hairline); }
.sf-config-group-name {
  font-size: 0.72rem; font-weight: 700;
  letter-spacing: 0.12em; text-transform: uppercase;
  color: var(--accent-ink);
}
.sf-config-required {
  font-size: 0.66rem; font-weight: 700;
  letter-spacing: 0.06em; text-transform: uppercase;
  padding: 0.12rem 0.5rem; border-radius: 999px;
  background: var(--primary-tint);
  color: var(--primary-deep);
}
.sf-config-option {
  display: flex; align-items: flex-start; gap: 0.75rem;
  padding: 0.5rem 0.6rem;
  border-radius: var(--radius-row);
  cursor: pointer;
}
.sf-config-option:hover { background: var(--primary-soft); }
.sf-config-option .modifier {
  font-variant-numeric: tabular-nums;
  font-size: 0.85rem; font-weight: 600;
  color: var(--accent-ink);
  flex-shrink: 0;
}
/* The running price and the Add button, pinned to the bottom of the scroll
   box (the dialog) or the screen (inline). Negative margins pull it out to
   the box's edges so it covers what scrolls under it; the box's padding is
   1.5rem in the modal and 1.25rem inline. Chrome measures a sticky offset
   from the scroll box's CONTENT edge, so `bottom: 0` left the footer sitting
   on the box's bottom padding; the negative offset takes it to the edge.
   Inline the scroll box is the viewport, which has no padding. */
.sf-config-footer {
  position: sticky;
  bottom: -1.5rem;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.75rem;
  margin: 1.25rem -1.5rem -1.5rem;
  padding: 0.75rem 1.5rem;
  background: var(--panel);
  border-top: 1px solid var(--hairline);
}
.sf-config--inline .sf-config-footer {
  bottom: 0;
  margin: 1.25rem -1.25rem -1.25rem;
  padding: 0.75rem 1.25rem;
}
.sf-config-footer .sf-btn { flex-shrink: 0; }
.sf-config-summary { min-width: 0; }
.sf-config-summary-options {
  font-size: 0.8rem;
  color: var(--ink-soft);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.sf-config-summary .total {
  font-family: var(--font-heading);
  font-variant-numeric: tabular-nums;
  font-size: 1.15rem;
  font-weight: 800;
}

/* ── Reduced motion ───────────────────────────────────────────────────────── */
@media (prefers-reduced-motion: reduce) {
  .sf-tile, .sf-catnav a { transition: none; }
  .sf-tile:hover { transform: none; }
}

/* ---------------------------------------------------------------------------
   Basket pill "item added" announcement
   ---------------------------------------------------------------------------
   The pill grows downward to expose a message, then shrinks back.

   🔴 The reveal panel is ABSOLUTELY POSITIONED behind the pill's face, so the
   pill only APPEARS to grow — its layout box never changes and the header keeps
   its height. The predecessor to this was a message in the document flow, which
   pushed the masthead down 48px on every add and let it spring back.

   Collapsed it is exactly the height of the face, which covers it completely;
   expanding it below the face is what "exposes" the message. min-width rather
   than a fixed width so a longer message grows leftward instead of clipping. */
.sf-pill { position: relative; display: inline-flex; }

.sf-pill__face {
  position: relative;
  z-index: 1;
  transition: background-color 220ms ease, border-color 220ms ease, color 220ms ease;
}

.sf-pill__reveal {
  position: absolute;
  top: 0;
  right: 0;
  min-width: 100%;
  height: 100%;
  z-index: 0;
  display: flex;
  align-items: flex-end;
  justify-content: center;
  padding: 0 0.5rem 0.28rem;
  border-radius: var(--radius-field, 0.5rem);
  background: var(--color-success);
  color: var(--color-success-content);
  font-size: 0.68rem;
  font-weight: 600;
  line-height: 1;
  white-space: nowrap;
  overflow: hidden;
  opacity: 0;
  pointer-events: none;
  transition: height 260ms cubic-bezier(0.2, 0.8, 0.3, 1), opacity 140ms ease;
}

.sf-pill--announcing .sf-pill__reveal { height: calc(100% + 1.45rem); opacity: 1; }

/* The face flashes to the same confirm colour, so the whole pill reads as one
   object changing state rather than a coloured tab appearing behind a button. */
.sf-pill--announcing .sf-pill__face {
  background-color: var(--color-success);
  border-color: var(--color-success);
  color: var(--color-success-content);
}

@media (prefers-reduced-motion: reduce) {
  .sf-pill__reveal, .sf-pill__face { transition-duration: 1ms; }
}

/* ---------------------------------------------------------------------------
   Mobile navigation drawer
   ---------------------------------------------------------------------------
   The shop's own pages were reachable only from a footer link at the bottom of
   the menu. They live here now, along with the opening/collection detail that
   used to eat the top of every phone screen. */
.sf-drawer__backdrop {
  position: fixed; inset: 0; z-index: 60;
  background: rgb(0 0 0 / 0.4);
  opacity: 0; visibility: hidden;
  transition: opacity 200ms ease, visibility 200ms;
}
.sf-drawer--open .sf-drawer__backdrop { opacity: 1; visibility: visible; }

.sf-drawer__panel {
  position: fixed; top: 0; left: 0; bottom: 0; z-index: 61;
  width: min(19rem, 84vw);
  display: flex; flex-direction: column;
  background: var(--panel, #fff);
  color: var(--ink);
  /* The drawer reaches the top of the screen, so IT carries the status-bar
     inset — the header no longer does once this is over it. Only the topmost
     element may, or you get a dead strip. */
  padding-top: env(safe-area-inset-top);
  padding-bottom: env(safe-area-inset-bottom);
  /* 🔴 No shadow while closed. The panel parks at translateX(-100%), just off
     the left edge, and a 40px shadow still reaches back onto the page — a
     grey smear down the left of every storefront, desktop and phone, that
     read as a template bug (design-lab review, 2026-09-24). */
  box-shadow: none;
  transform: translateX(-100%);
  transition: transform 240ms cubic-bezier(0.2, 0.8, 0.3, 1);
  overflow-y: auto;
}
.sf-drawer--open .sf-drawer__panel { transform: translateX(0); box-shadow: 0 0 40px -8px rgb(0 0 0 / 0.35); }

.sf-drawer__head {
  display: flex; align-items: center; justify-content: space-between; gap: 0.5rem;
  padding: 0.75rem 0.75rem 0.75rem 1.15rem;
  border-bottom: 1px solid var(--hairline);
}
.sf-drawer__title { font-weight: 700; font-size: 1rem; }

.sf-drawer__links { list-style: none; margin: 0; padding: 0.5rem 0; }
.sf-drawer__links--secondary { border-top: 1px solid var(--hairline); margin-top: auto; }
.sf-drawer__links a {
  display: block;
  padding: 0.85rem 1.15rem;
  font-weight: 600; font-size: 0.95rem;
  /* A comfortable tap target on a phone, and room for a chevron. */
  min-height: 2.75rem;
}
.sf-drawer__links a:active { background: color-mix(in srgb, var(--primary) 10%, transparent); }
.sf-drawer__links a::after { content: "›"; float: right; opacity: 0.35; font-weight: 400; }
.sf-drawer__links--secondary a { font-weight: 500; }

@media (prefers-reduced-motion: reduce) {
  .sf-drawer__panel, .sf-drawer__backdrop { transition-duration: 1ms; }
}

/* ---------------------------------------------------------------------------
   Masthead facts on a phone
   ---------------------------------------------------------------------------
   🔴 Only the FIRST fact survives, plus anything marked closed. FasciaFacts
   already emits them most-decisive-first (the ordering deadline leads), so the
   one a customer needs before ordering is the one that stays; the rest are a
   tap away in the drawer. A "closed today" fact is an exception state and is
   never hidden — that is the one time the top of the screen has to argue with
   the customer's assumption that they can order. */
@media (max-width: 639px) {
  .sf-masthead-facts > .sf-fact:nth-child(n + 2):not(.sf-fact--closed) { display: none; }
}

/* ---------------------------------------------------------------------------
   "There is more this way" on the category rail
   ---------------------------------------------------------------------------
   The rail hides its scrollbar, which left nothing at all to say the categories
   continue past the edge. Both the fade and the chevron are driven by measured
   scroll position, so neither appears on a rail that already fits — a permanent
   arrow pointing at nothing is worse than none. */
.sf-scrollhint { position: relative; }

.sf-scrollhint::before,
.sf-scrollhint::after {
  content: ""; position: absolute; top: 0; bottom: 0; width: 2.75rem;
  pointer-events: none; opacity: 0;
  transition: opacity 160ms ease;
  z-index: 1;
}
.sf-scrollhint::before {
  left: 0;
  background: linear-gradient(to right, var(--bg) 15%, transparent);
}
.sf-scrollhint::after {
  right: 0;
  background: linear-gradient(to left, var(--bg) 35%, transparent);
}
.sf-scrollhint--back::before { opacity: 1; }
.sf-scrollhint--more::after  { opacity: 1; }

.sf-scrollhint__more {
  position: absolute; right: 0.25rem; top: 50%;
  transform: translateY(-50%);
  z-index: 2;
  display: none;
  align-items: center; justify-content: center;
  width: 1.75rem; height: 1.75rem;
  border-radius: 999px;
  background: var(--panel);
  color: var(--ink);
  border: 1px solid var(--hairline);
  box-shadow: 0 1px 6px -2px rgb(0 0 0 / 0.35);
}
.sf-scrollhint__more svg { width: 0.9rem; height: 0.9rem; }
.sf-scrollhint--more .sf-scrollhint__more { display: flex; }

@media (prefers-reduced-motion: reduce) {
  .sf-scrollhint::before, .sf-scrollhint::after { transition-duration: 1ms; }
}

/* Sign out sits at the foot of the drawer — least-used control, and it was
   costing the shop's logo room in the header. */
.sf-drawer__signout {
  border-top: 1px solid var(--hairline);
  padding: 0.5rem 0;
}
.sf-drawer__signout-button {
  display: block;
  width: 100%;
  text-align: left;
  padding: 0.85rem 1.15rem;
  min-height: 2.75rem;
  font-size: 0.95rem;
  font-weight: 500;
  color: var(--ink);
  opacity: 0.75;
}
.sf-drawer__signout-button:active {
  background: color-mix(in srgb, var(--primary) 10%, transparent);
}

/* ── Allergens ─────────────────────────────────────────────────────────────
   Customer-facing allergen lines (docs/allergens.md). Quiet but never faint:
   this is safety information, so it stays above the description's weight and
   keeps a real contrast ratio rather than the 40% ink used for asides. */
.sf-allergens {
  margin-top: 0.25rem;
  font-size: 0.75rem;
  line-height: 1.35;
  color: var(--ink);
  opacity: 0.75;
}
.sf-allergens-label {
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.04em;
}
/* ── The saving, said out loud ─────────────────────────────────────────────
   A meal deal used to be a quiet green row in the checkout's arithmetic and
   nothing at all on the menu's basket. The customer should know they are
   getting a good deal (#468): a tinted panel on the theme's primary, the
   amount in bold, and the way through to which items made it. */
.sf-saving {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0.25rem 0.5rem;
  padding: 0.5rem 0.75rem;
  border-radius: 10px;
  background: color-mix(in srgb, var(--primary) 12%, transparent);
  border: 1px solid color-mix(in srgb, var(--primary) 30%, transparent);
  font-size: 0.8125rem;
  color: var(--ink);
}
.sf-saving__label { flex: 1 1 auto; }
.sf-saving__amount { font-weight: 700; white-space: nowrap; }
.sf-saving__link {
  text-decoration: underline;
  text-decoration-style: dotted;
  text-underline-offset: 2px;
  white-space: nowrap;
}

/* Minimum quantity (docs/minimum-quantity.md). The label sits under the item
   wherever it is offered; the shortfall marks the basket lines of a product
   that has not reached its minimum yet. Amber mixed INTO the shop's own
   background, so the ink already computed for --bg stays readable on it. */
.sf-minimum {
  font-size: 0.8125rem;
  font-weight: 600;
  color: var(--ink);
}
.sf-minimum-short {
  padding: 0.5rem 0.75rem;
  border-radius: 10px;
  background: color-mix(in srgb, #f59e0b 14%, var(--bg));
  border: 1px solid #b45309;
  font-size: 0.8125rem;
  color: var(--ink);
}
.sf-minimum-short strong { font-weight: 700; }
.sf-minimum-line {
  border-left: 3px solid #b45309;
  padding-left: 0.5rem;
}

/* The page's one "ask before you order" line (#462): the same size and
   contrast as a declared allergen — never fainter — but italic and unlabelled,
   so it reads as a note, not a warning on every row. */
/* 🔴 No opacity here, deliberately (#498). This is the one line on the page
   the shop is legally responsible for, and `opacity: 0.75` is a straight
   contrast tax on it: measured, it halves the ratio — 16.61 → 7.27 on white,
   and on a mid-grey background a readable 4.35 becomes a failing 3.16. The
   quiet treatment Graeme asked for (not a banner, #462) is carried by the
   size and the italic, which cost nothing a customer with an allergy needs.
   --ink is itself contrast-derived now; see StorefrontTheme#body_ink. */
.sf-allergens-notice {
  margin: 0.5rem 0;
  font-size: 0.75rem;
  line-height: 1.35;
  color: var(--ink);
  font-style: italic;
}

/* "Popular" — the shop-wide favourites marker on a menu row.
 *
 * Quiet on purpose. It sits beside the product name on a list where several
 * items may carry it, so it has to read as a footnote rather than a shout; a
 * loud badge repeated eight times down a menu stops meaning anything. Takes the
 * shop's own tint so it belongs to their palette rather than ours. */
.sf-popular {
  display: inline-block;
  margin-left: 0.45em;
  padding: 0 0.5em;
  border-radius: 999px;

  /* 🔴 SOLID, not a 13% tint. The first version used --primary-tint on the
     shop's own background and vanished into it — "a little too subtle. I
     missed it the first time" (Graeme, 2026-09-10).
     --primary-deep is the ladder's darkened step, so it is dark whatever the
     shop's primary is and white sits on it safely. Picking the shop's --accent
     instead would read better on some palettes and fail WCAG on others, because
     nothing tells us whether a given shop's accent is light or dark. */
  background: var(--primary-deep);
  color: #fff;

  font-size: 0.72em;
  font-weight: 700;
  letter-spacing: 0.03em;
  text-transform: uppercase;
  vertical-align: 0.1em;
  white-space: nowrap;

  /* 🔴 line-height 1 and a DELIBERATELY uneven padding, because the label is
     all capitals. A line box reserves room for descenders; no capital uses it,
     so symmetric padding leaves the ink sitting high in the pill — measured at
     4px above and 6px below on an 18px pill, which is exactly the "slightly
     off" Graeme spotted. The extra 0.1em on top is about half a descender and
     puts the caps on the optical centre. */
  line-height: 1;
  padding-top: 0.32em;
  padding-bottom: 0.22em;
}

/* ── Buttons — the storefront's own (#400's themed variant, seeded by #353) ──
   DaisyUI's `btn` is retired from views, and `.pk-btn` is the PLATFORM's teal:
   putting it on a storefront would repaint a tenant's shop in Pekkish colours.
   So this is the storefront's button — the shop's --primary and the ink the
   theme chose to sit on it (--primary-text carries the AA reasoning already;
   StorefrontTheme picks it).

   🔴 The radius is the THEME's, not a pill. The theme editor offers square /
   rounded / pill as "button shape", and DaisyUI's buttons followed it through
   --rounded-btn; the first cut of this class was a fixed 999px and silently
   overrode a setting the operator can see. Same for --circle, which is round
   by definition and the one exception.

   The default height, paddings and text sizes are DaisyUI v5's, so the
   migration from `btn` is one-to-one and every page keeps its rhythm:
     btn            → sf-btn              (primary; bare `btn` → --neutral)
     btn-ghost      → --quiet             btn-outline → --outline
     btn-sm/xs/lg   → --sm/--xs/--lg      btn-circle  → --circle
     btn-block      → --block

   🔴 In @layer components, where DaisyUI's .btn lived — NOT unlayered. A
   layered rule loses to every Tailwind utility (which sit in @layer
   utilities, ordered last by application.css), and that is what makes
   `lg:hidden`, `text-error`, `w-full`, `absolute` on a button keep working.
   The first cut was unlayered and outranked all of them: the phone drawer's
   hamburger appeared on desktop and "Remove" lost its red. */
@layer components {
  .sf-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 0.35rem;
    min-height: 2.5rem;
    padding: 0.5rem 1rem;
    border-radius: var(--radius, 0.6rem);
    border: 1px solid transparent;
    background: var(--primary);
    color: var(--primary-text);
    font-weight: 600;
    font-size: 0.875rem;
    line-height: 1.25;
    cursor: pointer;
    text-decoration: none;
    white-space: nowrap;
    transition: background-color 150ms ease, color 150ms ease;
  }
  .sf-btn:hover { background: var(--primary-deep); }
  /* The menu's Add / Customise buttons — the brand's second colour where it
     makes a good button (StorefrontTheme#action_colour). */
  .sf-btn--action { background: var(--action, var(--primary)); color: var(--action-text, var(--primary-text)); }
  .sf-btn--action:hover { background: color-mix(in srgb, var(--action, var(--primary)) 84%, #121212); }
  .sf-btn:focus-visible { outline: 2px solid var(--primary); outline-offset: 2px; }
  .sf-btn:disabled, .sf-btn[aria-disabled="true"] { opacity: 0.5; cursor: not-allowed; pointer-events: none; }

  /* On the shop's own primary (the confirmation page's referral card): the
     two tokens swap, so the button is the one thing on the card that reads as
     a control rather than as more of the card. */
  .sf-btn--inverse { background: var(--primary-text); color: var(--primary); }
  .sf-btn--inverse:hover { background: color-mix(in srgb, var(--primary-text) 88%, var(--primary)); }

  /* Neutral: DaisyUI's colourless `btn` — a quiet solid, the page's own ink
     on a light wash of it. Secondary actions that still want a surface. */
  .sf-btn--neutral {
    background: color-mix(in srgb, currentColor 8%, transparent);
    color: inherit;
  }
  .sf-btn--neutral:hover { background: color-mix(in srgb, currentColor 14%, transparent); }

  /* Outline: transparent with a hairline of the surrounding ink. Holds on a
     white card and on the primary card alike. */
  /* On the panel colour, for a control that sits on the page background —
     which on a shop with a photograph is the photograph. "← Back to the menu"
     was a bare text link on the picture (customer walk, 2026-09-18). */
  .sf-btn--panel {
    background: var(--panel);
    color: var(--ink);
    border: 1px solid var(--hairline);
  }
  .sf-btn--panel:hover { background: color-mix(in srgb, var(--panel) 92%, var(--ink)); }

  .sf-btn--outline {
    background: transparent;
    color: inherit;
    border-color: color-mix(in srgb, currentColor 28%, transparent);
  }
  .sf-btn--outline:hover { background: color-mix(in srgb, currentColor 8%, transparent); }

  /* Quiet (DaisyUI's ghost): no border, no surface until hovered — icon
     buttons, dismissals, a row's edit affordance. */
  .sf-btn--quiet {
    background: transparent;
    color: inherit;
  }
  .sf-btn--quiet:hover { background: color-mix(in srgb, currentColor 8%, transparent); }

  /* Sizes — DaisyUI v5's, so nothing moves on migration. */
  .sf-btn--sm { min-height: 2rem;   padding: 0.25rem 0.75rem;  font-size: 0.75rem; }
  .sf-btn--xs { min-height: 1.5rem; padding: 0.125rem 0.5rem;  font-size: 0.6875rem; }
  .sf-btn--lg { min-height: 3rem;   padding: 0.625rem 1.25rem; font-size: 1.125rem; }
  .sf-btn--block { width: 100%; }

  /* Icon-only: equal sides, round whatever the theme's radius says. */
  .sf-btn--circle { padding: 0; width: 2.5rem; aspect-ratio: 1; border-radius: 999px; }
  .sf-btn--circle.sf-btn--sm { width: 2rem; }
  .sf-btn--circle.sf-btn--xs { width: 1.5rem; }
  .sf-btn--circle.sf-btn--lg { width: 3rem; }
}

/* ── Modern layout (storefront/layouts/_modern) ─────────────────────────────
   One column, image-led. Everything here is scoped to the layout so the
   shared section and row partials keep their look elsewhere. */
.storefront-body[data-layout="modern"] { --sf-content-max: 64rem; }

/* One band, not two: the utility bar takes the masthead's own colour with no
   shadow, so the header and masthead read as a single fascia. */
body[data-layout="modern"] .storefront-header {
  background-color: var(--fascia, var(--primary));
  box-shadow: none;
}
/* Bigger, left-aligned type — the name is the brand moment. */
body[data-layout="modern"] .sf-shopname { font-size: clamp(2.2rem, 5.5vw, 3.8rem); letter-spacing: -0.02em; line-height: 1; }
body[data-layout="modern"] .sf-masthead-inner { padding-block: 1.5rem 1.75rem; }

.sf-modern-inner { max-width: 64rem; margin-inline: auto; padding-inline: 1rem; }

.sf-modern-nav {
  top: var(--sf-header-h);
  background: var(--bg);
  border-bottom: 1px solid var(--hairline);
}
.sf-modern-tabs {
  position: relative;
  display: flex; gap: 1.6rem;
  overflow-x: auto; scrollbar-width: none;
  padding-block: 0.9rem 0.8rem;
}
.sf-modern-tabs::-webkit-scrollbar { display: none; }
.sf-modern-tabs a {
  flex-shrink: 0; white-space: nowrap;
  font-weight: 600; font-size: 0.95rem;
  /* The page ink at 72%, not --ink-soft (mixed toward grey): on a dark page
     that grey made the inactive tabs near-invisible. */
  color: color-mix(in srgb, var(--ink) 72%, transparent);
  transition: color 0.2s ease;
}
.sf-modern-tabs a:hover, .sf-modern-tabs a[aria-current] { color: var(--ink); }
.sf-modern-tabs a:focus-visible { outline: 2px solid var(--primary); outline-offset: 4px; border-radius: 2px; }
.sf-modern-underline {
  position: absolute; left: 0; bottom: 0; height: 3px; width: 0;
  background: var(--primary); border-radius: 3px 3px 0 0;
  transition: transform 0.28s cubic-bezier(0.2, 0.8, 0.3, 1), width 0.28s cubic-bezier(0.2, 0.8, 0.3, 1);
}
@media (prefers-reduced-motion: reduce) { .sf-modern-underline, .sf-modern-tabs a { transition: none; } }

.sf-modern-section { padding-top: 2.25rem; scroll-margin-top: calc(var(--sf-header-h) + 3.5rem); }

/* The section itself sits on the page, not in a card: heading first, then the
   category photo as a wide frame, then the items. It therefore takes the
   PAGE's ink; the item cards below are panels and take the panel ink. */
body[data-layout="modern"] .sf-modern-section .menu-panel {
  --ink: var(--theme-ink, #191b20);
  --ink-soft: color-mix(in srgb, var(--ink) 60%, transparent);
  background: transparent; box-shadow: none; border: 0; border-radius: 0; overflow: visible;
  display: flex; flex-direction: column;
}
body[data-layout="modern"] .sf-modern-section .menu-panel-head { order: -1; padding: 0 0 1rem; border: 0; }
body[data-layout="modern"] .sf-modern-section .menu-panel-title {
  font-family: var(--font-display, var(--font-heading));
  font-size: clamp(1.6rem, 3.2vw, 2.25rem); line-height: 1.05; letter-spacing: -0.01em;
}
body[data-layout="modern"] .sf-modern-section .menu-panel-desc { max-width: 60ch; margin-top: 0.35rem; }
body[data-layout="modern"] .sf-modern-section .menu-panel-banner {
  aspect-ratio: 21 / 6; max-height: 14rem; border-radius: min(var(--radius), 1.25rem); margin-bottom: 1rem;
}

/* Items as a grid of cards. */
body[data-layout="modern"] .sf-modern-section .menu-rows {
  display: grid; gap: 0.75rem; grid-template-columns: 1fr;
}
/* Two-up only where a card is wide enough for a long sandwich name —
   "Herefordshire Beef, Creamed Horseradish and Local Salad Leaves" wrapped
   to three lines at tablet width. */
@media (min-width: 1024px) {
  body[data-layout="modern"] .sf-modern-section .menu-rows { grid-template-columns: 1fr 1fr; }
}
body[data-layout="modern"] .sf-modern-section .menu-row {
  --ink: color-mix(in srgb, #191b20 94%, var(--primary));
  --ink-soft: color-mix(in srgb, var(--ink) 55%, #9a9a9a);
  color: var(--ink);
  background: var(--panel);
  /* A visible edge on any page colour: an 8% hairline vanished on a pale
     teal page (Deli-H). */
  border: 1px solid color-mix(in srgb, var(--ink) 14%, transparent);
  border-radius: min(var(--radius), 1rem);
  padding: 1rem 1.05rem;
  align-items: center;
}
/* Full descriptions — catering items keep terms like "priced per person"
   in them — and a tap target that meets the 44px guidance. */
body[data-layout="modern"] .sf-modern-section .menu-row-desc { -webkit-line-clamp: 4; }
body.menu-catering .menu-row-desc,
body.menu-catering[data-layout="modern"] .sf-modern-section .menu-row-desc { -webkit-line-clamp: unset; display: block; overflow: visible; }
/* On a phone the full list beside the price was a 170px column of broken
   lines; the price and button drop beneath it instead, right-aligned. */
@media (max-width: 559px) {
  body.menu-catering .menu-row { flex-wrap: wrap; row-gap: 0.5rem; justify-content: flex-end; }
  body.menu-catering .menu-row-text { flex-basis: 100%; }
}
body[data-layout="modern"] .sf-modern-section .menu-row .sf-btn { min-height: 2.75rem; padding-inline: 1.1rem; font-size: 0.9rem; }
body[data-layout="modern"] .sf-modern-section .menu-row .sf-btn--circle { min-width: 2.75rem; }
body[data-layout="modern"] .sf-modern-section .menu-row + .menu-row { border-top: 1px solid color-mix(in srgb, var(--ink) 14%, transparent); }
body[data-layout="modern"] .sf-modern-section .menu-row:hover { background: var(--panel); border-color: var(--primary-line); }
body[data-layout="modern"] .sf-modern-section .menu-row-name { font-size: 1.02rem; }
body.cards-flat[data-layout="modern"] .sf-modern-section .menu-row { border-color: transparent; }
body:not(.cards-flat):not(.cards-outlined)[data-layout="modern"] .sf-modern-section .menu-row { box-shadow: 0 1px 2px rgb(0 0 0 / 0.04); }

/* A product's own photo, where the shop has one. */
.menu-row-photo { width: 5.5rem; height: 5.5rem; object-fit: cover; border-radius: min(var(--radius), 0.75rem); flex-shrink: 0; order: 3; }


/* ── Statement masthead (masthead_style "statement") ────────────────────────
   The shop's own line, large, in its display face; its photo fills the right
   half when it has one. Replaces a tall band holding a small centred logo —
   "lots of wasted space" (Graeme, design lab 2026-09-24). */
.sf-masthead--statement {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  background: var(--fascia, var(--primary));
  color: var(--fascia-ink, var(--primary-text));
}
.sf-statement-text {
  width: 100%; max-width: var(--sf-content-max, 72rem); margin-inline: auto;
  padding: 1.5rem 1rem 1.6rem;
  display: flex; flex-direction: column; align-items: flex-start; gap: 1rem;
}
.sf-statement-lockup { display: flex; align-items: center; gap: 0.85rem; }
/* Big enough to read: a strong badge at 56px became a favicon (Deli on the
   Quay, design-lab review). */
.sf-statement-lockup .sf-masthead-logo { max-height: 5.5rem; max-width: 16rem; }
.sf-statement-name { font-family: var(--font-body); font-size: 1.05rem; font-weight: 700; letter-spacing: 0.01em; }
.sf-statement {
  font-family: var(--font-display, var(--font-heading));
  font-weight: 800;
  font-size: clamp(2.3rem, 6vw, 4.4rem);
  line-height: 0.98;
  letter-spacing: -0.02em;
  max-width: 14ch;
  text-wrap: balance;
}
.sf-statement--long { font-size: clamp(1.9rem, 4.4vw, 3.3rem); max-width: 18ch; }
.sf-statement--longer { font-size: clamp(1.6rem, 3.4vw, 2.6rem); max-width: 24ch; line-height: 1.05; }
.sf-statement-facts { align-items: flex-start; text-align: left; }
.sf-statement { color: var(--statement-ink, inherit); }
/* A wordmark with no photo beside it: the logo leads, large, and the stack is
   centred at every width — a printed-menu masthead, not a column squeezed
   beside a wide logo (Dr Foster). */
.sf-masthead--statement[data-logo="wordmark"]:not(.sf-masthead--split) .sf-statement-text { align-items: center; text-align: center; padding-block: 2rem 2.25rem; }
.sf-masthead--statement[data-logo="wordmark"]:not(.sf-masthead--split) .sf-statement-lockup { justify-content: center; }
.sf-masthead--statement[data-logo="wordmark"]:not(.sf-masthead--split) .sf-statement-lockup .sf-masthead-logo { max-height: clamp(4.5rem, 10vw, 8.5rem); max-width: min(40rem, 88vw); }
.sf-masthead--statement[data-logo="wordmark"]:not(.sf-masthead--split) .sf-statement { max-width: 26ch; }
.sf-masthead--statement[data-logo="wordmark"]:not(.sf-masthead--split) .sf-statement-facts { align-items: center; text-align: center; }
.sf-statement-photo { background-size: cover; background-position: center; min-height: 14rem; position: relative; }
/* A soft fade at the top of the photo, so the floating bar's icons read over
   a light photograph. */
/* 🔴 Strong enough for TEXT, not just icons: at 45% over 7rem, "Delivery &
   how to order" and "Account" vanished into The Cheese & Pickle's pale
   linen (Graeme, 2026-09-24). The links also carry a shadow while the bar
   floats — see the floating bar below. */
.sf-statement-photo::before {
  content: ""; position: absolute; inset: 0 0 auto 0; height: 9rem;
  background: linear-gradient(rgb(0 0 0 / 0.62), rgb(0 0 0 / 0.28) 55%, transparent);
}
/* A site texture behind the whole statement, under a dark wash so the text
   and logo stay readable — the painted-sign look. */
.sf-masthead--textured {
  background:
    linear-gradient(color-mix(in srgb, var(--primary-deep) 55%, transparent), color-mix(in srgb, #0b0b0b 45%, transparent)),
    var(--statement-texture) center / cover;
  --fascia-ink: #ffffff;
  color: #ffffff;
}
.sf-statement-logo { display: none; }
/* The logo sits BESIDE the statement from 560px — not just on desktop. At
   tablet widths the stacked version left a small logo over half an empty band
   (Francombs), and fine lettering all but vanished (The Sandwich Shop):
   Graeme, 2026-09-24. Scaled with the screen so it fills its half. */
@media (min-width: 560px) {
  .sf-masthead--logo-beside {
    grid-template-columns: minmax(0, 1fr) auto;
    align-items: center;
    padding-inline: max(1rem, calc((100vw - var(--sf-content-max, 72rem)) / 2 + 1rem));
  }
  .sf-masthead--logo-beside .sf-statement-text { max-width: none; margin: 0; padding-inline: 0; }
  .sf-statement-logo { display: block; padding: 1.5rem 0 1.5rem clamp(1rem, 3vw, 2rem); }
  .sf-statement-logo img { max-height: clamp(6.5rem, 20vw, 13rem); max-width: 38vw; width: auto; height: auto; }
  .sf-masthead--logo-beside .sf-statement-lockup .sf-logoplate { display: none; }
}
/* On a phone the logo leads, stacked above the statement — big enough that
   fine lettering survives. */
@media (max-width: 559px) {
  .sf-statement-lockup .sf-masthead-logo { max-height: 7.5rem; max-width: 70vw; }
  /* Centred on a phone: a round badge hugging the left edge above a
     full-width headline looked lost (The Terrace, Graeme 2026-09-24). The
     headline follows so the stack shares one axis. */
  .sf-statement-text { align-items: center; text-align: center; }
  .sf-statement-lockup { justify-content: center; }
  .sf-statement-facts { align-items: center; text-align: center; }
  /* Tall and square marks carry their lettering small inside the mark —
     The Sandwich Shop's letter-spaced name vanished at 6.5rem. */
  .sf-masthead--statement[data-logo="tall"] .sf-statement-lockup .sf-masthead-logo,
  .sf-masthead--statement[data-logo="emblem"] .sf-statement-lockup .sf-masthead-logo { max-height: 9.5rem; max-width: 60vw; }
}
/* A split with no line to state: the logo IS the panel's content, so it
   carries the half the headline would have (Marvin's red wordmark on its own
   colour beside its own sandwiches, design lab batch 4). */
.sf-masthead--split:not(:has(.sf-statement)) .sf-statement-lockup .sf-masthead-logo { max-height: clamp(7rem, 14vw, 12rem); max-width: min(26rem, 80vw); }
@media (min-width: 900px) {
  .sf-masthead--split { grid-template-columns: minmax(0, 1.15fr) minmax(0, 1fr); }
  .sf-masthead--split .sf-statement-text { max-width: none; margin: 0; padding: 2rem 2.5rem 2rem max(1rem, calc((100vw - var(--sf-content-max, 72rem)) / 2 + 1rem)); justify-content: center; }
  .sf-masthead--split .sf-statement-photo { min-height: 100%; }
  .sf-statement { font-size: clamp(2.4rem, 4vw, 4.2rem); }
  .sf-statement.sf-statement--long { font-size: clamp(2rem, 3.4vw, 3.4rem); }
  .sf-statement.sf-statement--longer { font-size: clamp(1.7rem, 2.6vw, 2.6rem); }
}


/* ── Floating bar over a split statement (desktop) ──────────────────────────
   Graeme, 2026-09-24: "the one thing holding it back is the menu bar above
   the image". The masthead slides up under the sticky bar so its photo runs
   to the top of the page, and while the masthead is in view the bar is
   transparent (condensed_header_controller adds .masthead-in-view). Scroll
   past and the bar is solid again. Without JS the bar just stays solid over
   the photo's top edge — the text keeps its clearance either way. */
/* 🔴 Only when the masthead comes STRAIGHT after the bar (header + main).
   Anything between them — the demo-shop notice, an error, "Not live yet" —
   was slid over and hidden, and the photo stopped short of the top, so the
   see-through bar showed the page with white icons on it (lab shops,
   2026-09-25). With a banner there, the bar stays solid and nothing moves. */
@media (min-width: 900px) {
  body.storefront-body:has(.storefront-header + main .sf-masthead--split) .sf-masthead--split { margin-top: calc(-1 * var(--sf-header-h)); }
  body.storefront-body:has(.storefront-header + main .sf-masthead--split) .sf-masthead--split .sf-statement-text { padding-top: calc(var(--sf-header-h) + 1.75rem); }
  body.storefront-body.masthead-in-view:has(.storefront-header + main .sf-masthead--split) .storefront-header,
  .masthead-in-view:has(.storefront-header + main .sf-masthead--split) .storefront-header {
    background-color: transparent;
    box-shadow: none;
    color: #ffffff;
    text-shadow: 0 1px 3px rgb(0 0 0 / 0.55);
    transition: background-color 0.2s ease;
  }
  .masthead-in-view:has(.storefront-header + main .sf-masthead--split) .storefront-header svg { filter: drop-shadow(0 1px 2px rgb(0 0 0 / 0.5)); }
}

/* A photographed sign as the logo: a small mark beside the typed name. */
.sf-masthead--photo-logo .sf-masthead-logo { max-height: 3rem !important; max-width: 9rem !important; border-radius: 0.4rem; }
