/* 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. */
  --panel: color-mix(in srgb, #ffffff 94%, var(--primary));
  --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: color-mix(in srgb, var(--accent) 65%, var(--ink));
  /* 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);
}

/* ── 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. */
  background: linear-gradient(130deg, var(--primary) 20%,
    color-mix(in srgb, var(--primary) 86%, var(--fascia-motif, #121212)));
  color: 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) {
  .sf-masthead-motif { width: 78%; opacity: 0.08; }
}
.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(--primary-deep) 62%, transparent) 0%,
      color-mix(in srgb, var(--primary-deep) 20%, transparent) 38%,
      transparent 62%
    ),
    linear-gradient(
      to top,
      color-mix(in srgb, var(--primary-deep) 90%, transparent) 0%,
      color-mix(in srgb, var(--primary-deep) 48%, transparent) 55%,
      color-mix(in srgb, var(--primary-deep) 22%, transparent) 100%
    );
}
.sf-masthead-inner {
  width: 100%; max-width: 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; }
}

.sf-shopname {
  font-family: 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; }
}

/* Most logos are drawn for light backgrounds, so they get a white sign-board
   plate on the coloured masthead. Shops whose logo is made for dark backgrounds
   can turn the plate off (theme setting → body.logo-no-plate). */
.sf-logoplate {
  display: inline-flex; align-items: center; justify-content: center;
  background: #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. */
.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;
  }
}

/* ── 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); }

.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;
}
.sf-catnav .count {
  margin-left: auto;
  font-size: 0.72rem; font-variant-numeric: tabular-nums;
  color: var(--ink-soft);
}
/* 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;
}
.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)); }

.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);
}

/* ── 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;
}
.sf-board-count {
  display: block; margin-top: 0.1rem;
  font-size: 0.68rem; font-weight: 700;
  letter-spacing: 0.12em; text-transform: uppercase;
  color: var(--ink-soft);
}

/* 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);
}
.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;
}
.sf-config-preview {
  background: var(--primary-soft);
  border: 1px solid var(--primary-line);
  border-radius: var(--radius-row);
  padding: 0.85rem 1rem;
}
.sf-config-preview .total {
  font-family: var(--font-heading);
  font-variant-numeric: tabular-nums;
}

/* ── 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);
  box-shadow: 0 0 40px -8px rgb(0 0 0 / 0.35);
  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); }

.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;
}
/* "Nothing recorded" is deliberately the same size and weight — an operator's
   missing data must not read as quieter than a declared allergen. */
.sf-allergens--none { 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;
}
