/* ============================================
   KING CHEF RESTAURANT - Design System v2
   Deep forest green + gold + cream
   No blur, no fog, no overlay haze.
   Sharp, clean, premium.
   ============================================ */

/* --- Design Tokens --- */
:root {
  /* Colors */
  --color-bg: #101d14;
  --color-bg-alt: #16261b;
  --color-bg-card: #0c160f;
  --color-bg-elevated: #22342a;
  --color-bg-hero: #0a120c;
  --color-gold: #d3ad5f;
  --color-gold-light: #ecd39b;
  --color-gold-dark: #a98a43;
  --color-cream: #f0e7d0;
  --color-cream-dim: #c2baa4;
  --color-cream-muted: #8b8570;
  --color-terracotta: #cf6440;
  --color-white: #ffffff;
  --hero-ink: #faf6ec;
  --color-border: rgba(200, 166, 95, 0.18);
  --color-border-light: rgba(236, 227, 205, 0.06);

  /* Light-surface text pair. Every other surface on this site is dark, so these
     two exist solely for the Leaflet attribution plate, which is the one place
     a light background is used. They are needed because the standard tokens
     fail there: --color-gold measures ~1.7:1 on a light plate, and
     --color-cream-muted (a muted khaki) was the washed-out colour the client
     reported as unreadable on 2026-09-14. Do not use these on dark surfaces.

     THE LINK GOLD IS DEEPER THAN THE 2026-09-14 VALUE (#7d6220) ON PURPOSE.
     That value measured **3.99:1** against the composited cream plate, which is
     under WCAG AA's 4.5:1 floor for text at this size, and
     .cortex/verify-attribution.mjs reported exactly that on both maps at both
     widths (it was red from the drawn-map pass until 2026-09-21). #6b5418
     measures ~5:1 on the same plate and keeps the olive hue that reads as the
     same ink turned down rather than as a different colour. The token is used
     by the attribution link and NOWHERE ELSE, so that is the whole blast
     radius; a different surface needing a "deep gold" should be measured
     against its own background rather than reusing this one. */
  --color-ink-dark: #0a120c;
  --color-gold-deep: #6b5418;

  /* Map plate. The dot screen over the Leaflet basemap (client instruction,
     2026-09-17: the R i D A sourcing map's green field + dot texture applied to
     the REAL map) needs a field DARKER than the green basemap's own mid tone,
     or the map reads as a wash instead of as dots. Measured: this field is
     ~21/255 luminance against the map's ~37/255. */
  --color-map-void: #081a11;
  /* Screen geometry. A 6px cell with a ~4px opening: fine enough that the street
     network survives as texture, coarse enough to still read as a dot matrix.
     The dark field keeps the majority of the plate (61% field, 39% opening), so
     the map still reads as a field with dots in it rather than as a map with a
     grid laid over it. That majority is pinned by .cortex/verify-map-dots.mjs,
     which is the reason the opening is not simply opened all the way up.

     THE OPENING SIZE IS THE TEXT'S BUDGET. The screen paints the area BETWEEN
     the dots, so every pixel of a place name that lands under the veil loses
     brightness, and the opening decides how much of each glyph escapes.
     Measured (.cortex/probe-map-label-band.mjs, which reads this mask and the
     ramp out of the shipped files and runs both over real tiles): widening the
     opening from ~3px to ~4px lifts the open share from 32% to 39% and the label
     group's weighted contrast against the land from 4.17:1 to 5.62:1. Do NOT
     narrow this back to 1.5px/2.3px without re-running that probe. */
  --map-dot-pitch: 6px;
  --map-dot-mask: radial-gradient(circle at 50% 50%, transparent 1.8px, #000 2.4px);

  /* Typography

     One family carries both display and body, taken from the client's Visilex
     reference: Outfit is a geometric variable sans (weight 100-900) that reads
     well at heading size and at body size, so the pair has NO serif in it.
     This replaced Fraunces (display) + Manrope (body) on 2026-09-15.

     TWO THINGS TO KNOW BEFORE CHANGING THIS:

     1. Outfit ships NO ITALIC on Google Fonts (verified against the CSS API: it
        returns `font-style: normal` only). Rules elsewhere in this file set
        `font-style: italic` (the hero gold lines, the rotating hero word, the
        map popup names, the chairman signoff). Those now render a browser
        SYNTHESISED slant, not a drawn italic face. That is expected and is the
        same trade-off the Visilex site makes. Do NOT add `ital` to the Google
        Fonts URL in base.html to "fix" it: Outfit has no italic to load and the
        request would return no @font-face block for it.

     2. Do NOT put Fraunces-only axis values into `font-variation-settings`
        (the axes are `opsz`, `SOFT` and `WONK`). Outfit does not have them, and
        because a low-level `font-variation-settings` can leave `wght` unset,
        such a rule risks silently dropping the element back to weight 400. The
        three Fraunces axis rules that used to live here were removed for that
        reason.

     --font-mono is the third family from the same reference. It is used only
     for small uppercase labels and counters, never for copy. */
  --font-display: 'Outfit', 'Segoe UI', system-ui, sans-serif;
  --font-body: 'Outfit', 'Segoe UI', system-ui, sans-serif;
  --font-mono: 'JetBrains Mono', ui-monospace, 'Cascadia Mono', monospace;

  /* Type Scale */
  --text-xs: 0.75rem;
  --text-sm: 0.875rem;
  --text-base: 1rem;
  --text-lg: 1.125rem;
  --text-xl: 1.25rem;
  --text-2xl: 1.5rem;
  --text-3xl: 2rem;
  --text-4xl: 2.75rem;
  --text-5xl: 3.5rem;
  --text-6xl: 4.5rem;

  /* Spacing */
  --space-xs: 0.25rem;
  --space-sm: 0.5rem;
  --space-md: 1rem;
  --space-lg: 1.5rem;
  --space-xl: 2rem;
  --space-2xl: 3rem;
  --space-3xl: 4rem;
  --space-4xl: 6rem;
  --space-5xl: 8rem;

  /* Layout */
  --container-max: 1680px;
  --container-narrow: 860px;
  --section-padding: clamp(3.5rem, 6vw, 6rem);
  /* The block sitting under the catering cards: the wrapper's 2rem gap plus
     the "Explore Catering" button. The catering figure stops above this, so
     its bottom edge lands exactly on the cards' bottom edge (the client's
     "Weddings same line"). If the button or the gap changes, this is the one
     number to re-measure; verify-catering-band.mjs measures the alignment
     directly, so it goes red rather than drifting. */
  --catering-cta-block: 83px;

  /* Borders & Radius */
  --radius-sm: 4px;
  --radius-md: 8px;
  --radius-lg: 14px;
  --radius-full: 9999px;

  /* Shadows (tinted to bg hue, no pure black) */
  --shadow-sm: 0 2px 8px rgba(10, 18, 12, 0.4);
  --shadow-md: 0 8px 24px rgba(10, 18, 12, 0.5);
  --shadow-gold: 0 4px 20px rgba(200, 166, 95, 0.12);

  /* Transitions */
  --ease-out: cubic-bezier(0.16, 1, 0.3, 1);
  --transition-fast: 0.2s var(--ease-out);
  --transition-base: 0.35s var(--ease-out);
  --transition-slow: 0.6s var(--ease-out);

  /* Z-index */
  --z-header: 100;
  --z-mobile-nav: 200;
}

/* --- Reset --- */
*,
*::before,
*::after {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

html {
  scroll-behavior: smooth;
  font-size: 16px;
}

body {
  font-family: var(--font-body);
  background-color: var(--color-bg);
  color: var(--color-cream);
  line-height: 1.7;
  font-weight: 300;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  overflow-x: hidden;
}

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

a {
  color: var(--color-gold);
  text-decoration: none;
  transition: color var(--transition-fast);
}

a:hover {
  color: var(--color-gold-light);
}

ul, ol {
  list-style: none;
}

button {
  font-family: inherit;
  cursor: pointer;
  border: none;
  background: none;
}

/* --- Typography --- */
h1, h2, h3, h4, h5, h6 {
  font-family: var(--font-display);
  font-weight: 600;
  line-height: 1.15;
  color: var(--color-cream);
}

h1 {
  font-size: clamp(2.5rem, 5.5vw, var(--text-6xl));
  letter-spacing: -0.02em;
}

h2 {
  font-size: clamp(1.75rem, 3.5vw, var(--text-4xl));
  letter-spacing: -0.01em;
}

h3 {
  font-size: clamp(1.25rem, 2.5vw, var(--text-2xl));
}

h4 {
  font-size: var(--text-lg);
}

p {
  max-width: 62ch;
  color: var(--color-cream-dim);
}

/* --- Utility Classes --- */
.text-gold { color: var(--color-gold); }
.text-cream { color: var(--color-cream); }
.text-dim { color: var(--color-cream-dim); }
.text-center { text-align: center; }
.mt-sm { margin-top: var(--space-sm); }
.mt-md { margin-top: var(--space-md); }
.mt-lg { margin-top: var(--space-lg); }
.mt-xl { margin-top: var(--space-xl); }
.mt-2xl { margin-top: var(--space-2xl); }
.mt-3xl { margin-top: var(--space-3xl); }
.mt-4xl { margin-top: var(--space-4xl); }
.mt-5xl { margin-top: var(--space-5xl); }
.mb-md { margin-bottom: var(--space-md); }
.mb-lg { margin-bottom: var(--space-lg); }
.mb-xl { margin-bottom: var(--space-xl); }
.mb-2xl { margin-bottom: var(--space-2xl); }
.mb-3xl { margin-bottom: var(--space-3xl); }

/* --- Layout --- */
.container {
  width: 100%;
  max-width: var(--container-max);
  margin: 0 auto;
  padding: 0 clamp(1.25rem, 2.5vw, 2rem);
}

.container--narrow {
  max-width: var(--container-narrow);
}

.section {
  padding: var(--section-padding) 0;
}

.section--alt {
  background-color: var(--color-bg-alt);
}

.section--dark {
  background-color: var(--color-bg-card);
}

/* --- Section Header (no eyebrow by default) --- */
.section-header {
  margin-bottom: var(--space-3xl);
}

.section-header p {
  margin-top: var(--space-md);
}

.section-header--center p {
  margin-left: auto;
  margin-right: auto;
}

/* Intro paragraph under a section header. The compound selector is deliberate:
   it must outrank the base `.section-header p` rule directly above. */
.section-header .section-header__lead {
  margin-top: 0.8rem;
}

.section-header .section-header__lead--centered {
  max-width: 52ch;
  margin-inline: auto;
}

/* --- Eyebrow (used sparingly, max 2-3 per page) --- */
.eyebrow {
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  font-weight: 500;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--color-gold);
  margin-bottom: var(--space-sm);
  display: block;
}

/* --- Visually hidden -------------------------------------------------------
   Off screen but still in the accessibility tree: read aloud, focusable, and
   skipped by the pointer. It exists for a heading a SECTION needs and a PAGE
   does not want to draw - the /menu/ gallery is the only user (see
   templates/menu.html): the page carries one header, and the gallery keeps its
   own heading so screen-reader heading navigation can reach the 25 cards and
   so its landmark has a name.

   THREE THINGS THIS IS NOT, and each one matters:
     - not `display: none`, which takes it out of the accessibility tree, so
       nothing is announced and the landmark loses its name;
     - not `visibility: hidden`, same result;
     - not `left: -9999px`, a negative offset that can add a horizontal
       scrollbar on a page whose ancestor is transformed.

   A 1px clipped box is invisible to the eye and intact for assistive tech, and
   being out of flow it cannot move the grid by a single pixel. */
.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* --- Buttons --- */
.btn {
  display: inline-flex;
  align-items: center;
  gap: var(--space-sm);
  padding: 0.8rem 1.75rem;
  font-family: var(--font-body);
  font-size: var(--text-sm);
  font-weight: 500;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  border-radius: var(--radius-full);
  transition: all var(--transition-base);
  text-decoration: none;
  white-space: nowrap;
}

.btn:active {
  transform: scale(0.98);
}

.btn--primary {
  background-color: var(--color-gold);
  color: var(--color-bg);
}

.btn--primary:hover {
  background-color: var(--color-gold-light);
  color: var(--color-bg);
  transform: translateY(-2px);
  box-shadow: var(--shadow-gold);
}

.btn--outline {
  border: 1px solid var(--color-gold);
  color: var(--color-gold);
  background: transparent;
}

.btn--outline:hover {
  background-color: var(--color-gold);
  color: var(--color-bg);
  transform: translateY(-2px);
}

.btn--ghost {
  color: var(--color-cream);
  padding: 0.5rem 0;
  border-bottom: 1px solid var(--color-gold);
  border-radius: 0;
}

.btn--ghost:hover {
  color: var(--color-gold);
}

/* --- Header (SOLID background, NO blur) --- */
.delivery-banner {
  background-color: var(--color-bg-card);
  border-bottom: 1px solid var(--color-border);
  padding: var(--space-sm) 0;
  text-align: left;
  font-size: var(--text-sm);
  color: var(--color-cream-dim);
}

.delivery-banner a {
  color: var(--color-gold);
  font-weight: 500;
  margin: 0 var(--space-sm);
}

.delivery-banner a:hover {
  color: var(--color-gold-light);
}

/* Mid-page partner band (homepage, between dishes rail and bento stats) */
.delivery-banner--band {
  border-top: 1px solid var(--color-border);
  padding: var(--space-xl) 0;
  text-align: center;
  font-size: var(--text-base);
}

/* Tighten the seam: the section right after the band starts sooner */
.delivery-banner--band + .section {
  padding-top: clamp(2.75rem, 5vw, 4rem);
}

/* Stacked layout: label on top, logo row centered below */
.delivery-banner__row {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-lg);
}

.delivery-banner__label {
  display: inline-flex;
  align-items: center;
  gap: 0.9rem;
  font-size: var(--text-xs);
  letter-spacing: 0.24em;
  text-transform: uppercase;
  color: var(--color-gold);
  white-space: nowrap;
}

.delivery-banner__partners {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 20px;
  flex-wrap: wrap;
  width: 100%;
}

/* Official partner logo chips.
   Each logo PNG already carries its own branded rectangle background, so we
   give every chip the SAME height and let the logo keep its natural aspect
   ratio. No forced aspect-ratio, no letterboxing, no fake card behind it —
   the logo rectangle itself is the card, so all four fill cleanly. */
.delivery-banner .partner-logo {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  margin: 0;
  border-radius: var(--radius-md);
  overflow: hidden;
  box-shadow: 0 12px 24px -14px rgba(5, 10, 6, 0.8);
  transition: transform var(--transition-base), box-shadow var(--transition-base);
  height: clamp(2.5rem, 6vw, 4rem);
}

.delivery-banner .partner-logo img {
  display: block;
  height: 100%;
  width: auto;
  max-width: none;
}

.delivery-banner .partner-logo:hover,
.delivery-banner .partner-logo:focus-visible {
  transform: translateY(-3px);
  box-shadow: 0 16px 30px -14px rgba(5, 10, 6, 0.9);
}

@media (max-width: 900px) {
  .delivery-banner__row {
    justify-content: center;
    text-align: center;
  }
  .delivery-banner__partners {
    justify-content: center;
  }
}

/* Phone: keep the SAME single centered row as desktop (uniform height,
   natural widths) but shrink the logos and let the row scroll sideways if
   they don't all fit. No grid, no square cells \u2014 it mirrors the desktop
   look the user likes, just sized down for a phone. */
@media (max-width: 600px) {
  .delivery-banner__partners {
    display: flex;
    flex-wrap: nowrap;
    justify-content: flex-start;
    gap: 10px;
    max-width: 100%;
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
    scrollbar-width: none;
    padding-bottom: 4px;
  }
  .delivery-banner__partners::-webkit-scrollbar {
    display: none;
  }
  .delivery-banner .partner-logo {
    flex: 0 0 auto;
    height: 2.25rem;
    border-radius: var(--radius-sm);
  }
}

.header {
  position: sticky;
  top: 0;
  z-index: var(--z-header);
  background-color: var(--color-bg);
  border-bottom: 1px solid var(--color-border-light);
  transition: background-color var(--transition-base);
  width: 100%;
}

.header--scrolled {
  background-color: var(--color-bg-card);
}

/*
  ROOT CAUSE of "stuck in the middle":
  .header > .container inherits .container { max-width: 1320px; margin: 0 auto }.
  On wide viewports that centers the whole logo+nav cluster in a 1320px band,
  so flex-start INSIDE the container still looks middle-weighted.
  Override: full-bleed header rail, content hugs the TRUE left edge.
  No Tailwind, no Bootstrap, no !important.
*/
.header > .container {
  max-width: none;
  width: 100%;
  margin-left: 0;
  margin-right: 0;
  padding-left: clamp(0.75rem, 1.6vw, 1.5rem);
  padding-right: clamp(0.75rem, 1.6vw, 1.5rem);
}

.header__inner {
  display: flex;
  align-items: center;
  justify-content: flex-start;
  flex-wrap: nowrap;
  gap: clamp(1.5rem, 2.4vw, 2.75rem);
  padding: 0.85rem 0;
  height: 128px;
  width: 100%;
  max-width: none;
  margin: 0;
  box-sizing: border-box;
}

/* Logo + nav: shrink-to-content, pack LEFT. Do not let nav stretch full row. */
.header__inner > nav {
  display: block;
  flex: 0 0 auto;
  width: max-content;
  max-width: none;
  margin: 0;
  padding: 0;
}

.header__logo {
  display: inline-flex;
  align-items: center;
  flex: 0 0 auto;
  flex-shrink: 0;
  line-height: 1;
  margin: 0;
  padding: 0;
}

.header__logo img {
  display: block;
  height: 88px;
  width: auto;
  max-width: none;
  object-fit: contain;
}

@media (max-width: 768px) {
  .header > .container {
    padding-left: 0.75rem;
    padding-right: 0.75rem;
  }

  .header__inner {
    height: 96px;
    justify-content: flex-start;
    gap: 1rem;
  }

  .header__logo img {
    height: 56px;
  }
}

.nav__list {
  display: flex;
  align-items: center;
  justify-content: flex-start;
  flex-wrap: nowrap;
  width: max-content;
  max-width: none;
  margin: 0;
  padding: 0;
  gap: clamp(1.5rem, 2.2vw, 2.5rem);
  list-style: none;
}

.nav__link {
  font-size: clamp(1.125rem, 1.15vw, 1.25rem);
  font-weight: 700;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--color-cream-dim);
  transition: color var(--transition-fast);
  position: relative;
  padding: 0.6rem 0.25rem;
  white-space: nowrap;
  line-height: 1.2;
}

.nav__link:hover,
.nav__link--active {
  color: var(--color-gold);
}

/* No underline bar / dash under nav links (was showing as stray "-" under Home) */
.nav__link::after {
  content: none;
  display: none;
}

.nav__link:hover::after,
.nav__link--active::after {
  content: none;
  display: none;
  width: 0;
}

/* Mobile Menu Toggle */
.nav__toggle {
  display: none;
  flex-direction: column;
  gap: 5px;
  padding: var(--space-sm);
}

.nav__toggle span {
  width: 22px;
  height: 2px;
  background-color: var(--color-cream);
  /* Named properties, not `all`: the bars animate transform and opacity into
     the close X, and `all` would also transition the background-color on every
     hover of an ancestor that changes it. */
  transition: transform var(--transition-base), opacity var(--transition-base);
}

/* --- Hero (Left-aligned, no fog overlay) --- */
.hero {
  min-height: 100dvh;
  display: flex;
  align-items: center;
  position: relative;
  overflow: hidden;
}

.hero__bg {
  position: absolute;
  inset: 0;
  z-index: -1;
}

.hero__bg img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* Sharp gradient for text readability, NOT fog */
.hero__scrim {
  position: absolute;
  inset: 0;
  background: linear-gradient(
    to right,
    rgba(21, 34, 23, 0.92) 0%,
    rgba(21, 34, 23, 0.75) 45%,
    rgba(21, 34, 23, 0.2) 100%
  );
}

.hero__content {
  position: relative;
  z-index: 1;
  max-width: 640px;
  padding-top: var(--space-3xl);
}

.hero__title {
  margin-bottom: var(--space-lg);
}

.hero__title .accent {
  color: var(--color-gold);
  font-style: italic;
}

.hero__subtitle {
  font-size: var(--text-lg);
  color: var(--color-cream-dim);
  margin-bottom: var(--space-2xl);
  max-width: 48ch;
}

.hero__actions {
  display: flex;
  gap: var(--space-lg);
  flex-wrap: wrap;
}

/* --- Stats Bar --- */
.stats {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: var(--space-xl);
  padding: var(--space-2xl) 0;
  border-top: 1px solid var(--color-border);
  border-bottom: 1px solid var(--color-border);
}

.stat__number {
  font-family: var(--font-display);
  font-size: var(--text-5xl);
  font-weight: 300;
  color: var(--color-gold);
  line-height: 1;
}

.stat__label {
  font-size: var(--text-sm);
  color: var(--color-cream-dim);
  margin-top: var(--space-sm);
  text-transform: uppercase;
  letter-spacing: 0.08em;
}

/* --- Cards (Image-Forward Premium) --- */
.card {
  position: relative;
  background-color: var(--color-bg-card);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
  overflow: hidden;
  transition: border-color var(--transition-base), transform var(--transition-base), box-shadow var(--transition-base);
}

.card:hover {
  border-color: var(--color-gold);
  transform: translateY(-4px);
  box-shadow: 0 20px 40px -12px rgba(10, 18, 12, 0.6), 0 0 0 1px var(--color-gold);
}

.card__media {
  position: relative;
  overflow: hidden;
  aspect-ratio: 16 / 10;
}

.card__media img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: transform 0.8s var(--ease-out);
}

.card:hover .card__media img {
  transform: scale(1.06);
}

.card__media::after {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(180deg, transparent 50%, rgba(10, 18, 12, 0.7) 100%);
}

.card__badge {
  position: absolute;
  top: 1rem;
  left: 1rem;
  z-index: 2;
  padding: 0.4rem 0.9rem;
  background: var(--color-gold);
  color: var(--color-bg);
  font-size: var(--text-xs);
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  border-radius: var(--radius-full);
}

.card__body {
  padding: 1.5rem 1.6rem 1.8rem;
}

.card__eyebrow {
  font-size: var(--text-xs);
  font-weight: 500;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--color-gold);
  margin-bottom: 0.5rem;
}

.card__title {
  font-size: var(--text-xl);
  font-weight: 400;
  margin-bottom: 0.6rem;
  line-height: 1.25;
}

.card__text {
  font-size: var(--text-sm);
  color: var(--color-cream-dim);
  line-height: 1.7;
}

.card__footer {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 1rem 1.6rem;
  border-top: 1px solid var(--color-border-light);
}

.card__link {
  font-size: var(--text-sm);
  font-weight: 500;
  color: var(--color-gold);
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  transition: gap var(--transition-fast);
}

.card__link:hover {
  gap: 0.7rem;
}

.card__link svg {
  width: 14px;
  height: 14px;
}

/* Image-overlay card (text on top of image) */
.card--overlay {
  border: none;
}

.card--overlay .card__media {
  aspect-ratio: 3 / 4;
}

.card--overlay .card__body {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
  padding: 1.8rem;
  z-index: 2;
}

.card--overlay .card__media::after {
  background: linear-gradient(180deg, transparent 30%, rgba(10, 18, 12, 0.88) 100%);
}

.card--overlay .card__title {
  font-size: clamp(1.4rem, 3vw, 1.8rem);
  color: var(--color-cream);
}

.card--overlay .card__text {
  color: var(--color-cream-dim);
}

/* Horizontal card (image left, text right) */
.card--horizontal {
  display: grid;
  grid-template-columns: 1fr 1fr;
}

.card--horizontal .card__media {
  aspect-ratio: auto;
  height: 100%;
}

.card--horizontal .card__body {
  display: flex;
  flex-direction: column;
  justify-content: center;
  padding: 2rem;
}

@media (max-width: 720px) {
  .card--horizontal {
    grid-template-columns: 1fr;
  }
  .card--horizontal .card__media {
    aspect-ratio: 16 / 10;
  }
}

/* --- Grid Layouts (Premium Asymmetric) --- */
.grid-2 {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: var(--space-2xl);
  align-items: center;
}

.grid-3 {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: var(--space-xl);
}

.grid-4 {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: var(--space-lg);
}

/* Asymmetric grid for variety */
.grid-split {
  display: grid;
  grid-template-columns: 1.2fr 0.8fr;
  gap: var(--space-3xl);
  align-items: center;
}

.grid-split--reverse {
  grid-template-columns: 0.8fr 1.2fr;
}

/* Premium card grids — asymmetric, not 3-equal */
.grid-cards-2-1 {
  display: grid;
  grid-template-columns: 1.4fr 1fr;
  gap: 1.2rem;
}

.grid-cards-1-2 {
  display: grid;
  grid-template-columns: 1fr 1.4fr;
  gap: 1.2rem;
}

/* Bento-style card grid: featured large + 2 smaller */
.grid-featured {
  display: grid;
  grid-template-columns: 1.6fr 1fr;
  grid-template-rows: auto auto;
  gap: 1.2rem;
}

.grid-featured > :first-child {
  grid-row: span 2;
}

.grid-featured > :first-child .card__media {
  aspect-ratio: 4 / 5;
}

/* 3-card staggered row (1 tall + 2 stacked) */
.grid-stagger {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 1.2rem;
}

.grid-stagger__tall {
  grid-row: span 2;
}

.grid-stagger__tall .card__media {
  aspect-ratio: 3 / 4;
}

@media (max-width: 900px) {
  .grid-cards-2-1,
  .grid-cards-1-2,
  .grid-featured,
  .grid-stagger {
    grid-template-columns: 1fr;
  }
  .grid-featured > :first-child {
    grid-row: span 1;
  }
  .grid-featured > :first-child .card__media {
    aspect-ratio: 16 / 10;
  }
  .grid-stagger__tall {
    grid-row: span 1;
  }
}

/* --- Image with rounded corners --- */
.img-rounded {
  border-radius: var(--radius-lg);
  width: 100%;
}

/* --- Location Cards ---
   A vertical flex column so the footer link can be pinned to the card base
   with margin-top:auto. Cards in a grid row stretch to equal height, which
   means the hairlines above the footers line up across the row instead of
   floating at whatever height each card's copy happens to end. */
.location-card {
  position: relative;
  display: flex;
  flex-direction: column;
  background-color: var(--color-bg-card);
  /* Faint top sheen: the card is not a flat rectangle, it catches light
     from above. Kept under 4% alpha so it reads as depth, not a gradient. */
  background-image: linear-gradient(180deg, rgba(236, 227, 205, 0.035), transparent 38%);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
  overflow: hidden;
  transition: border-color var(--transition-base), transform var(--transition-base), box-shadow var(--transition-base);
}

/* Gold at ~60% rather than full gold: nine simultaneous full-gold borders
   is loud, and the lift plus shadow already signal the hover. */
.location-card:hover {
  border-color: rgba(211, 173, 95, 0.6);
  transform: translateY(-4px);
  box-shadow: 0 20px 40px -12px rgba(10, 18, 12, 0.6);
}

.location-card:focus-within {
  border-color: rgba(211, 173, 95, 0.42);
}

.location-card__image {
  position: relative;
  overflow: hidden;
  aspect-ratio: 16 / 9;
}

.location-card__image img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: transform 0.8s var(--ease-out);
}

.location-card:hover .location-card__image img {
  transform: scale(1.06);
}

.location-card__image::after {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(180deg, transparent 50%, rgba(10, 18, 12, 0.6) 100%);
}

.location-card__pin {
  position: absolute;
  top: 1rem;
  right: 1rem;
  z-index: 2;
  width: 2.2rem;
  height: 2.2rem;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--color-gold);
  border-radius: 50%;
  color: var(--color-bg);
}

.location-card__pin svg {
  width: 14px;
  height: 14px;
}

/* The padded column. Before this existed the cards had no padding at all,
   so the name, phone numbers and buttons sat flush against the shell border. */
.location-card__body {
  display: flex;
  flex-direction: column;
  flex: 1 1 auto;
  padding: 1.5rem 1.5rem 1.4rem;
}

/* House hairline marker, same one the cuisine cards use: it grows and turns
   gold on hover. It gives every card an identical, deliberate first beat. */
.location-card__body::before {
  content: "";
  display: block;
  flex: 0 0 auto;
  width: 2.4rem;
  height: 2px;
  border-radius: 2px;
  background: rgba(236, 227, 205, 0.22);
  transition: width 0.5s var(--ease-out), background-color 0.35s ease;
}

.location-card:hover .location-card__body::before,
.location-card:focus-within .location-card__body::before {
  width: 5.2rem;
  background: var(--color-gold);
}

.location-card__name {
  margin-top: 0.85rem;
  margin-bottom: 0.9rem;
  font-family: var(--font-display);
  font-size: var(--text-xl);
  font-weight: 500;
  line-height: 1.2;
  letter-spacing: -0.01em;
  color: var(--color-cream);
}

.location-card__detail {
  display: flex;
  align-items: flex-start;
  gap: 0.65rem;
  font-size: var(--text-sm);
  color: var(--color-cream-dim);
  margin-bottom: 0.45rem;
  line-height: 1.5;
}

.location-card__detail svg {
  width: 15px;
  height: 15px;
  color: var(--color-gold);
  flex-shrink: 0;
  margin-top: 3px;
}

/* Actions: a two-up grid rather than a flex row. The old flex row did not
   wrap, so on a three-column grid four or five buttons overflowed the card
   edge. The odd-last-child rule below lets a 3-button card and a 5-button
   card both close with a full-width final button instead of a half orphan. */
.location-card__actions {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 0.5rem;
  margin-top: 1.3rem;
}

.location-card__actions > :last-child:nth-child(odd) {
  grid-column: 1 / -1;
}

.location-card__actions .btn {
  justify-content: center;
  padding: 0.62rem 0.9rem;
  font-size: var(--text-xs);
  letter-spacing: 0.03em;
  text-align: center;
}

/* Footer link: the single quiet navigation affordance, pinned to the base.
   Same pinned-footer pattern as .cuisine-item__foot. */
.location-card__link {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  margin-top: auto;
  padding-top: 0.95rem;
  border-top: 1px solid var(--color-border-light);
  font-family: var(--font-body);
  font-size: var(--text-xs);
  font-weight: 500;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--color-gold);
}

.location-card__link svg {
  width: 15px;
  height: 15px;
  flex-shrink: 0;
  transition: transform var(--transition-base);
}

.location-card__link:hover svg,
.location-card__link:focus-visible svg {
  transform: translateX(4px);
}

.location-card--coming-soon {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  text-align: center;
  border-style: dashed;
  min-height: 14rem;
}

.location-card--coming-soon .location-card__body {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  width: 100%;
}

/* No hairline marker here: this card is a placeholder, not a branch, and the
   marker is what marks a card as a real, actionable entry. */
.location-card--coming-soon .location-card__body::before {
  content: none;
}

/* Gold, not cream. `.text-gold` on the heading cannot do this job: it is
   declared earlier in the file at the same specificity as
   `.location-card__name`, so the later rule wins. Raising specificity here
   is the reliable way. */
.location-card--coming-soon .location-card__name {
  color: var(--color-gold);
}

/* Blinking gold dot marks the card as a future opening. Static under
   reduced motion. */
.location-card--coming-soon .location-card__name::before {
  content: "";
  display: block;
  width: 7px;
  height: 7px;
  margin: 0 auto 0.7rem;
  border-radius: 50%;
  background: var(--color-gold);
  animation: kc-soon-pulse 2.4s ease-in-out infinite;
}

@keyframes kc-soon-pulse {
  0%, 100% { opacity: 1; transform: scale(1); }
  50% { opacity: 0.35; transform: scale(0.82); }
}

@media (prefers-reduced-motion: reduce) {
  .location-card--coming-soon .location-card__name::before {
    animation: none;
  }
}

/* Locations grid: three across on desktop, two on tablet, one on phone.
   #locGrid is an id, so it outranks .grid-3's own breakpoints and both the
   tablet and phone steps have to be declared here explicitly. */
#locGrid {
  grid-template-columns: repeat(3, minmax(0, 1fr));
}

@media (max-width: 1100px) {
  #locGrid {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

@media (max-width: 768px) {
  #locGrid {
    grid-template-columns: minmax(0, 1fr);
  }
}

/* Location grid */
.location-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 1.2rem;
}

/* Three EQUAL columns.
   This used to be `1.4fr 1fr 1fr`, a deliberately wider first card for
   Muhaisinah. Client instruction, 2026-09-17, pointing at the rendered
   grid: "here have all images same size". An fr ratio cannot deliver that:
   the photo fills the card, so under the ratio the three photos shared one
   HEIGHT (the cap on `.course .location-card__image`) but not one WIDTH.
   Measured before this change: 648x240 / 462x240 / 462x240 at 2308x1100,
   549x216 / 391x216 / 391x216 at 1440x900.
   Equal tracks make the cards, and therefore the photos, exactly one size.
   Do NOT put an fr ratio back here, and do not reintroduce a per-card
   variant: uniformity across the row is the whole point. The `--featured`
   name stays because the motion probes and the `.course` rules select on
   it; it no longer describes the column width. */
.location-grid--featured {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 1.2rem;
}

@media (max-width: 900px) {
  .location-grid,
  .location-grid--featured {
    grid-template-columns: 1fr 1fr;
  }
}
@media (max-width: 540px) {
  .location-grid,
  .location-grid--featured {
    grid-template-columns: 1fr;
  }
}

/* --- Map --- */
.map-container {
  /* Leaflet positions its panes absolutely, so the container has to establish
     the containing block. Leaflet's own stylesheet sets overflow:hidden but no
     position at all, and the gold frame overlay below depends on this too. */
  position: relative;
  width: 100%;
  /* THE PLATE IS SIZED TO THE COUNTRY, NOT TO THE PAGE. The UAE's bounding box is
     4.82 degrees of longitude by 3.56 of latitude, an aspect of about 1.23. In a
     full-width plate (measured 1440x500, an aspect of 2.75) the country is
     HEIGHT-limited, so every spare pixel arrives as dead green field on both
     sides: measured before this pass, the country rendered 438x356, i.e. 32% of
     the width of its own panel. That is what made the map read as empty rather
     than composed. Capping the width and raising the height puts the panel near
     1.6:1, as close to the country's own shape as a landscape band can be, so the
     frame is filled instead of mostly bare.

     THE 560px AND `zoomSnap: 0` IN map.js ARE ONE MECHANISM. Do not drop the
     height back to 500 or the max-width back to none without re-measuring: the
     country is fitted to exactly this box, so the two numbers together decide how
     large the country lands. Mobile is untouched (.map-container is 350px in the
     768px block, where a 390px-wide panel is already close to the country's own
     aspect). */
  max-width: 900px;
  height: 560px;
  margin-inline: auto;
  border-radius: var(--radius-lg);
  overflow: hidden;
  border: 1px solid var(--color-border);
  /* Dark neutral, matched to the DARKENED basemap's own tone, so the tiles fade
     in instead of the panel flashing light against its own dark content.
     Measured: the darkened map's mean luminance is ~41/255 and --color-bg-alt
     (#16261b) measures ~34, so the panel and the tiles agree.
     (This was #e9e6e0 in the light street-map era, matched to the light tiles
     back then.) */
  background-color: var(--color-bg-alt);

  /* The branch label's font size as a SINGLE PX VALUE, and it is a token rather
     than a plain declaration on the label rule for one reason: static/js/map.js
     PARSES THIS NUMBER to predict where a label will land before the browser has
     laid it out, which is how the fan keeps its labels inside the plate. Anything
     that needs resolving first (a rem, a clamp, a vw term) parses to the wrong
     number and breaks that guard silently instead of erroring. See `labelPx` in
     map.js. */
  --map-label-px: 10.5px;
}

/* TOUCH: THE PAGE KEEPS THE ONE-FINGER SCROLL, THE PLATE ARMS ON A DOUBLE-TAP.

   Client instruction, 2026-09-24, against the rendered homepage: on a phone
   the scroll got stuck at this band - "when mobile screen scrolling time that
   exact position comes then scrolled inside map ... if inside map need scroll
   double tap then map move". A finger landing on the plate was panning the MAP
   instead of the page, so the visitor could not scroll past the band at all.

   TWO LAYERS, because either one alone can be defeated:

   1. static/js/map.js starts with `dragging` and `touchZoom` DISABLED on a
      coarse pointer, so Leaflet never installs the touchmove handler that
      calls preventDefault(). THAT handler was eating the page's scroll, not
      the stylesheet.
   2. `touch-action: pan-y` below GUARANTEES the vertical pan to the browser.
      touch-action is enforced before any script runs preventDefault, so this
      is the layer that cannot be lost to a Leaflet upgrade or an extra
      handler. pinch-zoom stays allowed so the page itself remains zoomable
      from over the plate while it is disarmed.

   They switch together: map.js's double-tap sets `.is-touch-armed`, and the
   armed rule flips touch-action to `none` - leaving pan-y there would let the
   browser steal the drag back the instant the plate is armed.

   TWO CLASSES ON PURPOSE: leaflet.css loads AFTER style.css (templates/
   index.html extra_head) and targets `.leaflet-container` at (0,1,0), so a
   single-class rule here would tie and lose on source order. leaflet.css
   carries no touch-action today; this keeps the win even if it ever gains
   one. */
.map-container.leaflet-container {
  touch-action: pan-y pinch-zoom;
}

.map-container.leaflet-container.is-touch-armed {
  touch-action: none;
  /* The armed state must be VISIBLE: from here the page stops scrolling under
     the same finger, so the ring says the plate is live and the hint (created
     by map.js) names the double-tap that hands scrolling back. */
  box-shadow: inset 0 0 0 2px rgba(211, 173, 95, 0.55);
}

/* THE DOUBLE-TAP HINT. map.js creates it ONLY for coarse pointers, shows it on
   a touch of the plate and takes it away again after ~3.6s, so on a mouse it
   never exists and on a phone it never becomes furniture.

   z-index 660: above the branch labels (tooltip pane 650) so a name cannot
   bury it, below the popup pane (700) so a pin's card always wins, below the
   controls (800) where the attribution strip sits. Bottom-LEFT because the
   attribution owns bottom-right on both the 900px and the 350px plate. */
.map-hint {
  position: absolute;
  left: 10px;
  bottom: 10px;
  z-index: 660;
  margin: 0;
  padding: 5px 10px;
  border: 1px solid rgba(211, 173, 95, 0.35);
  border-radius: 999px;
  background: rgba(12, 22, 15, 0.9);
  color: var(--color-cream-dim);
  font-family: var(--font-body);
  font-size: 11px;
  font-weight: 500;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  white-space: nowrap;
  pointer-events: none;
  opacity: 0;
  transform: translateY(4px);
  transition: opacity var(--transition-fast), transform var(--transition-fast);
}

.map-hint.is-shown {
  opacity: 1;
  transform: translateY(0);
}

.map-container.is-touch-armed .map-hint {
  color: var(--color-gold-light);
  border-color: rgba(211, 173, 95, 0.6);
}

/* RETIRED 2026-09-17: THERE IS NO BASEMAP ANY MORE, THE COUNTRY IS DRAWN.

   The client's instruction was to stop wrestling with a real map: "i given world
   map ... no need world map ... but use map using clean same font color yellow &
   green inside dot using animating uae focused deisgn only enogh". So
   static/js/map.js requests no tiles, the `kc-map-ramp` paint server was retired
   from templates/base.html, and everything below this banner is the DRAWN map:
   the dot-matrix UAE land, the gold pins and the labels.

   KEEP READING THE BANNER BELOW ONLY AS HISTORY. The two-stage tile filter it
   documents was measured, correct and load-bearing right up to the moment the
   tiles went; it is preserved because it is the record of why a tile basemap
   could never give the client what they asked for three times in a row, and that
   is the argument for drawing the country instead. Nothing in this file depends
   on it now.

   ---------------------------------------------------------------------------
   Basemap treatment: Esri "World Street Map", darkened and turned GREEN, its
   ROADS turned GOLD and its place-name labels left LIGHT, then read through a
   dot-matrix screen. (Client instruction, 2026-09-17: the R i D A sourcing map's
   green field and dot texture, applied to the REAL map rather than to a drawn
   world outline. Then, the same day, three refinements: "location text make it
   visible font in light color", the location dot keeping its brand gold, and the
   roads marked in gold too.)

   The provider is Esri "World Street Map"; the tile choice and the full
   reasoning live in static/js/map.js. It is a LIGHT street style, so it is
   inverted here to match the design system rather than left as a bright
   rectangle punched into a dark page.

   THIS TREATMENT IS THE CLIENT'S CHOICE, made 2026-09-13. They were shown a
   six-way side-by-side of live basemaps (.cortex/shots/basemaps/, with the exact
   filter for each recorded in results.json) and picked the darkened street map:
   "b-street-dark ... use it instead of fully light".

   THE CHAIN IS TWO STAGES, and the second is why this pane is no longer simply
   filtered to a colour.

   1. invert(1) turns the LIGHT tiles dark. It also flips hue, and that flip is
      what makes the LABELS light: on this style the place names are dark text
      with a white halo, so inverting leaves them the brightest thing on the map.
      That is the client's "location text ... visible font in light color", and
      it is also what puts the road network and the labels at the opposite end of
      the curve from the land.

   2. url(#kc-map-ramp) is a LUMINANCE RAMP, defined as a hidden SVG in
      templates/base.html. It maps the grey scale onto ONE curve: the darkest
      pixels become the deep green field, the bright road band becomes gold, and
      the brightest pixels (the labels) become cream. Without it the map can only
      ever be ONE hue, which is exactly what this rule used to be: invert plus
      sepia plus a single hue-rotate turned every feature green, plate and roads
      alike, so "make the roads golden" was impossible to express.

      WHERE THE CURVE TURNS, MEASURED. The inverted basemap's luminance
      histogram (.cortex/probe-map-tone-hist.mjs) is strongly bimodal: 85% of the
      pixels sit below 51/255, which is land and nothing else, and the whole
      feature network is the thin tail above 77/255. The pixels that come out
      gold there measured a mean SOURCE luminance of 134/255, so the
      green-to-gold knot sits at 128/255, and the labels, which measured 202/255,
      fall on the cream end of the ramp. The table carries 13 stops because
      `feFuncX type="table"` spaces its values EVENLY (13 stops is one every
      21/255), which keeps the green-to-gold edge a single step wide instead of a
      slow olive blend smeared across the desert.

   Do not expect the PLATE to look different from the day before this change:
   the land lands on the same dark green it always did. What moved is the road
   network and the labels, which is what was asked for.

   DO NOT inline the table here or move it into this file: CSS cannot express a
   ramp, which is the whole reason the filter lives in the markup.

   DO NOT drop `invert(1)` while keeping the ramp. The ramp is written against
   the INVERTED luminance, so with the light tiles as its input the land reaches
   the ramp's bright end: measured, the plate goes to a mean luminance of 228
   against 44 with the ramp correct, i.e. a glaring bright cream-gold rectangle,
   and the labels sink into the green at the dark end. The invert is what puts
   land, roads and labels on the right thirds of the curve.

   DO NOT remove the ramp reference either, and note that its failure is SILENT:
   measured, the browser drops the unresolvable function and keeps `invert(1)`,
   so the map comes back as the un-green NAVY plate the client rejected on
   2026-09-13, with no error anywhere. The id is pinned by
   .cortex/check_map_tiles.py and its loss is a red leg in
   .cortex/verify-map-tone.mjs.

   WHY NOT hue-rotate ALONE. hue-rotate(115deg) with no sepia was built and
   measured (`.cortex/probe-map-dots.mjs`, candidate f03): mean RGB (42,35,32).
   Rotating the inverted navy map far enough to make its water green also turns
   the desert tan magenta and the parks orange, and the mix reads brown. The
   single-tone collapse is what makes "the whole plate is green" possible, and a
   single HUE is not the monochrome plate the note below bans: that ban is about
   GREY.

   DO NOT reinstate grayscale(). It was tried on 2026-09-13 and rejected in as
   many words ("i need color one no need black one"). It strips the colour
   entirely and turns the map back into the flat monochrome plate this treatment
   exists to replace.

   DO NOT delete the filter to "simplify". An absent rule is the raw LIGHT
   street map, which is the glaring bright rectangle this exists to remove. */
/* THE DRAWN MAP. Three layers, each with exactly one job, and no tiles to colour:

     1. THE PLATE  `.map-container.leaflet-container` below is the deep green
                   field (#081a11) that every dot is read against.
     2. THE LAND   the UAE ring from map.js, filled with the `#kc-uae-dots` paint
                   server defined in templates/base.html. A paint server can only
                   be a url(), which is the whole reason the pattern lives in the
                   markup and not here.
     3. THE GOLD   the pins (map.js) and the hairline frame (`.map-dots__frame`).

   The old `.map-container .leaflet-tile-pane` filter rule that stood here is
   retired with the tiles: with no tile layer the pane is empty, so a filter on it
   could only be dead code pretending to do something.

   WHY THE FILL IS SET IN TWO PLACES. `fill: url(#kc-uae-dots)` is declared here
   AND passed as `fillColor` from map.js. Leaflet writes the fill as an SVG
   ATTRIBUTE and this is a CSS PROPERTY, so this one wins; the map.js copy is the
   belt to this braces, in case a future Leaflet stops honouring a non-colour
   fillColor and would otherwise paint the country solid black.

   The stroke is the brand gold at 55%, not at full strength. A bright outline
   around a dot texture reads as a sticker or a cut-out rather than as a coast,
   and the pins have to stay the brightest thing on the plate. */
.map-container .leaflet-overlay-pane path.map-land {
  fill: url(#kc-uae-dots);
  stroke: rgba(211, 173, 95, 0.55);
  stroke-width: 1.5;
  stroke-linejoin: round;
  /* The "animating" half of the brief, and deliberately the ONLY motion on the
     land: the pins already carry a pulse, and two competing rhythms on one plate
     read as noise. `opacity` is animated rather than the pattern's own fill so
     the whole landmass cross-fades as one composited layer, instead of the
     browser repainting every dot in the texture on every frame. */
  animation: uae-land-breathe 6.5s ease-in-out infinite;
}

@keyframes uae-land-breathe {
  0%, 100% { opacity: 1; }
  50%      { opacity: 0.78; }
}

/* Branch labels. ONE PER PIN, ALWAYS ON.

   WHY THE COLOUR AND THE HALO. The client's instruction has been "location text
   make it visible font in light color" through three passes, and on a DRAWN
   country this is finally a decision rather than a fight: the plate under the
   text is one flat green, so the only thing that can ever sit behind a label is
   the dot texture. The halo is two dark text-shadows rather than a plate, because
   a plate at label size would cover the coast it is naming.

   WHY THEY ARE PERMANENT NOW, which is the change of 2026-09-17. These used to
   appear on hover only, and the reason was sound at the time: eight pins on their
   true coordinates put eight labels in a thirty-pixel pile. static/js/map.js now
   DISPLACES the pins onto a fan, and the fan is precisely what makes a permanent
   label readable, so hover-only would hide the single thing the change was for.
   The client's ask was "all location proply need visible", and a name you have to
   find with a mouse is not visible.

   WHY THE SIZE IS A TOKEN AND NOT A literal. `--map-label-px` (declared on
   `.map-container`) is PARSED by map.js to keep every label inside the plate, so
   it has to be one px number rather than anything that needs resolving.

   Leaflet's own tooltip chrome is switched OFF rather than toned down: the
   default is a white rounded card with a border and an arrow, which on this map
   reads as a sticky note. */
.map-container .leaflet-tooltip.map-label {
  padding: 0;
  border: 0;
  background: transparent;
  box-shadow: none;
  color: var(--color-gold-light);
  font-family: var(--font-display);
  font-size: var(--map-label-px);
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  white-space: nowrap;
  text-shadow:
    0 1px 3px rgba(4, 12, 8, 0.95),
    0 0 9px rgba(4, 12, 8, 0.9);
  transition: color var(--transition-fast);
}

/* THE LABEL THE POINTER IS ON, lit up by map.js adding `is-hot` to the pair.

   Without this, hovering a pin brightens the pin and leaves eight identical
   labels to be searched by eye, which on a plate this busy is most of the work of
   reading it. The hot colour is the cream end of the ramp rather than a second
   gold, so it reads as the same ink turned up rather than as a different colour. */
.map-container .leaflet-tooltip.map-label.is-hot {
  color: var(--color-cream);
}

/* Leaflet draws the tooltip's arrow as a ::before triangle. */
.map-container .leaflet-tooltip.map-label::before {
  display: none;
}

/* AT THE COUNTRY VIEW THE EIGHT BRANCH NAMES ARE NOT A LABEL LAYER, THEY ARE A
   SMUDGE: all eight branches sit inside about 30px of Dubai, and eight 10px
   names in 30px collide into illegibility (measured: 22 overlapping pairs).
   The name row under the plate is the index at this zoom, and its buttons light
   the matching pin; past BRANCH_LABEL_ZOOM in map.js the names come back and
   placeLabels gives each one a clear side. `display: none`, not opacity: a
   hidden name must keep no box, or it still collides in anything that measures
   rectangles, gates included. */
.map-container.is-country .leaflet-tooltip.map-label {
  display: none;
}

/* The one label that is always on: the city every branch sits in. A divIcon
   (map.js) rather than a tooltip, so it cannot be dismissed and does not compete
   with the hover labels on the pins beneath it.

   It is set in the site's own display face at the site's own weight, per "same
   font": the map should not introduce a typeface the rest of the page does not
   use. */
.map-container .map-hub-label {
  pointer-events: none;
}

.map-container .map-hub-label span {
  position: absolute;
  /* CENTRED ON THE HUB AND LIFTED CLEAR OF THE RINGS, which is the only direction
     with room: the fan spends the whole 180 degrees BELOW the hub (its sweep runs
     west through south to east), so directly above the city is the one gap left
     on the plate.

     `left: 0` resolves to the marker point itself rather than to half the box,
     because the divIcon is 0x0 (`iconSize: [0, 0]` in map.js). The translate is
     therefore carrying the whole centring on its own, and it is not decoration:
     remove it and the word hangs off the hub's right shoulder. */
  left: 0;
  top: -78px;
  transform: translateX(-50%);
  white-space: nowrap;
  color: var(--color-gold-light);
  font-family: var(--font-display);
  font-size: 0.82rem;
  font-weight: 700;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  text-shadow:
    0 1px 4px rgba(4, 12, 8, 0.95),
    0 0 12px rgba(4, 12, 8, 0.9);
}

/* THE HUB RINGS. Three concentric hairlines centred on the city, so the cluster
   of branch pins has a marked middle instead of reading as one gold smudge.

   WHY THE PLATE NEEDS THEM. Every branch sits within roughly 25km of the city
   centre, and at a country fit that whole cluster is only about 35px across, so
   eight pulsing pins on their own are a single bright blob with no centre. The
   rings define that centre before the eye reads any one pin, which is the same
   job a hub node does in a network diagram. They are deliberately hairlines at
   low opacity: the pins have to stay the brightest thing on the plate.

   THE ELEMENTS ARE `<i>`, NOT `<span>`, AND THAT IS LOAD-BEARING. The rule above
   selects `.map-container .map-hub-label span`, which matches ANY descendant
   span, so rings built from spans would inherit the label's uppercase gold type
   and paint the word three extra times. See the matching note in map.js.

   `left/top: 50%` on a divIcon is not a centring trick here. The marker's icon
   box is 0x0 (`iconSize: [0, 0]`), so 50% resolves to the marker point itself and
   the translate is what centres each ring on it. */
.map-container .map-hub-label i {
  position: absolute;
  left: 50%;
  top: 50%;
  border-radius: 50%;
  border: 1px solid rgba(211, 173, 95, 0.32);
  transform: translate(-50%, -50%);
  pointer-events: none;
}

.map-container .map-hub-label i:nth-of-type(1) {
  width: 46px;
  height: 46px;
  border-color: rgba(211, 173, 95, 0.5);
}

.map-container .map-hub-label i:nth-of-type(2) {
  width: 78px;
  height: 78px;
  border-color: rgba(211, 173, 95, 0.3);
}

.map-container .map-hub-label i:nth-of-type(3) {
  width: 118px;
  height: 118px;
  border-color: rgba(211, 173, 95, 0.18);
}

/* THE LEADER LINES: a hairline from each displaced pin back to the hub.

   WHY THEY ARE NOT OPTIONAL. map.js moves the pins OFF their true coordinates,
   because that is the only way eight branches inside one city can be separately
   visible at a country fit. A displaced pin with no visible thread back to the
   place it belongs is just a gold dot standing in the desert; the lines are what
   make the fan read as "all of these are here" instead of as eight arbitrary
   marks, and they are the whole difference between a callout diagram and a mess.

   WHY THEY ARE THIS FAINT. The reading work is done by a line and the attention is
   held by a dot, so eight full-strength gold lines over a dot texture would swamp
   the country they are drawn across. The dash is doing a second job on top of
   that: a dashed line reads as a CONNECTION, where a solid hairline at this weight
   reads as a border of something.

   THE STAGGER COMES FROM `--i`, which map.js sets on each path. A line that
   arrived before its pin would draw attention to a place where nothing is yet, so
   each line fades in after the pin it belongs to. */
.map-container .leaflet-overlay-pane path.map-leader {
  fill: none;
  stroke: var(--color-gold);
  stroke-width: 1;
  stroke-opacity: 0;
  stroke-linecap: round;
  stroke-dasharray: 3 3;
  transition: stroke-opacity 0.6s var(--ease-out) calc(var(--i, 0) * 80ms + 300ms);
}

.map-container.is-drawn .leaflet-overlay-pane path.map-leader {
  stroke-opacity: 0.42;
}

/* The line the pointer is on, lit together with its pin and its label. Solid as
   well as brighter, because the question being asked is "which line reaches THIS
   label", and a dash answers that less clearly than a continuous stroke. */
.map-container.is-drawn .leaflet-overlay-pane path.map-leader.is-hot {
  stroke-opacity: 0.95;
  stroke-dasharray: none;
}

/* THE ENTRANCE, and the reason the plate is not just a static graphic.

   WHY IT IS PACED THIS WAY. The land was already breathing, so the whole of the
   new motion is a single orchestrated arrival: the hub rings come up first, then
   the pins land one after another (the stagger index comes from `--i`, set per
   pin in map.js). That is one moment rather than a rhythm competing with the
   land's breathe and the pins' pulse, which stay exactly as they were.

   WHY THE PARENT ANIMATES OPACITY ONLY. Leaflet POSITIONS every marker with a
   `transform: translate3d(...)` on this exact element. Animating `transform` here
   would overwrite that and throw the pin to the wrong place on the plate, which
   is why the scale lives on the inner `.custom-marker__dot` and the outer element
   only fades.

   WHY `backwards` FILL AND NOT `both`. With `both`, the finished keyframe would
   keep owning the properties, and an animation always beats a declaration, so the
   dot's `:hover { transform: scale(1.12) }` would stop working for the rest of the
   session. `backwards` applies the hidden first frame only during the stagger
   delay and then hands the properties straight back, which also means the pins are
   plain visible if `.is-drawn` is never added at all. */
.map-container.is-drawn .custom-marker {
  animation: uae-pin-fade 0.5s var(--ease-out) calc(var(--i, 0) * 80ms + 240ms) backwards;
}

.map-container.is-drawn .custom-marker__dot {
  animation: uae-pin-drop 0.6s var(--ease-out) calc(var(--i, 0) * 80ms + 240ms) backwards;
}

.map-container.is-drawn .map-hub-label i {
  animation: uae-hub-in 0.9s var(--ease-out) 140ms backwards;
}

@keyframes uae-pin-fade {
  from { opacity: 0; }
}

@keyframes uae-pin-drop {
  from { transform: scale(0.2); }
}

@keyframes uae-hub-in {
  from {
    transform: translate(-50%, -50%) scale(0.72);
    opacity: 0;
  }
}

/* Reduced motion is a SEPARATE BLOCK rather than an addition to the one below.
   That block's body is an anchor for a red-test mutation in
   .cortex/check_map_tiles.py, which matches the land rule and the block's closing
   brace as one literal string: inserting a rule inside it would break the anchor
   and the mutation would be skipped silently instead of failing loudly. */
@media (prefers-reduced-motion: reduce) {
  .map-container.is-drawn .custom-marker,
  .map-container.is-drawn .custom-marker__dot,
  .map-container.is-drawn .map-hub-label i {
    animation: none;
  }
}

@media (prefers-reduced-motion: reduce) {
  .map-container .leaflet-overlay-pane path.map-land {
    animation: none;
    opacity: 1;
  }
}

/* The dot screen, and the gold frame.

   WHERE THESE LIVE, AND WHY THEY ARE NOT PSEUDO-ELEMENTS. The screen has to be
   painted between the basemap and the branch markers, and on a Leaflet map that
   strip cannot be reached from this stylesheet. Leaflet's own
   `.leaflet-pane { z-index: 400 }` lands on `.leaflet-map-pane`, which makes it
   a STACKING CONTEXT and traps every inner pane index (tiles 200, markers 600)
   inside it: a `.map-container` pseudo-element paints either under every tile or
   over every marker, whichever index it is given. That is measured, not assumed
   (`.cortex/probe-map-dots.mjs`): a red overlay at z-index 350 rendered with 0%
   red pixels, entirely behind the tiles. THIS IS WHY THE OLD `.map-container::after`
   GOLD FRAME WAS INVISIBLE: it was painted under the basemap for its whole life,
   despite the comment above it claiming otherwise.

   So `static/js/map.js` creates both elements inside Leaflet's MAP pane, and
   which pane they go in is load-bearing for a second reason on top of the paint
   order: see the BLEND note on `.map-dots__screen` below. The box comes from the
   screen being sized to the map container, so the CSS below only has to say what
   it looks like.

   `.map-dots__screen` is the film: a dark-green field MASKED by a dot grid,
   so the green basemap shows through the openings as brighter dots. The field
   must be darker than the map's own mid tone or the map reads as a uniform wash
   instead of as dots: this field measures ~21/255 luminance against the map's
   ~37/255.

   `.map-dots__frame` is the gold frame the map always meant to have: a hairline
   ring just inside the border plus a soft vignette, so the plate reads as framed
   rather than as a hole punched into the page. It is a SEPARATE element because
   a mask applies to a whole element's rendering, pseudo-elements included, so a
   frame drawn inside the screen would come out dotted. The vignette stays
   softened at 26px/0.22 rather than returning to the old 62% strength: the
   darkened basemap is already very dark, so a heavy vignette would only mud the
   corners, and the gold hairline does the framing work. */
.map-dots {
  /* NO z-index HERE, ON PURPOSE, and adding one breaks the screen's blend. A
     z-index on this element would make it a stacking context, which isolates
     `.map-dots__screen` from the basemap underneath it: the blend would then
     have nothing to blend with and the screen would paint flat, which is exactly
     what was measured inside the overlay pane (see the BLEND note below). The
     index lives on the two children instead, so it is resolved in Leaflet's own
     stacking context where the tile pane is a sibling. */
  position: absolute;
  top: 0;
  left: 0;
  pointer-events: none;
}

.map-dots__screen {
  position: absolute;
  inset: 0;
  /* The stacking slot: above the tile pane (200), below the marker pane (600),
     so branch pins still draw over the screen. */
  z-index: 350;
  background-color: var(--color-map-void);
  /* THE SCREEN DARKENS BY BLEND, NOT BY VEIL. (Client instruction, 2026-09-17:
     "text is not clearl" -- the third pass on this map's legibility.)

     WHAT WAS WRONG. An alpha veil multiplies a glyph and a road by the same
     factor, so it drags every bright thing toward the same tone: the place names
     and the road network end up reading as one more road. The screen was 0.30,
     and moving that number around cannot fix it, because BOTH sides of the
     comparison move together. Measured (.cortex/probe-map-text.mjs): the labels
     arrive from the ramp as cream #FAF4E2 and leave the screen as grey #b0b1a0,
     with text-against-road 2.76:1.

     WHAT FIXES IT. `mix-blend-mode` scales with the BACKDROP rather than with a
     fixed factor. Soft-light darkens a dark backdrop hard and a bright one barely
     at all, which is precisely the separation needed: the land takes the dots and
     the lettering keeps its brightness. Measured on the glyph itself
     (.cortex/probe-map-glyph.mjs, which derives a lettering mask from an
     unscanned render, mean luminance 200/255, and then scores every state on
     those same pixels as a share of the no-screen ceiling):

       no screen (ceiling)     glyph 200.3  surround 68.9  100%   (the max any
                                                                     screen keeps)
       veil 0.30 (was shipped) glyph 167.8  surround 60.2   82.0%  <-- the bug
       blend soft-light 0.65   glyph 187.8  surround 55.1  101.0%
       blend soft-light 0.80   glyph 185.0  surround 52.0  101.2%  <-- shipped
       blend soft-light 1.00   glyph 181.2  surround 47.8  101.5%

     The old veil threw away 18% of the text's contrast. The blend now keeps it
     all, AND darkens the surround 14% further than the veil did, so the dots are
     stronger than before rather than weaker: this is not a trade, both sides of
     it improved. 0.80 is chosen over 1.00 because it holds the glyph at 185
     against 181 while keeping more of the map's green, and over 0.65 because it
     puts another 3 luminance levels into the dots.

     WHY 101% IS POSSIBLE. The score is glyph MINUS its own local surround. The
     blend leaves the glyph alone and darkens the surround, so the step can exceed
     the ceiling's: the ceiling has no screen darkening anything.

     WHERE IT HAS TO LIVE FOR THIS TO WORK. A blend composites against whatever is
     painted underneath INSIDE ITS ISOLATED GROUP, and Leaflet's OVERLAY pane is a
     stacking context with nothing beneath the screen in it. Measured
     (.cortex/probe-map-text.mjs): all five darkening modes painted the field flat
     there, land coming back as exactly the field colour. So the screen is a child
     of `.leaflet-map-pane` -- the stacking context that actually contains the tile
     pane -- with its own z-index in between the panes. `.map-dots` above must stay
     free of a z-index or it will re-isolate the screen and undo all of this.

     DO NOT go back to an opaque field with the dots punched out ("a sieve"): at
     any hole size a sieve cuts every glyph stroke every few pixels, which was
     measured to return empty OCR text.

     DO NOT drop the blend and raise the veil to compensate. A veil and a blend are
     not interchangeable at any value; the whole point is that the blend spares
     bright pixels and the veil cannot. (.cortex/verify-map-dots.mjs and
     .cortex/verify-map-glyph.mjs guard this.) */
  mix-blend-mode: soft-light;
  opacity: 0.8;
  -webkit-mask-image: var(--map-dot-mask);
  mask-image: var(--map-dot-mask);
  -webkit-mask-size: var(--map-dot-pitch) var(--map-dot-pitch);
  mask-size: var(--map-dot-pitch) var(--map-dot-pitch);
  -webkit-mask-repeat: repeat;
  mask-repeat: repeat;
}

.map-dots__frame {
  position: absolute;
  inset: 0;
  /* Above the screen, which now carries an index of its own. */
  z-index: 351;
  pointer-events: none;
  box-shadow:
    inset 0 0 0 1px rgba(211, 173, 95, 0.45),
    inset 0 0 26px rgba(8, 14, 10, 0.22);
  background: radial-gradient(
    130% 95% at 50% 45%,
    transparent 58%,
    rgba(211, 173, 95, 0.05) 100%
  );
}

/* The panel behind the tiles, second half of the same specificity story as the
   attribution strip below. Leaflet paints `.leaflet-container { background: #ddd }`,
   a (0,1,0) rule in leaflet.css, which loads AFTER this stylesheet and therefore
   TIES with `.map-container` and wins on source order. Measured before this
   rule: computed background rgb(221,221,221), not the dark surface the rule
   above sets. Nothing showed while opaque tiles covered the panel, but the dot
   screen now leaves 35% of the plate open, so a slow tile load flashes a light
   speckle where the map's own dark field should be. Carrying BOTH classes lifts
   this to (0,2,0) and settles the tie. Do not "simplify" it back to one class. */
.map-container.leaflet-container {
  background-color: var(--color-map-void);
}

/* Leaflet's attribution strip. Dark text on a LIGHT plate, per the client's
   instruction on 2026-09-14.

   WHY THE SELECTOR CARRIES TWO CLASSES. Leaflet's own rule for this element is
   `.leaflet-container .leaflet-control-attribution`, which is specificity
   (0,2,0), and leaflet.css is loaded AFTER style.css (base.html puts it in the
   `extra_head` block, which renders below the stylesheet link). A plain
   `.map-container .leaflet-control-attribution` also scores (0,2,0), so it
   TIED and lost on source order. The consequence was subtle and invisible: the
   background stayed Leaflet's white, while the `color` declaration still won
   because Leaflet's colour rule is only (0,1,0). That left a muted khaki on
   white at a 2.38:1 contrast ratio, which is the faint bar the client
   reported. Leaflet adds `leaflet-container` to this same div at runtime, so
   matching it lifts this rule to (0,3,0) and settles the tie regardless of
   load order. Do not "simplify" this back to a single class.

   The plate is the design system cream at 92%, so it reads as a deliberate
   label rather than a default white strip, and the body text is near-black ink
   at roughly 15:1. Links use --color-gold-deep instead of --color-gold because
   the normal gold only manages ~1.7:1 on a light plate. */
.map-container.leaflet-container .leaflet-control-attribution {
  background: var(--color-cream);
  background: color-mix(in srgb, var(--color-cream) 92%, transparent);
  color: var(--color-ink-dark);
  font-size: 12px;
  font-weight: 500;
  line-height: 1.5;
  padding: 3px 9px;
  border-radius: var(--radius-sm) 0 0 0;
}

.map-container.leaflet-container .leaflet-control-attribution a {
  color: var(--color-gold-deep);
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-underline-offset: 2px;
}

.map-container.leaflet-container .leaflet-control-attribution a:hover,
.map-container.leaflet-container .leaflet-control-attribution a:focus {
  color: var(--color-ink-dark);
  text-decoration-thickness: 2px;
}

.map-container.leaflet-container .leaflet-control-attribution a:focus-visible {
  outline: 2px solid var(--color-gold-dark);
  outline-offset: 2px;
}

/* Branch markers. Leaflet's default pin is ~20px, and the previous custom icon
   was the same size, so a branch disappeared into the dense basemap. The marker
   is now a gold disc inside a near-black ring, plus a soft light outer edge and
   a drop shadow, with a slow pulse ring behind it so the set of branches reads
   as the focal point of the map.

   The two rings are what carry the marker over the darkened basemap: the light
   gold outer ring gives the disc an edge against dark land, and the dark inner
   border separates the gold from the busy road network underneath. Keep both;
   a bare gold disc alone has almost no edge on either a light or a dark plate.

   The 36px box is set by iconSize in static/js/map.js; nothing is sized here so
   the two stay in step with the popup anchor. */
.custom-marker {
  background: none;
  border: 0;
}

.custom-marker__pulse {
  position: absolute;
  inset: 0;
  border-radius: 50%;
  background: rgba(211, 173, 95, 0.5);
  animation: kc-marker-pulse 2.6s var(--ease-out) infinite;
}

@keyframes kc-marker-pulse {
  0% {
    transform: scale(0.6);
    opacity: 0.8;
  }

  70%,
  100% {
    transform: scale(1.9);
    opacity: 0;
  }
}

.custom-marker__dot {
  position: absolute;
  inset: 6px;
  border-radius: 50%;
  background: var(--color-gold);
  border: 2px solid var(--color-bg-hero);
  box-shadow:
    0 0 0 2px var(--color-gold-light),
    0 3px 10px rgba(10, 18, 12, 0.75);
  transition: background-color var(--transition-fast), transform var(--transition-fast);
}

.custom-marker:hover .custom-marker__dot {
  background: var(--color-gold-light);
  transform: scale(1.12);
}

/* Popup card. Leaflet ships a white box of ~12px type, which is too small to
   read at a glance and reads as unfinished chrome against this design system.
   Restyled to the dark design-system card at the base type scale so branch
   names and numbers are legible in place. The card sits over a dark basemap, so
   it is separated by the gold hairline border and the tip rather than by value
   contrast alone, and it matches the zoom buttons and attribution strip. */
.map-container .leaflet-popup-content-wrapper {
  background: var(--color-bg-card);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-md);
  color: var(--color-cream);
}

.map-container .leaflet-popup-content {
  margin: 1.05rem 1.2rem;
  font-family: var(--font-body);
  font-size: var(--text-base);
  line-height: 1.5;
}

.map-container .leaflet-popup-content h4 {
  font-family: var(--font-display);
  font-size: var(--text-xl);
  font-weight: 600;
  line-height: 1.25;
  letter-spacing: 0.01em;
  color: var(--color-gold-light);
  margin: 0 0 0.55rem;
  /* Keep the name clear of the close button. */
  padding-right: 1.25rem;
}

.map-container .leaflet-popup-content p {
  font-size: var(--text-sm);
  color: var(--color-cream-dim);
  margin: 0.2rem 0;
}

.map-container .leaflet-popup-content a {
  display: inline-block;
  margin-top: 0.7rem;
  font-size: var(--text-sm);
  font-weight: 600;
  color: var(--color-gold);
  border-bottom: 1px solid rgba(211, 173, 95, 0.35);
  padding-bottom: 1px;
}

.map-container .leaflet-popup-content a:hover,
.map-container .leaflet-popup-content a:focus {
  color: var(--color-gold-light);
  border-bottom-color: var(--color-gold-light);
}

/* The pointer tip shares Leaflet's default white background with the wrapper,
   so overriding the wrapper alone leaves a white notch in the dark card. The
   border continues the card outline down onto the visible two edges. */
.map-container .leaflet-popup-tip {
  background: var(--color-bg-card);
  border: 1px solid var(--color-border);
  box-shadow: none;
}

/* Leaflet's close button is a grey "x", which is invisible on a dark card. */
.map-container .leaflet-popup-close-button {
  color: var(--color-cream-muted);
  font-size: 22px;
  font-weight: 400;
  padding: 6px 8px 0 0;
}

.map-container .leaflet-popup-close-button:hover,
.map-container .leaflet-popup-close-button:focus {
  background: none;
  color: var(--color-gold);
}

/* Under reduced motion the pulse is replaced by a static halo rather than
   removed, so the marker keeps its added visual weight. */
@media (prefers-reduced-motion: reduce) {
  .custom-marker__pulse {
    animation: none;
    transform: scale(1.35);
    opacity: 0.35;
  }
}

/* --- Reviews (Premium with avatars) --- */
.review-card {
  background-color: var(--color-bg-card);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
  padding: 1.8rem;
  transition: border-color var(--transition-base), transform var(--transition-base);
  display: flex;
  flex-direction: column;
  min-width: 20rem;
  max-width: 24rem;
}

.review-card:hover {
  border-color: rgba(200, 166, 95, 0.35);
  transform: translateY(-2px);
}

.review-card__header {
  display: flex;
  align-items: center;
  gap: 0.9rem;
  margin-bottom: 1.2rem;
}

.review-card__avatar {
  width: 2.8rem;
  height: 2.8rem;
  border-radius: 50%;
  overflow: hidden;
  flex-shrink: 0;
  border: 2px solid var(--color-border);
}

.review-card__avatar img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.review-card__meta {
  display: flex;
  flex-direction: column;
}

.review-card__author {
  font-size: var(--text-sm);
  color: var(--color-cream);
  font-weight: 500;
}

.review-card__title {
  font-family: var(--font-display);
  font-size: 1.3rem;
  line-height: 1.35;
  color: var(--color-gold);
  margin-bottom: 0.7rem;
}

.review-card__date {
  font-size: var(--text-xs);
  color: var(--color-cream-muted);
}

.review-card__stars {
  color: var(--color-gold);
  font-size: var(--text-sm);
  margin-bottom: 0.8rem;
  display: flex;
  gap: 2px;
}

.review-card__stars svg {
  width: 14px;
  height: 14px;
  fill: var(--color-gold);
}

.review-card__text {
  font-size: var(--text-sm);
  color: var(--color-cream-dim);
  line-height: 1.75;
  flex: 1;
}

.review-card__source {
  margin-top: 1.2rem;
  padding-top: 1rem;
  border-top: 1px solid var(--color-border-light);
  display: flex;
  align-items: center;
  gap: 0.5rem;
  font-size: var(--text-xs);
  color: var(--color-cream-muted);
}

.review-card__source svg {
  width: 14px;
  height: 14px;
  color: var(--color-gold);
}

/* --- Guest reviews: real Google quotes on the marquee rail (2026-09-16) ---
   The five reviews the client supplied as Google screenshots ride the marquee
   right to left, under the rating line, instead of sitting in a static grid.
   Two things carry the whole block:

   1. Every selector carries the section class. `.course .review-card` further
      down this file floors a rail card at 26rem (416px) and sizes the type for
      a slower band, and a same-specificity override here would lose to it by
      source order, which stretched the phone page column past the viewport.
   2. The rail card is SHRUNK. A card sliding past is read at a glance, not
      studied, so the type steps down and the long review is clamped. The full
      text stays in the DOM and in the Restaurant schema, so nothing is lost to
      a crawler or to a screen reader. */
.course .guest-marquee .review-card {
  min-width: 19rem;
  max-width: 21rem;
  padding: 1.5rem 1.35rem;
  gap: 0.6rem;
  /* Not centred like the rail's default: the source line is pushed to the
     bottom by `__text { flex: 1 }`, so every "Posted on Google" sits on the
     same line across the rail and the cards read as one band. */
  justify-content: flex-start;
}

.course .guest-marquee .review-card__header {
  gap: 0.7rem;
  margin-bottom: 0.9rem;
}

.course .guest-marquee .review-card__avatar {
  width: 2.2rem;
  height: 2.2rem;
}

.course .guest-marquee .review-card__author {
  font-size: var(--text-sm);
}

/* The meta line must be allowed to WRAP. sardar zohaib's "Local Guide ·
   14 reviews · 4 photos · a month ago" is a single 416px line under the
   nowrap the rail inherits from `.marquee`, which cannot fit a shrunk card. */
.course .guest-marquee .review-card__date,
.course .guest-marquee .review-card__source {
  font-size: 0.7rem;
  white-space: normal;
}

.course .guest-marquee .review-card__stars {
  font-size: var(--text-xs);
  margin-bottom: 0.6rem;
}

/* Clamped to nine lines: a real review is far longer than a rail card, and
   the clamp is what keeps the band a rail rather than a wall of text. */
.course .guest-marquee .review-card__text {
  font-size: 0.9rem;
  line-height: 1.6;
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 9;
  overflow: hidden;
}

.course .guest-marquee .review-card__text p + p {
  margin-top: 0.6rem;
}

.course .guest-marquee .review-card__source {
  margin-top: 0.9rem;
  padding-top: 0.75rem;
}

/* No profile photos were supplied, so the avatar is the reviewer's initials
   on a faint gold disc: honest about being a stand-in, and it keeps the
   header row's rhythm identical to a photo avatar. */
.review-card__avatar--initials {
  display: grid;
  place-items: center;
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  letter-spacing: 0.04em;
  color: var(--color-gold);
  background: rgba(200, 166, 95, 0.12);
}

/* Reduced motion: the rail does not travel, so it becomes a hand-scrolled one
   and the duplicate half is dropped. Without this the track simply stops
   mid-slide and the band is a static strip clipped at both masked edges, with
   no way to reach the reviews further along it. */
@media (prefers-reduced-motion: reduce) {
  .marquee.guest-marquee {
    overflow-x: auto;
  }

  .guest-marquee .review-card[aria-hidden="true"] {
    display: none;
  }
}

/* --- Editorial prose (About page) ---
   Client instruction (2026-09-17): no bullet points on /about/, every block
   reads as flowing paragraphs. The Pasons portfolio and the eight values
   commitments used to ride boxed `.experience-list` rows with a bullet glyph;
   that machinery is gone and these rules carry the copy instead: a reading
   measure, a book-like line height, and a heading that sits in the same
   rhythm as the paragraphs below it. */
.about-prose__heading {
  font-family: var(--font-display);
  font-size: var(--text-xl);
  color: var(--color-cream);
  margin-bottom: var(--space-md);
}

.about-prose p {
  max-width: 62ch;
  font-size: var(--text-base);
  line-height: 2;
  color: var(--color-cream-dim);
}

.about-prose p + p {
  margin-top: var(--space-md);
}

/* --- Vision & Mission (About page) ---
   The client's reference (pasonsme.com/about-us) carries its vision and
   mission as full flowing paragraphs, and the standing rule for this page
   is prose only, so the two thin boxed `.card` rows became two prose
   columns. Each column is a title with a short gold rule above it, the
   statement itself as a larger cream lead, and a supporting paragraph in
   the same register as .about-prose (62ch measure, line-height 2).
   The columns stack under 860px, the same breakpoint the chairman grid
   uses, so the phone reads one continuous prose flow. */
.vm {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: clamp(2.5rem, 6vw, 5.5rem);
}

.vm__title {
  position: relative;
  padding-top: var(--space-lg);
  margin-bottom: var(--space-md);
  font-family: var(--font-display);
  font-size: var(--text-2xl);
  color: var(--color-gold);
}

.vm__title::before {
  content: "";
  position: absolute;
  top: 0;
  left: 0;
  width: 2.75rem;
  height: 1px;
  background: var(--color-gold);
}

.vm__lead {
  max-width: 62ch;
  margin-bottom: var(--space-md);
  font-size: var(--text-lg);
  line-height: 1.9;
  color: var(--color-cream);
}

.vm__body {
  max-width: 62ch;
  font-size: var(--text-base);
  line-height: 2;
  color: var(--color-cream-dim);
}

@media (max-width: 860px) {
  .vm {
    grid-template-columns: minmax(0, 1fr);
    gap: var(--space-xl);
  }
}

/* --- Chairman's Message (About page) ---
   Portrait beside the message, one grid, so the photo cannot drift away from
   the words it attributes.
   align-items: start, NOT center. The message column is far taller than the
   portrait, so centring the photo would float it into the middle of the
   paragraphs and detach it from its own caption underneath.
   The grid is capped at 1060px rather than left at the container width because
   the message track would otherwise stretch past 1200px on a wide screen. The
   paragraphs cap themselves at the global 62ch, so an uncapped track would
   leave a wide empty gutter to the right of the text. */
.chairman {
  display: grid;
  grid-template-columns: minmax(0, 340px) minmax(0, 1fr);
  gap: clamp(2rem, 5vw, 4.5rem);
  align-items: start;
  max-width: 1060px;
}

.chairman__portrait {
  overflow: hidden;
  background-color: var(--color-bg-card);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
}

/* 4/5 rather than the source's 1066x984, so the plate reads as a portrait.
   object-fit: cover does the cropping; object-position keeps it centred on the
   subject, who sits in the middle of the frame. */
.chairman__portrait img {
  display: block;
  width: 100%;
  height: auto;
  aspect-ratio: 4 / 5;
  object-fit: cover;
  object-position: center;
}

.chairman__portrait figcaption {
  display: flex;
  flex-direction: column;
  gap: 0.1rem;
  padding: 0.9rem 1.15rem 1rem;
  border-top: 1px solid var(--color-border);
}

.chairman__name {
  font-family: var(--font-display);
  font-size: var(--text-lg);
  line-height: 1.3;
  color: var(--color-gold);
}

.chairman__role {
  font-size: var(--text-xs);
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--color-cream-muted);
}

/* The base .section-header carries a 4rem bottom margin, sized for a heading
   that sits above a full-width block. Here it sits above five paragraphs in a
   single column, so the gap is tightened. */
.chairman__message .section-header {
  margin-bottom: var(--space-xl);
}

/* line-height 2 is what this message used before it grew a second column, so
   the reading rhythm of the text itself is unchanged by the layout move. */
.chairman__copy {
  font-size: var(--text-lg);
  line-height: 2;
}

.chairman__copy p + p {
  margin-top: var(--space-lg);
}

.chairman__signoff {
  margin-top: var(--space-2xl);
  padding-top: var(--space-xl);
  border-top: 1px solid var(--color-border);
}

.chairman__signoff p:first-child {
  font-family: var(--font-display);
  font-size: var(--text-xl);
}

/* One column: portrait above the message it belongs to. */
@media (max-width: 860px) {
  .chairman {
    grid-template-columns: 1fr;
    gap: var(--space-2xl);
  }

  .chairman__portrait {
    max-width: 300px;
  }
}

/* --- Footer --- */
/* THE FOOTER IS TWO LAYERS: content above, one giant brand ghost below.
   The watermark is a ::before with a CSS BACKGROUND, never an <img>:
   check_menu_seo.py and probe-menu-seo.mjs both pin "no rendered image ships
   an EMPTY alt" on /menu/, and the footer renders on every route, so a
   decorative image would either burn an alt slot or break that pin. A
   pseudo-element has no DOM node at all - invisible to every image-inventory
   gate, invisible to screen readers, and pointer-events: none keeps it out of
   every hit test. It reuses the SAME asset as the brand logo above (one brand
   mark, two scales, one cached URL).
   GEOMETRY, measured from the asset (client-approved "script logo ghost",
   GitHub issue #2): the script ink fills 96.6% x 95.3% of its 1808x870
   canvas (almost no padding) at a 2.08:1 aspect - so a ghost wide enough to
   bleed off both sides is always taller than the footer and a BOTTOM-anchored
   one would cut the letter tops off. That is why the ghost is TOP-anchored at
   -2%: the tops stay visible just under the footer's top edge while the
   descenders and both sides run off and are cut by overflow: hidden below.
   That is the RiDA composition this was asked for (tops read, bottoms and
   sides bleed). Tuned against .cortex/shots/footer/*.png; probe-footer.mjs
   pins the band (opacity 0.03-0.12, wider than the container), the hit-test
   and a pixel proof that it actually paints. */
.footer {
  position: relative;
  overflow: hidden;
  background-color: var(--color-bg-card);
  border-top: 1px solid var(--color-border);
  padding: var(--space-4xl) 0 var(--space-2xl);
}

.footer::before {
  content: "";
  position: absolute;
  left: 50%;
  top: -2%;
  transform: translateX(-50%);
  width: min(110%, 2200px);
  aspect-ratio: 1808 / 870;
  background: url("../img/KING_CHEF.png") center / contain no-repeat;
  opacity: 0.08;
  pointer-events: none;
}

/* On phones the footer is tall (stacked columns) while the viewport is
   narrow, so the desktop proportions would leave a small ghost high up.
   Drop it into the lower half instead, and keep it INSIDE the box: the
   previous width here was 170%, which made the element 663px wide on a
   390px screen, so .footer's overflow: hidden cut ~136px off EACH end
   and only ~57% of the script rendered ("ng Che" instead of King Chef).
   Measured 2026-09-24: at width 100% the box spans the footer exactly
   and nothing is clipped at 320/360/390/414/768. Do not widen past 100%
   here -- the box may bleed on desktop (that is the RiDA composition) but
   on a phone both ends of the wordmark must stay on screen. */
@media (max-width: 768px) {
  .footer::before {
    top: auto;
    bottom: 6%;
    width: 100%;
  }
}

.footer > .container {
  position: relative;
  z-index: 1;
}

.footer__grid {
  display: grid;
  grid-template-columns: 2fr 1fr 1fr 1fr;
  gap: var(--space-3xl);
  margin-bottom: var(--space-4xl);
}

.footer__brand {
  margin-bottom: var(--space-lg);
}

.footer__brand img {
  display: block;
  height: 72px;
  width: auto;
  object-fit: contain;
}

@media (max-width: 768px) {
  .footer__brand img {
    height: 56px;
  }
}

.footer__desc {
  font-size: var(--text-lg);
  line-height: 1.6;
  color: var(--color-cream-dim);
  max-width: 34ch;
}

.footer__title {
  font-size: var(--text-xl);
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: 0.12em;
  color: var(--color-gold);
  margin-bottom: var(--space-lg);
}

.footer__link {
  display: block;
  font-size: var(--text-lg);
  line-height: 1.5;
  color: var(--color-cream-dim);
  margin-bottom: var(--space-md);
  transition: color var(--transition-fast);
}

.footer__link:hover {
  color: var(--color-gold);
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-underline-offset: 4px;
}

.footer__bottom {
  border-top: 1px solid var(--color-border);
  padding-top: var(--space-xl);
  display: flex;
  justify-content: space-between;
  align-items: center;
  font-size: var(--text-base);
  color: var(--color-cream-dim);
}

.footer__pasons {
  display: flex;
  align-items: center;
  gap: var(--space-sm);
  color: var(--color-cream-dim);
}

/* --- Page Header (inner pages) --- */
.page-header {
  padding: var(--space-4xl) 0 var(--space-2xl);
  text-align: left;
}

.page-header__title {
  margin-bottom: var(--space-md);
}

.page-header__subtitle {
  font-size: var(--text-lg);
  color: var(--color-cream-dim);
  max-width: 55ch;
  margin: 0;
}

/* A second, quieter line of the page header, used on /menu/ only: it is the
   sentence that says the printed menu's cards open when clicked. One size down
   from the subtitle and the same tone, so the two paragraphs read as ONE intro
   rather than as a title and a subtitle of their own - which is exactly the
   fault being avoided on that page (two header bands, client 2026-09-21).

   The colour is --color-cream-dim, not --color-cream-muted: it is body copy at
   16px, and dim measures 8.99:1 on --color-bg where muted measures 4.71:1 -
   over the 4.5:1 floor, but only just. Keep the pair with .page-header__subtitle. */
.page-header__note {
  margin-top: var(--space-md);
  font-size: var(--text-base);
  color: var(--color-cream-dim);
  max-width: 62ch;
}

/* The run-in labels on those two paragraphs ("In short:", "Key takeaway:").
   They are <strong>, NOT headings, and that is load-bearing rather than
   semantic taste: a heading on either line would be a second laid-out heading
   above the gallery, which is the fault the client reported on /menu/ and the
   one .cortex/probe-menu-header.mjs fails on. Gold marks the label as a label;
   --color-gold measures far above the floor on --color-bg, and 600 keeps it a
   step under the H1's weight. */
.page-header__subtitle strong,
.page-header__note strong {
  color: var(--color-gold);
  font-weight: 600;
}

/* --- Journey prose (About page) ---
   The story used to ride a dotted timeline: a gold hairline down the left with
   a bullet dot per era. Same client instruction as the prose block above, so
   each era is now ONE paragraph, with the era name and its title as run-in
   labels inside the sentence flow, and a generous gap between paragraphs
   instead of a connecting line. */
.journey {
  max-width: 62ch;
}

.journey__para {
  font-size: var(--text-base);
  line-height: 2;
  color: var(--color-cream-dim);
}

.journey__para + .journey__para {
  margin-top: var(--space-xl);
}

.journey__era {
  font-family: var(--font-display);
  font-style: italic;
  font-size: var(--text-lg);
  color: var(--color-gold);
  margin-right: 0.75ch;
}

.journey__title {
  font-weight: 600;
  color: var(--color-cream);
  margin-right: 0.75ch;
}

/* --- Catering (Image-Rich Cards) --- */
.catering-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
  gap: 1.2rem;
}

.catering-item {
  position: relative;
  overflow: hidden;
  border-radius: var(--radius-lg);
  background: var(--color-bg-card);
  border: 1px solid var(--color-border);
  transition: border-color var(--transition-base), transform var(--transition-base);
}

.catering-item:hover {
  border-color: var(--color-gold);
  transform: translateY(-3px);
}

.catering-item__image {
  aspect-ratio: 16 / 10;
  overflow: hidden;
}

.catering-item__image img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: transform 0.8s var(--ease-out);
}

.catering-item:hover .catering-item__image img {
  transform: scale(1.06);
}

.catering-item__body {
  padding: 1.2rem 1.4rem 1.4rem;
}

.catering-item__icon {
  width: 2.4rem;
  height: 2.4rem;
  display: flex;
  align-items: center;
  justify-content: center;
  background: rgba(200, 166, 95, 0.1);
  border-radius: var(--radius-md);
  color: var(--color-gold);
  margin-bottom: 0.8rem;
}

.catering-item__icon svg {
  width: 18px;
  height: 18px;
}

.catering-item__title {
  font-size: var(--text-lg);
  color: var(--color-cream);
  margin-bottom: 0.3rem;
  font-family: var(--font-display);
}

.catering-item__desc {
  font-size: var(--text-sm);
  color: var(--color-cream-dim);
  line-height: 1.6;
}

/* --- Gallery ----------------------------------------------------------------
   The /gallery/ tile grid. WHAT CHANGED ON 2026-09-22, and why the old rules
   could not simply be tuned:

   THE TILES WERE RIBBONS. The grid was `grid-auto-rows: 10rem` - a FIXED 160px
   row - while the columns grew with the viewport, so a tile measured 334x160
   (2.09:1) at 1440 and 394x160 (2.47:1) at 1920. Fifteen wide slivers is the
   "fully stack image" the client described, and the fault is structural: a
   fixed row height cannot stay in proportion to a fluid column.

   THE FIX IS `aspect-ratio` ON THE TILE, NOT A ROW HEIGHT. A grid row is
   `auto`-sized, so it takes the height its content asks for, and the content
   (a 4:3 box) derives its height from the COLUMN WIDTH. The tile is therefore
   4:3 at every viewport width by construction rather than by tuning - there is
   no magic number left to go stale.

   THE SPAN CLASSES ALREADY EXISTED AND WERE NEVER USED. `.gallery-item--wide /
   --tall / --large` have been in this stylesheet while the template applied
   none of them, so the masonry the CSS was built for never rendered - the same
   shape of fault as `.grid-split--reverse`, which also never did what its name
   said. The template now uses `--large` on one anchor per section.

   THE ANCHOR STRETCHES RATHER THAN DECLARING A RATIO, deliberately: it spans
   2 cols x 2 rows, so `align-self: stretch` (the default) makes it exactly the
   height of the two singles beside it. Declaring `aspect-ratio: 4/3` on it too
   would make it the same shape but would fight the row sizing and leave the
   grid short. This way one number - the tile ratio - governs the whole grid.

   THREE COLUMNS, and the count is what makes the anchor work: two singles
   stacked in column 1 next to a 2x2 anchor needs 3 tracks. */
.gallery-grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 0.9rem;
}

.gallery-item {
  position: relative;
  aspect-ratio: 4 / 3;
  border-radius: var(--radius-md);
  overflow: hidden;
  background-color: var(--color-bg-alt);
  transition: transform var(--transition-base), box-shadow var(--transition-base);
}

.gallery-item:hover {
  transform: translateY(-3px);
  box-shadow: 0 16px 32px -8px rgba(10, 18, 12, 0.5);
  z-index: 2;
}

.gallery-item img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: transform 0.8s var(--ease-out);
}

.gallery-item:hover img {
  transform: scale(1.05);
}

/* Hover wash. A gradient rather than a flat scrim, so the tile reads as lit
   from the top rather than as a grey rectangle laid over the photograph. */
.gallery-item::after {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(to top, rgba(10, 18, 12, 0.55), rgba(10, 18, 12, 0.12));
  opacity: 0;
  transition: opacity var(--transition-base);
  pointer-events: none;
}

.gallery-item:hover::after {
  opacity: 1;
}

/* The anchor. `grid-auto-flow: dense` is NOT used: the template places the
   anchor FIRST in every section, so the singles fill in behind it and no
   backfill pass is needed. If a section ever puts the anchor last, the grid
   would leave a hole - place it first, or turn dense on and re-measure. */
.gallery-item--large {
  grid-column: span 2;
  grid-row: span 2;
}

/* `--wide` and `--tall` are kept because they are part of the published
   vocabulary and cost nothing when unused. NOTE they break the one-ratio rule
   above: a `--wide` tile is 2:3 (2 cols x 1 row) and a `--tall` tile is 2:3
   (1 col x 2 rows) INVERTED, so either would crop a 4:3 file and would need its
   own asset shape. Nothing on /gallery/ uses them today. */
.gallery-item--wide {
  grid-column: span 2;
}

.gallery-item--tall {
  grid-row: span 2;
}

/* Two columns on a tablet. The anchor keeps its 2x2 span, so it becomes the
   full width of the grid with a single tile beneath it - still one shape. */
@media (max-width: 900px) {
  .gallery-grid {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 0.75rem;
  }
}

/* One column on a phone, and the anchor stops spanning. At 390 a 2-column grid
   would make each tile 169px wide, where the 4:3 box is 127px tall - smaller
   than the caption a photograph needs to say anything. Full width instead. */
@media (max-width: 600px) {
  .gallery-grid {
    grid-template-columns: minmax(0, 1fr);
    gap: 0.6rem;
  }

  .gallery-item--large {
    grid-column: span 1;
    grid-row: span 1;
  }
}

/* --- The named food tile (2026-09-23) --- */
/* The client: "below name card need show that name exact from have image". A
   food tile is now photo + NAME CARD: the dish's own name sits below the
   photograph, inside the same rounded card (the card's `--color-bg-alt`
   surface is the name's plate), so the grid reads as a menu of peers instead
   of twenty-four anonymous plates.

   The names are the master filenames - the client's own menu names as
   filenames - and they match the printed menu word for word (the underscore in
   files like "Kozhi Green_pepper Tawa" is just a space). ONE exception, noted
   to the client: the file "mutton biriyan.png" prints on their menu as "Mutton
   Biriyani", so the menu spelling wins (the Zinker precedent).

   THE 4:3 FRAME MOVES FROM THE TILE ONTO THE SHOT. The tile carried
   `aspect-ratio: 4/3`; with a name band below the photo that ratio would
   squeeze the photo to make room for the text, so the frame goes onto the shot
   wrapper and the tile grows to photo + name. The hover wash moves onto the
   shot with it: a wash covering the whole tile would darken the name plate on
   the very hover that is supposed to highlight the name. */
.gallery-item--named {
  aspect-ratio: auto;
}

.gallery-item--named .gallery-item__shot {
  display: block;
  position: relative;
  overflow: hidden;
  aspect-ratio: 4 / 3;
}

.gallery-item--named img {
  aspect-ratio: 4 / 3;
  height: auto;
}

.gallery-item--named::after {
  content: none;
}

.gallery-item--named .gallery-item__shot::after {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(to top, rgba(10, 18, 12, 0.55), rgba(10, 18, 12, 0.12));
  opacity: 0;
  transition: opacity var(--transition-base);
  pointer-events: none;
}

.gallery-item--named:hover .gallery-item__shot::after {
  opacity: 1;
}

/* The name line. The longest name (Baby Corn Salt N Pepper Mushroom, 32
   characters) fits on ONE line at 390px, so all twenty-four cards stay one
   size at every viewport - the client's "same size" contract. Small type gets
   slight positive tracking, and `position: relative` keeps the words above
   anything the photo animates. Gold on hover joins the lift, shadow and zoom
   as the tile's feedback. */
.gallery-item__name {
  position: relative;
  margin: 0;
  padding: 0.7rem 0.85rem 0.8rem;
  font-size: var(--text-base);
  font-weight: 600;
  line-height: 1.3;
  letter-spacing: 0.01em;
  color: var(--color-cream);
  transition: color var(--transition-base);
}

.gallery-item--named:hover .gallery-item__name {
  color: var(--color-gold);
}

/* --- Contact Form --- */
.form-group {
  margin-bottom: var(--space-lg);
}

.form-label {
  display: block;
  font-size: var(--text-sm);
  font-weight: 500;
  color: var(--color-cream);
  margin-bottom: var(--space-sm);
}

.form-input,
.form-textarea {
  width: 100%;
  padding: var(--space-md) var(--space-lg);
  background-color: var(--color-bg-card);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  color: var(--color-cream);
  font-family: var(--font-body);
  font-size: var(--text-base);
  transition: border-color var(--transition-fast);
}

.form-input:focus,
.form-textarea:focus {
  outline: none;
  border-color: var(--color-gold);
}

.form-textarea {
  min-height: 150px;
  resize: vertical;
}

/* --- Menu Category Items --- */
.menu-categories {
  display: grid;
  gap: var(--space-md);
  margin-top: var(--space-xl);
}

.menu-category {
  padding: var(--space-md) var(--space-lg);
  background: var(--color-bg-card);
  border-radius: var(--radius-md);
  border: 1px solid var(--color-border);
  transition: border-color var(--transition-fast);
}

.menu-category:hover {
  border-color: var(--color-gold);
}

.menu-category__title {
  color: var(--color-gold);
  font-size: var(--text-base);
  margin-bottom: var(--space-xs);
}

.menu-category__desc {
  font-size: var(--text-sm);
  color: var(--color-cream-dim);
}

/* --- Tag Pills --- */
.tag-list {
  display: flex;
  gap: var(--space-md);
  justify-content: center;
  flex-wrap: wrap;
}

.tag {
  display: inline-block;
  padding: 0.6rem 1.5rem;
  border: 1px solid var(--color-gold);
  color: var(--color-gold);
  border-radius: var(--radius-full);
  font-size: var(--text-sm);
  font-weight: 400;
  letter-spacing: 0.03em;
}

/* --- CTA Band --- */
.cta-band {
  padding: var(--space-4xl) 0;
  text-align: left;
  border-top: 1px solid var(--color-border);
}

.cta-band__actions {
  display: flex;
  gap: var(--space-lg);
  justify-content: flex-start;
  flex-wrap: wrap;
  margin-top: var(--space-2xl);
}

.cta-band__title {
  margin-top: var(--space-md);
  font-size: clamp(2rem, 5vw, 3.5rem);
}

.cta-band__lead {
  margin-top: var(--space-md);
  font-size: var(--text-lg);
}

.cta-band__email {
  margin-top: var(--space-lg);
  font-size: var(--text-lg);
  letter-spacing: 0.01em;
}

.cta-band__email a {
  color: var(--color-gold);
  text-decoration: none;
  border-bottom: 1px solid color-mix(in srgb, var(--color-gold) 45%, transparent);
  padding-bottom: 2px;
  transition: color 0.25s var(--ease-out), border-color 0.25s var(--ease-out);
}

.cta-band__email a:hover,
.cta-band__email a:focus-visible {
  color: var(--color-cream);
  border-bottom-color: var(--color-cream);
}

/* --- Animations --- */
@keyframes fadeInUp {
  from {
    opacity: 0;
    transform: translateY(24px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

.reveal {
  opacity: 0;
  transform: translateY(24px);
  transition: opacity 0.7s var(--ease-out), transform 0.7s var(--ease-out);
}

.reveal--visible {
  opacity: 1;
  transform: translateY(0);
}

/* --- Responsive --- */
@media (max-width: 1024px) {
  .grid-4 {
    grid-template-columns: repeat(2, 1fr);
  }

  .footer__grid {
    grid-template-columns: repeat(2, 1fr);
  }

  .grid-split,
  .grid-split--reverse {
    grid-template-columns: 1fr;
    gap: var(--space-2xl);
  }
}

@media (max-width: 768px) {
  .grid-2,
  .grid-3,
  .grid-4 {
    grid-template-columns: 1fr;
  }

  /* The drawer's LAYOUT is designed ONCE, for phones and tablets alike, in the
     "SMALL-SCREEN DRAWER" section at the foot of this file. This block only
     switches it on, so the two breakpoints cannot drift apart. */
  .nav__list {
    display: none;
  }

  .nav__list--open {
    display: flex;
  }

  .header__inner {
    justify-content: flex-start;
    gap: var(--space-md);
  }

  /* Toggle is the first DOM child so it sits far-left; logo follows */
  .nav__toggle {
    display: flex;
  }

  .hero__actions {
    flex-direction: column;
    align-items: flex-start;
  }

  .stats {
    grid-template-columns: 1fr;
    gap: var(--space-lg);
  }

  .footer__grid {
    grid-template-columns: 1fr;
    gap: var(--space-2xl);
  }

  .footer__bottom {
    flex-direction: column;
    gap: var(--space-md);
    text-align: center;
  }

  .map-container {
    height: 350px;

    /* The labels shrink with the plate, and BOTH halves of that matter: this
       token PAINTS the type, and static/js/map.js PARSES it to keep every label
       inside the frame. It shrinks here rather than in a rule of its own so the
       two can never disagree. */
    --map-label-px: 9px;
  }

  /* THE HUB RINGS COME DOWN WITH THE PLATE. The outermost ring is 118px, which is
     13% of the desktop map's width and 34% of the phone's: at that share the three
     rings stop reading as a marked centre and start reading as a target painted
     over the country. They shrink together, keeping the 1:1.7:2.6 ratio. */
  .map-container .map-hub-label i:nth-of-type(1) {
    width: 34px;
    height: 34px;
  }

  .map-container .map-hub-label i:nth-of-type(2) {
    width: 56px;
    height: 56px;
  }

  .map-container .map-hub-label i:nth-of-type(3) {
    width: 84px;
    height: 84px;
  }

  /* The word sits above the outermost ring, which is now 42px away instead of 59,
     so it travels down with the rings it clears. Leaving it at -78px would float
     it a ring's worth of empty plate above the hub. */
  .map-container .map-hub-label span {
    top: -56px;
  }
}

  /* statement-band mobile layout is handled in its own responsive block */

/* Tablet and small laptop widths: the desktop header needs ~1250px (logo 233px
   + nav 942px + gaps and padding). Below that the nav overflows the viewport, so
   the drawer takes over from 769px up to 1279px.

   THAT WHOLE RANGE IS NOW COVERED BY THE SINGLE `@media (max-width: 1279px)`
   DRAWER SECTION AT THE FOOT OF THIS FILE, which styles phones and tablets
   identically on purpose. There used to be a second copy of the drawer rules
   here, and the two copies drifted: neither of them reset `width: max-content`
   from the desktop rule at the top of this file, and on a fixed box `width`
   BEATS `left: 0; right: 0`. So the open menu measured 171px wide on every
   screen from 320px to 768px, with the page showing beside it. Do not
   reintroduce a second copy of the drawer rules; extend the one at the foot. */

@media (max-width: 480px) {
  .delivery-banner {
    font-size: var(--text-xs);
  }
}

/* --- Reduced Motion --- */
@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }

  .reveal {
    opacity: 1;
    transform: none;
  }
}

/* --- Focus States --- */
:focus-visible {
  outline: 2px solid var(--color-gold);
  outline-offset: 2px;
}

/* --- Selection --- */
::selection {
  background-color: var(--color-gold);
  color: var(--color-bg);
}

/* --- Scrollbar --- */
::-webkit-scrollbar {
  width: 8px;
}

::-webkit-scrollbar-track {
  background: var(--color-bg);
}

::-webkit-scrollbar-thumb {
  background: var(--color-gold-dark);
  border-radius: 4px;
}

::-webkit-scrollbar-thumb:hover {
  background: var(--color-gold);
}

/* ============================================
   CINEMATIC LAYER (2026)
   Smooth scroll + scroll-driven reveals + full-bleed imagery.
   No blur, no fog, no haze overlays.
   ============================================ */

/* --- Lenis smooth scroll plumbing --- */
html.lenis,
html.lenis body {
  height: auto;
}
.lenis.lenis-smooth {
  scroll-behavior: auto !important;
}
.lenis.lenis-smooth [data-lenis-prevent] {
  overscroll-behavior: contain;
}
.lenis.lenis-stopped {
  overflow: hidden;
}

/* No-JS guard: keep everything visible if GSAP never runs */
html.no-js [data-reveal],
html.no-js [data-stagger],
html.no-js .m-reveal {
  opacity: 1 !important;
  transform: none !important;
}

/* Full-bleed section helper */
.fullbleed {
  position: relative;
  overflow: hidden;
  width: 100%;
}
.fullbleed img,
.fullbleed .fullbleed__media {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* --- Everyday welcome: short copy, a clear action and the real dining room --- */
.hero-welcome {
  --hero-ink: #faf6ec;
  position: relative;
  isolation: isolate;
  overflow: hidden;
  background: var(--color-bg);
  color: var(--hero-ink);
}

.hero-welcome__photo {
  position: absolute;
  inset: 0;
  z-index: -2;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: center 55%;
}

.hero-welcome::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  background:
    linear-gradient(to top, rgba(16, 29, 20, 0.5) 0%, rgba(16, 29, 20, 0) 28%),
    linear-gradient(90deg, rgba(16, 29, 20, 0.92) 0%, rgba(16, 29, 20, 0.78) 42%, rgba(16, 29, 20, 0.18) 76%, rgba(16, 29, 20, 0.06) 100%);
}

.hero-welcome__inner {
  display: flex;
  align-items: center;
  width: 100%;
  max-width: 1440px;
  /* Fill the visible screen at any browser zoom, with a floor for short phones */
  min-height: max(32rem, calc(100svh - var(--header-h, 0px)));
  margin-inline: auto;
  padding: clamp(3rem, 6vw, 6rem) clamp(1.25rem, 5vw, 4.5rem);
}

.hero-welcome__copy {
  width: 100%;
  max-width: 36rem;
}

.hero-welcome__label {
  display: inline-flex;
  align-items: center;
  gap: 0.75rem;
  margin-bottom: 1.75rem;
  color: var(--color-gold);
  font-size: 0.8125rem;
  font-weight: 600;
  letter-spacing: 0.22em;
  text-transform: uppercase;
}

.hero-welcome__label::before {
  content: "";
  width: 2.5rem;
  height: 1px;
  background: var(--color-gold);
}

/* Bold sans statement line answered by the brand serif in gold italic */
.hero-welcome__title {
  margin: 0;
  color: var(--hero-ink);
  font-family: var(--font-body);
  font-size: clamp(2.05rem, 8.2vw, 2.9rem);
  font-style: normal;
  font-weight: 800;
  letter-spacing: -0.035em;
  line-height: 1.04;
  text-wrap: balance;
}

.hero-welcome__title span {
  display: block;
}

.hero-welcome__line--gold {
  color: var(--color-gold);
  font-family: var(--font-display);
  font-style: italic;
  font-weight: 700;
  letter-spacing: 0;
  word-spacing: 0.14em;
  margin-top: 0.14em;
}

.hero-welcome__description {
  max-width: 30rem;
  margin-top: 1.5rem;
  color: rgba(250, 246, 236, 0.85);
  font-size: 1.0625rem;
  font-weight: 400;
  line-height: 1.75;
}

.hero-welcome__actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.75rem 1.75rem;
  margin-top: 2rem;
}

/* Where to start: the hero's quiet checklist (homepage SEO/AEO pass,
   2026-09-23). Three rows, each opening with a gold run-in label - the same
   label treatment as .page-header__note strong - then one real link. No
   markers (role="list" keeps the semantics), no new colours: the rows borrow
   the description's ink and the links take the hero's gold. */
.hero-welcome__start {
  margin-top: 1.5rem;
  padding: 0;
  list-style: none;
  display: grid;
  gap: 0.55rem;
  max-width: 34rem;
}

.hero-welcome__start li {
  font-size: var(--text-base);
  line-height: 1.6;
  color: rgba(250, 246, 236, 0.85);
}

.hero-welcome__start strong {
  color: var(--color-gold);
  font-weight: 600;
}

.hero-welcome__start a {
  color: var(--color-gold-light);
  text-decoration: underline;
  text-underline-offset: 3px;
  transition: color var(--transition-fast);
}

.hero-welcome__start a:hover {
  color: var(--color-cream);
}

.hero-welcome__menu,
.hero-welcome__order {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 48px;
  font-size: 0.9375rem;
  font-weight: 600;
  transition: color var(--transition-fast), background-color var(--transition-fast);
}

.hero-welcome__menu {
  padding: 0.75rem 1.5rem;
  border-radius: var(--radius-md);
  background: var(--color-gold);
  color: var(--color-bg);
}

.hero-welcome__menu:hover {
  background: var(--color-gold-light);
  color: var(--color-bg);
}

.hero-welcome__order {
  padding: 0.75rem 1.5rem;
  border: 1px solid rgba(250, 246, 236, 0.4);
  border-radius: var(--radius-md);
  color: var(--hero-ink);
  text-decoration: none;
  transition: color var(--transition-fast), border-color var(--transition-fast), background-color var(--transition-fast);
}

.hero-welcome__order:hover {
  border-color: var(--color-gold-light);
  background: rgba(211, 173, 95, 0.1);
  color: var(--color-gold-light);
}

.hero-welcome__actions a:focus-visible {
  outline: 2px solid var(--hero-ink);
  outline-offset: 5px;
}

.hero-welcome__actions a:active {
  opacity: 0.85;
}

@media (max-width: 767px) {
  .hero-welcome::before {
    background: linear-gradient(90deg, rgba(16, 29, 20, 0.91), rgba(16, 29, 20, 0.76));
  }

  .hero-welcome__inner {
    min-height: max(22rem, calc(62svh - var(--header-h, 0px)));
    padding-block: 2.5rem;
  }

  .hero-welcome__copy {
    max-width: 32rem;
  }
}

/* Desktop / laptop: scale the hero content up so it does not look small
   against a large viewport. Mobile (<768px) keeps the base rules above and
   is NOT affected, so the mobile hero you approved stays untouched. */
@media (min-width: 768px) {
  .hero-welcome__label {
    font-size: 1rem;
  }

  .hero-welcome__title {
    font-size: clamp(2.9rem, 4.6vw, 4.5rem);
  }

  .hero-welcome__description {
    max-width: 36rem;
    margin-top: 2rem;
    font-size: 1.25rem;
  }

  .hero-welcome__actions {
    margin-top: 2.75rem;
  }

  .hero-welcome__menu,
  .hero-welcome__order {
    min-height: 60px;
    font-size: 1.125rem;
    padding: 1rem 2rem;
  }

  .hero-welcome__copy {
    max-width: 48rem;
  }
}

@media (max-width: 380px) {
  .hero-welcome__actions {
    align-items: flex-start;
    flex-direction: column;
    gap: 0.5rem;
  }
}

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

/* --- Cinematic hero --- */
/* Approximate header height for first paint (JS measures the real one and
   overrides this via an inline custom property) — without a seed the hero
   is briefly 100svh tall and the page shifts once JS lands. */
:root {
  --header-h: 129px;
}
.hero-cinema {
  position: relative;
  min-height: calc(100svh - var(--header-h, 0px));
  display: flex;
  align-items: stretch;
  justify-content: flex-start;
  overflow: hidden;
  background: var(--color-bg-hero);
}
.hero-cinema__bg {
  position: absolute;
  inset: 0;
  z-index: 0;
}
.hero-cinema__bg img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  will-change: transform;
}
/* CSS-only Ken Burns fallback when the GSAP engine is not running */
html:not(.js-anim) .hero-cinema__bg img {
  animation: kenBurns 16s ease-out both;
}
@keyframes kenBurns {
  from { transform: scale(1.12); }
  to { transform: scale(1); }
}

/* --- Frosted glass: a progressive blur over the LEFT of the photo only.
   Two stacked layers (heavy then light) give a real falloff instead of a
   hard blur edge, and the mask dissolves the glass well before the middle
   of the frame — the right side of the photo stays completely sharp. --- */
.hero-cinema__frost {
  position: absolute;
  top: 0;
  bottom: 0;
  left: 0;
  width: min(78%, 90rem);
  z-index: 1;
  pointer-events: none;
  backdrop-filter: blur(7px) saturate(112%);
  -webkit-backdrop-filter: blur(7px) saturate(112%);
  -webkit-mask-image: linear-gradient(97deg, #000 0%, #000 46%, rgba(0, 0, 0, 0.45) 72%, transparent 94%);
  mask-image: linear-gradient(97deg, #000 0%, #000 46%, rgba(0, 0, 0, 0.45) 72%, transparent 94%);
}
.hero-cinema__frost::before {
  content: "";
  position: absolute;
  inset: 0;
  backdrop-filter: blur(20px) saturate(108%);
  -webkit-backdrop-filter: blur(20px) saturate(108%);
  -webkit-mask-image: linear-gradient(97deg, #000 0%, #000 30%, rgba(0, 0, 0, 0.5) 52%, transparent 72%);
  mask-image: linear-gradient(97deg, #000 0%, #000 30%, rgba(0, 0, 0, 0.5) 52%, transparent 72%);
}
/* Hairline of light where the glass meets the sharp photo */
.hero-cinema__frost::after {
  content: "";
  position: absolute;
  top: 0;
  bottom: 0;
  right: 26%;
  width: 1px;
  background: linear-gradient(180deg, rgba(236, 211, 155, 0) 0%, rgba(236, 211, 155, 0.16) 42%, rgba(236, 211, 155, 0) 100%);
}
/* Tonal wash for contrast. Sits above the glass, still no box around the type. */
.hero-cinema__scrim {
  position: absolute;
  inset: 0;
  z-index: 2;
  pointer-events: none;
  background:
    linear-gradient(
      180deg,
      rgba(10, 18, 12, 0.34) 0%,
      rgba(10, 18, 12, 0.06) 34%,
      rgba(10, 18, 12, 0.36) 74%,
      var(--color-bg-hero) 100%
    ),
    linear-gradient(
      96deg,
      rgba(10, 18, 12, 0.84) 0%,
      rgba(10, 18, 12, 0.64) 34%,
      rgba(10, 18, 12, 0.22) 58%,
      rgba(10, 18, 12, 0) 76%
    );
}
/* Without backdrop-filter the glass never paints, so carry contrast on tone alone */
@supports not ((backdrop-filter: blur(2px)) or (-webkit-backdrop-filter: blur(2px))) {
  .hero-cinema__scrim {
    background:
      linear-gradient(
        180deg,
        rgba(10, 18, 12, 0.38) 0%,
        rgba(10, 18, 12, 0.12) 34%,
        rgba(10, 18, 12, 0.4) 74%,
        var(--color-bg-hero) 100%
      ),
      linear-gradient(
        96deg,
        rgba(10, 18, 12, 0.92) 0%,
        rgba(10, 18, 12, 0.74) 34%,
        rgba(10, 18, 12, 0.24) 56%,
        rgba(10, 18, 12, 0) 72%
      );
  }
}

.hero-cinema__stage {
    position: relative;
    z-index: 3;
    display: grid;
    grid-template-columns: minmax(0, 1fr);
    align-items: end;
    gap: clamp(2rem, 4vw, 4.5rem);
    width: 100%;
    max-width: 1560px;
    margin: 0 auto;
    padding-block: clamp(2.5rem, 3.5vw, 4rem) clamp(7rem, 9vh, 9rem);
    padding-inline: clamp(1.5rem, 4vw, 4.5rem);
    box-sizing: border-box;
}

/* Open column. Left side */
.hero-cinema__content {
  position: relative;
  z-index: 3;
  box-sizing: border-box;
  width: 100%;
  flex: 0 1 auto;
  align-self: center;
  margin: 0;
  padding: 0;
  text-align: left;
}
.hero-cinema__eyebrow {
  display: inline-flex;
  align-items: center;
  gap: 0.85rem;
  letter-spacing: 0.3em;
  text-transform: uppercase;
  font-size: clamp(0.78rem, 1vw, 1.05rem);
  color: var(--color-gold-light);
  font-weight: 600;
  text-shadow: 0 1px 12px rgba(0, 0, 0, 0.5);
  animation: fadeUp 0.9s var(--ease-out) 0.55s both;
}
.hero-cinema__eyebrow::before {
  content: "";
  width: 2.75rem;
  height: 1.5px;
  background: var(--color-gold);
  flex-shrink: 0;
}
.hero-cinema__title {
  margin: clamp(0.9rem, 1.9vh, 2rem) 0 0;
  color: #faf6ec;
  max-width: 100%;
  overflow: visible;
}
.hero-cinema__line {
  display: block;
  overflow: visible;
  padding-bottom: 0.08em;
  max-width: 100%;
}
.hero-cinema__line > span {
  display: inline-block;
  max-width: 100%;
  will-change: transform;
  animation: lineRise 1.05s var(--ease-out) both;
  padding-bottom: 0.06em;
}
.hero-cinema__line--display > span {
  font-family: var(--font-display);
  font-size: clamp(2.35rem, min(4.6vw, 6.6vh), 5.5rem);
  line-height: 1.08;
  font-weight: 500;
  letter-spacing: -0.015em;
  color: #faf6ec;
  animation-delay: 0.18s;
  text-shadow: 0 2px 26px rgba(0, 0, 0, 0.6), 0 1px 2px rgba(0, 0, 0, 0.45);
}
.hero-cinema__line--accent > span {
  font-family: var(--font-display);
  font-size: clamp(3.4rem, min(7.4vw, 10.5vh), 8.25rem);
  line-height: 0.98;
  font-weight: 600;
  font-style: italic;
  letter-spacing: -0.02em;
  color: var(--color-gold-light);
  animation-delay: 0.34s;
  text-shadow: 0 2px 34px rgba(0, 0, 0, 0.66), 0 1px 3px rgba(0, 0, 0, 0.5);
}

/* The host's welcome line under the headline */
.hero-cinema__welcome {
  margin: clamp(1.15rem, 2.6vh, 2.4rem) 0 0;
  max-width: 34rem;
  font-size: clamp(1.1rem, min(1.42vw, 2.6vh), 1.6rem);
  color: rgba(250, 246, 236, 0.93);
  line-height: 1.5;
  font-weight: 500;
  text-shadow: 0 1px 18px rgba(0, 0, 0, 0.6);
  animation: fadeUp 0.9s var(--ease-out) 0.75s both;
}

/* --- The rotating kitchen. A small label and one large word that flips
   in place. The word is now clearly subordinate to the headline above it
   (it reads as "Our kitchens: Indian / Arabic / Chinese", not as a third
   headline line), and every word is one line so the column never jumps. --- */
.hero-cinema__rotate {
  display: grid;
  gap: 0.45rem;
  margin-top: clamp(1.05rem, 2.2vh, 1.8rem);
  perspective: 1100px;
  animation: fadeUp 0.9s var(--ease-out) 0.5s both;
}
.hero-cinema__rotate-label {
  display: inline-flex;
  align-items: center;
  gap: 0.6rem;
  font-size: clamp(0.68rem, min(0.78vw, 1.4vh), 0.82rem);
  font-weight: 700;
  letter-spacing: 0.28em;
  text-transform: uppercase;
  color: var(--color-gold);
  text-shadow: 0 1px 12px rgba(0, 0, 0, 0.6);
}
.hero-cinema__rotate-label::after {
  content: "";
  width: 2.4rem;
  height: 1px;
  background: rgba(236, 211, 155, 0.4);
  flex-shrink: 0;
}
.hero-cinema__rotate-stage {
  display: grid;
}
.hero-cinema__rotate-word {
  grid-area: 1 / 1;
  justify-self: start;
  font-family: var(--font-display);
  font-size: clamp(2.6rem, min(5.6vw, 8vh), 6.25rem);
  line-height: 0.98;
  font-weight: 600;
  font-style: italic;
  letter-spacing: -0.02em;
  color: var(--color-gold-light);
  white-space: nowrap;
  transform-origin: 50% 50% -0.42em;
  transform: rotateX(90deg);
  opacity: 0;
  backface-visibility: hidden;
  -webkit-backface-visibility: hidden;
  will-change: transform, opacity;
  text-shadow: 0 2px 34px rgba(0, 0, 0, 0.66), 0 1px 3px rgba(0, 0, 0, 0.5);
  transition:
    opacity 0.42s var(--ease-out),
    transform 0.62s cubic-bezier(0.62, 0.02, 0.28, 1);
}
.hero-cinema__rotate-word.is-active {
  opacity: 1;
  transform: rotateX(0deg);
}
.hero-cinema__rotate-word.is-leaving {
  opacity: 0;
  transform: rotateX(-90deg);
  transition:
    opacity 0.26s var(--ease-out),
    transform 0.62s cubic-bezier(0.62, 0.02, 0.28, 1);
}
/* No-JS: the first kitchen simply stays up */
html.no-js .hero-cinema__rotate-word:first-child {
  opacity: 1;
  transform: none;
}

/* --- Everything that changes with the kitchen: the line and its dishes.
   Styled as a refined translucent culinary marquee card with high-end glass,
   warm ambient gold trim, and readable typography hierarchy. --- */
.hero-cinema__details {
  display: grid;
  margin: clamp(1.2rem, 2.8vh, 2.5rem) 0 0;
  max-width: 48rem;
  padding: clamp(1.25rem, 1.8vw, 1.85rem) clamp(1.4rem, 2.2vw, 2.2rem);
  background: linear-gradient(135deg, rgba(16, 26, 18, 0.72) 0%, rgba(8, 14, 9, 0.82) 100%);
  border: 1px solid rgba(236, 211, 155, 0.28);
  border-left: 3px solid var(--color-gold);
  border-radius: 16px;
  box-shadow:
    0 18px 45px -12px rgba(0, 0, 0, 0.65),
    inset 0 1px 0 rgba(255, 255, 255, 0.12);
  backdrop-filter: blur(12px);
  -webkit-backdrop-filter: blur(12px);
  animation: fadeUp 0.9s var(--ease-out) 0.75s both;
}
.hero-cinema__detail {
  grid-area: 1 / 1;
  color: rgba(250, 246, 236, 0.96);
  line-height: 1.5;
  opacity: 0;
  transform: translateY(0.55rem);
  transition:
    opacity 0.3s var(--ease-out),
    transform 0.5s var(--ease-out);
}
.hero-cinema__detail.is-active {
  opacity: 1;
  transform: none;
  transition:
    opacity 0.45s var(--ease-out),
    transform 0.55s var(--ease-out);
}
html.no-js .hero-cinema__detail:first-child {
  opacity: 1;
  transform: none;
}
.hero-cinema__note {
  margin: 0;
}

/* Where this kitchen's cooking actually comes from */
.hero-cinema__origin {
  display: inline-flex;
  align-items: center;
  gap: 0.6rem;
  margin: 0 0 clamp(0.6rem, 1.1vh, 0.95rem);
  font-size: clamp(0.88rem, 1.02vw, 1.05rem);
  font-weight: 700;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--color-gold);
  text-shadow: 0 1px 12px rgba(0, 0, 0, 0.6);
}
.hero-cinema__origin::after {
  content: "";
  display: inline-block;
  width: 2rem;
  height: 1px;
  background: rgba(236, 211, 155, 0.4);
}

/* Dishes you can actually order from this kitchen, each with how it is cooked */
.hero-cinema__dishes {
  display: grid;
  gap: clamp(0.55rem, 1.15vh, 0.85rem);
  margin: 0;
  padding: 0;
  list-style: none;
}
.hero-cinema__dishes li {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  column-gap: 0.75rem;
  row-gap: 0.15rem;
  font-size: clamp(1.05rem, 1.22vw, 1.3rem);
  line-height: 1.4;
  text-shadow: 0 1px 14px rgba(0, 0, 0, 0.65);
}
.hero-cinema__dish {
  font-weight: 700;
  letter-spacing: 0.02em;
  color: #fff9ed;
  position: relative;
}
.hero-cinema__dish::before {
  content: "•";
  margin-right: 0.45rem;
  color: var(--color-gold);
  font-size: 0.9em;
}
.hero-cinema__dish-note {
  font-size: clamp(0.92rem, 1.02vw, 1.06rem);
  font-weight: 400;
  color: rgba(240, 231, 208, 0.82);
  letter-spacing: 0.01em;
}

/* Breadth of the menu for this kitchen, kept quiet under the named dishes */
.hero-cinema__sections {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0.65rem;
  margin: clamp(0.85rem, 1.4vh, 1.25rem) 0 0;
  padding-top: clamp(0.65rem, 1.1vh, 0.95rem);
  border-top: 1px solid rgba(236, 211, 155, 0.16);
  font-size: clamp(0.95rem, 1.06vw, 1.12rem);
  color: rgba(240, 231, 208, 0.88);
  text-shadow: 0 1px 14px rgba(0, 0, 0, 0.6);
}
.hero-cinema__sections span {
  font-size: 0.82em;
  font-weight: 700;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--color-gold-light);
}

/* --- Kitchen switcher: shows all three at once, lights the live one --- */
.hero-cinema__switch {
  display: flex;
  flex-wrap: nowrap;
  gap: 0.6rem;
  margin-top: clamp(1.4rem, 2.8vh, 2.4rem);
  animation: fadeUp 0.9s var(--ease-out) 0.9s both;
}
.hero-cinema__chip {
  font-family: var(--font-body);
  font-size: clamp(0.78rem, 0.9vw, 0.95rem);
  font-weight: 600;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--color-cream);
  background: rgba(16, 26, 18, 0.6);
  border: 1px solid rgba(236, 211, 155, 0.32);
  border-radius: 999px;
  padding: clamp(0.55rem, 0.75vw, 0.8rem) clamp(1.1rem, 1.45vw, 1.55rem);
  cursor: pointer;
  box-shadow: 0 4px 14px rgba(0, 0, 0, 0.35);
  transition:
    color var(--transition-base),
    border-color var(--transition-base),
    background-color var(--transition-base),
    transform var(--transition-base),
    box-shadow var(--transition-base);
}
.hero-cinema__chip:hover {
  color: #14200f;
  background: var(--color-gold-light);
  border-color: var(--color-gold-light);
  transform: translateY(-2px);
  box-shadow: 0 6px 18px rgba(212, 175, 55, 0.3);
}
.hero-cinema__chip.is-active {
  color: #14200f;
  background: var(--color-gold-light);
  border-color: var(--color-gold-light);
  box-shadow: 0 0 20px rgba(212, 175, 55, 0.4);
}
.hero-cinema__bottom-row {
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: 0.9rem;
  margin-top: clamp(1.4rem, 2.8vh, 2.4rem);
  animation: fadeUp 0.9s var(--ease-out) 0.9s both;
}
.hero-cinema__bottom-row .hero-cinema__switch {
  width: 100%;
  margin-top: 0;
  animation: none;
}
.hero-cinema__bottom-row .hero-cinema__chip {
  flex: 1 1 0;
  text-align: center;
}
.hero-cinema__actions {
  display: flex;
  flex-wrap: nowrap;
  width: 100%;
  gap: 0.75rem;
}
.hero-cinema__bottom-row .btn {
  flex: 1 1 0;
  justify-content: center;
  padding: 0.85rem 1.6rem;
  font-size: 0.82rem;
}

.hero-showcase-card {
  position: relative;
  display: none;
  background: linear-gradient(145deg, rgba(16, 26, 18, 0.78) 0%, rgba(9, 15, 10, 0.88) 100%);
  border: 1px solid rgba(236, 211, 155, 0.28);
  border-radius: 20px;
  overflow: hidden;
  box-shadow:
    0 24px 50px -15px rgba(0, 0, 0, 0.75),
    inset 0 1px 0 rgba(255, 255, 255, 0.12);
  backdrop-filter: blur(14px);
  -webkit-backdrop-filter: blur(14px);
  transition: transform var(--transition-base), box-shadow var(--transition-base), border-color var(--transition-base);
}

.hero-showcase-card.is-active {
  display: block;
  animation: showcaseEnter 0.55s cubic-bezier(0.16, 1, 0.3, 1) both;
}

@keyframes showcaseEnter {
  from {
    opacity: 0;
    transform: translateY(16px) scale(0.97);
  }
  to {
    opacity: 1;
    transform: translateY(0) scale(1);
  }
}

.hero-showcase-card:hover {
  border-color: rgba(236, 211, 155, 0.45);
  box-shadow:
    0 30px 60px -15px rgba(0, 0, 0, 0.85),
    0 0 25px rgba(212, 175, 55, 0.15),
    inset 0 1px 0 rgba(255, 255, 255, 0.2);
}

.hero-showcase-card__visual {
  position: relative;
  width: 100%;
  height: 200px;
  overflow: hidden;
}

.hero-showcase-card__visual img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: transform 0.9s cubic-bezier(0.16, 1, 0.3, 1);
}

.hero-showcase-card:hover .hero-showcase-card__visual img {
  transform: scale(1.06);
}

.hero-showcase-card__visual::after {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(180deg, rgba(9, 15, 10, 0.1) 0%, rgba(9, 15, 10, 0.75) 100%);
}

.hero-showcase-card__badge {
  position: absolute;
  top: 1rem;
  left: 1rem;
  z-index: 2;
  font-size: 0.72rem;
  font-weight: 700;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: #14200f;
  background: var(--color-gold-light);
  padding: 0.35rem 0.85rem;
  border-radius: 999px;
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.4);
}

.hero-showcase-card__body {
  padding: 1.35rem 1.45rem 1.4rem;
  display: flex;
  flex-direction: column;
  gap: 0.7rem;
}

.hero-showcase-card__meta {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.5rem;
}

.hero-showcase-card__tag {
  font-size: 0.78rem;
  font-weight: 700;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--color-gold);
}

.hero-showcase-card__rating {
  font-size: 0.8rem;
  font-weight: 600;
  color: #ffe082;
}

.hero-showcase-card__title {
  font-family: var(--font-display);
  font-size: 1.35rem;
  font-weight: 600;
  color: #fff9ed;
  margin: 0;
  line-height: 1.25;
}

.hero-showcase-card__desc {
  font-size: 0.88rem;
  color: rgba(240, 231, 208, 0.85);
  line-height: 1.5;
  margin: 0;
}

.hero-showcase-card__highlights {
  display: flex;
  flex-wrap: wrap;
  gap: 0.45rem;
  margin-top: 0.2rem;
}

.hero-showcase-card__pill {
  font-size: 0.74rem;
  color: var(--color-cream);
  background: rgba(236, 211, 155, 0.1);
  border: 1px solid rgba(236, 211, 155, 0.2);
  padding: 0.25rem 0.65rem;
  border-radius: 6px;
}

.hero-showcase-card__action {
  margin-top: 0.4rem;
  padding-top: 0.75rem;
  border-top: 1px solid rgba(236, 211, 155, 0.14);
}

.hero-showcase-card__btn {
  display: inline-flex;
  align-items: center;
  gap: 0.55rem;
  color: var(--color-gold-light);
  font-size: 0.84rem;
  font-weight: 600;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  transition: gap var(--transition-base), color var(--transition-base);
}

.hero-showcase-card__btn svg {
  width: 14px;
  height: 14px;
  stroke-width: 2.5;
  transition: transform var(--transition-base);
}

.hero-showcase-card__btn:hover {
  color: #ffffff;
  gap: 0.8rem;
}

.hero-showcase-card__btn:hover svg {
  transform: translateX(3px);
}

/* Quick Delivery Partner Bar */
.hero-delivery-strip {
  display: flex;
  flex-direction: column;
  gap: 0.6rem;
  background: rgba(12, 20, 14, 0.65);
  border: 1px solid rgba(236, 211, 155, 0.18);
  border-radius: 14px;
  padding: 0.85rem 1.15rem;
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
}

.hero-delivery-strip__label {
  display: flex;
  align-items: center;
  gap: 0.55rem;
  font-size: 0.78rem;
  font-weight: 600;
  letter-spacing: 0.08em;
  color: rgba(240, 231, 208, 0.88);
  text-transform: uppercase;
}

.hero-delivery-strip__dot {
  width: 7px;
  height: 7px;
  background: #34d399;
  border-radius: 50%;
  box-shadow: 0 0 10px #34d399;
  animation: deliveryPulse 2s infinite ease-in-out;
}

@keyframes deliveryPulse {
  0%, 100% { opacity: 0.8; transform: scale(1); }
  50% { opacity: 1; transform: scale(1.3); }
}

.hero-delivery-strip__logos {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.5rem;
}

.hero-delivery-partner {
  font-size: 0.82rem;
  font-weight: 700;
  letter-spacing: 0.04em;
  color: rgba(250, 246, 236, 0.75);
  padding: 0.25rem 0.55rem;
  border-radius: 6px;
  background: rgba(255, 255, 255, 0.04);
  border: 1px solid rgba(255, 255, 255, 0.06);
  transition: color var(--transition-base), background var(--transition-base);
}

.hero-delivery-partner:hover {
  color: var(--color-gold-light);
  background: rgba(255, 255, 255, 0.08);
}

@media (max-width: 1120px) {
  .hero-cinema__stage {
    grid-template-columns: 1fr;
    align-items: stretch;
    gap: 3rem;
  }
}

@media (max-width: 980px) {
  .hero-cinema__stage {
    padding: 3.5rem 1.25rem 8rem;
  }
  .hero-cinema__content {
    width: min(40rem, 100%);
  }
  .hero-cinema__frost {
    width: 92%;
  }
}
@media (max-width: 720px) {
  /* Portrait: the glass becomes a band behind the copy and lets go well
     before the foot of the frame, so the photograph still reads sharp. */
  :root {
    --header-h: 97px;
  }
  /* End the hero above the fixed action bar (4.5rem) rather than behind it */
  .hero-cinema {
    min-height: calc(100svh - var(--header-h, 0px) - 4.5rem);
  }
  .hero-cinema__stage {
    padding: 1.5rem 1.25rem 5.5rem;
  }
  /* Portrait can't hold every row above the fold. The menu-breadth line is
     the one the Menu button already covers, so it steps out here. */
  .hero-cinema__sections {
    display: none;
  }
  /* Copy runs the full width on a phone, so darken top-down rather than
     left-to-right or the text sits on the bright half of the photo. */
  .hero-cinema__scrim {
    background: linear-gradient(
      180deg,
      rgba(10, 18, 12, 0.74) 0%,
      rgba(10, 18, 12, 0.66) 40%,
      rgba(10, 18, 12, 0.4) 62%,
      rgba(10, 18, 12, 0.5) 84%,
      var(--color-bg-hero) 100%
    );
  }
  .hero-cinema__frost {
    width: 100%;
    backdrop-filter: blur(5px) saturate(110%);
    -webkit-backdrop-filter: blur(5px) saturate(110%);
    -webkit-mask-image: linear-gradient(180deg, #000 0%, #000 44%, rgba(0, 0, 0, 0.4) 64%, transparent 80%);
    mask-image: linear-gradient(180deg, #000 0%, #000 44%, rgba(0, 0, 0, 0.4) 64%, transparent 80%);
  }
  .hero-cinema__frost::before,
  .hero-cinema__frost::after {
    display: none;
  }
  /* The stage's own bottom padding reserves the facts strip; reserving it
     here as well pushed the hero past the fold. */
  .hero-cinema__content {
    width: 100%;
  }
  .hero-cinema__line--display > span {
    font-size: clamp(1.85rem, 7vw, 2.7rem);
  }
  .hero-cinema__rotate-word {
    font-size: clamp(2.1rem, 9.5vw, 3.4rem);
  }
  .hero-cinema__line--accent > span {
    font-size: clamp(2.6rem, 11.5vw, 4.2rem);
  }
  .hero-cinema__details {
    max-width: none;
  }
  .hero-cinema__dishes {
    gap: 0.45rem;
  }
  .hero-cinema__bottom-row {
    gap: 1rem;
  }
  .hero-cinema__chip {
    padding-inline: 0.6rem;
    letter-spacing: 0.1em;
  }
  .hero-cinema__actions {
    margin-left: 0;
  }
}
@media (max-width: 480px) {
  .hero-cinema__actions {
    flex-direction: column;
    flex-wrap: nowrap;
    align-items: stretch;
    width: 100%;
    gap: 0.6rem;
  }
  .hero-cinema__bottom-row .btn {
    justify-content: center;
  }
}
@keyframes lineRise {
  from { transform: translateY(115%); }
  to { transform: translateY(0); }
}
@keyframes fadeUp {
  from { opacity: 0; transform: translateY(1.2rem); }
  to { opacity: 1; transform: translateY(0); }
}
/* at-a-glance facts bar anchoring the hero bottom */
.hero-cinema__facts {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 3;
  border-top: 1px solid rgba(212, 175, 55, 0.22);
  background: linear-gradient(180deg, rgba(10, 18, 12, 0) 0%, rgba(10, 18, 12, 0.6) 100%);
  animation: fadeUp 1s var(--ease-out) 1.2s both;
}
.hero-cinema__facts-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
}
.hero-cinema__fact {
  display: flex;
  align-items: baseline;
  gap: 0.85rem;
  padding: clamp(1.35rem, 2.2vw, 1.85rem) clamp(1.5rem, 2.4vw, 2.25rem);
  border-left: 1px solid rgba(240, 231, 208, 0.14);
}
.hero-cinema__fact:first-child {
  border-left: 0;
  padding-left: 0;
}
.hero-cinema__fact strong {
  font-family: var(--font-display);
  font-size: clamp(2rem, 2.7vw, 2.6rem);
  font-weight: 600;
  line-height: 1;
  color: var(--color-gold);
}
.hero-cinema__fact span {
  font-size: clamp(0.86rem, 1.02vw, 1rem);
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--color-cream);
}
.hero-cinema__fact--cta {
  margin-left: auto;
}
.hero-cinema__fact--cta a {
  color: var(--color-gold-light);
  font-size: clamp(0.86rem, 1.02vw, 1rem);
  letter-spacing: 0.14em;
  text-transform: uppercase;
  padding-bottom: 0.35rem;
  border-bottom: 1px solid rgba(212, 175, 55, 0.45);
  transition: color var(--transition-base), border-color var(--transition-base);
}
.hero-cinema__fact--cta a:hover {
  color: var(--color-gold);
  border-color: var(--color-gold);
}
@media (max-width: 720px) {
  .hero-cinema__fact {
    flex: 1 1 0;
    min-width: 0;
    border-left: 0;
    gap: 0.45rem;
    padding: 0.85rem 0.5rem 0.85rem 0;
  }
  .hero-cinema__fact strong {
    font-size: 1.6rem;
  }
  .hero-cinema__fact span {
    font-size: 0.74rem;
    letter-spacing: 0.1em;
    line-height: 1.3;
  }
  /* The action bar already offers Locations — don't say it twice */
  .hero-cinema__fact--cta {
    display: none;
  }
}


/* --- Bento stats (premium with image cells) --- */
.bento {
  display: grid;
  grid-template-columns: repeat(12, 1fr);
  grid-auto-rows: minmax(10rem, auto);
  gap: 1rem;
}
.bento__cell {
  position: relative;
  border: 1px solid var(--color-border);
  background: var(--color-bg-card);
  border-radius: var(--radius-lg);
  padding: 2rem 1.6rem;
  overflow: hidden;
  transition: border-color var(--transition-base), transform var(--transition-base);
}
.bento__cell::before {
  content: "";
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 3px;
  background: linear-gradient(90deg, var(--color-gold), transparent);
  opacity: 0;
  transition: opacity var(--transition-base);
}
.bento__cell:hover {
  border-color: var(--color-gold);
  transform: translateY(-3px);
}
.bento__cell:hover::before {
  opacity: 1;
}
/* Cell spans */
.bento__cell--span-3 { grid-column: span 3; }
.bento__cell--span-4 { grid-column: span 4; }
.bento__cell--span-5 { grid-column: span 5; }
.bento__cell--span-6 { grid-column: span 6; }
.bento__cell--span-7 { grid-column: span 7; }
.bento__cell--span-8 { grid-column: span 8; }
.bento__cell--row-2 { grid-row: span 2; }

/* Image cell (fills with image, text overlays) */
.bento__cell--image {
  padding: 0;
}
.bento__cell--image img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  position: absolute;
  inset: 0;
  transition: transform 1s var(--ease-out);
}
.bento__cell--image:hover img {
  transform: scale(1.05);
}
.bento__cell--image::after {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(
    180deg,
    rgba(10, 18, 12, 0.12) 0%,
    rgba(10, 18, 12, 0.5) 42%,
    rgba(10, 18, 12, 0.88) 78%,
    rgba(10, 18, 12, 0.95) 100%
  );
  z-index: 1;
}
.bento__cell--image .bento__cell-content {
  position: relative;
  z-index: 2;
  height: 100%;
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
  padding: 1.9rem 1.8rem;
}

.bento__cell__num {
  font-family: var(--font-display);
  font-size: clamp(3rem, 5.4vw, 4.6rem);
  font-weight: 600;
  line-height: 1;
  color: var(--color-gold);
}
.bento__cell__label {
  margin-top: 0.85rem;
  color: var(--color-cream);
  font-weight: 700;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  font-size: 0.84rem;
}
.bento__cell__meta {
  margin-top: 0.55rem;
  font-size: 1rem;
  line-height: 1.5;
  color: var(--color-cream-dim);
}

/* Headline cell over photography */
.bento__cell--feature {
  min-height: 22rem;
}
.bento__cell-content h3 {
  margin-top: 0.6rem;
  font-size: clamp(1.75rem, 2.9vw, 2.9rem);
  line-height: 1.1;
  color: #faf6ec;
  text-wrap: balance;
}
.bento__cell-content > p {
  margin-top: 0.7rem;
  max-width: 42ch;
  font-size: clamp(1rem, 1.1vw, 1.16rem);
  line-height: 1.55;
  color: var(--color-cream);
}
.bento__cell--image .eyebrow {
  font-size: 0.82rem;
  font-weight: 700;
  letter-spacing: 0.22em;
  color: var(--color-gold-light);
}

@media (max-width: 900px) {
  .bento {
    grid-template-columns: 1fr 1fr;
  }
  .bento__cell--span-3,
  .bento__cell--span-4,
  .bento__cell--span-5,
  .bento__cell--span-6,
  .bento__cell--span-7,
  .bento__cell--span-8 {
    grid-column: span 2;
  }
  .bento__cell--row-2 {
    grid-row: span 1;
  }
}
@media (max-width: 540px) {
  .bento {
    grid-template-columns: 1fr;
  }
  .bento__cell--span-3,
  .bento__cell--span-4,
  .bento__cell--span-5,
  .bento__cell--span-6,
  .bento__cell--span-7,
  .bento__cell--span-8 {
    grid-column: span 1;
  }
}

/* --- Signature Dishes: poster rail, cleanly below the hero facts --- */
.dish-rail {
  position: relative;
  z-index: 6;
  margin-top: 0;
  padding-top: clamp(2.5rem, 4.5vw, 3.75rem);
  padding-bottom: clamp(2.5rem, 4.5vw, 3.75rem);
  background: transparent;
}
/* Gentle seam that separates the hero's facts strip from the dishes section */
.dish-rail::before {
  content: "";
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  height: clamp(2.5rem, 4.5vw, 3.75rem);
  z-index: -1;
  background: linear-gradient(to bottom, rgba(16, 29, 20, 0) 0%, var(--color-bg) 100%);
}
.dish-rail .split-head {
  margin-bottom: 2.25rem;
}
.dish-rail .split-head p {
  color: var(--color-cream);
}
/* The hero facts carry the scroll cue, so the old cue steps out */
.hero-cinema__scroll {
  display: none;
}

.dish-carousel {
  --carousel-gutter: max(clamp(1.25rem, 2.5vw, 2rem), calc((100vw - var(--container-max)) / 2 + clamp(1.25rem, 2.5vw, 2rem)));
  --card-w: clamp(15rem, 18vw, 16.75rem);
  position: relative;
  padding: 0 0 1rem;
}
.dish-carousel__viewport {
  position: relative;
}
.dish-carousel__viewport:focus-visible {
  outline: 2px solid rgba(211, 173, 95, 0.6);
  outline-offset: 4px;
  border-radius: 8px;
}
/* Soft edge fades so cards never hard-clip at the viewport edges */
.dish-carousel__viewport::before,
.dish-carousel__viewport::after {
  content: "";
  position: absolute;
  top: 1.2rem;
  bottom: 1.8rem;
  width: clamp(1.5rem, 5vw, 5rem);
  pointer-events: none;
  z-index: 12;
  transition: opacity 0.4s ease;
}
.dish-carousel__viewport::before {
  left: 0;
  background: linear-gradient(to right, rgba(10, 16, 11, 0.88), rgba(10, 16, 11, 0));
}
.dish-carousel__viewport::after {
  right: 0;
  background: linear-gradient(to left, rgba(10, 16, 11, 0.88), rgba(10, 16, 11, 0));
}
.dish-carousel.at-start .dish-carousel__viewport::before { opacity: 0; }
.dish-carousel.at-end .dish-carousel__viewport::after { opacity: 0; }
/* Track: start flush with the container gutter so the rail reads full-width,
   not as a single centred card floating in empty space. */
.dish-carousel__track {
  display: flex;
  gap: 0;
  overflow-x: auto;
  padding: 2.75rem calc(50% - var(--card-w) / 2) 3.25rem;
  scroll-padding-inline: calc(50% - var(--card-w) / 2);
  -webkit-overflow-scrolling: touch;
  scrollbar-width: none;
  -ms-overflow-style: none;
  cursor: default;
}
.dish-carousel__track::-webkit-scrollbar {
  display: none;
}

/* Individual dish card: transforms are driven per-frame by JS (distance from center) */
.dish-card {
  flex: 0 0 var(--card-w);
  position: relative;
  aspect-ratio: 0.72;
  background: var(--color-bg-card);
  border: 1px solid var(--color-border);
  border-radius: 14px;
  overflow: hidden;
  transform: translate3d(0, 0, 0) scale(1);
  transform-origin: 50% 50%;
  will-change: transform, opacity;
  transition: border-color 0.4s ease, box-shadow 0.4s ease;
}
.dish-card.is-active {
  border-color: rgba(211, 173, 95, 0.6);
  box-shadow:
    0 40px 80px -24px rgba(0, 0, 0, 0.85),
    0 0 0 1px rgba(211, 173, 95, 0.28),
    0 0 44px -6px rgba(211, 173, 95, 0.22);
}
.dish-card:hover {
  border-color: rgba(211, 173, 95, 0.4);
}
.dish-card__image {
  position: absolute;
  inset: 0;
  overflow: hidden;
  background: var(--color-bg-elevated);
}
.dish-card__image img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: center 30%;
  transition: transform 0.9s var(--ease-out);
}
.dish-card:hover .dish-card__image img {
  transform: scale(1.07);
}
/* Depth dimmer on non-focused cards; JS sets --dim from distance to center */
.dish-card__image::after {
  content: "";
  position: absolute;
  inset: 0;
  background: rgba(6, 10, 7, 0.72);
  opacity: var(--dim, 0);
  pointer-events: none;
  transition: opacity 0.2s linear;
}
.dish-card.is-active .dish-card__image img {
  filter: saturate(1.06) contrast(1.04);
}
.dish-card__badge {
  position: absolute;
  top: 0.9rem;
  left: 0.9rem;
  padding: 0.3rem 0.75rem;
  background: var(--color-gold);
  color: var(--color-bg-hero);
  font-size: 0.6rem;
  font-weight: 700;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  border-radius: 100px;
  z-index: 2;
}
.dish-card__info {
  position: absolute;
  inset: auto 0 0 0;
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
  padding: 0.8rem 0.95rem 0.9rem;
  text-align: left;
  background: rgba(9, 16, 11, 0.55);
  backdrop-filter: blur(16px) saturate(1.2);
  -webkit-backdrop-filter: blur(16px) saturate(1.2);
  border-top: 1px solid rgba(211, 173, 95, 0.24);
  opacity: calc(1 - (var(--dim, 0) * 0.3));
}
/* Soften where the glass meets the photo above it */
.dish-card__info::before {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: 100%;
  height: 2.75rem;
  background: linear-gradient(to top, rgba(9, 16, 11, 0.55), rgba(9, 16, 11, 0));
  pointer-events: none;
}
.dish-card__cuisine {
  display: block;
  margin-bottom: 0.42rem;
  font-size: 0.62rem;
  font-weight: 700;
  letter-spacing: 0.19em;
  text-transform: uppercase;
  color: var(--color-gold);
}
.dish-card__info h3 {
  font-family: var(--font-display);
  font-size: 1.1rem;
  font-weight: 600;
  color: var(--color-cream);
  line-height: 1.18;
  text-wrap: balance;
}
.dish-card__info p {
  margin-top: 0.28rem;
  margin-bottom: 0.6rem;
  font-size: 0.78rem;
  color: var(--color-cream-dim);
  line-height: 1.4;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

/* Card footer: menu link pinned to the bottom for uniform heights */
.dish-card__foot {
  margin-top: 0;
  padding-top: 0.55rem;
  border-top: 1px solid rgba(240, 231, 208, 0.16);
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
}
.dish-card__link {
  width: 32px;
  height: 32px;
  margin-left: auto;
  flex-shrink: 0;
  border-radius: 50%;
  border: 1px solid rgba(240, 231, 208, 0.18);
  color: var(--color-cream);
  display: flex;
  align-items: center;
  justify-content: center;
  transition: background 0.3s ease, border-color 0.3s ease, color 0.3s ease, transform 0.3s ease;
}
.dish-card__link svg {
  width: 18px;
  height: 18px;
}
.dish-card__link:hover {
  background: var(--color-gold);
  border-color: var(--color-gold);
  color: var(--color-bg-hero);
  transform: translate(2px, -2px);
}
.dish-card__link:focus-visible {
  outline: 2px solid var(--color-gold);
  outline-offset: 2px;
}

/* Carousel navigation buttons (sit in the split header) */
.dish-carousel__btn {
  width: 48px;
  height: 48px;
  border-radius: 50%;
  border: 1px solid rgba(200, 166, 95, 0.55);
  background: transparent;
  color: var(--color-gold);
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  transition: all 0.3s ease;
}
.dish-carousel__btn svg {
  width: 22px;
  height: 22px;
}
.dish-carousel__btn:hover {
  background: var(--color-gold);
  color: var(--color-bg-hero, #0a120c);
  border-color: var(--color-gold);
}
.dish-carousel__btn:active {
  transform: scale(0.92);
}
.dish-carousel__btn:disabled {
  opacity: 0.3;
  pointer-events: none;
}

/* Scroll progress bar, aligned to the container */
.dish-progress {
  height: 2px;
  margin: 0.25rem var(--carousel-gutter) 0;
  background: rgba(240, 231, 208, 0.12);
  border-radius: 2px;
  overflow: hidden;
}
.dish-progress span {
  display: block;
  height: 100%;
  width: 0;
  background: var(--color-gold);
  border-radius: 2px;
  transition: width 0.2s ease-out;
}

/* Split section head: title left, actions right */
.split-head {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: 2rem;
  margin-bottom: 2.75rem;
}
.split-head h2 {
  font-size: clamp(2.2rem, 3.9vw, 4rem);
  font-weight: 600;
  line-height: 1.08;
  letter-spacing: -0.018em;
  color: #faf6ec;
  text-wrap: balance;
}
.split-head .eyebrow,
.split-head .eyebrow-line {
  font-size: 0.82rem;
  font-weight: 700;
  letter-spacing: 0.22em;
  color: var(--color-gold-light);
}
.split-head__intro {
  flex: 1 1 auto;
  max-width: none;
}
.split-head__intro h2 {
  margin-top: 1.1rem;
}
.split-head__intro p {
  margin-top: 1rem;
  font-size: clamp(1.02rem, 1.1vw, 1.24rem);
  line-height: 1.55;
  color: var(--color-cream);
  max-width: none;
  text-wrap: pretty;
}
.split-head__actions {
  display: flex;
  align-items: center;
  gap: 1.25rem;
  flex-shrink: 0;
  padding-bottom: 0.35rem;
}
.dish-carousel__nav {
  display: flex;
  align-items: center;
  gap: 0.6rem;
}

/* Slide counter, e.g. 01 / 08 */
.dish-counter {
  font-family: var(--font-mono);
  font-size: 0.92rem;
  letter-spacing: 0.16em;
  color: var(--color-cream-dim);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
  padding-left: 1.1rem;
  border-left: 1px solid rgba(212, 175, 55, 0.25);
}
.dish-counter strong {
  font-size: 1.12rem;
  color: var(--color-gold);
  font-weight: 700;
}
/* Safety: prevent any icon/arrow SVG from ever scaling to full viewport
   if it ever ends up outside its sized container. Icons inherit the link size. */
.course svg {
  max-width: 100%;
  flex-shrink: 0;
}
.head-link {
  display: inline-flex;
  align-items: center;
  gap: 0.6rem;
  padding-bottom: 0.5rem;
  font-family: var(--font-mono);
  font-size: 0.84rem;
  font-weight: 700;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--color-gold-light);
  text-decoration: none;
  border-bottom: 1px solid rgba(200, 166, 95, 0.35);
  transition: color 0.3s ease, gap 0.3s ease;
}
.head-link svg {
  width: 15px;
  height: 15px;
}
.head-link:hover {
  color: var(--color-gold-light);
  gap: 0.9rem;
}

/* Responsive carousel */
@media (max-width: 768px) {
  .dish-rail {
    margin-top: 0;
    padding-top: 2.5rem;
  }
  .dish-carousel {
    --card-w: min(74vw, 20rem);
  }
  .dish-carousel__track {
    gap: 0;
    padding-top: 1.75rem;
    padding-bottom: 2.5rem;
    scroll-snap-type: x mandatory;
    cursor: auto;
  }
  .dish-card {
    scroll-snap-align: center;
  }
  .split-head {
    flex-direction: column;
    align-items: flex-start;
    gap: 1.5rem;
    margin-bottom: 2rem;
  }
  .split-head__actions {
    width: 100%;
    justify-content: space-between;
  }
}
@media (max-width: 480px) {
  .dish-carousel {
    --card-w: min(76vw, 19rem);
  }
}

/* --- Cuisine cards (uniform editorial grid) --- */
.cuisine-grid {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 1.5rem;
}
.cuisine-item {
  display: flex;
  flex-direction: column;
  background: var(--color-bg-card);
  border: 1px solid var(--color-border);
  border-radius: 18px;
  overflow: hidden;
  transition: transform 0.4s var(--ease-out), border-color 0.4s ease, box-shadow 0.4s ease;
}
.cuisine-item:hover {
  transform: translateY(-6px);
  border-color: rgba(200, 166, 95, 0.45);
  box-shadow: 0 24px 48px -20px rgba(0, 0, 0, 0.55);
}
.cuisine-item__media {
  position: relative;
  aspect-ratio: 4 / 3;
  overflow: hidden;
  background: var(--color-bg-elevated);
}
.cuisine-item__media img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: transform 1s var(--ease-out);
}
.cuisine-item:hover .cuisine-item__media img {
  transform: scale(1.07);
}
.cuisine-item__count {
  position: absolute;
  top: 0.9rem;
  left: 0.9rem;
  padding: 0.35rem 0.8rem;
  background: rgba(10, 18, 12, 0.72);
  -webkit-backdrop-filter: blur(6px);
  backdrop-filter: blur(6px);
  border: 1px solid rgba(200, 166, 95, 0.35);
  border-radius: 100px;
  font-size: 0.8rem;
  font-weight: 700;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--color-gold-light);
}
.cuisine-item__body {
  display: flex;
  flex-direction: column;
  flex: 1;
  gap: 0.5rem;
  padding: 1.6rem 1.6rem 1.75rem;
}
.cuisine-item__body h3 {
  font-family: var(--font-display);
  font-size: clamp(1.75rem, 2vw, 2.15rem);
  font-weight: 600;
  color: #faf6ec;
  line-height: 1.12;
}
.cuisine-item__body p {
  font-size: 1.02rem;
  line-height: 1.55;
  color: var(--color-cream-dim);
  text-wrap: pretty;
  flex: 1;
}
.cuisine-item__link {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  margin-top: 0.75rem;
  font-size: 0.8rem;
  font-weight: 700;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--color-gold-light);
  text-decoration: none;
  transition: color 0.3s ease, gap 0.3s ease;
}
.cuisine-item__link svg {
  width: 14px;
  height: 14px;
}
.cuisine-item__link:hover {
  color: var(--color-gold-light);
  gap: 0.8rem;
}

@media (max-width: 1080px) {
  .cuisine-grid {
    grid-template-columns: 1fr 1fr;
  }
}
@media (max-width: 560px) {
  .cuisine-grid {
    grid-template-columns: 1fr;
  }
}

/* --- Restaurant Experience: a flat dark plate, no photography.
   Until 2026-09-17 the photo WAS the background and a forest-green gradient
   scrim existed only to buy the copy its contrast over it. The client pulled
   the photo out and asked for a plain dark panel like the reference they sent,
   so there is nothing left to photograph, nothing left to scrim, and no
   contrast risk to manage: the copy now sits on one uniform colour and is
   measured at ~13:1 (see .cortex/verify-experience-fullbleed.mjs).
   The shade is --color-bg-alt, one step off the page base --color-bg so the
   band reads as a LIFT rather than a hole, and one step off the
   --color-bg-card reviews section that follows it so the two do not merge.
   Selectors are `.statement-band.course` because the shared `.course` rule
   (display:flex; min-height:100svh) would otherwise win on source order. */
.statement-band.course {
  display: grid;
  align-items: center;
  /* NO FLOOR. The box used to be viewport-sized so the photograph had
     something to cover; with the photo gone the two columns decide the height
     on their own. `min-height: 0` is what keeps overriding the shared `.course`
     rule (style.css ~L5377 sets min-height:100svh); delete this line and the
     band snaps back to a full screen, which is the exact bulk the client asked
     to remove. */
  min-height: 0;
  /* Tightened again on 2026-09-17 with the flag "top to bottom size reduce":
     60/60 was tuned for a band whose cards were 938px tall. Every px taken off
     the padding is a px off the band, and the cards need less frame now that
     they are horizontal. */
  padding-block: clamp(2.25rem, 4.5vh, 3rem);
  background-color: var(--color-bg-alt);
}
/* THE TWO COLUMNS ARE ONE CARD EACH AND THE SAME SIZE (client, 2026-09-17:
   "bath same size of card need"). Three things make that true together, and
   losing any one of them breaks it:
     1. EQUAL TRACKS. The right track used to be 1.02fr, a hair wider than the
        left, because the letter is the longer read. With the letter cut to
        three paragraphs the two halves carry comparable copy, so the asymmetry
        earned nothing and it stopped them being the same width.
     2. align-items: STRETCH, not start. This is what makes the two cards the
        same HEIGHT without either column knowing the other's content: the
        shorter card is stretched down to the taller one.
     3. THE CARD SURFACE ON THE COLUMN ITSELF (below). Same padding, same plate,
        same hairline, same radius on both, because both are the same selector. */
.statement-band__content {
  position: relative;
  z-index: 1;
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  /* Tighter than the old 4rem. The columns now carry their own 2.25rem of
     padding, so the old gap stacked on top of it and read as two thirds of a
     screen of air between the cards. */
  gap: clamp(1.25rem, 2.5vw, 2.5rem);
  align-items: stretch;
  /* FILL THE BAND (client, 2026-09-17: "both card horizontally need fully fit on
     card many place right & left side empty have that fill it"). This rule used
     to be `max-width: var(--container-max)` plus a padding-inline of
     `max(2rem, calc((100vw - var(--container-max)) / 2 + 2rem))`, copied from the
     `.container` pattern where the max-width is a CONTENT measure. Here it is a
     border-box measure (the stylesheet's global `box-sizing: border-box`), so the
     two fought: every px the viewport grew added a px of padding INSIDE the
     1680px cap, and the cards got NARROWER the wider the screen got.
     Measured before this fix, at the 2308px viewport the client's own screenshot
     was taken at: 474px cards, 660px of dead plate down EACH side of the row
     (the row used 42.8% of the screen), and the chairman heading crushed into a
     116px lane where it broke to one word per line. On a 1440 screen the same
     rule measured 670px cards and 95% of the width, which is why a probe that
     only looked at 1440 could never see it.
     So: NO cap, and one gutter that grows a little and then stops, so the two
     cards always span the band and the dead plate is gone at every width. */
  width: 100%;
  max-width: none;
  margin-inline: auto;
  padding-inline: clamp(1.25rem, 3vw, 4rem);
  text-align: left;
}
/* The card. --color-bg-card is the site's token for a card sitting ON an alt
   plate, which is exactly this relationship: the band is --color-bg-alt
   (#16261b) and these are one step down (#0c160f), so the cards read as raised
   rather than as a hole.
   Padding lives here rather than on the inner blocks so both cards get the
   same inset from the one rule, and so the hairline borders inside each card
   (the occasions' rules, the signoff's rule) stop short of the card's edge. */
.statement-band__col {
  display: flex;
  flex-direction: column;
  min-height: 0;
  min-width: 0;
  padding: clamp(1.5rem, 2.2vw, 2.25rem);
  background-color: var(--color-bg-card);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
}

/* --- The chairman's block, inside the band. HORIZONTAL, not a vertical stack.
   Client, 2026-09-17: "top to bottom very long card no need ... place card fully
   horizontal utilize it not vertical ... chairman image size reduce it".
   It used to be a stack: a 248px portrait, the caption under it, then the
   eyebrow and heading, then the letter. That is what made this card 938px tall
   (portrait 386 + gap + message 462) and the band 1037px.
   It now reads left to right in three lanes:
     row 1   [ portrait tile + byline ][ eyebrow + heading ]
     row 2   the letter, at the card's FULL width
     row 3   the signature, anchored to the card's foot
   THE FIRST LANE IS A MEASURED WIDTH, NOT `auto` (the 2026-09-17 fix, client:
   "both card horizontally need fully fit on card many place right & left side
   empty have that fill it"). An `auto` track sizes to MAX-CONTENT, and the
   byline is a long single line, so the figure claimed ~300px of the card's 606
   and handed the heading only 269: the 30px heading then broke into a
   ONE-WORD-PER-LINE ladder (seen on screen as "A / Journey / Built / on /
   Trust") while the width it should have had sat empty beside the name. Lane 1
   is now `minmax(252px, 0.95fr)` and EVERY pixel left over goes to lane 2, so
   the heading reads on two lines and neither half of the card is dead.
   `.chairman__message` gets
   `display: contents` so the heading can join the header row while the letter
   keeps the full card width: the wrapper leaves the box tree and its children
   become items of THIS grid, which is why the DOM order is untouched.
   Row 2 is the ELASTIC one (`1fr`): when the occasions card is the taller of the
   two, the height it lends this card opens between the letter and the signature,
   where a letter's own whitespace belongs, instead of dangling under the card. */
.statement-band .chairman {
  /* The tile's own width, shared by the img and by lane 1's first column, so
     the photo and its byline can never disagree about the track. */
  --chairman-tile: 124px;
  display: grid;
  /* Lane 1 = the photo + its byline, with a FLOOR so the byline always has a
     readable column, and a CEILING so a wide card cannot hand it half the row;
     lane 2 = the eyebrow and heading, which own every px left over.
     `clamp(252px, 26%, 360px)` replaced an even 0.95fr/1.05fr split on
     2026-09-17. An fr pair is a RATIO, so once the cards grew with the viewport
     it gave lane 1 455px of a 1070px card: a 124px portrait, a ~120px byline and
     210px of blank between them, while the heading beside it got a 116px lane
     and broke into a five-line, one-word-per-line ladder. The clamp holds lane 1
     between the width the byline NEEDS and the width it can USE. */
  grid-template-columns: clamp(252px, 26%, 360px) minmax(0, 1fr);
  grid-template-rows: auto 1fr auto;
  gap: 0 clamp(1.25rem, 2vw, 2rem);
  align-items: start;
  /* Fill the card, so row 2 has a height to be elastic within. */
  flex: 1 1 auto;
  max-width: none;
}
.statement-band .chairman__message {
  display: contents;
}
/* The figure is the horizontal unit: the tile, with its caption BESIDE it rather
   than under it. That alone is worth ~52px of card height, and it is what fills
   the empty band that used to sit to the right of the photo. The tile keeps a
   frame of its own, so the plate is the photo's and not a box around the name. */
.statement-band .chairman__portrait {
  grid-column: 1;
  grid-row: 1;
  display: grid;
  grid-template-columns: var(--chairman-tile) minmax(0, 1fr);
  gap: 0.85rem;
  align-items: center;
  max-width: none;
  overflow: visible;
  background-color: transparent;
  border: 0;
  border-radius: 0;
}
.statement-band .chairman__portrait img {
  width: var(--chairman-tile);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
}
.statement-band .chairman__portrait figcaption {
  border-top: 0;
  padding: 0;
}
/* The byline's column is lane 1's second column, which is ~115px at a 1440
   viewport. The name is set a step down from its 18px base so it wraps as
   "Nassar / Pokkaratil" inside that column instead of running under the heading,
   and `overflow-wrap` is the safety net for the case where the column is ever
   narrower than the single longest word. */
.statement-band .chairman__name {
  font-size: var(--text-base);
  overflow-wrap: break-word;
}
/* The eyebrow and heading sit BESIDE the tile, in the header row, and carry no
   bottom margin: the row is closed by the letter's own margin, and the base
   rule's 2rem would open a hole wherever the heading is shorter than the tile.
   The three classes are still load-bearing: `.course .section-header` (0,2,0)
   ties a two-class selector and wins on source order. */
.statement-band .chairman__message .section-header {
  grid-column: 2;
  grid-row: 1;
  /* CENTRED against the photo, not pinned to the top of the row: the tile is
     ~155px tall and this block is ~100px, so centring spends the difference as
     27px above and 27px below instead of leaving 55px of dead band under the
     heading. */
  align-self: center;
  margin-bottom: 0;
}
/* The band's headline is the occasion heading, so the letter's own heading
   steps down from it rather than matching it and competing for the same weight.
   The selector carries `.section-header` because `.course .section-header h2`
   (style.css ~L5340) is a (0,2,1) TIE with a bare `.statement-band
   .chairman__message h2` and wins on source order. MEASURED, not guessed: with
   the shorter selector the letter's heading rendered at 56.16px, i.e. LARGER
   than the band's own 56px headline, which is the exact opposite of the intent. */
.statement-band .chairman__message .section-header h2 {
  /* GREW WITH THE CARD on 2026-09-17. The old cap of 2.25rem was sized for the
     350px lane lane 1 used to leave it; with the cards filling the band, lane 2
     is ~700px at a 2308 viewport and a 36px heading in it left two thirds of the
     row empty, which is the same "right & left side empty" the client pointed
     at. The type now tracks the lane: 48px beside a 700px lane, 30px on a 1440
     screen, never smaller than the 1.7rem it started at. It still steps DOWN
     from the band's own headline at every width (that is the ratio to watch:
     2.35vw against the headline's 3.4vw). */
  font-size: clamp(1.7rem, 2.35vw, 3.25rem);
  /* Fill lane 2 edge to edge: the 40rem cap the band's own headline carries is
     pointless here (lane 2 is never that wide) and the 1rem bottom margin left
     the centred header block off balance. */
  max-width: none;
  margin-bottom: 0;
}
.statement-band .chairman__copy {
  /* Span the whole card: the letter is the one block that wants the full
     measure, and giving it the full width is what keeps it ~8 lines instead of
     the ~14 it would run at the 350px the stacked column used to give it. */
  grid-column: 1 / -1;
  grid-row: 2;
  margin-top: var(--space-lg);
  /* A MEASURE, NOT A FIXED SIZE (2026-09-17). The letter spans the full card, so
     on a 1070px card a flat 16px gave ~120 characters to the line. The type now
     scales with the viewport between the body size and 1.3rem, which holds the
     line at roughly 60-95 characters at every width while still filling the
     card edge to edge (no measure cap: a cap would put the empty side back). */
  font-size: clamp(1rem, 0.9vw, 1.3rem);
  line-height: 1.9;
}
.statement-band .chairman__copy p {
  color: var(--color-cream);
}
.statement-band .chairman__copy p + p {
  margin-top: var(--space-md);
}
.statement-band .chairman__signoff {
  grid-column: 1 / -1;
  grid-row: 3;
  margin-top: var(--space-lg);
  padding-top: var(--space-md);
}
.statement-band__content .eyebrow-line {
  display: inline-block;
  padding: var(--space-sm) var(--space-lg);
  border: 1px solid var(--color-gold);
  border-radius: var(--radius-full);
  font-size: var(--text-sm);
  letter-spacing: 0.15em;
  text-transform: uppercase;
  color: var(--color-gold-light);
  margin-bottom: var(--space-lg);
}
.statement-band__accent-line {
  width: 48px;
  height: 2px;
  background: linear-gradient(90deg, var(--color-gold), var(--color-gold-dark));
  border-radius: 1px;
  margin-bottom: var(--space-lg);
}
.statement-band__content h2 {
  font-family: var(--font-display);
  /* 3.5rem was sized for a full-width band. Inside a half-width card it wrapped
     to two lines of 56px and was the single tallest block in the occasions
     card, so the client's "top to bottom size reduce" lands here too.
     The CEILING is higher since the cards fill the band (2026-09-17): on a 1070px
     card the old 3rem cap left ~350px of bare plate beside the last word of the
     heading, which is exactly the dead space the client pointed at. The heading
     now keeps its two lines at every width and spends the extra room on type
     instead of on plate. */
  font-size: clamp(2rem, 3.4vw, 4.25rem);
  line-height: 1.08;
  font-weight: 300;
  letter-spacing: -0.02em;
  color: var(--color-cream);
  margin-bottom: var(--space-md);
  /* No width cap inside the card: the card IS the measure now. The 40rem cap
     left a 96px empty gutter to the right of the heading on a wide screen,
     which is one of the "right & left side empty" places the client pointed at
     on 2026-09-17. */
  max-width: none;
}
.statement-band__lead {
  font-size: clamp(1.05rem, 1.4vw, 1.25rem);
  line-height: 1.7;
  color: var(--color-cream-dim);
  /* Fills the card edge to edge. The 34rem cap stopped the paragraph 60px short
     of the card's own padding at a 1440 viewport: a visibly ragged right edge
     and a dead strip of plate beside the last word of every line. */
  max-width: none;
  margin: 0 0 var(--space-lg);
}

/* Occasions: a 2x2 list separated by hairlines.
   FILL, DO NOT HOARD (client, 2026-09-17: "both card horizontally need fully fit
   on card many place right & left side empty have that fill it"). The two cards
   are stretched to the same height and one of them is always the taller, so this
   card is handed surplus height. It used to be parked in ONE 145px band between
   the lead and the list (measured at 1120x900) and read as a hole in the middle
   of the card. The list now takes that height itself (`flex: 1 0 auto`), its two
   auto rows grow equally (grid `align-content` defaults to stretch), and each
   occasion is centred in its taller row, so the same height is spent as four
   small even gaps instead of one void.
   The earlier pass deliberately kept the rows content-sized because the client
   had signed off on the list's rhythm; that instruction has been superseded, and
   the 2x2 split still reads because the hairline lives on the cell's
   `border-bottom`, not on a gap that would open up. `flex-shrink: 0` is on
   purpose: this block must never be the thing that gives height back. */
/* The four occasion titles are <h3> now (heading-ladder fix, homepage SEO/AEO
   pass 2026-09-23: they were bare h4s under a section h2, an h2 -> h4 skip on
   every run of the page). The SIZE stays at the h4 step on purpose: the tag
   serves the outline, the size serves the row, and the base h3 clamp would
   grow these ~4px and shift the card heights the equal-card probes pin. */
.statement-band__feature-text h3 {
  font-size: var(--text-lg);
}

.statement-band__features {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 0 clamp(1.5rem, 3vw, 2.75rem);
  flex: 1 0 auto;
  margin-top: var(--space-lg);
  padding-top: var(--space-md);
  border-top: 1px solid var(--color-border);
}
.statement-band__feature {
  /* --space-md, not --space-lg: the two card heights are decided by whichever
     card is taller, so the list's own rhythm is part of the band's height.
     `align-content: center` centres each occasion inside the row it was given,
     which is what turns the surplus into even margins on both sides of the
     icon, the title and the description rather than blank under them. */
  display: grid;
  align-content: center;
  padding: var(--space-md) 0;
  border-bottom: 1px solid var(--color-border-light);
}
.statement-band__feature:nth-last-child(-n + 2) {
  border-bottom: 0;
}
.statement-band__feature-icon {
  width: 34px;
  height: 34px;
  margin: 0 0 var(--space-sm);
  color: var(--color-gold);
  transition: transform var(--transition-base);
}
.statement-band__feature:hover .statement-band__feature-icon {
  transform: translateY(-3px);
}
.statement-band__feature h4 {
  font-size: var(--text-lg);
  font-weight: 600;
  margin-bottom: var(--space-xs);
  color: var(--color-cream);
  transition: color var(--transition-base);
}
.statement-band__feature:hover h4 {
  color: var(--color-gold-light);
}
.statement-band__feature p {
  font-size: var(--text-sm);
  line-height: 1.55;
  color: var(--color-cream-dim);
  margin: 0;
  /* NO 20rem cap any more (2026-09-17, "many place right & left side empty have
     that fill it"). With the cards filling the band each occasion cell is ~480px
     wide, and a 320px measure left a dead strip of plate beside every one of the
     four descriptions. */
  max-width: none;
}

/* Tablet and down: one column. There is no image to re-anchor and no floor to
   shrink any more, so this breakpoint only has to restack the two columns. */
@media (max-width: 980px) {
  /* One column: the letter follows the occasions rather than sitting beside
     them, so neither is squeezed to a 300px measure. */
  .statement-band__content {
    grid-template-columns: minmax(0, 1fr);
    gap: var(--space-2xl);
  }
  .statement-band__features {
    max-width: none;
  }
}

/* THE ONE BAND WIDTH THAT STILL CANNOT HOLD A HEADER ROW (re-measured
   2026-09-17, first once lane 1 stopped being an `auto` track and again once the
   cards started filling the band).
   In the narrow side-by-side range each card is only ~420-540px wide and its
   inner box ~400-490px. Lane 1 has a 252px floor (the photo plus a readable
   byline for a name as long as "Pokkarattil"), so lane 2 would fall to ~100px,
   which is the one-word-per-line ladder again. Measured: lane 2 is ~177px at a
   1120 viewport and ~212px at 1180, so 1180 is where it is comfortably readable
   and it only grows from there; above 1180 the header is a ROW and both lanes are
   filled. Below 981 the two CARDS stack into a ~600px card that the row fits
   comfortably, so the header stacks in exactly two places: this narrow
   side-by-side range and the phone, where the card's inner box is 302px and lane
   2 would have nothing left. */
@media (min-width: 981px) and (max-width: 1180px), (max-width: 620px) {
  /* Four rows: figure, eyebrow and heading, letter, signature. The letter keeps
     the elastic row, so the signature still lands on the card's foot. */
  .statement-band .chairman {
    grid-template-columns: minmax(0, 1fr);
    grid-template-rows: auto auto 1fr auto;
  }
  .statement-band .chairman__portrait,
  .statement-band .chairman__message .section-header,
  .statement-band .chairman__copy,
  .statement-band .chairman__signoff {
    grid-column: 1;
  }
  .statement-band .chairman__portrait {
    grid-row: 1;
  }
  /* The eyebrow and heading now take the card's full width under the tile, so
     the row needs its own lead-in: in the horizontal header the gap came from
     the row itself. */
  .statement-band .chairman__message .section-header {
    grid-row: 2;
    margin-top: var(--space-lg);
  }
  .statement-band .chairman__copy {
    grid-row: 3;
  }
  .statement-band .chairman__signoff {
    grid-row: 4;
  }
}

/* Phone: single-column occasions, icon beside text, full hairline set. */
@media (max-width: 620px) {
  .statement-band.course {
    padding-block: clamp(2.25rem, 8vw, 3.5rem);
  }
  .statement-band__features {
    grid-template-columns: 1fr;
    gap: 0;
  }
  .statement-band__feature {
    display: flex;
    align-items: flex-start;
    gap: var(--space-md);
    border-bottom: 1px solid var(--color-border-light);
  }
  .statement-band__feature:nth-last-child(-n + 2) {
    border-bottom: 1px solid var(--color-border-light);
  }
  .statement-band__feature:last-child {
    border-bottom: 0;
  }
  .statement-band__feature-icon {
    margin: 0;
    flex-shrink: 0;
  }
  .statement-band__feature-text {
    flex: 1;
  }
  /* The header stacks here (see the block above), so the figure gets a ROW of
     its own, and that row is 302px of card at a 390 viewport. The caption stays
     BESIDE the tile rather than under it: beside it, the tile and the byline
     together fill that row, and stacking them instead left a 158px-wide,
     266px-tall blank column down the right of the card. Only the tile itself
     steps down, from its 124px base. */
  .statement-band .chairman {
    --chairman-tile: 108px;
  }
}

/* --- Marquee (reviews / logos) --- */
.marquee {
  overflow: hidden;
  white-space: nowrap;
  -webkit-mask-image: linear-gradient(90deg, transparent, #000 6%, #000 94%, transparent);
  mask-image: linear-gradient(90deg, transparent, #000 6%, #000 94%, transparent);
}
.marquee__track {
  display: inline-flex;
  gap: 1.2rem;
  padding-right: 1.2rem;
  will-change: transform;
  /* Phase 6 P7 (Living Rail) deliberately does NOT change this duration.
     The rail reads the page's scroll velocity, but the rate is applied from
     main.js with the Web Animations API's `playbackRate`, not by rewriting the
     duration here. Measured reason: changing `animation-duration` on a running
     animation keeps the elapsed time and recomputes the iteration progress
     against the NEW duration, so the track TELEPORTS along its own keyframe. A
     single 1.0 -> 1.1 step moved it 36.96px, and a 1.0 -> 1.6 step moved it
     216px. `playbackRate` scales the animation's own clock instead, which
     measured a 0px move for the same step. So the duration stays the honest
     50s base and the rate rides on top of it. */
  animation: marquee 50s linear infinite;
}
.marquee:hover .marquee__track {
  animation-play-state: paused;
}
@keyframes marquee {
  from { transform: translateX(0); }
  to { transform: translateX(-50%); }
}
.marquee .review-card {
  white-space: normal;
}

/* --- Instagram feed grid --- */
.insta-grid {
  display: grid;
  /* One row of eight thumbs on desktop; the 980px and 620px queries below
     fold it to three and two columns where eight would be unreadable. */
  grid-template-columns: repeat(8, minmax(0, 1fr));
  gap: 0.75rem;
}
.insta-card {
  position: relative;
  display: block;
  aspect-ratio: 4 / 5;
  border-radius: var(--radius-md);
  overflow: hidden;
  border: 1px solid rgba(201, 168, 94, 0.16);
  background: rgba(255, 255, 255, 0.03);
  transition: transform var(--transition-base), border-color var(--transition-base);
}
.insta-card:hover,
.insta-card:focus-visible {
  transform: translateY(-4px);
  border-color: rgba(201, 168, 94, 0.45);
}
.insta-card img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: center 40%;
  transition: transform 0.9s var(--ease-out);
}
.insta-card:hover img,
.insta-card:focus-visible img {
  transform: scale(1.07);
}
.insta-card__overlay {
  position: absolute;
  inset: 0;
  z-index: 1;
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
  padding: 1.1rem 1.2rem;
  background: linear-gradient(to top, rgba(7, 13, 9, 0.9) 0%, rgba(7, 13, 9, 0.35) 48%, rgba(7, 13, 9, 0) 75%);
  opacity: 0;
  transition: opacity var(--transition-base);
}
.insta-card:hover .insta-card__overlay,
.insta-card:focus-visible .insta-card__overlay {
  opacity: 1;
}
.insta-card__stats {
  display: flex;
  align-items: center;
  gap: 1.1rem;
  color: var(--color-cream);
  font-size: 0.85rem;
  letter-spacing: 0.04em;
}
.insta-card__stats svg {
  width: 1rem;
  height: 1rem;
  color: var(--color-gold);
  margin-right: 0.4rem;
  vertical-align: -0.15em;
}
.insta-card__caption {
  display: block;
  margin-top: 0.45rem;
  font-size: 0.9rem;
  color: var(--color-cream-dim);
}
/* CTA tile closes the grid */
.insta-card--cta {
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 1.5rem;
  background:
    radial-gradient(120% 120% at 50% 0%, rgba(201, 168, 94, 0.14) 0%, rgba(201, 168, 94, 0) 60%),
    rgba(255, 255, 255, 0.02);
}
.insta-card__cta-inner {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.7rem;
  text-align: center;
}
.insta-card--cta svg {
  width: 2.1rem;
  height: 2.1rem;
  color: var(--color-gold);
  transition: transform var(--transition-base);
}
.insta-card--cta:hover svg {
  transform: scale(1.12);
}
.insta-card__cta-handle {
  font-family: var(--font-display, serif);
  font-size: 1.35rem;
  color: var(--color-cream);
}
.insta-card__cta-note {
  font-size: 0.78rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--color-cream-dim);
}
.insta-actions {
  display: flex;
  justify-content: center;
  gap: 1rem;
  flex-wrap: wrap;
  margin-top: 2.5rem;
}
@media (max-width: 980px) {
  .insta-grid { grid-template-columns: repeat(3, 1fr); gap: 0.8rem; }
}
@media (max-width: 620px) {
  .insta-grid { grid-template-columns: repeat(2, 1fr); gap: 0.6rem; }
  .insta-card__overlay { padding: 0.8rem; }
  .insta-card__caption { font-size: 0.8rem; }
}

/* --- Header hide/show on scroll (premium) --- */
.header--hidden {
  transform: translateY(-100%);
}
.header {
  transition: transform var(--transition-base), background-color var(--transition-base),
    box-shadow var(--transition-base), border-color var(--transition-base);
}

/* --- Generic reveal defaults (JS-driven) --- */
[data-reveal] {
  will-change: transform, opacity;
}
[data-stagger] > * {
  will-change: transform, opacity;
}

/* --- Eyebrow + divider rhythm --- */
.eyebrow-line {
  display: flex;
  align-items: center;
  gap: 1rem;
  font-family: var(--font-mono);
  letter-spacing: 0.24em;
  text-transform: uppercase;
  font-size: 0.82rem;
  color: var(--color-gold-light);
  font-weight: 700;
}
.eyebrow-line::before {
  content: "";
  width: 3rem;
  height: 1px;
  background: var(--color-gold);
  transform-origin: left center;
}
.eyebrow-line--center {
  justify-content: center;
}
.eyebrow-line--center::after {
  content: "";
  width: 3rem;
  height: 1px;
  background: var(--color-gold);
  transform-origin: right center;
}

/* --- Phase 2 P2: Rule Draw ------------------------------------------------
   The eyebrow's gold rule is WRITTEN rather than printed: it draws itself left
   to right over 700ms so it arrives with the words instead of ahead of them.
   `initRuleDraw()` in main.js toggles `.is-in` on each `.eyebrow-line` from the
   same `top 84%` line the headings use, so rule and heading land together.

   Gated on `.js-anim`, which initCinematic() sets ONLY when GSAP is live and
   motion is allowed. Without that flag (no JS, or prefers-reduced-motion) the
   rules sit at their natural scaleX(1): nothing is ever hidden from a reader
   who cannot see the animation play. */
.js-anim .eyebrow-line::before,
.js-anim .eyebrow-line--center::after {
  transform: scaleX(0);
  /* NO transition here, deliberately. The base rule above is scaleX(1) so a
     reader without the engine still sees the rules, which means the moment
     `.js-anim` lands this declaration changes scaleX(1) into scaleX(0). With a
     transition on THIS rule that change animates, so every gold rule on the
     page was painted at full width and then visibly RETRACTED backwards to
     nothing over 700ms on load. Measured: a probe caught it mid-retraction at
     matrix(0.858926, ...) while `.is-in` was absent. The retracted state must
     snap; the transition belongs to the DRAW, one rule below. */
}
.js-anim .eyebrow-line.is-in::before,
.js-anim .eyebrow-line.is-in::after {
  transform: scaleX(1);
  /* The transition lives on the drawn state, which is the only state that
     should be animated. `.is-in` sets both the target and the transition, so
     crossing the trigger animates 0 -> 1. probe-rule-draw.mjs section [4]
     proves the stroke still animates (a mid-draw sample must land strictly
     between 0 and 1), so this cannot silently become a snap. */
  transition: transform 700ms var(--ease-out);
}

/* The statement band's eyebrow is a bordered gold PILL (line 4381), and its
   parent is `display: inline-block`, not flex. Its ::before is therefore a
   non-replaced INLINE box: width and height are ignored and transform does not
   apply, so that rule has never rendered inside the pill. Measured, not
   assumed: removing the pseudo-element leaves the pill's box 0.00px different.
   Named here so a future edit that blockifies the pill cannot silently grow a
   stray 3rem gold line inside the border. */
.statement-band__content .eyebrow-line::before {
  content: none;
}
.text-gold-italic {
  color: var(--color-gold);
  font-family: var(--font-display);
  font-style: italic;
}

/* --- Section Divider (gold line) --- */
.section-divider {
  width: 4rem;
  height: 1px;
  background: linear-gradient(90deg, transparent, var(--color-gold), transparent);
  margin: 0 auto;
}

/* --- Premium image figure (with caption) --- */
.figure-premium {
  position: relative;
  overflow: hidden;
  border-radius: var(--radius-lg);
}

.figure-premium img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  /* TWO SCALES, TWO PROPERTIES, ON PURPOSE (interaction-plan Phase 5).
     The hover zoom lives on the individual `scale` property and the scroll Arc
     from main.js lives on `transform`, so the two MULTIPLY instead of one
     overwriting the other. Before this, the hover used `transform: scale(1.04)`
     on the same property the Arc drives, which meant whichever wrote last won,
     and the hover's 1s transition smeared every frame of the Arc.

     WHY `!important`, which is not decoration. The Arc tweens `scale` through
     GSAP, and GSAP's CSSPlugin writes the result into `transform` while it
     CLEARS the individual transform properties INLINE:

         style="transform: scale(1.08, 1.08);
                translate: none; rotate: none; scale: none;"

     An inline declaration is a direct rule, so that `scale: none` beat this
     stylesheet's `scale: 1` and left the computed value at `none` for the rest
     of the visit: the hover zoom was dead on the catering figure from the
     moment the Arc touched it, silently, with no console error. Measured
     2026-09-17 before the `!important`, with the pointer on the figure:
     computed `scale` was `none` and the image never zoomed. `!important` is the
     only thing that outranks an inline declaration, and it costs nothing here,
     because a hover multiplies GSAP's matrix by 1.04 instead of replacing it.
     Measured after: `scale=1.04` with `transform` still `none` under reduced
     motion, and the Arc still re-inflates the figure on scroll. */
  scale: 1 !important;
  transition: scale 1s var(--ease-out);
}

.figure-premium:hover img {
  scale: 1.04 !important;
}

/* Caption plate for the catering feature figure.
   The copy sits over the photograph, so the plate does two jobs at once: it
   darkens the full width of the photo's bottom edge, and it puts the text on a
   nearly flat dark surface so legibility does not depend on whichever pixels
   happen to sit behind a given word.
   That second job is the point. A plain fade measured 0.26 alpha under the
   eyebrow, so its contrast swung from 7.7:1 to 5.3:1 purely because
   object-fit: cover shows a different part of the photo at each viewport. A
   flat 0.92 plate composites to a dark surface over ANY photo (measured
   elsewhere on this site, alpha blending happens in sRGB, so even a white
   photo under it lands near 12:1 for --color-cream), and the short fade ABOVE
   the plate is what stops it reading as a hard bar. No blur, no glass card.
   The markup is a real <figure>/<figcaption> pair, so the sentence is content
   a screen reader and a crawler both read, not decoration.

   HEIGHT: the plate is pinned to the photo's bottom edge, so the only way it
   can sit LOWER is for the plate itself to get SHORTER: its top edge drops and
   more of the photograph shows. That is what the tight vertical padding and
   gap below are for; do not grow them back without re-measuring.
   The plate's own height is 93 / 89 / 87px. Its SHARE of the photo moves with
   how tall the figure is, and the figure grew on 2026-09-14 when it was aligned
   to the catering cards' bottom edge, so the shares went from 20.8 / 28.6 /
   20.7% to 15.0% (93px on the 619px desktop figure), 14.4% (89px on the 619px
   short viewport) and 20.6% (87px on the 350x422 phone). Read that the useful
   way round: a taller figure shows MORE photograph above the plate, not a
   taller plate.
   The plate is a label, not a takeover, so the share is gated at 12-40% and
   the contrast floor at 8:1 (measured 12.02 to 12.63:1 across the three). */
.figure-premium__caption {
  position: absolute;
  right: 0;
  bottom: 0;
  left: 0;
  display: flex;
  flex-direction: column;
  gap: 0.45rem;
  padding: clamp(0.55rem, 1.25vh, 1rem) clamp(1rem, 2.6vw, 1.5rem);
  color: var(--color-cream);
  background: rgba(7, 13, 9, 0.92);
  pointer-events: none;
}

/* The fade is anchored above the plate and has its own fixed height, so it can
   never eat into the text's backing however the copy wraps. */
.figure-premium__caption::before {
  content: "";
  position: absolute;
  right: 0;
  bottom: 100%;
  left: 0;
  height: clamp(2rem, 6vw, 3.5rem);
  background: linear-gradient(to top, rgba(7, 13, 9, 0.92), rgba(7, 13, 9, 0));
}

.figure-premium__caption .eyebrow-line {
  font-size: 0.75rem;
  letter-spacing: 0.2em;
}

.figure-premium__caption .eyebrow-line::before {
  width: 1.5rem;
}

/* The band's own paragraphs are --color-cream-dim. The caption deliberately is
   not: over a photograph the brighter cream is what buys the headroom to stay
   legible whatever the photo hides under the plate. */
.figure-premium__caption p {
  max-width: 42ch;
  color: var(--color-cream);
  font-size: var(--text-sm);
  line-height: 1.55;
}

/* On a phone the figure is about a third of its desktop height, so the plate
   hands some of it back. */
@media (max-width: 620px) {
  .figure-premium__caption {
    gap: 0.32rem;
  }

  .figure-premium__caption p {
    font-size: 0.82rem;
  }
}

/* --- Numbered list (premium) --- */
.numbered-list {
  counter-reset: item;
  display: grid;
  gap: 0.8rem;
}

.numbered-list__item {
  counter-increment: item;
  display: flex;
  align-items: flex-start;
  gap: 1.2rem;
  padding: 1.2rem 1.4rem;
  background: var(--color-bg-card);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  transition: border-color var(--transition-fast);
}

.numbered-list__item:hover {
  border-color: var(--color-gold);
}

.numbered-list__item::before {
  content: counter(item, decimal-leading-zero);
  font-family: var(--font-display);
  font-size: var(--text-xl);
  color: var(--color-gold);
  min-width: 2rem;
  line-height: 1;
}

.numbered-list__content h4 {
  font-size: var(--text-base);
  color: var(--color-cream);
  margin-bottom: 0.2rem;
}

.numbered-list__content p {
  font-size: var(--text-sm);
  color: var(--color-cream-dim);
}

@media (max-width: 720px) {
  .dish-row { grid-template-columns: 1fr; gap: 1rem; }
  .dish-row .dish-figure { grid-column: auto !important; }
  .dish-figure { aspect-ratio: 4 / 5; }
  .hero-cinema__scroll { display: none; }
}

@media (prefers-reduced-motion: reduce) {
  .marquee__track { animation: none; white-space: normal; }
  .hero-cinema__scroll-line::after { animation: none; }
}

/* ============================================
   FAQ SECTION (AEO-optimized)
   ============================================ */
.faq-section {
  background: var(--color-bg-alt);
}

.faq-list {
  max-width: 800px;
  margin: 3rem auto 0;
}

.faq-item {
  border-bottom: 1px solid var(--color-border);
}

.faq-item:first-child {
  border-top: 1px solid var(--color-border);
}

/* The FAQ question is an <h3> WRAPPING the button (the WAI-ARIA accordion
   pattern), so the page has a real heading outline (h2 section -> h3 question)
   and a screen-reader user can jump question to question. The wrapper must NOT
   change the button's metrics, and by default it would: h1-h6 carry the display
   face, weight 600 and line-height 1.15, and a <button> INHERITS line-height,
   so every row would grow and the hairline under it would shift. So the heading
   is reset to a positioning wrapper and the button inside owns the type.
   Do not "simplify" this away: the h3 stays in the DOM for the outline, and
   this rule is what keeps it invisible to the layout. The /menu/ FAQ is the
   only consumer today (the homepage FAQ has no heading wrapper); the rule is
   inert there. Pinned in .cortex/check_menu_seo.py, measured by
   .cortex/probe-menu-seo.mjs. */
.faq-item__heading {
  margin: 0;
  font-family: inherit;
  font-size: inherit;
  font-weight: inherit;
  line-height: inherit;
  letter-spacing: normal;
  color: inherit;
}

.faq-item__question {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  width: 100%;
  padding: 1.25rem 0;
  background: none;
  border: none;
  cursor: pointer;
  text-align: left;
  font-family: var(--font-body);
  font-size: var(--text-lg);
  font-weight: 600;
  color: var(--color-cream);
  transition: color var(--transition-fast);
}

.faq-item__question:hover {
  color: var(--color-gold-light);
}

.faq-item__question:focus-visible {
  outline: 2px solid var(--color-gold);
  outline-offset: 4px;
  border-radius: var(--radius-sm);
}

.faq-item__icon {
  width: 1.25rem;
  height: 1.25rem;
  flex-shrink: 0;
  color: var(--color-gold);
  transition: transform var(--transition-base);
}

.faq-item.is-open .faq-item__icon {
  transform: rotate(180deg);
}

.faq-item__answer {
  max-height: 0;
  overflow: hidden;
  transition: max-height 0.4s var(--ease-out), padding 0.4s var(--ease-out);
}

.faq-item.is-open .faq-item__answer {
  max-height: 20rem;
}

.faq-item__answer p {
  padding: 0 0 1.25rem;
  font-size: var(--text-base);
  line-height: 1.7;
  color: var(--color-cream-dim);
}

.faq-item__answer a {
  color: var(--color-gold);
  text-decoration: underline;
  text-underline-offset: 3px;
  transition: color var(--transition-fast);
}

.faq-item__answer a:hover {
  color: var(--color-gold-light);
}

/* ============================================
   FAQ PAGE (the standalone /faq/ page)
   ============================================
   The /faq/ page puts the questions in a two-column split: a sticky intro
   column (eyebrow, heading, lead, contact CTA) on the LEFT and the accordion
   list on the RIGHT. This is scoped to .faq-page so the shared .faq-item
   accordion component (also used by the retired menu-FAQ snippet) is untouched.

   Why a split and not the old centred .faq-list: the previous layout ran the
   list at max-width 800px against a much wider container, so the questions sat
   in a narrow left column with a large empty right side. The split fills the
   width and gives the page a real composition. The .faq-list max-width is
   lifted here and the grid owns the two columns. */
.faq-page {
  padding: var(--space-4xl) 0 var(--space-5xl);
  background: var(--color-bg-alt);
}

.faq-page__grid {
  display: grid;
  grid-template-columns: minmax(0, 0.9fr) minmax(0, 1.4fr);
  gap: var(--space-3xl);
  align-items: start;
}

.faq-page__intro {
  position: sticky;
  top: var(--space-4xl);
}

.faq-page__heading {
  margin: var(--space-md) 0 var(--space-md);
}

.faq-page__lead {
  font-size: var(--text-lg);
  line-height: 1.6;
  color: var(--color-cream-dim);
  max-width: 34ch;
  margin: 0 0 var(--space-lg);
}

.faq-page__note {
  font-size: var(--text-base);
  line-height: 1.7;
  color: var(--color-cream-dim);
  max-width: 36ch;
  margin: 0 0 var(--space-xl);
}

.faq-page__facts {
  list-style: none;
  margin: 0 0 var(--space-xl);
  padding: 0;
  display: grid;
  gap: var(--space-sm);
  max-width: 36ch;
}

.faq-page__facts li {
  font-size: var(--text-base);
  line-height: 1.5;
  color: var(--color-cream-dim);
  padding-left: var(--space-md);
  position: relative;
}

.faq-page__facts li::before {
  content: "";
  position: absolute;
  left: 0;
  top: 0.6em;
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--color-gold);
}

.faq-page__facts strong {
  color: var(--color-cream);
  font-weight: 600;
}

.faq-page__cta {
  display: inline-flex;
  align-items: center;
  gap: var(--space-sm);
}

/* Lift the shared .faq-list max-width inside the page so the grid controls the
   column width instead of the 800px cap. */
.faq-page .faq-list {
  max-width: none;
  margin: 0;
}

@media (max-width: 1024px) {
  .faq-page__grid {
    grid-template-columns: 1fr;
    gap: var(--space-2xl);
  }

  .faq-page__intro {
    position: static;
  }
}

/* ============================================
   MOBILE STICKY ACTION BAR
   ============================================ */
.mobile-action-bar {
  display: none;
  position: fixed;
  bottom: 0;
  left: 0;
  right: 0;
  z-index: calc(var(--z-header) + 10);
  background: var(--color-bg-card);
  border-top: 1px solid var(--color-border);
  padding: 0.5rem 0;
  padding-bottom: calc(0.5rem + env(safe-area-inset-bottom, 0px));
}

@media (max-width: 768px) {
  .mobile-action-bar {
    display: grid;
    grid-template-columns: repeat(4, 1fr);
  }

  /* Prevent footer content from being hidden behind the bar */
  .footer {
    padding-bottom: 5rem;
  }
}

.mobile-action-bar__item {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.2rem;
  padding: 0.4rem 0.5rem;
  text-decoration: none;
  color: var(--color-cream-dim);
  font-size: 0.65rem;
  font-weight: 600;
  letter-spacing: 0.03em;
  text-transform: uppercase;
  transition: color var(--transition-fast);
}

.mobile-action-bar__item svg {
  width: 1.35rem;
  height: 1.35rem;
}

.mobile-action-bar__item:active {
  color: var(--color-gold);
  transform: scale(0.95);
}

.mobile-action-bar__item--highlight {
  color: var(--color-gold);
}

.mobile-action-bar__item--highlight svg {
  stroke-width: 2;
}

/* --- Location search filter --- */
.loc-filter {
  max-width: 40rem;
  margin: 0 0 2.4rem;
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  gap: 0.8rem;
  align-items: center;
}
@media (max-width: 560px) {
  .loc-filter {
    grid-template-columns: 1fr;
  }
}
.loc-filter select {
  padding: 0.85rem 1.2rem;
  border-radius: 999px;
  border: 1px solid var(--color-border);
  background: var(--color-bg-card);
  color: var(--color-cream);
  font-family: var(--font-body);
  font-size: var(--text-sm);
  letter-spacing: 0.02em;
  cursor: pointer;
  transition: border-color var(--transition-fast), box-shadow var(--transition-fast);
}
.loc-filter select:focus {
  outline: none;
  border-color: var(--color-gold);
  box-shadow: 0 0 0 3px rgba(211, 173, 95, 0.14);
}
.loc-filter input {
  width: 100%;
  padding: 0.85rem 1.2rem 0.85rem 2.85rem;
  border-radius: 999px;
  border: 1px solid var(--color-border);
  background: var(--color-bg-card);
  /* Magnifier sits inside the field. Declared after the background
     shorthand, which would otherwise reset background-image to none. */
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23d3ad5f' stroke-width='2' stroke-linecap='round'%3E%3Ccircle cx='11' cy='11' r='7'/%3E%3Cpath d='M20.5 20.5l-3.9-3.9'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: 1.05rem center;
  background-size: 17px 17px;
  color: var(--color-cream);
  font-family: var(--font-body);
  font-size: var(--text-sm);
  letter-spacing: 0.02em;
  transition: border-color var(--transition-fast), box-shadow var(--transition-fast);
}
.loc-filter input::placeholder {
  color: var(--color-cream-dim);
}
.loc-filter input:focus {
  outline: none;
  border-color: var(--color-gold);
  box-shadow: 0 0 0 3px rgba(211, 173, 95, 0.14);
}
.loc-filter__empty {
  text-align: center;
  color: var(--color-cream-dim);
  font-size: var(--text-sm);
  margin: -1rem 0 2rem;
}
.loc-filter__empty a {
  color: var(--color-gold);
}
.location-card__actions .btn--ghost {
  border-color: transparent;
  color: var(--color-gold-light);
  text-decoration: underline;
  text-underline-offset: 4px;
}
.location-card__actions .btn--ghost:hover {
  color: var(--color-gold);
}

/* --- Visible breadcrumbs --- */
.breadcrumbs {
  padding-top: 1.1rem;
}
.breadcrumbs ol {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.55rem;
  list-style: none;
  margin: 0;
  padding: 0;
  font-size: var(--text-xs);
  letter-spacing: 0.14em;
  text-transform: uppercase;
}
.breadcrumbs li {
  display: flex;
  align-items: center;
  gap: 0.55rem;
  color: var(--color-cream-dim);
}
.breadcrumbs li + li::before {
  content: "";
  width: 1.4rem;
  height: 1px;
  background: rgba(212, 175, 55, 0.45);
}
.breadcrumbs a {
  color: var(--color-cream-dim);
  transition: color var(--transition-fast);
}
.breadcrumbs a:hover {
  color: var(--color-gold);
}
.breadcrumbs [aria-current="page"] {
  color: var(--color-gold-light);
}

/* --- AEO at-a-glance summary --- */
.aeo-summary {
  padding: 3rem 0 0;
}
.aeo-summary__inner {
  max-width: none;
  margin: 0;
  text-align: left;
  border-block: 1px solid rgba(212, 175, 55, 0.24);
  padding: 2.4rem 1.5rem;
}
.aeo-summary__inner p {
  margin: 0;
  color: var(--color-cream);
  font-size: clamp(1.06rem, 1.2vw, 1.35rem);
  line-height: 1.68;
  text-wrap: pretty;
}
.aeo-summary__inner strong {
  color: var(--color-gold-light);
  font-weight: 700;
}

/* ============================================
   COURSES — full-height sections
   The page was thirteen variations of "padded band on a flat green field",
   so nothing owned the screen. Each section now fills the viewport and the
   card chrome comes off: photography becomes the ground and the frosted
   glass established in the hero holds the type on top of it.
   ============================================ */

.course {
  position: relative;
  min-height: 100svh;
  display: flex;
  flex-direction: column;
  justify-content: center;
  padding-block: clamp(2rem, 4.5vh, 3.75rem);
  overflow-x: hidden;
  overflow-y: visible;
}
.course > .container {
  width: 100%;
  flex: 1 1 auto;
  min-height: 0;
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: clamp(1.1rem, 2.4vh, 2.1rem);
}

/* The primary block of each course takes the leftover height, so the section
   is filled by its content rather than by empty space around it. */
.course .cuisine-grid,
.course .bento,
.course .location-grid--featured,
.course .grid-split,
.course .grid-split--reverse,
.course .insta-grid,
.course .faq-list,
.course .marquee {
  flex: 1 1 auto;
  min-height: 0;
}
.course .section-header,
.course .split-head,
.course .insta-actions {
  flex: 0 0 auto;
  margin-bottom: 0;
}

/* Media inside a stretched block covers its cell instead of keeping a fixed
   aspect ratio and leaving gaps. */
/* Columns inside a split fill their side, so no column ends early */
.course .grid-split > *,
.course .grid-split--reverse > * {
  display: flex;
  flex-direction: column;
  min-height: 0;
}
.course .catering-grid {
  flex: 1 1 auto;
}
.course .location-card,
.course .catering-item,
.course .insta-card,
.course .figure-premium,
.course .catering-grid {
  height: 100%;
}
/* Card = flex column: the photo takes the slack, the text block keeps its
   own height. Forcing the image to 100% pushed the name, phone and buttons
   out through the card's overflow:hidden.
   Only the CATERING cards still work that way. The location cards opt out
   below, because a photo that eats the slack is wrong for a card whose job
   is to show a branch name, a phone number and three buttons. */
.course .location-card,
.course .catering-item {
  display: flex;
  flex-direction: column;
}
/* 4:3 is this band's FRAME (the box ratio these cards originally shipped
   with), and declaring it is what keeps two different photo ratios one size
   at every width. The grid is auto-fit and its rows size to content, so
   there is no slack to fall back on: left as auto the wrapper takes each
   PHOTO's own ratio, and the cards' 592x592 and 1448x1086 photos rendered
   348x348 and 348x261 side by side at the 1-up phone rows (2026-09-23).
   A 4:3 photo crops nothing here; the square one takes a centred 75% slice
   (probe-corporate-image.mjs reports it per viewport). */
.course .catering-item__image {
  flex: 1 1 auto;
  min-height: 0;
  aspect-ratio: 4 / 3;
}
.course .catering-item__body {
  flex: 0 0 auto;
}

/* Featured location cards: the photo is a BAND with a ceiling.
   As a plain flex grower it swallowed every spare pixel the grid row handed
   the card and measured 412px tall inside a 597px card, so 69% of the card
   was photograph and the name, the two detail rows and the buttons read like
   a caption under it.
   The photo stays FLEXIBLE, it just cannot grow past the cap. That ordering
   matters: the grid row height comes from the course's leftover space, not
   from the card's own content, so whichever element is flexible absorbs the
   difference in BOTH directions. Keeping the photo flexible means a squeezed
   card shortens the picture, never the copy, and the cap means a tall card
   stops enlarging it and hands the slack to the body instead.
   Do not replace this with `flex: 0 0 auto` plus a fixed height: that makes
   the body the only flexible item, and a compressed body silently loses part
   of the buttons to the card's overflow:hidden. */
.course .location-card__image {
  flex: 1 1 auto;
  min-height: 0;
  aspect-ratio: auto;
  /* A DEFINITE height, not only a ceiling. `aspect-ratio: auto` above lets the
     box shrink-wrap to its content, and the img's `height: 100%` cannot resolve
     against an auto-height parent, so it falls back to the photo's OWN intrinsic
     ratio. Three real branch photos carry three ratios (1.76, 1.84, 1.50), so
     the band rendered at three heights, measured 200/196/203px at 390 wide, and
     the row stopped being one size. It only ever looked uniform before because
     the stock placeholders all shared a 1.6 ratio. Declaring the height makes
     the band independent of the source ratio; max-height stays as the ceiling
     flex-grow must not pass, and min-height:0 plus flex-shrink still let a
     squeezed card shorten the photo rather than the copy. */
  height: clamp(180px, 24vh, 240px);
  max-height: clamp(180px, 24vh, 240px);
}
/* The body takes the surplus when the photo is already at its cap, and the
   actions row is pinned to the card base, so the buttons sit level across the
   row instead of floating wherever the copy happens to end. */
.course .location-card__body {
  flex: 1 1 auto;
}
.course .location-card__image img,
.course .catering-item__image img,
.course .insta-card img,
.course .figure-premium img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}
/* Inside a .course the figure is sized by its grid/flex parent and the image
   fills it via object-fit: cover, so no aspect-ratio is forced here. */
/* The catering feature figure: a BAND with a definite height, the same idea
   as the featured-location photo.
   The photo is static/img/catering.png, 1122x1402 (a 0.8 portrait), so at this
   figure's 525px width the photo wants to be about 655px tall, which is TALLER
   than the 520px ceiling: the cap is doing real work, not just guarding
   against a hypothetical. Before this rule had a ceiling the figure
   took its height straight from whatever image was behind it: the 2:3 portrait
   that used to sit here measured 525x787, which is 87% of a 900px viewport, and
   it made the figure the tallest thing in the split, so the BAND's row height
   was being decided by an image URL. Swapping the photo would have grown the
   band with no CSS change at all.
   This is `height`, not `max-height`, and that distinction is load-bearing.
   The image inside is `height: 100%`, and a percentage resolves against a
   DEFINITE parent height. Under a max-height the parent is still auto, so the
   percentage falls back to the photo's intrinsic 787px and the photo simply
   overflows the box and is HARD-CLIPPED by overflow: hidden (measured: img
   787px inside a 450px figure). With a definite height the image box becomes
   525x450 and object-fit: cover crops the portrait properly instead.
   Keep the figure FLEXIBLE with a ceiling; do not replace it with
   `flex: 0 0 auto` plus a fixed px height, because the vh term is what lets
   the photo shrink on a short viewport. Do not delete the ceiling: that is the
   photo-driven band. */
.course .figure-premium {
  aspect-ratio: auto;
  flex: 1 1 auto;
  min-height: 0;
  height: clamp(280px, 50vh, 520px);
}

/* The figure's column STRETCHES down to the catering cards' bottom edge.
   The client's ask, 2026-09-14: "charcoal card below have blank place need
   fill, then on bottom side 'Weddings' same line need come charcoal card also".
   The figure's wrapper is the second grid item of .grid-split, which is
   `align-items: center`, so on its own it shrinks to the figure's cap and
   floats, leaving clear space above AND below it (measured: 126px each way, and
   its bottom 43px above the cards' bottom). Making that one item
   `align-self: stretch` fills the whole grid row, and the bottom margin carves
   out the CTA block underneath the cards, so the figure stops exactly level
   with the cards' bottom edge instead of running past it down to the button.
   That margin is the ONE measured constant here (--catering-cta-block, 83px =
   the CTA wrapper's 2rem gap + the button's height). verify-catering-band.mjs
   measures the figure's bottom against the cards' bottom at every viewport, so
   if the button grows or the gap moves, the gate fails instead of the
   alignment quietly drifting by a few pixels.
   `height: auto` is what lets the figure fill the stretched wrapper:
   `.figure-premium` is `flex: 1 1 auto; min-height: 0` inside it, so it takes
   the wrapper's full height. The base clamp above still governs the stacked
   layout below 1025px, where the split has no second column to line up with.
   Do NOT go back to `align-content: center` plus `flex: 1 1 0`: that is what
   inflated this figure to the full 702px column height and overshot the cards
   down to the button, which the client rejected on 2026-09-14. */
@media (min-width: 1025px) {
  /* The split's row must be the height of the TEXT COLUMN, not the split's own
     stretched height, or `stretch` overshoots. .grid-split is `flex: 1 1 auto`
     inside the section's flex column, so it grows past its content: measured,
     its row was 819px against a 702px column, and a stretched figure came out
     736px with its bottom 59px BELOW the cards. `align-content: center` brings
     the single row back to the content height and centres it in the split,
     which leaves the text column exactly where it was (99..801) and makes the
     stretch below mean "the column", which is the number we actually want. */
  .course .grid-split:has(.catering-grid) {
    align-content: center;
  }

  .course .grid-split:has(.catering-grid) > div:has(.figure-premium) {
    align-self: stretch;
    margin-bottom: var(--catering-cta-block);
  }

  .course .grid-split:has(.catering-grid) .figure-premium {
    height: auto;
  }

  /* The figure's height must come from its WRAPPER, never from the photo.
     On desktop `height: auto` makes the figure's flex base size its CONTENT,
     and its only in-flow content is the image. The image is `height: 100%`, and
     a percentage height needs a DEFINITE parent: while the figure's own height
     is auto that percentage cannot resolve, so the image falls back to its
     INTRINSIC height and the figure inherits it.
     With the old landscape photo (1374x1145) that intrinsic height was 437px,
     comfortably under the wrapper's 619px, so flex-grow quietly filled the rest
     and the flaw stayed invisible. The 2026-09-15 charcoal-chicken photo is a
     PORTRAIT (1122x1402) that wants 525 x 1402/1122 = 655.8px at this width, and
     it exposed the flaw exactly as measured: the figure's computed height became
     655.781px, the grid ROW grew past the 702px text column to fit it, and the
     figure's bottom finished 19px BELOW the catering cards, breaking the
     "Weddings same line" alignment the client asked for on 2026-09-14.
     Taking the image OUT OF FLOW fixes it at the root: with no in-flow content
     the figure's flex base size is zero, so `flex: 1 1 auto` has nothing to
     measure but the wrapper, and the photo's aspect ratio can never move the
     band again. This is the same lesson as the height-vs-max-height trap above,
     one level down: a percentage height needs a definite box, and an image that
     cannot resolve its percentage height will silently lend its own to the
     parent.
     Scoped to this split and to >= 1025px on purpose. Below 1025px the split
     stacks, the base `height: clamp(280px, 50vh, 520px)` makes the figure
     definite, and the image's `height: 100%` resolves normally. */
  .course .grid-split:has(.catering-grid) .figure-premium img {
    position: absolute;
    inset: 0;
  }
}

/* ---------------------------------------------------------------------------
   MOBILE (<= 1024px): the catering band shows the two cards ONLY. The feature
   figure is hidden here, and hidden means `display: none` - never deletion. The
   markup (photo + caption) stays in the served HTML exactly as it is, and
   desktop renders it exactly as it always has.

   Client, 2026-09-28: "catering-item in mobile view ... in this i given card
   only need display 'Corporate ...' ... other 'Weddings ...' figure-premium
   need hidden from screen in mobile view but not remove, desktop view current
   what have fully need display".

   1024px is not a new number. It is the same line the global stylesheet stacks
   `.grid-split` on (`.grid-split, .grid-split--reverse { grid-template-columns:
   1fr }`), i.e. the width where this figure stops sitting BESIDE the cards and
   starts sitting UNDER them. Above 1024px nothing here applies, and the
   `@media (min-width: 1025px)` block above still owns the desktop alignment.

   THE WRAPPER is hidden, not the figure, and that is deliberate: a
   `display: none` element leaves the grid entirely, so no empty row is left
   behind. Hiding only the figure would keep its wrapper as an empty grid item,
   and the split's `gap` would still open that space above and below nothing.

   `:has(.catering-grid)` scopes the whole thing to THIS band. `.figure-premium`
   is also the Pasons Group band's class (a div, higher up the same page) and
   that band is not part of this instruction - a bare `.figure-premium` selector
   here would blank a photo the client never asked to touch.

   `align-content: center` is the guard for the column LEFT BEHIND. The split is
   `flex: 1 1 auto` inside a `min-height: 100svh` course, so whenever the text
   column is SHORTER than the viewport the grid has free space to hand out and
   the default `align-content: stretch` would pour all of it into the one
   remaining row. `.course .catering-grid` is `flex: 1 1 auto` and so is
   `.course .catering-item__image`, so the cards would grow and the two 4:3
   photos would stretch taller than their frame and stop being one size.
   Centring the row keeps every card at its natural height - the same height it
   renders at on desktop. Inert on a phone (the stacked cards already overflow
   the viewport, so there is no free space to distribute) and inert above
   1024px.
   --------------------------------------------------------------------------- */
@media (max-width: 1024px) {
  .course .grid-split:has(.catering-grid) > div:has(.figure-premium) {
    display: none;
  }

  /* The Weddings card leaves the phone as well. Client, 2026-09-28: "this also
     need hide from mobile screen only 'Corporate' is need dipaly on on mobile
     screen".

     This band carries exactly two cards in a fixed order - Corporate is the
     1st, Weddings the 2nd - so `:nth-child(2)` IS the Weddings card. The gate
     ties that positional selector to card 2's own title AND photo, so
     reordering the cards fails loudly instead of quietly hiding the wrong one.

     `display: none` takes the card out of the LAYOUT, never out of the page:
     the markup ships exactly as before, the same way the figure above does.

     The grid is `repeat(auto-fit, minmax(260px, 1fr))`, so with the second card
     gone its collapsed track hands the row to the one card left. On a phone
     that is the width the Corporate card already had (both cards were stacked
     1-up there, one track either way); at tablet widths the lone card widens to
     the column. Nothing else in the band moves. */
  .course .catering-grid > .catering-item:nth-child(2) {
    display: none;
  }

  .course .grid-split:has(.catering-grid) {
    align-content: center;
  }
}

/* --- Make each course's content actually reach the edges of its plate --- */

/* Reviews has the same shape as the dish rail: the marquee is full-bleed and
   sits outside .container, so the container must not grow and centre the
   lone heading. */
/* One row of testimonials is not a screen's worth of content. Same rule as
   the statement band: a course earns full height only if it can fill it. */
.course:has(> .marquee) {
  min-height: 0;
  padding-block: clamp(3.5rem, 8vh, 6rem);
}
.course:has(> .marquee) > .container {
  flex: 0 0 auto;
  justify-content: flex-start;
}
.course:has(> .marquee) > .marquee {
  flex: 0 0 auto;
}
.course > .marquee {
  flex: 1 1 auto;
  min-height: 0;
  display: flex;
  flex-direction: column;
  justify-content: center;
}

/* Reviews: stretching the marquee box left 110px cards floating in 713px of
   air. The cards themselves have to carry the height — bigger panels and
   bigger type, not a taller empty container. */
.course .marquee__track {
  align-items: stretch;
}
.course .review-card {
  min-width: 26rem;
  max-width: 30rem;
  padding: clamp(1.9rem, 3.4vh, 2.9rem);
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: 0.9rem;
  white-space: normal;
}
.course .review-card__title {
  font-family: var(--font-display);
  font-size: clamp(1.35rem, 1.9vw, 1.85rem);
  line-height: 1.18;
  color: #faf6ec;
}
.course .review-card__text {
  font-size: clamp(1rem, 1.15vw, 1.18rem);
  line-height: 1.6;
  color: var(--color-cream-dim);
}

/* Locations: rows stretch so the cards fill the grid */
.course .location-grid--featured {
  grid-auto-rows: minmax(0, 1fr);
  align-items: stretch;
}
/* These four rules keep a course-band card on its ORIGINAL anatomy. The
   standalone card (locations page, branch pages) now carries a hairline
   marker above the name and a two-up button grid. Neither suits a card that
   leads with a photo: the marker would sit under the image with no job, and
   a 2-up grid pushes a third button onto a new row, which steals height from
   the photo because the image is the flex-grown element here. */
.course .location-card {
  background-image: none;
}
.course .location-card__body::before {
  content: none;
}
.course .location-card__name {
  margin-top: 0;
  font-size: var(--text-lg);
  font-weight: 400;
}
.course .location-card__actions {
  /* flex, not grid: keep the wrapping row. */
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem;
  /* Pinned to the card base. The body grows on a tall viewport, and the
     buttons belong on the bottom edge, level across the row, with the two
     detail rows held up under the branch name. */
  margin-top: auto;
}
.course .location-card__actions .btn {
  padding: 0.5rem 0.9rem;
  font-size: 0.68rem;
}
.course .location-card__body {
  padding: 1rem 1.1rem 1.1rem;
}
.course .location-card:hover {
  border-color: var(--color-gold);
}

/* Feed strip: ONE row of eight thumbs, and the band hugs its content.
   Three things changed together, because each alone half-works:
   - flex: 0 0 auto beats the shared flex: 1 1 auto above, so the grid stops
     absorbing the section's leftover viewport height;
   - the tiles keep a definite 4/5 ratio instead of stretching to fill a
     row track, so one row stays one row of thumbs;
   - min-height: 0 lifts the 100svh floor on THIS band only, which is what
     removes the dead space parked under the buttons. */
.course:has(.insta-grid) {
  min-height: 0;
}
.course .insta-grid {
  flex: 0 0 auto;
  grid-auto-rows: auto;
}
.course .insta-card {
  height: auto;
  aspect-ratio: 4 / 5;
}
/* Thumb-sized tiles: tighten the hover overlay and the CTA type so both
   still read at roughly 150px wide. */
.course .insta-card__overlay {
  padding: 0.7rem 0.75rem;
}
.course .insta-card__stats {
  gap: 0.6rem;
  font-size: 0.75rem;
}
.course .insta-card__stats svg {
  width: 0.85rem;
  height: 0.85rem;
  margin-right: 0.25rem;
}
.course .insta-card__caption {
  margin-top: 0.3rem;
  font-size: 0.78rem;
}
.course .insta-card--cta {
  padding: 0.9rem 0.6rem;
}
.course .insta-card--cta svg {
  width: 1.6rem;
  height: 1.6rem;
}
.course .insta-card__cta-handle {
  font-size: 1rem;
}
.course .insta-card__cta-note {
  font-size: 0.62rem;
  letter-spacing: 0.1em;
}

/* Dish rail: the carousel is full-bleed and lives OUTSIDE .container, so the
   container holds only the header. Letting it grow and centre made a band of
   dead space above and below the heading. The carousel takes the slack. */
.course.dish-rail > .container {
  flex: 0 0 auto;
  justify-content: flex-start;
}
.course.dish-rail > .dish-carousel {
  flex: 0 0 auto;
  min-height: auto;
  display: flex;
  flex-direction: column;
  justify-content: flex-start;
}

/* Dish rail: bigger posters so the carousel occupies the plate.
   --card-w is declared on .dish-carousel itself, so the override has to be
   at least as specific or it never lands. */
.course.dish-rail .dish-carousel {
  --card-w: clamp(13.5rem, 16vw, 17rem);
}

/* FAQ fills by breathing, not by stretching rows apart */
.course .faq-list {
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: 0.5rem;
}
.course .faq-item__question {
  font-size: clamp(1.05rem, 1.35vw, 1.3rem);
  padding-block: clamp(1rem, 1.9vh, 1.5rem);
}

/* Pasons is a compact band again, so its photo must not run away */
.course--quiet .figure-premium {
  max-height: min(44vh, 420px);
}

/* Section headers were left at default size while .split-head got the lift */
.course .section-header {
  max-width: none;
}
.course .section-header h2 {
  font-size: clamp(2.2rem, 3.9vw, 4rem);
  font-weight: 600;
  line-height: 1.08;
  letter-spacing: -0.018em;
  color: #faf6ec;
  text-wrap: balance;
}
.course .section-header p {
  font-size: clamp(1.02rem, 1.15vw, 1.24rem);
  line-height: 1.6;
  color: var(--color-cream);
}

/* Hairline seam between courses, so the sequence reads as separate plates */
.course + .course::before {
  content: "";
  position: absolute;
  top: 0;
  left: 50%;
  transform: translateX(-50%);
  width: min(100% - 3rem, var(--container-max));
  height: 1px;
  background: linear-gradient(90deg, transparent, rgba(211, 173, 95, 0.22), transparent);
}

/* --- The at-a-glance line becomes the opening statement, set at display size --- */
.course--statement {
  min-height: 0;
  justify-content: flex-start;
  padding-block: 40px;
}
.aeo-summary.course--statement {
  padding-top: 40px;
  padding-bottom: 40px;
}
.course--statement > .container {
  max-width: none;
  width: 100%;
  padding-inline: 248px;
}
.course--statement .aeo-summary__inner {
  max-width: none;
}
/* Minimal two-tone statement: a light sans line in hero ink answered by a
   serif italic line in gold. Short copy, no wall of text. */
.course--statement .aeo-summary__lead {
  margin: 0 0 clamp(1.5rem, 3vh, 2.4rem);
  font-family: var(--font-body);
  font-size: clamp(1.9rem, 3.4vw, 3.4rem);
  font-weight: 300;
  line-height: 1.16;
  letter-spacing: -0.015em;
  color: var(--hero-ink);
  text-wrap: balance;
}
.course--statement .aeo-summary__line--gold {
  font-family: var(--font-display);
  font-style: italic;
  font-weight: 500;
  color: var(--color-gold);
}
.course--statement .aeo-summary__inner p:not(.aeo-summary__lead) {
  max-width: 62ch;
  font-size: clamp(1.02rem, 1.15vw, 1.22rem);
  line-height: 1.7;
  color: var(--color-cream-dim);
}
.course--statement .aeo-summary__inner strong {
  font-weight: 700;
  color: var(--color-gold-light);
}

/* --- Cuisines: asymmetric editorial photo plates. Two wide, two narrow,
   copy set directly on a deep scrim instead of a flat glass band. --- */
.course--cuisines .cuisine-grid {
  grid-template-columns: repeat(12, 1fr);
  grid-auto-rows: clamp(24rem, 30vw, 31rem);
  gap: 1rem;
}
.course--cuisines .cuisine-item:nth-child(1),
.course--cuisines .cuisine-item:nth-child(4) {
  grid-column: span 7;
}
.course--cuisines .cuisine-item:nth-child(2),
.course--cuisines .cuisine-item:nth-child(3) {
  grid-column: span 5;
}
.course--cuisines .cuisine-item {
  position: relative;
  background: none;
  border: 0;
  border-radius: 22px;
  overflow: hidden;
  isolation: isolate;
}
.course--cuisines .cuisine-item::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 4;
  border-radius: inherit;
  border: 1px solid rgba(236, 211, 155, 0.16);
  pointer-events: none;
  transition: border-color 0.45s ease;
}
.course--cuisines .cuisine-item:hover::after {
  border-color: rgba(236, 211, 155, 0.45);
}
.course--cuisines .cuisine-item:hover {
  transform: translateY(-6px);
  box-shadow: 0 36px 64px -28px rgba(0, 0, 0, 0.78);
}
.course--cuisines .cuisine-item__media {
  position: absolute;
  inset: 0;
  aspect-ratio: auto;
}
.course--cuisines .cuisine-item__media img {
  filter: saturate(1.06) contrast(1.04);
}
.course--cuisines .cuisine-item__media::after {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(
    180deg,
    rgba(8, 14, 9, 0.42) 0%,
    rgba(8, 14, 9, 0.06) 34%,
    rgba(8, 14, 9, 0.52) 62%,
    rgba(6, 11, 7, 0.94) 100%
  );
}
.course--cuisines .cuisine-item__count {
  z-index: 3;
  top: 1.15rem;
  left: 1.15rem;
  padding: 0.42rem 0.95rem;
  font-size: 0.72rem;
  letter-spacing: 0.18em;
  background: rgba(8, 14, 9, 0.55);
  border-color: rgba(236, 211, 155, 0.4);
}
.course--cuisines .cuisine-item__body {
  position: relative;
  z-index: 2;
  margin-top: auto;
  flex: 0 0 auto;
  gap: 0.6rem;
  padding: 2rem 2rem 1.9rem;
  background: none;
  backdrop-filter: none;
  -webkit-backdrop-filter: none;
  border-top: 0;
}
.course--cuisines .cuisine-item__body h3 {
  font-size: clamp(2.1rem, 2.6vw, 2.9rem);
  position: relative;
  padding-bottom: 0.7rem;
  text-shadow: 0 2px 18px rgba(0, 0, 0, 0.6);
}
.course--cuisines .cuisine-item__body h3::after {
  content: "";
  position: absolute;
  left: 0;
  bottom: 0;
  width: 3.2rem;
  height: 2px;
  background: linear-gradient(90deg, var(--color-gold), rgba(236, 211, 155, 0));
  transition: width 0.5s var(--ease-out);
}
.course--cuisines .cuisine-item:hover .cuisine-item__body h3::after {
  width: 6.5rem;
}
.course--cuisines .cuisine-item__body p {
  flex: 0 0 auto;
  font-size: 1.05rem;
  max-width: 46ch;
  color: rgba(240, 231, 208, 0.86);
  text-shadow: 0 1px 10px rgba(0, 0, 0, 0.55);
}
.course--cuisines .cuisine-item__body .cuisine-item__sig {
  font-family: var(--font-display);
  font-style: italic;
  font-size: 1.06rem;
  color: var(--color-gold-light);
  text-shadow: 0 1px 10px rgba(0, 0, 0, 0.55);
}
.course--cuisines .cuisine-item__link {
  margin-top: 0.35rem;
  font-size: 0.82rem;
}

/* --- Clean cuisine cards (no images) --- */
.course--cuisines .cuisine-item--clean {
  position: relative;
  background: rgba(18, 26, 18, 0.5);
  border: 1px solid rgba(236, 211, 155, 0.12);
  border-radius: 22px;
  padding: 2.2rem 2.4rem;
  display: flex;
  flex-direction: column;
  gap: 0.7rem;
  transition: border-color 0.4s ease, transform 0.4s ease, box-shadow 0.4s ease;
  overflow: visible;
}
.course--cuisines .cuisine-item--clean:hover {
  border-color: rgba(236, 211, 155, 0.4);
  transform: translateY(-4px);
  box-shadow: 0 24px 48px -24px rgba(0, 0, 0, 0.5);
}
.course--cuisines .cuisine-item--clean .cuisine-item__tag {
  font-size: 0.72rem;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--color-gold-light);
  opacity: 0.7;
}
.course--cuisines .cuisine-item--clean h3 {
  font-size: clamp(1.6rem, 2vw, 2rem);
  position: relative;
  padding-bottom: 0.7rem;
}
.course--cuisines .cuisine-item--clean h3::after {
  content: "";
  position: absolute;
  left: 0;
  bottom: 0;
  width: 3.2rem;
  height: 2px;
  background: linear-gradient(90deg, var(--color-gold), rgba(236, 211, 155, 0));
  transition: width 0.5s var(--ease-out);
}
.course--cuisines .cuisine-item--clean:hover h3::after {
  width: 6.5rem;
}
.course--cuisines .cuisine-item--clean p {
  font-size: 1.05rem;
  max-width: 46ch;
  color: rgba(240, 231, 208, 0.8);
  line-height: 1.6;
}
.course--cuisines .cuisine-item--clean .cuisine-item__sig {
  font-family: var(--font-display);
  font-style: italic;
  font-size: 1.06rem;
  color: var(--color-gold-light);
}
.course--cuisines .cuisine-item--clean .cuisine-item__link {
  margin-top: 0.35rem;
  font-size: 0.82rem;
}

/* --- Text-only bento cells --- */
.course--story .bento__cell--text {
  background: linear-gradient(160deg, rgba(18, 26, 18, 0.85), rgba(12, 21, 15, 0.95));
  border: 1px solid rgba(211, 173, 95, 0.14);
  border-radius: 20px;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 2rem;
}
.course--story .bento__cell--text .bento__cell__num {
  font-size: clamp(2.4rem, 4vw, 3.4rem);
  color: var(--color-gold-light);
  line-height: 1;
  margin-bottom: 0.4rem;
}
.course--story .bento__cell--text .bento__cell__label {
  font-size: clamp(1rem, 1.5vw, 1.2rem);
  font-weight: 600;
  margin-bottom: 0.3rem;
}
.course--story .bento__cell--text .bento__cell__meta {
  font-size: 0.88rem;
  color: rgba(240, 231, 208, 0.6);
  max-width: 30ch;
  text-align: center;
}

/* --- Stats: figures on the ground, not numbers in boxes --- */
.course--story .bento {
  grid-auto-rows: minmax(0, 1fr);
  gap: 0.75rem;
}
.course--story .bento__cell {
  background: linear-gradient(160deg, rgba(24, 40, 28, 0.55), rgba(12, 21, 15, 0.35));
  border-color: rgba(211, 173, 95, 0.14);
  border-radius: 20px;
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
}
.course--story .bento__cell--image {
  border-color: transparent;
}
.course--story .bento__cell__num {
  font-size: clamp(3.4rem, 6vw, 5.6rem);
}

/* Stat cells were bottom-aligned, leaving the top half of each cell empty */
.course--story .bento__cell:not(.bento__cell--image) {
  justify-content: center;
}
.course--story .bento__cell--image .eyebrow {
  text-shadow: 0 1px 14px rgba(0, 0, 0, 0.75);
}

/* Scale(1.15) grows from the card center. Padding must cover that extra
   height or overflow-x:auto on the track also clips the poster tops. */
.course.dish-rail .dish-carousel__track {
  padding-top: 4rem;
  padding-bottom: 4.25rem;
}
.course.dish-rail .split-head {
  margin-bottom: 1.5rem;
}


/* Pasons credit is a footnote to the story, not a full plate of its own */
.course--quiet {
  min-height: 0;
  padding-block: clamp(4rem, 9vh, 7rem);
}

@media (max-width: 900px) {
  .course {
    min-height: auto;
    padding-block: clamp(3rem, 8vh, 4.5rem);
  }
  .course--statement,
  .course--cuisines,
  .course--story {
    min-height: auto;
  }
  /* Image-based cuisine cards — keep fixed height */
  .course--cuisines .cuisine-item:not(.cuisine-item--clean),
  .course--cuisines .cuisine-item:nth-child(1),
  .course--cuisines .cuisine-item:nth-child(2),
  .course--cuisines .cuisine-item:nth-child(3),
  .course--cuisines .cuisine-item:nth-child(4) {
    grid-column: span 12;
    min-height: 24rem;
  }
  /* Clean editorial cards — no fixed height */
  .course--cuisines .cuisine-item--clean {
    grid-column: span 12;
    min-height: auto;
    padding: 1.8rem 1.6rem;
  }
  .course--cuisines .cuisine-item__body {
    padding: 1.6rem 1.5rem 1.6rem;
  }
  .course--story .bento {
    min-height: 0;
  }
  /* Text cells stack on mobile */
  .course--story .bento__cell--text {
    min-height: 10rem;
  }
}

/* --- Reduced motion: everything lands visible, nothing moves --- */
@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-delay: 0ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
  html {
    scroll-behavior: auto;
  }
  .hero-cinema__line > span {
    transform: none;
    animation: none;
  }
  .hero-cinema__rotate-word {
    animation: none !important;
    transition: none;
    transform: none;
    opacity: 0;
  }
  .hero-cinema__rotate-word.is-active {
    opacity: 1;
  }
  .hero-cinema__rotate-word.is-leaving {
    opacity: 0;
  }
  .hero-cinema__detail {
    transition: none;
    transform: none;
  }
  .hero-cinema__eyebrow,
  .hero-cinema__details,
  .hero-cinema__rotate,
  .hero-cinema__switch,
  .hero-cinema__actions,
  .hero-cinema__facts {
    animation: none;
    opacity: 1;
    transform: none;
  }
  html:not(.js-anim) .hero-cinema__bg img {
    animation: none;
  }
  .dish-carousel__track {
    scroll-snap-type: x mandatory;
    scroll-behavior: auto;
  }
  .dish-card {
    scroll-snap-align: center;
  }
  [data-reveal],
  [data-stagger] > *,
  .m-reveal {
    opacity: 1 !important;
    transform: none !important;
    /* Phase 3 gives `display` hooks a blur-to-sharp focus pull. If a visitor
       turns reduced motion on mid-session, a from-state blur can be left
       sitting on the element, so the filter is reset with the rest. */
    filter: none !important;
  }
  /* Phase 5 P1 gives section photography an inline GSAP transform while the
     Arc plays. initArc() bails at load under reduced motion, but a visitor who
     turns the setting on MID-SESSION keeps whatever the tween had already
     written, and a photograph can be stranded at up to scale 1.12 with no
     engine left to clear it. The hover zoom is deliberately NOT reset: it
     lives on the individual `scale` property rather than on `transform`, so
     this leaves it behaving exactly as the card hovers do under reduced
     motion, which this project has always allowed.
     The statement band's own photo left this list on 2026-09-17 together with
     the photo itself: the band is a flat dark plate now and owns no Arc
     target, so there is nothing here for a stranded transform to sit on. */
  .figure-premium img,
  .ba-slider__img {
    transform: none !important;
  }
}

/* ============================================
   DISH COVERFLOW — full-bleed rail
   Heading stays on the plate. The carousel sits
   outside .container so posters span the viewport.
   Center card is largest; sides recede.
   ============================================ */

/* Do not lock this section to 100svh. Heading + scaled posters are taller
   than one screen; min-height:100svh + justify-content:center clipped the
   H2 and the top of the center card. */
.course.dish-rail {
  min-height: 0;
  justify-content: flex-start;
  overflow: visible;
}

.course.dish-rail > .container {
  flex: 0 0 auto;
  justify-content: flex-start;
}

.course.dish-rail > .dish-carousel {
  width: 100%;
  max-width: none;
  flex: 0 0 auto;
  min-height: auto;
}

.course.dish-rail .dish-carousel__track {
  padding-inline: calc(50% - var(--card-w) / 2);
  overflow-x: hidden;
  overflow-y: hidden;
}

@media (max-width: 768px) {
  .course.dish-rail .dish-carousel__track {
    overflow-x: auto;
  }
}

.course--statement .aeo-summary__inner {
  display: grid;
  grid-template-columns: minmax(0, 1.08fr) minmax(0, 0.92fr);
  gap: clamp(2rem, 4vw, 4.5rem);
  align-items: center;
  width: 100%;
  max-width: none;
  padding: clamp(1.75rem, 3vw, 2.75rem) clamp(1.75rem, 3.2vw, 3rem);
  background:
    linear-gradient(180deg, rgba(34, 52, 42, 0.72) 0%, rgba(12, 22, 15, 0.42) 100%);
  border: 1px solid rgba(211, 173, 95, 0.32);
  border-radius: 2px;
  box-shadow:
    inset 0 1px 0 rgba(236, 211, 155, 0.1),
    0 18px 40px -24px rgba(10, 18, 12, 0.7);
}

.course--statement .aeo-summary__lead {
  display: flex;
  flex-direction: column;
  gap: 0.18em;
  margin: 0;
  max-width: none;
  font-size: clamp(1.9rem, 3.2vw, 3.2rem);
  line-height: 1.14;
  letter-spacing: -0.015em;
  text-wrap: balance;
}

.course--statement .aeo-summary__line {
  display: block;
}

.course--statement .aeo-summary__body,
.course--statement .aeo-summary__inner p:not(.aeo-summary__lead) {
  max-width: none;
  margin: 0;
  padding-left: clamp(1.5rem, 2.8vw, 2.4rem);
  border-left: 1px solid rgba(211, 173, 95, 0.28);
  font-size: clamp(1.05rem, 1.15vw, 1.28rem);
  line-height: 1.72;
}

@media (max-width: 1400px) {
  .course--statement > .container {
    padding-inline: clamp(1.5rem, 6vw, 248px);
  }
}

@media (max-width: 900px) {
  .course--statement {
    min-height: 0;
    padding-block: 40px;
  }
  .course--statement > .container {
    padding-inline: clamp(1.25rem, 5vw, 2rem);
  }
  .course--statement .aeo-summary__inner {
    grid-template-columns: 1fr;
    align-items: start;
    gap: 1.35rem;
    padding: 1.35rem 1.25rem;
  }
  .course--statement .aeo-summary__lead {
    max-width: none;
    font-size: clamp(1.55rem, 7vw, 2.1rem);
  }
  .course--statement .aeo-summary__body,
  .course--statement .aeo-summary__inner p:not(.aeo-summary__lead) {
    padding-left: 0;
    border-left: 0;
    padding-top: 1.15rem;
    border-top: 1px solid rgba(211, 173, 95, 0.28);
  }
}

/* The story grid must be content-sized, not stretched to fill the viewport.
   `.course` forces min-height:100svh and `.course .bento` uses flex:1 1 auto,
   which grows the cards to fill the screen regardless of any cell min-height.
   Killing both on the story variant lets the cards shrink to what they need. */
.course--story {
  min-height: auto;
}

.course--story .bento {
  flex: 0 0 auto;
  min-height: 0;
}

/* Same contract for the cuisines grid. `.course--cuisines .cuisine-grid--asym`
   is explicitly content-sized (flex: 0 0 auto), so the section can no longer
   fill the viewport on its own: the 100svh box kept ~400px of empty flex space
   that justify-content:center split above and below the cards. That read as a
   dead band between the menus and the transform scene. Opting the section out
   of 100svh, exactly as the story variant above does, lets it size to content. */
.course--cuisines {
  min-height: auto;
}

.course--story .bento__cell--image {
  min-height: 8.5rem;
}

/* The feature card spans the whole left column and holds a heading + paragraph,
   so it needs more room than the small stat cards below it. */
.course--story .bento__cell--feature {
  min-height: 16rem;
}

.course--story .bento__cell:not(.bento__cell--image) {
  justify-content: flex-end;
}

.course--cuisines .cuisine-grid {
  min-height: min(62vh, 38rem);
}

.course--cuisines .cuisine-item {
  min-height: 22rem;
}

/* statement-band occasion grid is handled in its own responsive block */

/* ============================================================
   PLAN-AN-EVENT FAB + INQUIRY MODAL (deep-research report UX)
   ============================================================ */
.event-fab {
  position: fixed;
  right: 1.25rem;
  bottom: 1.25rem;
  z-index: 900;
  display: inline-flex;
  align-items: center;
  gap: 0.55rem;
  padding: 0.85rem 1.35rem;
  font-family: var(--font-body);
  font-size: var(--text-sm);
  font-weight: 700;
  letter-spacing: 0.04em;
  color: #142016;
  background: linear-gradient(135deg, var(--color-gold-light), var(--color-gold) 55%, var(--color-gold-dark));
  border: none;
  border-radius: 999px;
  cursor: pointer;
  box-shadow: 0 18px 38px -14px rgba(0, 0, 0, 0.65), 0 0 0 1px rgba(236, 211, 155, 0.35);
  transition: transform 0.25s cubic-bezier(0.16, 1, 0.3, 1), box-shadow 0.25s ease;
}
.event-fab svg {
  width: 17px;
  height: 17px;
  flex-shrink: 0;
}
.event-fab:hover {
  transform: translateY(-3px);
  box-shadow: 0 24px 46px -14px rgba(0, 0, 0, 0.7), 0 0 0 1px var(--color-gold-light);
}
.event-fab:active { transform: translateY(-1px) scale(0.98); }
.event-fab:focus-visible {
  outline: 2px solid var(--color-gold-light);
  outline-offset: 3px;
}

/* --- Phone stack (2026-09-24): the event FAB now sits [12px above the bar]
   instead of inside it. Before this pass it was at bottom: 1.25rem, INSIDE
   the bar's own zone, where it painted over the Order item (bar z-index 110
   < FAB 900). The bar itself is UNCHANGED: all four items, Order included,
   stay in their grid columns (client instruction: "order button old had same
   position ... event button ... keep it").
   Bar height measured from its own rules: 1px border-top + 8px pad + item
   (12.8 pad + 21.6 svg + 3.2 gap + 17.7 label at 10.4px x line-height 1.7) +
   8px pad = 72.3px + safe-area, which the 4.5rem token below matches. */
@media (max-width: 768px) {
  :root {
    --stack-bar-h: 4.5rem;
    --stack-gap: 0.75rem;
  }
  .event-fab {
    bottom: calc(var(--stack-bar-h) + var(--stack-gap) + env(safe-area-inset-bottom, 0px));
  }
}

.event-modal {
  position: fixed;
  inset: 0;
  z-index: 1000;
  display: grid;
  place-items: center;
  padding: 1rem;
}
.event-modal[hidden] { display: none; }
.event-modal__backdrop {
  position: absolute;
  inset: 0;
  background: rgba(6, 11, 7, 0.78);
  backdrop-filter: blur(6px);
  animation: evFade 0.25s ease both;
}
.event-modal__panel {
  position: relative;
  width: min(560px, 100%);
  max-height: min(88vh, 52rem);
  overflow-y: auto;
  padding: 2.25rem 2rem 1.75rem;
  background: linear-gradient(165deg, var(--color-bg-elevated), var(--color-bg-card) 60%);
  border: 1px solid var(--color-border);
  border-radius: 1.25rem;
  box-shadow: 0 40px 90px -30px rgba(0, 0, 0, 0.85);
  animation: evRise 0.35s cubic-bezier(0.16, 1, 0.3, 1) both;
}
@keyframes evFade { from { opacity: 0; } to { opacity: 1; } }
@keyframes evRise { from { opacity: 0; transform: translateY(26px) scale(0.97); } to { opacity: 1; transform: none; } }
.event-modal__close {
  position: absolute;
  top: 1rem;
  right: 1rem;
  display: grid;
  place-items: center;
  width: 2.25rem;
  height: 2.25rem;
  color: var(--color-cream-dim);
  background: rgba(240, 231, 208, 0.06);
  border: 1px solid var(--color-border-light);
  border-radius: 50%;
  cursor: pointer;
  transition: color 0.2s, border-color 0.2s, transform 0.2s;
}
.event-modal__close svg { width: 15px; height: 15px; }
.event-modal__close:hover { color: var(--color-gold-light); border-color: var(--color-gold); transform: rotate(90deg); }
.event-modal__eyebrow {
  font-size: 0.7rem;
  font-weight: 700;
  letter-spacing: 0.24em;
  text-transform: uppercase;
  color: var(--color-gold);
}
.event-modal__title {
  margin-top: 0.5rem;
  font-family: var(--font-display);
  font-size: clamp(1.5rem, 4vw, 2rem);
  font-weight: 500;
  line-height: 1.15;
  color: var(--color-cream);
}
.event-modal__sub {
  margin-top: 0.5rem;
  font-size: var(--text-sm);
  line-height: 1.6;
  color: var(--color-cream-dim);
}
.event-form { margin-top: 1.5rem; }
.event-form__row {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 0.9rem;
}
.event-form__field { margin-bottom: 0.9rem; }
.event-form__field label {
  display: block;
  margin-bottom: 0.35rem;
  font-size: 0.75rem;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--color-cream-dim);
}
.event-form__field label span { text-transform: none; letter-spacing: 0; color: var(--color-cream-muted); }
.event-form__field input,
.event-form__field select,
.event-form__field textarea {
  width: 100%;
  padding: 0.7rem 0.85rem;
  font-family: var(--font-body);
  font-size: var(--text-sm);
  color: var(--color-cream);
  background: rgba(10, 18, 12, 0.6);
  border: 1px solid var(--color-border);
  border-radius: 0.6rem;
  transition: border-color 0.2s, box-shadow 0.2s;
}
.event-form__field input::placeholder,
.event-form__field textarea::placeholder { color: var(--color-cream-muted); }
.event-form__field input:focus,
.event-form__field select:focus,
.event-form__field textarea:focus {
  outline: none;
  border-color: var(--color-gold);
  box-shadow: 0 0 0 3px rgba(211, 173, 95, 0.18);
}
.event-form__field select option { background: var(--color-bg-card); color: var(--color-cream); }
.event-form__field textarea { resize: vertical; }
.event-form__submit {
  width: 100%;
  margin-top: 0.4rem;
  padding: 0.95rem 1.5rem;
  font-family: var(--font-body);
  font-size: var(--text-base);
  font-weight: 700;
  letter-spacing: 0.03em;
  color: #142016;
  background: linear-gradient(135deg, var(--color-gold-light), var(--color-gold) 55%, var(--color-gold-dark));
  border: none;
  border-radius: 0.7rem;
  cursor: pointer;
  transition: transform 0.2s cubic-bezier(0.16, 1, 0.3, 1), box-shadow 0.2s;
  will-change: transform;
}
.event-form__submit:hover { box-shadow: 0 16px 34px -12px rgba(211, 173, 95, 0.45); }
.event-form__submit:active { transform: scale(0.98); }
.event-form__note {
  margin-top: 0.7rem;
  font-size: 0.72rem;
  text-align: center;
  color: var(--color-cream-muted);
}
@media (max-width: 560px) {
  .event-form__row { grid-template-columns: 1fr; }
  .event-fab span { display: none; }
  .event-fab { padding: 1rem; border-radius: 50%; }
}

/* ============================================================
   SIGNATURE DISH STORY MODAL (full-screen overlay)
   ============================================================ */
.dish-modal {
  position: fixed;
  inset: 0;
  z-index: 1000;
  display: grid;
  place-items: center;
  padding: 1rem;
}
.dish-modal[hidden] { display: none; }
.dish-modal__backdrop {
  position: absolute;
  inset: 0;
  background: rgba(6, 11, 7, 0.86);
  backdrop-filter: blur(8px);
  animation: evFade 0.25s ease both;
}
.dish-modal__panel {
  position: relative;
  display: grid;
  grid-template-columns: 1.05fr 1fr;
  width: min(960px, 100%);
  max-height: min(88vh, 40rem);
  overflow: hidden;
  background: var(--color-bg-card);
  border: 1px solid var(--color-border);
  border-radius: 1.4rem;
  box-shadow: 0 50px 100px -30px rgba(0, 0, 0, 0.9);
  animation: evRise 0.4s cubic-bezier(0.16, 1, 0.3, 1) both;
}
.dish-modal__media { position: relative; min-height: 20rem; }
.dish-modal__media img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
}
.dish-modal__media::after {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(90deg, transparent 55%, var(--color-bg-card) 100%);
}
.dish-modal__body {
  padding: 2.5rem 2.25rem 2rem;
  overflow-y: auto;
  display: flex;
  flex-direction: column;
}
.dish-modal__cuisine {
  font-size: 0.7rem;
  font-weight: 700;
  letter-spacing: 0.24em;
  text-transform: uppercase;
  color: var(--color-gold);
}
.dish-modal__title {
  margin-top: 0.6rem;
  font-family: var(--font-display);
  font-size: clamp(1.7rem, 4vw, 2.4rem);
  font-weight: 500;
  line-height: 1.12;
  color: var(--color-cream);
}
.dish-modal__story {
  margin-top: 0.9rem;
  font-size: var(--text-base);
  line-height: 1.7;
  color: var(--color-cream-dim);
}
.dish-modal__ingredients {
  margin-top: 1.25rem;
  padding-top: 1.1rem;
  border-top: 1px solid var(--color-border-light);
}
.dish-modal__ingredients h3 {
  font-size: 0.72rem;
  font-weight: 700;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--color-cream-muted);
}
.dish-modal__ingredients ul {
  display: flex;
  flex-wrap: wrap;
  gap: 0.45rem;
  margin-top: 0.6rem;
  padding: 0;
  list-style: none;
}
.dish-modal__ingredients li {
  padding: 0.3rem 0.75rem;
  font-size: 0.75rem;
  color: var(--color-cream-dim);
  background: rgba(240, 231, 208, 0.05);
  border: 1px solid var(--color-border-light);
  border-radius: 999px;
}
.dish-modal__cta {
  margin-top: auto;
  padding-top: 1.5rem;
}
.dish-modal__cta a {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.85rem 1.6rem;
  font-weight: 700;
  font-size: var(--text-sm);
  letter-spacing: 0.04em;
  color: #142016;
  text-decoration: none;
  background: linear-gradient(135deg, var(--color-gold-light), var(--color-gold) 55%, var(--color-gold-dark));
  border-radius: 999px;
  transition: transform 0.2s cubic-bezier(0.16, 1, 0.3, 1), box-shadow 0.2s;
  will-change: transform;
}
.dish-modal__cta a:hover { transform: translateY(-2px); box-shadow: 0 14px 30px -10px rgba(211, 173, 95, 0.5); }
.dish-modal__cta svg { width: 15px; height: 15px; }
.dish-modal__close {
  position: absolute;
  top: 1rem;
  right: 1rem;
  z-index: 2;
  display: grid;
  place-items: center;
  width: 2.4rem;
  height: 2.4rem;
  color: var(--color-cream);
  background: rgba(10, 18, 12, 0.7);
  border: 1px solid var(--color-border);
  border-radius: 50%;
  cursor: pointer;
  transition: color 0.2s, border-color 0.2s, transform 0.25s;
}
.dish-modal__close svg { width: 15px; height: 15px; }
.dish-modal__close:hover { color: var(--color-gold-light); border-color: var(--color-gold); transform: rotate(90deg); }
.dish-card { cursor: pointer; }
.dish-card__hint {
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  font-size: 0.7rem;
  font-weight: 600;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--color-gold);
  opacity: 0;
  transform: translateY(4px);
  transition: opacity 0.3s, transform 0.3s;
}
.dish-card:hover .dish-card__hint,
.dish-card:focus-within .dish-card__hint { opacity: 1; transform: none; }
@media (max-width: 760px) {
  .dish-modal__panel { grid-template-columns: 1fr; max-height: 90vh; overflow-y: auto; }
  .dish-modal__media { min-height: 14rem; }
  .dish-modal__media::after { background: linear-gradient(180deg, transparent 40%, var(--color-bg-card) 100%); }
  .dish-modal__body { padding: 1.5rem 1.25rem; }
}

/* ============================================================
   HERO MOUSE PARALLAX
   ============================================================ */
.hero-welcome__photo[data-hero-drift] { will-change: transform; }

/* ==========================================================================
   Desktop hero emphasis (2026-09-11)
   The hero copy was tuned phone-first, so on a wide desktop it read as a small
   block adrift in a large photo. This layer scales the type and the copy
   column up from 768px, lifts them again from 1280px, gives the calls to
   action a heavier presence, and widens the content zone so the larger
   headline is not boxed in. Every rule sits inside a min-width query, so the
   mobile hero (<768px) is untouched.
   ========================================================================== */

@media (min-width: 768px) {
  .hero-welcome__label {
    font-size: 0.9375rem;
    letter-spacing: 0.24em;
    margin-bottom: 2rem;
  }

  .hero-welcome__title {
    font-size: clamp(3.15rem, 5vw, 4.9rem);
  }

  .hero-welcome__description {
    max-width: 38rem;
    margin-top: 2.25rem;
    font-size: 1.25rem;
    line-height: 1.7;
  }

  .hero-welcome__actions {
    gap: 1rem 2rem;
    margin-top: 3rem;
  }

  .hero-welcome__menu,
  .hero-welcome__order {
    min-height: 62px;
    padding: 1.05rem 2.15rem;
    font-size: 1.125rem;
  }

  .hero-welcome__copy {
    max-width: 50rem;
  }
}

/* Large desktop: let the headline carry the room. */
@media (min-width: 1280px) {
  .hero-welcome__inner {
    max-width: 1520px;
  }

  .hero-welcome__copy {
    max-width: 54rem;
  }

  .hero-welcome__label {
    font-size: 1.0625rem;
    margin-bottom: 2.25rem;
  }

  .hero-welcome__label::before {
    width: 3.25rem;
  }

  .hero-welcome__title {
    font-size: clamp(3.9rem, 4.9vw, 5.9rem);
  }

  .hero-welcome__line--gold {
    margin-top: 0.1em;
  }

  .hero-welcome__description {
    max-width: 42rem;
    margin-top: 2.5rem;
    font-size: 1.3125rem;
    line-height: 1.72;
  }

  .hero-welcome__actions {
    gap: 1.1rem 2.25rem;
    margin-top: 3.4rem;
  }

  .hero-welcome__menu,
  .hero-welcome__order {
    min-height: 66px;
    padding: 1.15rem 2.4rem;
    font-size: 1.1875rem;
  }
}

/* Keep the larger headline legible: extend the strong side of the scrim across
   the band it now crosses. Still one clean directional gradient, no haze. */
@media (min-width: 1024px) {
  .hero-welcome::before {
    background:
      linear-gradient(to top, rgba(16, 29, 20, 0.58) 0%, rgba(16, 29, 20, 0) 30%),
      linear-gradient(90deg, rgba(16, 29, 20, 0.93) 0%, rgba(16, 29, 20, 0.82) 44%, rgba(16, 29, 20, 0.42) 68%, rgba(16, 29, 20, 0.16) 86%, rgba(16, 29, 20, 0.05) 100%);
  }
}

/* ============================================================
   #KINGCHEFMOMENT HASHTAG STRIP
   ============================================================ */
.moment-strip {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: 0.75rem 1.5rem;
  margin-top: 2.25rem;
  padding: 1.1rem 1.5rem;
  text-align: center;
  background: linear-gradient(120deg, rgba(211, 173, 95, 0.09), rgba(211, 173, 95, 0.03));
  border: 1px dashed rgba(211, 173, 95, 0.4);
  border-radius: 1rem;
}
.moment-strip__tag {
  font-family: var(--font-display);
  font-style: italic;
  font-size: clamp(1.15rem, 3vw, 1.5rem);
  color: var(--color-gold-light);
  text-decoration: none;
}
.moment-strip__tag:hover { color: var(--color-gold); }
.moment-strip__text {
  font-size: var(--text-sm);
  color: var(--color-cream-dim);
}

@media (prefers-reduced-motion: reduce) {
  .event-fab,
  .event-modal__panel,
  .dish-modal__panel,
  .event-modal__backdrop,
  .dish-modal__backdrop { animation: none; }
}

/* ==========================================================================
   Report pass 2026-09-10: hero cinemagraph, ingredient-to-plate scene,
   split-text reveals, cursor glow, review rating line
   ========================================================================== */

/* --- Review rating line --- */
.review-stats {
  margin-top: 0.9rem;
  font-size: 0.95rem;
  color: var(--color-cream-dim, #b9c4b9);
}
.review-stats__stars {
  color: var(--color-gold);
  letter-spacing: 0.18em;
  margin-right: 0.45rem;
}
/* The listing average is 4.4, so the fifth star is filled only to the middle:
   the glyph is clipped to a two-stop gradient, gold to the midline and a faint
   cream beyond it, instead of pretending to a fifth full star. */
.review-stats__star-half {
  background: linear-gradient(90deg, var(--color-gold) 0 50%, rgba(240, 231, 208, 0.25) 50% 100%);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}
.review-stats strong { color: var(--color-cream); }

/* --- Hero cinemagraph: rising steam + ember flicker --- */
.hero-welcome__steam {
  position: absolute;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  overflow: hidden;
}
.hero-welcome__steam i {
  position: absolute;
  bottom: -18%;
  left: var(--x, 50%);
  width: clamp(90px, 14vw, 220px);
  height: clamp(160px, 30vh, 340px);
  background: radial-gradient(closest-side, rgba(255, 250, 235, 0.16), rgba(255, 250, 235, 0) 70%);
  filter: blur(18px);
  opacity: 0;
  animation: kc-steam-rise 9s linear infinite;
}
.hero-welcome__steam i:nth-child(1) { --x: 58%; }
.hero-welcome__steam i:nth-child(2) { --x: 70%; animation-delay: 3s; animation-duration: 11s; }
.hero-welcome__steam i:nth-child(3) { --x: 46%; animation-delay: 6s; animation-duration: 10s; }
@keyframes kc-steam-rise {
  0%   { opacity: 0; transform: translate3d(0, 6%, 0) scale(0.85, 0.8); }
  18%  { opacity: 0.55; }
  60%  { opacity: 0.32; transform: translate3d(-3%, -26%, 0) scale(1.05, 1.15); }
  100% { opacity: 0; transform: translate3d(4%, -58%, 0) scale(1.25, 1.45); }
}
.hero-welcome__ember {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: 34%;
  z-index: -1;
  pointer-events: none;
  background: radial-gradient(60% 90% at 62% 100%, rgba(214, 138, 46, 0.2), rgba(214, 138, 46, 0) 70%);
  animation: kc-ember-flicker 4.2s ease-in-out infinite;
}
@keyframes kc-ember-flicker {
  0%, 100% { opacity: 0.75; }
  22% { opacity: 0.95; }
  38% { opacity: 0.6; }
  55% { opacity: 0.9; }
  74% { opacity: 0.68; }
}

/* --- Ingredient-to-plate before/after slider --- */
.transform-scene__hint {
  margin-top: 0.8rem;
  font-size: 0.95rem;
  color: var(--color-cream-dim, #b9c4b9);
}
.ba-slider {
  --pos: 50%;
  position: relative;
  margin-top: clamp(1.5rem, 4vw, 2.5rem);
  aspect-ratio: 16 / 9;
  border-radius: 1.25rem;
  overflow: hidden;
  border: 1px solid rgba(211, 173, 95, 0.22);
  box-shadow: 0 30px 60px -30px rgba(0, 0, 0, 0.65);
  cursor: ew-resize;
  touch-action: pan-y;
  outline-offset: 4px;
  /* Dragging the handle must not select the tags or start a text selection. */
  user-select: none;
}
.ba-slider:focus-visible { outline: 2px solid var(--color-gold); }
.ba-slider__img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  /* CRITICAL: both layers are <img>, and images are natively draggable. Without
     this, pressing and dragging the handle starts a browser IMAGE DRAG, which
     fires pointercancel ~2 moves in and kills the gesture mid-drag (pointerup
     never arrives, so `dragging` is stuck until the next pointerdown). Measured
     with probe-drag.mjs: down=1 move=2 up=0 cancel=1 without it. The
     draggable="false" attributes in the template belt-and-brace this. */
  -webkit-user-drag: none;
  user-select: none;
}
.ba-slider__img--top { clip-path: inset(0 calc(100% - var(--pos)) 0 0); }
.ba-slider__line {
  position: absolute;
  top: 0;
  bottom: 0;
  left: var(--pos);
  width: 2px;
  margin-left: -1px;
  background: linear-gradient(to bottom, rgba(240, 231, 208, 0.15), rgba(240, 231, 208, 0.9), rgba(240, 231, 208, 0.15));
}
.ba-slider__knob {
  position: absolute;
  top: 50%;
  left: 50%;
  translate: -50% -50%;
  display: grid;
  place-items: center;
  width: 46px;
  height: 46px;
  border-radius: 50%;
  background: rgba(16, 29, 20, 0.82);
  border: 1px solid rgba(211, 173, 95, 0.65);
  color: var(--color-gold);
  backdrop-filter: blur(6px);
  /* Inner top highlight simulates the lit edge of real glass; the outer shadow
     lifts it off the photography. Together they read as a physical control
     sitting ON the image rather than a flat circle drawn over it. */
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.14),
    0 10px 24px -12px rgba(0, 0, 0, 0.8);
  /* `scale` carries hover/press, `transform` carries the scroll-velocity
     compression. They are separate properties on purpose: the individual
     translate/rotate/scale properties COMPOSE with transform instead of
     overwriting it, so the two scales multiply and never fight. */
  scale: 1;
  transform: scale(calc(1 - 0.08 * var(--press, 0)));
  will-change: transform;
  transition:
    scale 0.34s cubic-bezier(0.22, 1, 0.36, 1),
    border-color 0.3s ease,
    background-color 0.3s ease,
    box-shadow 0.3s ease;
}

/* Halo. Blooms out on hover and tightens while dragging, so the control
   acknowledges the pointer the way a physical knob would. It also inflates a
   little with scroll momentum, which is the only cue that the handle is being
   driven rather than sitting still. */
.ba-slider__knob::after {
  content: "";
  position: absolute;
  inset: -7px;
  border-radius: 50%;
  border: 1px solid rgba(211, 173, 95, 0.55);
  opacity: calc(0.6 * var(--press, 0));
  transform: scale(calc(0.88 + 0.12 * var(--press, 0)));
  pointer-events: none;
  transition:
    opacity 0.3s ease,
    transform 0.34s cubic-bezier(0.22, 1, 0.36, 1);
}
.ba-slider:hover .ba-slider__knob,
.ba-slider:focus-visible .ba-slider__knob { scale: 1.08; }
.ba-slider:hover .ba-slider__knob::after,
.ba-slider:focus-visible .ba-slider__knob::after { opacity: 0.7; transform: scale(1); }
.ba-slider:active .ba-slider__knob { scale: 0.94; }
.ba-slider:active .ba-slider__knob::after { opacity: 1; transform: scale(1.1); }
.ba-slider__knob svg { width: 20px; height: 20px; }
.ba-slider__tag {
  position: absolute;
  top: 1rem;
  padding: 0.35rem 0.7rem;
  border-radius: 999px;
  font-size: 0.72rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  background: rgba(16, 29, 20, 0.72);
  color: var(--color-cream);
  border: 1px solid rgba(240, 231, 208, 0.16);
  backdrop-filter: blur(4px);
}
.ba-slider__tag--before { left: 1rem; }
.ba-slider__tag--after { right: 1rem; }

/* --- Split-text word reveal --- */
.sr-word {
  display: inline-block;
  overflow: hidden;
  vertical-align: top;
}
.sr-word__in {
  display: inline-block;
  will-change: transform, filter;
}

/* --- Cursor-following ripple highlight on CTAs --- */
/* NOTE: .event-fab is deliberately NOT in this list. Its own rule sets
   position: fixed (bottom-right floating CTA); adding it here would give it
   position: relative at equal specificity LATER in the cascade, which drops
   it into the document flow AFTER the footer and opens a 45px blank strip
   below the page. position: fixed already establishes a positioned
   containing block, so the absolute ::before ripple works without it. */
.btn,
.hero-welcome__menu,
.hero-welcome__order { position: relative; }
.btn::before,
.hero-welcome__menu::before,
.hero-welcome__order::before,
.event-fab::before {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  pointer-events: none;
  background: radial-gradient(140px circle at var(--mx, 50%) var(--my, 50%), rgba(255, 255, 255, 0.22), transparent 62%);
  opacity: 0;
  transition: opacity 0.25s ease;
}
.btn:hover::before,
.hero-welcome__menu:hover::before,
.hero-welcome__order:hover::before,
.event-fab:hover::before,
.btn:focus-visible::before,
.hero-welcome__menu:focus-visible::before,
.hero-welcome__order:focus-visible::before,
.event-fab:focus-visible::before { opacity: 1; }

@media (max-width: 768px) {
  .ba-slider { aspect-ratio: 4 / 3; }
  .ba-slider__tag { top: 0.75rem; font-size: 0.65rem; }
}

@media (prefers-reduced-motion: reduce) {
  .hero-welcome__steam,
  .hero-welcome__ember { display: none; }
  .sr-word__in { transform: none !important; filter: none !important; opacity: 1 !important; }
  /* The scroll engine is skipped entirely under reduced motion, so the handle
     only moves on direct drag. Drop the hover/press springs with it. */
  .ba-slider__knob,
  .ba-slider__knob::after { transition: none; }
  .ba-slider__knob { transform: none !important; scale: 1 !important; }
  .ba-slider__knob::after { opacity: 0 !important; }
}

/* ============================================================
   Cards redesign (2026-09-10): one giant card + quiet minis
   per kingchef-cards-content-design-plan.md.
   Lead card earns its size and carries the only gold accent;
   mini cards hold a single sharp fact: plain border, no gold,
   no italic, sentence case.
   ============================================================ */

/* --- Section A: brand stats --- */
.course--story .bento--asym {
  grid-template-columns: 7fr 5fr;
  align-items: stretch;
}
.course--story .bento--asym .bento__cell--lead {
  border: 1px solid rgba(211, 173, 95, 0.14);
  border-top: 2px solid var(--color-gold);
  padding: clamp(2.2rem, 3.6vw, 3.4rem);
  justify-content: center;
}
.course--story .bento--asym .bento__cell--lead::before { content: none; }
.course--story .bento--asym .bento__cell--lead:hover {
  transform: none;
  border-color: rgba(211, 173, 95, 0.14);
  border-top-color: var(--color-gold);
}
.bento__lead-title {
  font-family: var(--font-display);
  font-size: clamp(2.1rem, 3.2vw, 3.1rem);
  font-weight: 600;
  line-height: 1.08;
  letter-spacing: -0.012em;
  color: var(--hero-ink);
}
.bento__cell-content .bento__lead-intro {
  margin-top: 1.15rem;
  font-family: var(--font-body);
  font-size: clamp(1.05rem, 1.25vw, 1.22rem);
  font-weight: 400;
  line-height: 1.75;
  color: var(--color-cream-dim);
  max-width: 44ch;
  text-wrap: pretty;
}
/* Pointed-out neighbourhoods (client 2026-09-17): each area reads as its own
   outlined tag instead of one comma-run sentence, so a visitor can find their
   area at a glance. Hairline pills, no markers, no numbering: numbering would
   read as a branch count, which the copy rules forbid. */
.bento__areas {
  display: flex;
  flex-wrap: wrap;
  gap: 0.55rem;
  margin: 1.5rem 0 0;
  padding: 0;
  list-style: none;
  max-width: 56ch;
}
.bento__area {
  padding: 0.5rem 1.05rem;
  border: 1px solid rgba(211, 173, 95, 0.28);
  border-radius: 999px;
  font-family: var(--font-body);
  font-size: 0.92rem;
  line-height: 1.3;
  color: var(--color-cream-dim);
  transition: border-color 0.25s ease, color 0.25s ease;
}
.bento__area:hover {
  border-color: var(--color-gold);
  color: var(--color-cream);
}
.bento__minis {
  display: grid;
  /* ONE heritage panel now fills the column (client 2026-09-17 removed the
     dish-count and Pasons minis), so a single full-height row replaces the
     three-row stack; repeat(3, 1fr) would leave two empty tracks. */
  grid-template-rows: 1fr;
  gap: 0.75rem;
  min-width: 0;
}
.course--story .bento--asym .bento__cell--mini {
  background: rgba(22, 38, 27, 0.55);
  border: 1px solid var(--color-border-light);
  padding: 1.45rem 1.7rem;
  justify-content: center;
}
.course--story .bento--asym .bento__cell--mini::before { content: none; }
.course--story .bento--asym .bento__cell--mini:hover {
  transform: none;
  border-color: rgba(236, 227, 205, 0.14);
}
.bento__mini-title {
  font-family: var(--font-display);
  font-size: 1.32rem;
  font-weight: 600;
  color: var(--color-cream);
  line-height: 1.2;
}
.bento__mini-body {
  margin-top: 0.35rem;
  font-family: var(--font-body);
  font-size: 0.95rem;
  line-height: 1.55;
  color: var(--color-cream-muted);
  text-wrap: pretty;
}
/* The heritage panel is the column's only card now, so it carries more copy:
   a larger year, reading-rhythm body, and a gap between its two paragraphs. */
.course--story .bento--asym .bento__mini-title {
  font-size: clamp(1.9rem, 2.5vw, 2.5rem);
  color: var(--color-gold);
}
.course--story .bento--asym .bento__mini-body {
  font-size: 1rem;
  line-height: 1.75;
}
.course--story .bento--asym .bento__mini-body + .bento__mini-body {
  margin-top: 0.8rem;
}

/* --- Section B: cuisines ---
   Four uniform cards on a mirrored 7/5, 5/7 span rhythm. No giant panel:
   every card carries the same anatomy (hairline marker, title, one line of
   copy, pinned footer). The grid is content-sized so a card is only as tall
   as its copy, and the hairline turning gold on hover is the section's one
   accent. */
.course--cuisines .cuisine-grid--asym {
  grid-template-columns: repeat(12, 1fr);
  grid-auto-rows: auto;
  align-content: start;
  gap: 1rem;
  flex: 0 0 auto; /* content-sized: never stretched to fill the 100svh course */
  /* The legacy `.course--cuisines .cuisine-grid` rule below still pins this grid
     to min(62vh, 38rem). With align-content:start that left the extra height as
     dead space under the last row, which stacked on top of the section's own
     padding and read as a second empty band. Zeroing it makes the grid hug its
     rows, which is what "content-sized" above actually promises. */
  min-height: 0;
}
.course--cuisines .cuisine-grid--asym > .cuisine-item:nth-child(1),
.course--cuisines .cuisine-grid--asym > .cuisine-item:nth-child(4) {
  grid-column: span 7;
}
.course--cuisines .cuisine-grid--asym > .cuisine-item:nth-child(2),
.course--cuisines .cuisine-grid--asym > .cuisine-item:nth-child(3) {
  grid-column: span 5;
}

/* Card shell */
.course--cuisines .cuisine-grid--asym .cuisine-item {
  position: relative;
  display: flex;
  flex-direction: column;
  min-height: 0;
  overflow: hidden;
  padding: clamp(1.5rem, 2vw, 1.9rem);
  background: rgba(18, 26, 18, 0.5);
  border: 1px solid var(--color-border-light);
  border-radius: 18px;
  transition: border-color 0.35s ease, background-color 0.35s ease;
}
.course--cuisines .cuisine-grid--asym .cuisine-item::after { content: none; }
.course--cuisines .cuisine-grid--asym .cuisine-item:hover {
  transform: none;
  box-shadow: none;
  border-color: rgba(236, 227, 205, 0.16);
  background-color: rgba(23, 37, 26, 0.62);
}
.course--cuisines .cuisine-grid--asym .cuisine-item:focus-within {
  border-color: rgba(211, 173, 95, 0.42);
}

/* Card body: hairline marker, title, one line of copy */
.course--cuisines .cuisine-grid--asym .cuisine-item__body {
  display: flex;
  flex-direction: column;
  gap: 0.6rem;
  flex: 1 1 auto;
  margin-top: 0;
  padding: 0;
  background: none;
}
.course--cuisines .cuisine-grid--asym .cuisine-item__body::before {
  content: "";
  display: block;
  flex: 0 0 auto;
  width: 2.4rem;
  height: 2px;
  border-radius: 2px;
  background: rgba(236, 227, 205, 0.22);
  transition: width 0.5s var(--ease-out), background-color 0.35s ease;
}
.course--cuisines .cuisine-grid--asym .cuisine-item:hover .cuisine-item__body::before,
.course--cuisines .cuisine-grid--asym .cuisine-item:focus-within .cuisine-item__body::before {
  width: 5.2rem;
  background: var(--color-gold);
}
.course--cuisines .cuisine-grid--asym .cuisine-item__body h3 {
  font-size: clamp(1.4rem, 1.7vw, 1.75rem);
  padding-bottom: 0;
  text-shadow: none;
}
.course--cuisines .cuisine-grid--asym .cuisine-item__body h3::after { content: none; }
.course--cuisines .cuisine-grid--asym .cuisine-item__body p {
  flex: 0 0 auto;
  font-size: 1rem;
  line-height: 1.6;
  max-width: 62ch;
  color: var(--color-cream-dim);
  text-shadow: none;
}

/* Pinned footer: signature left, arrow right */
.cuisine-item__foot {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  margin-top: auto;
  padding-top: 1rem;
  border-top: 1px solid rgba(236, 227, 205, 0.08);
}
.course--cuisines .cuisine-grid--asym .cuisine-item__sig {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0.5rem;
  margin: 0;
  font-family: var(--font-display);
  font-style: italic;
  font-size: 0.95rem;
  color: var(--color-cream);
  text-shadow: none;
}
.cuisine-item__sig-label {
  font-family: var(--font-body);
  font-style: normal;
  font-size: 0.7rem;
  letter-spacing: 0.02em;
  color: var(--color-cream-muted);
}
.cuisine-item__cue {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: 0 0 auto;
  width: 1.85rem;
  height: 1.85rem;
  border: 1px solid rgba(236, 227, 205, 0.14);
  border-radius: 50%;
  color: var(--color-cream-dim);
  transition: color 0.3s ease, background-color 0.3s ease, border-color 0.3s ease,
    transform 0.35s var(--ease-out);
}
.cuisine-item__cue svg {
  width: 0.85rem;
  height: 0.85rem;
}
.course--cuisines .cuisine-grid--asym .cuisine-item:hover .cuisine-item__cue,
.course--cuisines .cuisine-grid--asym .cuisine-item:focus-within .cuisine-item__cue {
  color: #0f1a12;
  background: var(--color-gold);
  border-color: var(--color-gold);
  transform: translateX(2px);
}

/* The whole card is one click target: the title link stretches over it */
.cuisine-item__title-link {
  color: inherit;
  text-decoration: none;
}
.cuisine-item__title-link::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
}

/* --- Mobile: one card per row, story minis single column --- */
@media (max-width: 900px) {
  .course--story .bento--asym,
  .course--cuisines .cuisine-grid--asym {
    grid-template-columns: 1fr;
  }
  /* Stacked single column: the desktop `grid-auto-rows: minmax(0, 1fr)` carried
     over, so the 1fr rows made the lead card exactly as tall as the three-mini
     stack below it. The lead only holds one heading and one paragraph, so that
     left ~150px of empty space under its copy. Content-sized rows remove it. */
  .course--story .bento--asym {
    grid-auto-rows: auto;
  }
  /* Reset the 7/5 spans; nth-child(n) outranks the span rules above */
  .course--cuisines .cuisine-grid--asym > .cuisine-item:nth-child(n) {
    grid-column: auto;
    min-height: 0;
  }
  .bento__minis {
    grid-template-rows: none;
    grid-auto-rows: 1fr;
  }
}

/* ============================================================
   CARD SPOTLIGHT (interaction plan, Phase 1)
   A soft gold radial follows the pointer across the content
   cards. `initCursorGlow` in main.js writes --mx / --my in px; it
   already did that for the CTAs, and Phase 1 widens its selector
   list to these four cards.

   rgba(211, 173, 95, 0.10) is --color-gold at 10% alpha, so no new
   colour enters the approved palette. The whole block sits behind
   (hover: hover) and (pointer: fine), which mirrors the
   `matchMedia('(hover: none)')` bail in initCursorGlow: a touch
   device never pays for a pointer effect it cannot use.

   .insta-card is deliberately NOT in the list. It already runs the
   richest hover on the page (lift, a 0.9s image zoom, and a dark
   overlay at z-index 1), so a radial painted underneath would be
   hidden by its own overlay.

   The radial is a ::before, not a ::after, and that is load-bearing.
   .course--cuisines .cuisine-item::after already exists: it is the
   hairline border overlay at z-index 4, and line 7034 switches it
   off inside .cuisine-grid--asym with `content: none` at (0,3,0).
   A spotlight drawn on ::after would inherit that `content: none`
   and never render on the very grid this phase exists to light up.
   ::before is unused on all four cards, so there is no collision.

   z-index: -1 puts the glow behind the card's own copy, so it can
   never wash over text or move a contrast reading. That only works
   if the card paints its background first, hence isolation: isolate,
   which makes the card its own stacking context. Without it the
   glow would fall behind the card's opaque background and vanish.
   ============================================================ */
@media (hover: hover) and (pointer: fine) {
  .cuisine-item,
  .location-card,
  .catering-item,
  .review-card {
    position: relative;
    isolation: isolate;
  }

  .cuisine-item::before,
  .location-card::before,
  .catering-item::before,
  .review-card::before {
    content: "";
    position: absolute;
    inset: 0;
    z-index: -1;
    border-radius: inherit;
    pointer-events: none;
    opacity: 0;
    transition: opacity 320ms var(--ease-out);
    background: radial-gradient(
      260px circle at var(--mx, 50%) var(--my, 50%),
      rgba(211, 173, 95, 0.1),
      transparent 68%
    );
  }

  .cuisine-item:hover::before,
  .location-card:hover::before,
  .catering-item:hover::before,
  .review-card:hover::before {
    opacity: 1;
  }
}

/* ============================================================
   SCROLL SPINE (interaction-plan Phase 4)
   A 2px gold hairline across the top of the viewport, filling from
   the left as the page is read, so a long page has a position
   indicator. Created in main.js, so no page's markup changed.

   WHY z-index IS NOT 60, AS THE PLAN FIRST SPECIFIED:
   the plan proposed `z-index: 60`. Measured, that leaves the spine
   INVISIBLE on every page. `.header` is `position: sticky; top: 0`
   with an opaque `background-color` and `z-index: var(--z-header)`
   (100), so it owns the viewport's top 2px in every scroll state,
   and 60 paints the spine underneath it. The spine therefore sits
   at `calc(var(--z-header) + 1)`: over the header it is pinned to,
   under the mobile nav (200) and the two dialogs (1000), so the
   menu and the modals still cover it.

   `pointer-events: none` because it spans the full width and must
   never eat a click. No `will-change`: a fixed element with a
   constant transform is already composited, and a permanent hint
   would pin a layer alive for the life of the page.
   ============================================================ */
.scroll-spine {
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: 2px;
  z-index: calc(var(--z-header) + 1);
  transform: scaleX(var(--progress, 0));
  transform-origin: left center;
  background: linear-gradient(90deg, var(--color-gold-dark), var(--color-gold-light));
  pointer-events: none;
}

@media (prefers-reduced-motion: reduce) {
  /* main.js does not create the element under reduced motion, so this only
     covers the case where one is already in the document. */
  .scroll-spine { display: none; }
}

/* Map redesign: the UAE is the composition, not a wide empty Leaflet widget. */
.map-container {
  /* A BAND, NOT A BOX: FULL WIDTH AND A SHORT HEIGHT, LIKE THE CARDS ABOVE IT.
     Client, on the tall centred plate (2026-09-17): "min_height its reduce top
     to bottom then left to right horizontal fully utilize it ... card have its
     above have card full width take it". So the 900px cap and the country-shaped
     aspect are both gone: the plate takes the container's whole width and a
     viewport-scaled height.

     A wide band makes the fit HEIGHT-limited, which leaves a bare field down
     each side of whatever is fitted. THAT FIELD IS DELIBERATE, NOT A GAP TO
     FILL. The five offshore emirate names and their dashed leaders that used to
     occupy it were removed at the client's request (the leader lines on
     2026-09-18, the name-and-dot layer on 2026-09-21), and the branch index
     beside the plate is what carries the names now. Do not re-fill the side
     fields with a label layer.

     map.js fits with `padding: [18, 18]` plus a geographic pad of its own, and
     since 2026-09-21 it fits the BRANCH CLUSTER on load and the country only
     when the branch row's toggle is used, so at rest the bare side field is the
     normal look rather than an accident. The phone plate opts out of the height
     clamp in the 640px block below and keeps the country's own aspect instead. */
  width: 100%;
  max-width: none;
  height: clamp(300px, 40vh, 460px);
  aspect-ratio: auto;
  min-height: 0;
  margin-inline: auto;
  background: var(--color-map-void, #081a11);
  border: 1px solid rgba(211, 173, 95, 0.32);
  border-radius: var(--radius-lg);
  overflow: hidden;
  --map-label-px: 10px;
}

.map-container .leaflet-map-pane,
.map-container .leaflet-overlay-pane,
.map-container .leaflet-marker-pane {
  z-index: auto;
}

.map-container .leaflet-overlay-pane path.map-land {
  fill: url(#kc-uae-dots);
  stroke: rgba(211, 173, 95, 0.65);
  stroke-width: 1.4;
  animation: none;
}

.map-container .leaflet-overlay-pane path.map-leader {
  fill: none;
  stroke: rgba(211, 173, 95, 0.58);
  stroke-width: 1;
  stroke-dasharray: 2 5;
  stroke-linecap: round;
  opacity: 0;
  transition: opacity 300ms var(--ease-out);
}

.map-container.is-drawn .leaflet-overlay-pane path.map-leader {
  opacity: 1;
  transition-delay: calc(var(--i, 0) * 55ms);
}

.map-container .map-hub-label {
  display: none;
}

.map-container .leaflet-tooltip.map-label {
  padding: 0;
  border: 0;
  background: transparent;
  box-shadow: none;
  color: var(--color-gold-light);
  font-family: var(--font-display);
  font-size: var(--map-label-px);
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  white-space: nowrap;
  text-shadow: 0 1px 3px rgba(4, 12, 8, 0.95), 0 0 9px rgba(4, 12, 8, 0.9);
}

.map-container .leaflet-tooltip.map-label::before {
  display: none;
}

.map-container .leaflet-marker-pane .custom-marker {
  display: block;
  opacity: 0;
  transform: none;
  transition: opacity 420ms var(--ease-out);
}

.map-container.is-drawn .leaflet-marker-pane .custom-marker {
  opacity: 1;
  transition-delay: calc(var(--i, 0) * 55ms);
}

.map-container .custom-marker__dot {
  /* 4px on a 22px pin leaves a 14px dot. The old 5px was set against the fan's
     32px pin, and on a 22px one it left a dot too small to carry the ring. */
  inset: 4px;
  background: var(--color-gold);
  border: 2px solid var(--color-bg-hero);
  box-shadow: 0 0 0 2px var(--color-gold-light), 0 3px 9px rgba(10, 18, 12, 0.72);
  transition: transform 180ms var(--ease-out), background-color 180ms var(--ease-out);
}

.map-container .custom-marker:hover .custom-marker__dot,
.map-container .custom-marker:focus .custom-marker__dot,
.map-container .custom-marker.is-selected .custom-marker__dot {
  background: var(--color-gold-light);
  transform: scale(1.18);
}

.map-container .custom-marker:focus-visible {
  outline: 2px solid var(--color-cream);
  outline-offset: 5px;
  border-radius: 50%;
}

.map-container .leaflet-popup-content-wrapper {
  background: var(--color-bg-card);
  border: 1px solid rgba(211, 173, 95, 0.48);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-md);
}

.map-container .leaflet-popup-content {
  margin: 1rem 1.1rem;
  color: var(--color-cream);
  font-size: var(--text-sm);
}

.map-container .leaflet-popup-content h4 {
  margin: 0 0 0.45rem;
  padding-right: 1.2rem;
  color: var(--color-gold-light);
  font-size: var(--text-lg);
}

.map-container .leaflet-popup-content p {
  margin: 0.2rem 0;
  color: var(--color-cream-dim);
}

.map-container .leaflet-popup-content a {
  margin-top: 0.65rem;
  color: var(--color-gold-light);
}

.map-container .leaflet-popup-tip {
  background: var(--color-bg-card);
  border: 1px solid rgba(211, 173, 95, 0.48);
}

/* The pin's lit state. Reached from the name row below the plate, and the only
   reliable way to pick out the two branches whose pins nearly touch. */
.map-container .custom-marker.is-hot .custom-marker__dot {
  background: var(--color-gold-light);
  transform: scale(1.35);
}

.map-container .leaflet-tooltip.map-label.is-hot {
  color: #fff;
}

/* --- The emirate name-and-dot layer: REMOVED 2026-09-21 ---
   Client, on the rendered plate: "abudhabi & dubai like mentioned emirates
   name & dot need remove". The marker rules, their ping keyframes and their
   label-size token that lived here were deleted together with the map.js
   layer that drew them, on both pages at once (the builder is shared, same
   precedent as the zoom control and the leader lines). This comment names
   them in PROSE only: the gates assert the class strings are absent from this
   file, and a comment quoting them would read as the rules coming back.
   Do not re-add a name or dot layer over the land: the branch index beside
   the plate and the popups are the map's only labels now. What survives of
   the emirate hierarchy is the land rule below. */

/* Dubai's coast, one step brighter and carrying a warm glow, so the emirate with
   the restaurants in it is found before it is read. `drop-shadow` on the path
   rather than a second overlay: the fill is a paint server, so there is no
   background colour here to lighten. */
.map-container .leaflet-overlay-pane path.map-land--home {
  stroke: rgba(233, 201, 133, 0.92);
  stroke-width: 1.8;
  filter: drop-shadow(0 0 7px rgba(211, 173, 95, 0.38));
}

/* THE COUNTRY VIEW: pins become dots and the branch names go away.

   All eight branches are inside ~30px of each other at this zoom, so eight
   permanent labels there is a smudge rather than a label layer, and eight 22px
   pins is one gold blob. Shrinking the DOT (not the pin element) keeps the 22px
   hit area for touch while making the eight separable, and the names move to the
   row under the map, which lists all eight unambiguously.

   `visibility`, not `opacity`: Leaflet writes an inline `opacity: 1` on every
   tooltip, and an inline declaration beats a stylesheet one, so an opacity rule
   here would be silently ignored. Nothing sets visibility inline. */
.map-container.is-country .custom-marker__dot {
  inset: 7px;
  border-width: 1.5px;
  box-shadow: 0 0 0 1.5px var(--color-gold-light), 0 2px 6px rgba(10, 18, 12, 0.7);
}

.map-container:not(.is-zoomed) .leaflet-tooltip.map-label {
  visibility: hidden;
}

.map-container.is-zoomed .leaflet-tooltip.map-label {
  visibility: visible;
}

/* Hovering or focusing a name in the row lights its pin. At the country view the
   dot is 8px, so it also grows back to full size, which is the only feedback
   that survives the shrink. */
.map-container.is-country .custom-marker.is-hot .custom-marker__dot {
  inset: 4px;
}

/* --- Branch name row, built by static/js/map.js from BRANCHES --- */
.map-branches {
  list-style: none;
  margin: var(--space-sm) auto 0;
  padding: 0;
  width: min(100%, 900px);
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 0.5rem;
}

/* --- The homepage band: map left, branch index right (2026-09-18) ---

   Client: "left hand side map right hand side list it this ul vertically top
   to bottom". The wrapper lives in templates/index.html only; map.js inserts
   ul.map-branches afterend of the map div, so inside the wrapper it becomes
   the second grid child with no JS change. /locations/ has no wrapper and
   keeps the centred pill row above, which is why every rule here is scoped
   to .map-split instead of restyling .map-branches globally.

   The list column is a clamp, not an fr ratio: the names are one line each
   and the column should never grow past what the longest one needs, while
   the map takes every pixel left over. `align-items: stretch` plus
   `justify-content: space-between` lets the eight rows fill the plate's
   height, so the column reads as an index beside the map rather than a
   short stack pinned to its top edge. */
.map-split {
  display: grid;
  grid-template-columns: minmax(0, 1fr) clamp(220px, 24%, 320px);
  gap: clamp(1rem, 2vw, 1.75rem);
  align-items: stretch;
}

/* THE INDEX IS ONE PANEL, NOT EIGHT PILLS (design pass, 2026-09-18).
   Eight identical full-round pills in a column read as a tag cloud: the radius
   rounds both ends of a FULL-WIDTH row, every branch looks equally important,
   and nothing separates the list from the band behind it. It is now a single
   hairline panel one step darker than the band, the same surface the
   statement-band cards use, with the branches divided into rows.

   THE RING IS THE PANEL'S EDGE, NOT A BORDER, on purpose: a 1px border would
   narrow the content box by 2px and `.cortex/probe-map-split.mjs` asserts each
   row is the full width of the list column within a 2px tolerance, so a border
   would sit exactly ON the tolerance. A 1px shadow ring draws the same hairline
   without touching layout. */
.map-split .map-branches {
  width: auto;
  margin: 0;
  flex-direction: column;
  flex-wrap: nowrap;
  align-items: stretch;
  justify-content: flex-start;
  gap: 0;
  background: var(--color-bg-card);
  border-radius: var(--radius-lg);
  box-shadow: 0 0 0 1px var(--color-border),
              inset 0 1px 0 var(--color-border-light);
  overflow: hidden;
}

.map-split .map-branches > li {
  display: flex;
  min-height: 0;
}

.map-split .map-branch {
  position: relative;
  flex: 1 1 auto;
  width: 100%;
  justify-content: flex-start;
  gap: 0.65rem;
  padding: 0.55rem clamp(0.7rem, 1vw, 1rem);
  border: 0;
  border-radius: 0;
  background: transparent;
  text-align: left;
  font-size: 0.82rem;
}

/* The row divider. Only rows after the first carry it, so the panel's own ring
   is the top edge and there is never a doubled hairline. */
.map-split .map-branches > li + li .map-branch {
  border-top: 1px solid rgba(211, 173, 95, 0.1);
}

/* THE GOLD RAIL is the row's state light: it grows from the middle of the left
   edge under the pointer and STAYS lit on the branch the map is focused on,
   which is the one piece of feedback a branch index needs and the pills had
   none of at all. `transform-origin: center` is what grows it from the middle
   instead of wiping it down from the top. */
.map-split .map-branch::before {
  content: '';
  position: absolute;
  left: 0;
  top: 0;
  bottom: 0;
  width: 2px;
  background: var(--color-gold);
  transform: scaleY(0);
  transform-origin: center;
  transition: transform 320ms var(--ease-out);
}

/* A chevron drawn from two borders, so no icon font or SVG is needed. It holds
   the row's right edge and leans forward under the pointer. */
.map-split .map-branch::after {
  content: '';
  flex: none;
  margin-left: auto;
  width: 6px;
  height: 6px;
  border-top: 1.5px solid currentColor;
  border-right: 1.5px solid currentColor;
  transform: rotate(45deg);
  opacity: 0.38;
  transition: transform 320ms var(--ease-out), opacity 320ms var(--ease-out);
}

/* THE PIN ECHOES THE MAP. Every marker on the plate, emirate and branch alike,
   is a ring around a core, so the index uses the same shape: a hollow gold ring
   that fills solid when the row is hovered or held. */
.map-split .map-branch__pin {
  flex: none;
  width: 9px;
  height: 9px;
  background: transparent;
  border: 1.5px solid var(--color-gold);
  box-shadow: none;
  transition: background-color 260ms var(--ease-out),
              box-shadow 260ms var(--ease-out);
}

/* HOVER AND FOCUS DO NOT FLIP TO A SOLID GOLD BLOCK. The base pill inverts
   (gold fill, dark text), which is right for one pill among many on a line and
   far too loud for a row inside a panel: it would flash a full-width gold bar.
   The row takes a warm wash instead, the name brightens, the ring fills and the
   chevron leans in. These rules outrank the base ones on specificity, so the
   base pill stays exactly as it is for the /locations/ row. */
.map-split .map-branch:hover,
.map-split .map-branch:focus-visible {
  background: rgba(211, 173, 95, 0.075);
  color: var(--color-gold-light);
}

.map-split .map-branch:hover .map-branch__pin,
.map-split .map-branch:focus-visible .map-branch__pin,
.map-split .map-branch.is-active .map-branch__pin {
  background: var(--color-gold);
  box-shadow: 0 0 0 3px rgba(211, 173, 95, 0.16);
}

.map-split .map-branch:hover::before,
.map-split .map-branch:focus-visible::before,
.map-split .map-branch.is-active::before {
  transform: scaleY(1);
}

.map-split .map-branch:hover::after,
.map-split .map-branch:focus-visible::after {
  opacity: 0.9;
  transform: rotate(45deg) translate(2px, -2px);
}

/* The branch the map is focused on: rail lit, warm tint held, chevron still
   leaning, so "press again to return to the whole country" is legible before
   the press rather than being a surprise. */
.map-split .map-branch.is-active {
  background: linear-gradient(90deg,
              rgba(211, 173, 95, 0.13),
              rgba(211, 173, 95, 0.035));
  color: var(--color-gold-light);
}

.map-split .map-branch.is-active::after {
  opacity: 0.85;
}

/* FOCUS RING, INSET. The panel clips its children, so the base rule's
   `outline-offset: 2px` would be cut away on the rows at the panel's edge and a
   keyboard user would lose the ring exactly where it is hardest to see. A
   negative offset draws it inside the row instead. */
.map-split .map-branch:focus-visible {
  outline: 2px solid var(--color-gold-light);
  outline-offset: -3px;
}

/* On the wide layout the rows SHARE the plate's height rather than sitting at
   their natural height with the slack pushed into the gaps, so the panel reads
   as a full index. `flex-basis: 0` is only safe while the ul has a definite
   height, which is exactly what the grid's `align-items: stretch` gives it, and
   the min-width keeps this out of the stacked layout where the column's height
   is auto and the same rule would collapse every row to nothing. */
@media (min-width: 901px) {
  .map-split .map-branches > li {
    flex: 1 1 0;
  }
}

/* Under 900px the plate needs the whole width again, so the band stacks:
   map on top, the same index panel under it. The rows keep their natural height
   here, because the column's height is no longer definite. */
@media (max-width: 900px) {
  .map-split {
    grid-template-columns: 1fr;
  }

  .map-split .map-branches {
    justify-content: flex-start;
  }
}

.map-branch {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  padding: 0.45rem 0.9rem;
  border-radius: 999px;
  border: 1px solid var(--color-border);
  background: var(--color-bg-card);
  color: var(--color-gold-light);
  font-family: var(--font-display);
  font-size: 0.78rem;
  font-weight: 600;
  letter-spacing: 0.03em;
  cursor: pointer;
  transition: background-color 180ms var(--ease-out), color 180ms var(--ease-out),
              border-color 180ms var(--ease-out);
}

.map-branch__pin {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--color-gold);
  box-shadow: 0 0 0 2px rgba(211, 173, 95, 0.22);
}

.map-branch:hover,
.map-branch:focus-visible {
  background: var(--color-gold);
  border-color: var(--color-gold);
  color: var(--color-bg-hero);
}

.map-branch:hover .map-branch__pin,
.map-branch:focus-visible .map-branch__pin {
  background: var(--color-bg-hero);
  box-shadow: 0 0 0 2px rgba(10, 18, 12, 0.25);
}

/* THE SELECTED PILL KEEPS THE HOVER LOOK. map.js marks the focused branch on
   BOTH maps, and on this page the row is a pill, so without this the press would
   carry a state that nothing shows. It is the same surface the hover uses,
   which is what makes "held" read as "still selected" rather than as a new
   state that needs learning. The homepage panel's own `.map-split` rules are
   more specific and keep their wash, so this cannot reach them. */
.map-branch.is-active {
  background: var(--color-gold);
  border-color: var(--color-gold);
  color: var(--color-bg-hero);
}

.map-branch.is-active .map-branch__pin {
  background: var(--color-bg-hero);
  box-shadow: 0 0 0 2px rgba(10, 18, 12, 0.25);
}

.map-branch:focus-visible {
  outline: 2px solid var(--color-gold-light);
  outline-offset: 2px;
}

@media (prefers-reduced-motion: reduce) {
  .map-branch { transition: none; }
  /* The index panel's own motion, which the plain .map-branch rule above cannot
     reach: the pseudo-elements and the pin carry their own transitions. */
  .map-split .map-branch::before,
  .map-split .map-branch::after,
  .map-split .map-branch__pin { transition: none; }
}

@media (max-width: 640px) {
  .map-container {
    width: 100%;
    /* The band's height clamp above must not freeze the phone plate: at 390px
       there is no side field to fill, so the plate keeps the country's own
       aspect and `height` goes back to auto for the ratio below to decide. */
    height: auto;
    /* Same solve as the desktop ratio above, at a ~350px plate. map.js fits with
       `padding: [18, 18]`, so the box the bounds go into is (W - 36) x (H - 36)
       and it has to be 1.275:1, the Mercator aspect of the seven emirates plus
       the five offshore names. At W = 350: H = 36 + 314 / 1.275 = 282, so the
       plate is 1.24:1. The 36px of gutter is a much bigger share of a small
       plate than of the 900px one, which is why this is not the desktop 1.26. */
    aspect-ratio: 1.24 / 1;
    border-radius: var(--radius-md);

    /* The label size steps down here, and it has to be set in THIS block:
       the `.map-container` rule above at ~8300 comes later in the file than the
       768px block that also sets `--map-label-px`, and at equal specificity
       source order wins, so a media query placed earlier in the file is simply
       ignored. That is why the phone used to render 10px branch labels. */
    --map-label-px: 9px;
  }

  .map-container .leaflet-popup-content {
    margin: 0.8rem 0.9rem;
  }

  .map-branch {
    padding: 0.4rem 0.7rem;
    font-size: 0.72rem;
  }

  /* The index panel carries its own row padding and type size at a higher
     specificity than the rule above, so the phone step-down has to be repeated
     for it or the small screen would keep the desktop row metrics. */
  .map-split .map-branch {
    padding: 0.5rem 0.7rem;
    font-size: 0.75rem;
  }
}

@media (prefers-reduced-motion: reduce) {
  .map-container .leaflet-marker-pane .custom-marker,
  .map-container.is-drawn .leaflet-marker-pane .custom-marker {
    transition: none;
    transform: none;
  }
}

/* ============================================================================
   SMALL-SCREEN DRAWER (<= 1279px)
   ----------------------------------------------------------------------------
   Client (2026-09-18): "in small screen navbar nav list ul need improve design
   more responsive design need", with a screenshot of the open menu.

   WHAT WAS WRONG, measured with `.cortex/probe-mobile-nav.mjs` before this
   section existed, at 320, 360, 390, 414, 768 and 1024 wide:
     - the open menu was 171px wide at EVERY one of those widths, so a slab sat
       over the left fifth of the screen with the page still showing beside it.
       The cause was one declaration: `.nav__list { width: max-content }` from
       the desktop rule, which neither mobile block reset. On a fixed box
       `width` beats `left: 0; right: 0`, so it resolved to the widest label,
       "LOCATIONS".
     - the links were centred inside that 171px, so their boxes measured 89px
       to 171px and were raggedly aligned, with "LOCATIONS" touching both edges.
     - `justify-content: center` plus a 4rem gap left the list floating in the
       middle of the screen with a large void above and below it.
     - the toggle was a 38x32px tap target, under the 44px minimum the rest of
       the site keeps.

   THE DESIGN. One full-bleed plate in the header's own colour, so the brand bar
   and the list read as a single surface with the bar's existing hairline doing
   the dividing. Below it, label rows the full width of the screen, separated by
   a gold hairline, each with the ring pin and chevron borrowed from the branch
   index panel beside the map, so the drawer and the map read as one material.
   Rows stretch to fill the drawer, which is what removes the floating
   mid-screen block. The hamburger crosses into an X in place, so the way out is
   the control you already pressed, where you left it.

   SCOPE. This is the ONLY place the drawer is styled. The two legacy mobile
   blocks above keep just the `display` switch and the toggle's presence.
   ========================================================================= */
@media (max-width: 1279px) {
  .nav__list {
    display: none;

    /* `inset: 0` alone is not enough: a `width` in a same-specificity rule
       outranks it, which is exactly how this drawer became a 171px slab.
       Both axes are pinned here, explicitly. */
    position: fixed;
    inset: 0;
    width: auto;
    max-width: none;
    height: 100dvh;
    margin: 0;

    /* The brand bar floats above the drawer, so the list starts below it.
       `--header-h` is published by main.js from the header's real height, so
       the two can never disagree. The bottom term keeps the last row clear of
       a notched phone's home indicator. */
    padding: calc(var(--header-h, 97px) + clamp(0.5rem, 2vh, 1.25rem))
      clamp(1.25rem, 6vw, 2.5rem)
      calc(clamp(1rem, 4vh, 2rem) + env(safe-area-inset-bottom, 0px));

    flex-direction: column;
    align-items: stretch;
    justify-content: flex-start;
    flex-wrap: nowrap;
    gap: 0;

    background-color: var(--color-bg);
    overflow-y: auto;
    overflow-x: hidden;
    /* Scrolling the list must never chain through to the page behind it. */
    overscroll-behavior: contain;
    -webkit-overflow-scrolling: touch;
    z-index: var(--z-mobile-nav);
  }

  .nav__list--open {
    display: flex;
  }

  /* --- One divided panel of rows ---------------------------------------- */
  .nav__list li {
    display: flex;

    /* Rows share the drawer's height rather than huddling at natural size with
       the slack stranded below them. `min-height` is the floor: past it the
       drawer scrolls instead of squeezing a row under the touch minimum. */
    flex: 1 1 auto;
    min-height: 56px;

    border-top: 1px solid rgba(211, 173, 95, 0.12);
  }

  /* Seven dividers, never eight: the first row's top edge would otherwise
     double the header's own border-bottom into a 2px line. */
  .nav__list li:first-child {
    border-top: 0;
  }

  .nav__link {
    display: flex;
    align-items: center;
    gap: clamp(0.75rem, 3vw, 1.15rem);
    width: 100%;
    height: 100%;
    padding: 0.5rem 0.15rem;

    /* Sized off the VIEWPORT, not fixed: 22px on a 320px phone, 32px on a
       tablet, and nothing in between that overflows or shrinks away. */
    font-size: clamp(1.3rem, 4.6vw, 1.9rem);
    font-weight: 700;
    letter-spacing: 0.12em;
    text-transform: uppercase;
    line-height: 1.15;

    /* The desktop rule is `white-space: nowrap`, which is what would push a
       long label past the screen edge on a small phone. It must wrap. */
    white-space: normal;
    text-align: left;

    color: var(--color-cream);
    transition: color var(--transition-fast), background-color var(--transition-fast);
  }

  /* The ring pin echoes the emirate markers on the map and the pin in the
     branch index panel: same hollow-gold ring, same fill-on-state. */
  .nav__link::before {
    content: "";
    flex: 0 0 auto;
    width: 11px;
    height: 11px;
    border: 1.5px solid var(--color-gold);
    border-radius: 50%;
    background-color: transparent;
    transition: background-color var(--transition-fast), box-shadow var(--transition-fast);
  }

  /* The chevron is two borders, no icon file. `margin-left: auto` is what holds
     it against the right edge: the label is an anonymous flex item and cannot
     be given `flex-grow` from here. */
  .nav__link::after {
    content: "";
    flex: 0 0 auto;
    margin-left: auto;
    width: 7px;
    height: 7px;
    border-top: 1.5px solid currentColor;
    border-right: 1.5px solid currentColor;
    transform: rotate(45deg);
    opacity: 0.45;
    transition: transform var(--transition-fast), opacity var(--transition-fast);
  }

  /* A warm wash, not the desktop's full gold flip: a solid gold bar per row
     inside a panel would flash the whole screen gold. */
  .nav__link:hover,
  .nav__link:focus-visible {
    background-color: rgba(211, 173, 95, 0.07);
  }

  .nav__link:hover::before,
  .nav__link:focus-visible::before {
    background-color: var(--color-gold);
    box-shadow: 0 0 0 4px rgba(211, 173, 95, 0.14);
  }

  .nav__link:hover::after {
    transform: translateX(3px) rotate(45deg);
    opacity: 0.9;
  }

  /* The page you are on. Two signals, the same pair the map panel's focused
     row uses: gold type AND a filled ring, so the state is never carried by
     colour alone. */
  .nav__link--active {
    color: var(--color-gold);
  }

  .nav__link--active::before {
    background-color: var(--color-gold);
    box-shadow: 0 0 0 4px rgba(211, 173, 95, 0.16);
  }

  .nav__link--active::after {
    opacity: 0.85;
  }

  /* --- Entrance -----------------------------------------------------------
     The rows arrive in reading order, which is the one orchestrated moment
     this surface gets. Under prefers-reduced-motion the global block sets
     `animation-duration: 0.01ms`, and the `both` fill lands every row on its
     end state, so the menu is never left invisible for a reduced-motion
     visitor. */
  .nav__list--open li {
    animation: nav-row-in 0.4s var(--ease-out) both;
  }

  .nav__list--open li:nth-child(1) {
    animation-delay: 0ms;
  }

  .nav__list--open li:nth-child(2) {
    animation-delay: 40ms;
  }

  .nav__list--open li:nth-child(3) {
    animation-delay: 80ms;
  }

  .nav__list--open li:nth-child(4) {
    animation-delay: 120ms;
  }

  .nav__list--open li:nth-child(5) {
    animation-delay: 160ms;
  }

  .nav__list--open li:nth-child(6) {
    animation-delay: 200ms;
  }

  .nav__list--open li:nth-child(7) {
    animation-delay: 240ms;
  }

  /* --- The toggle --------------------------------------------------------- */
  .nav__toggle {
    display: flex;
    align-items: center;
    justify-content: center;
    /* 44px, the same touch minimum the rest of the site's controls keep. */
    width: 44px;
    height: 44px;
    padding: 0;
    z-index: calc(var(--z-mobile-nav) + 1);
  }

  /* The bars cross into an X, so the way out is the control that opened it,
     in the same place and the same size. The 7px offsets are the span's own
     2px plus the 5px gap between them. */
  .nav__toggle--active span:nth-child(1) {
    transform: translateY(7px) rotate(45deg);
  }

  .nav__toggle--active span:nth-child(2) {
    opacity: 0;
  }

  .nav__toggle--active span:nth-child(3) {
    transform: translateY(-7px) rotate(-45deg);
  }
}

@keyframes nav-row-in {
  from {
    opacity: 0;
    transform: translateY(14px);
  }

  to {
    opacity: 1;
    transform: none;
  }
}

/* THE BRAND BAR AND THE DRAWER ARE ONE SURFACE WHILE THE MENU IS OPEN.
   ----------------------------------------------------------------------------
   Three declarations, and the first one is load-bearing rather than cosmetic.
   A transformed ancestor becomes the containing block for `position: fixed`
   descendants, so a header caught mid-hide (`translateY(-100%)` from
   `.header--hidden`) would drag the whole drawer up with it and leave the menu
   half off-screen with the toggle out of reach. `.header.header--open` is (0,2,0)
   against `.header--hidden`'s (0,1,0), so it wins whatever the file order.

   The z-index is the second trap. The header is `z-index: 100` on a positioned
   element, which makes it a STACKING CONTEXT, so the drawer's own 200 only
   orders it among the header's children; against the page it inherits the
   header's 100. That is below Leaflet's panes (`z-index: 400`, and
   `.map-container` is `position: relative` with no z-index, so its panes are
   NOT contained) and below the Plan-an-Event FAB (900). 950 lifts the whole
   open surface above those and still leaves the true full-screen modals
   (1000) on top of it, which is the correct order.

   Scoped to this breakpoint on purpose: the toggle only exists below 1280px, so
   a class left over from a resize cannot affect the desktop header. */
@media (max-width: 1279px) {
  .header.header--open {
    transform: none;
    z-index: 950;
    background-color: var(--color-bg);
  }
}

/* ============================================================================
   FULL MENU PAGE GALLERY + PAGE VIEWER (templates/menu.html, 2026-09-21)
   ----------------------------------------------------------------------------
   The /menu/ page shows the real menu: 28 page images rendered out of
   static/img/menu/KINGCHEF MENU.pdf. Everything in this block belongs to that
   one section and its <dialog>, and nothing here is shared with another page.

   THE GRID SHOWS THE 512x724 THUMBS AND THE VIEWER LOADS THE 1242x1756 PAGES.
   That split is the whole performance story: 28 full pages in one view is
   5.9MB of images, the same grid built from the thumbs is about 1MB, and the
   viewer only ever fetches the single page it is showing.

   THE PAGES ARE A4 CROPPED TO THE PRINT TRIM, so 1242/1756 is not a round
   number picked for the layout, it is the files' own ratio, and painting it
   here reserves every card's full height before an image loads. If the menu is
   ever re-rendered, this ratio and the width/height attributes in the template
   have to move with it.

   `.menu-lightbox[open]` IS LOAD-BEARING AND `[open]` IS THE POINT. The UA
   sheet ships `dialog[open] { display: block }`, which is (0,1,1) and outranks
   a bare `.menu-lightbox` at (0,1,0), so the flex column below only takes
   effect from the attribute selector. Written as a plain class it looks
   correct in the source and lays the toolbar out as a stacked block in the
   browser. Do not "simplify" it back. */

/* THE GALLERY BAND AND THE PAGE HEADER READ AS ONE UNIT, so this band's top
   padding is cut. A full --section-padding here stacked on top of the page
   header's own padding-bottom and opened a ~300px void between the last line of
   the header and the first card - measured 297px at 1440, 306 at 1920 and 334
   at 390 - which is the empty plate the client saw between the two header
   bands.

   `clamp(2rem, 3vw, 3rem)` keeps the space in the same, small range at every
   width instead of scaling with the viewport: measured 80px at 390, 92px at
   1440 and 96px at 1920, against a section that no longer announces itself.
   .cortex/probe-menu-header.mjs pins it with a floor AND a ceiling, and reads
   it from the last line of COPY rather than from the header's box edge, because
   the box edge carries 48px of the void inside it. */
.section.menu-book {
  padding-top: clamp(2rem, 3vw, 3rem);
}

.menu-book__grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: clamp(0.85rem, 1.5vw, 1.5rem);
  margin: 0;
  padding: 0;
  list-style: none;
}

@media (min-width: 560px) {
  .menu-book__grid {
    grid-template-columns: repeat(3, minmax(0, 1fr));
  }
}

@media (min-width: 820px) {
  .menu-book__grid {
    grid-template-columns: repeat(4, minmax(0, 1fr));
  }
}

@media (min-width: 1120px) {
  .menu-book__grid {
    grid-template-columns: repeat(5, minmax(0, 1fr));
  }
}

@media (min-width: 1560px) {
  .menu-book__grid {
    grid-template-columns: repeat(6, minmax(0, 1fr));
  }
}

.menu-book__card {
  display: block;
  color: inherit;
  text-decoration: none;
}

/* The page frame. The hairline plus the deep soft shadow are a PAIR, and both
   are needed for the two dark pages (the cover and the mix-grill spread): on
   the dark section behind them a borderless card reads as a hole in the page
   rather than as a sheet lying on it. */
.menu-book__shot {
  display: block;
  overflow: hidden;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  background-color: var(--color-bg-card);
  box-shadow: 0 20px 44px -26px rgba(0, 0, 0, 0.92);
  transition: transform 0.45s var(--ease-out), border-color 0.45s var(--ease-out),
    box-shadow 0.45s var(--ease-out);
}

.menu-book__shot img {
  display: block;
  width: 100%;
  height: auto;
  aspect-ratio: 1242 / 1756;
}

.menu-book__card:hover .menu-book__shot,
.menu-book__card:focus-visible .menu-book__shot {
  transform: translateY(-4px);
  border-color: var(--color-gold-dark);
  box-shadow: 0 30px 60px -26px rgba(0, 0, 0, 0.98);
}

/* The card is a link, so the focus ring is the only cue a keyboard visitor
   gets: it is on the card, not on the inner frame, or the ring would be clipped
   by `.menu-book__shot`'s overflow. */
.menu-book__card:focus-visible {
  outline: 2px solid var(--color-gold);
  outline-offset: 5px;
  border-radius: var(--radius-sm);
}

.menu-book__meta {
  display: flex;
  align-items: baseline;
  gap: 0.6rem;
  margin-top: 0.75rem;
}

.menu-book__num {
  flex: 0 0 auto;
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  letter-spacing: 0.08em;
  color: var(--color-gold);
}

.menu-book__label {
  font-size: var(--text-sm);
  line-height: 1.3;
  color: var(--color-cream-dim);
  transition: color 0.45s var(--ease-out);
}

.menu-book__card:hover .menu-book__label,
.menu-book__card:focus-visible .menu-book__label {
  color: var(--color-cream);
}

/* --- the page viewer ------------------------------------------------------- */

.menu-lightbox[open] {
  display: flex;
  flex-direction: column;
  width: min(94vw, 880px);
  max-width: none;
  max-height: 94vh;
  margin: auto;
  padding: 0;
  overflow: hidden;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
  background-color: var(--color-bg-card);
  color: var(--color-cream);
}

.menu-lightbox::backdrop {
  background-color: rgba(5, 10, 7, 0.88);
}

.menu-lightbox__bar {
  display: flex;
  align-items: center;
  gap: 0.85rem;
  flex: 0 0 auto;
  padding: 0.6rem 0.6rem 0.6rem 1.05rem;
  border-bottom: 1px solid var(--color-border-light);
  background-color: var(--color-bg-card);
}

.menu-lightbox__count {
  flex: 0 0 auto;
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  letter-spacing: 0.08em;
  color: var(--color-gold);
}

/* min-width: 0 plus the ellipsis is the reason a long label ("North Special
   Mutton") cannot push the three controls off the bar at 390px. */
.menu-lightbox__label {
  flex: 1 1 auto;
  min-width: 0;
  overflow: hidden;
  font-size: var(--text-sm);
  white-space: nowrap;
  text-overflow: ellipsis;
  color: var(--color-cream);
}

.menu-lightbox__controls {
  display: flex;
  align-items: center;
  gap: 0.35rem;
  flex: 0 0 auto;
}

/* The zoom stepper (out / readout / in). The readout is a BUTTON, not a label:
   it prints the current level and resets to the fitted page, so the way back
   from a zoomed-in page is the control that told you the level. The level
   multiplies the MEASURED fit in main.js, so 100% is exactly the fitted page at
   any viewport instead of a percentage guess at the stage's width. */
.menu-lightbox__zoom {
  display: flex;
  align-items: center;
  gap: 0.25rem;
  flex: 0 0 auto;
}

.menu-lightbox__btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 40px;
  height: 40px;
  padding: 0;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-full);
  background-color: transparent;
  color: var(--color-cream);
  cursor: pointer;
  transition: background-color 0.25s var(--ease-out), color 0.25s var(--ease-out),
    border-color 0.25s var(--ease-out);
}

.menu-lightbox__btn svg {
  width: 18px;
  height: 18px;
}

.menu-lightbox__btn:hover,
.menu-lightbox__btn:focus-visible {
  border-color: var(--color-gold);
  background-color: rgba(211, 173, 95, 0.14);
  color: var(--color-gold-light);
}

.menu-lightbox__btn:focus-visible {
  outline: 2px solid var(--color-gold);
  outline-offset: 2px;
}

.menu-lightbox__btn--close:hover,
.menu-lightbox__btn--close:focus-visible {
  border-color: var(--color-gold-light);
  background-color: rgba(211, 173, 95, 0.2);
  color: var(--color-white);
}

/* The readout. Width auto rather than the 40px circle, tabular figures so the
   number cannot jitter the bar as it counts 100% 150% 200%, and the mono face
   the counter in the same bar already uses. */
.menu-lightbox__btn--readout {
  width: auto;
  min-width: 52px;
  padding: 0 0.6rem;
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  font-variant-numeric: tabular-nums;
  letter-spacing: 0.04em;
  color: var(--color-cream-dim);
}

/* AT THE ENDS OF THE LADDER THE BUTTON IS OFF, NOT INERT. A click that silently
   does nothing reads as a broken control, so main.js sets the `disabled`
   attribute and this cancels the hover paint the disabled button would
   otherwise still get from the rule above it. */
.menu-lightbox__btn[disabled] {
  opacity: 0.4;
  cursor: default;
}

.menu-lightbox__btn[disabled]:hover,
.menu-lightbox__btn[disabled]:focus-visible {
  border-color: var(--color-border);
  background-color: transparent;
  color: var(--color-cream);
}

/* The stage is the only scrolling box in the viewer and it carries
   data-lenis-prevent in the markup, so Lenis leaves wheel and touch inside it
   native. `min-height: 0` is what lets a flex child shrink far enough to
   actually scroll instead of forcing the dialog past the viewport; `align-self:
   flex-start` on the page below is what stops the flex cross-axis stretch from
   overriding the image's own height.

   `justify-content: center` IS THE FIT, AND EVERY ZOOM LEVEL THE PAGE STILL
   FITS AT. A flex line whose item is wider than the container overflows BOTH
   edges under `center`, and the overflow past the start edge cannot be scrolled
   to, so a page that has outgrown the stage would have its left half
   permanently unreachable. main.js therefore writes `flex-start` inline for
   exactly that case, deciding it from the RENDERED width rather than from
   `fit * level`, and leaves this rule in charge at the fit and at every level
   the page still fits at, where centring is what puts it in the middle of the
   stage. */
.menu-lightbox__stage {
  display: flex;
  justify-content: center;
  flex: 1 1 auto;
  min-height: 0;
  overflow: auto;
  padding: clamp(0.6rem, 1.6vw, 1.25rem);
  background-color: var(--color-bg-hero);
  overscroll-behavior: contain;
}

.menu-lightbox__stage img {
  display: block;
  align-self: flex-start;
  width: auto;
  max-width: 100%;
  height: auto;
  max-height: calc(94vh - 7rem);
  border-radius: 2px;
  background-color: var(--color-white);
}

/* Full screen below 900px. A fitted A4 page at 390px is a postage stamp, so on
   a phone the page is laid out at the full width of the screen and the stage
   scrolls; pinch-to-zoom is still there, because base.html's viewport meta does
   not set user-scalable=no.

   THE BAR CARRIES SIX CONTROLS ON A PHONE (zoom out, readout, zoom in, prev,
   next, close) and they have to fit BESIDE the counter and the label in 390px.
   The round buttons step down to 34px and the bar's gaps tighten, which is
   measured, not guessed: at the 40px desktop size the label is squeezed to
   nothing and the close button is pushed past the right edge, which the probe
   asserts against directly (`close right edge <= 390`). */
@media (max-width: 899px) {
  .menu-lightbox[open] {
    width: 100vw;
    height: 100dvh;
    max-height: 100dvh;
    border: 0;
    border-radius: 0;
  }

  .menu-lightbox__bar {
    gap: 0.55rem;
    padding: 0.5rem 0.5rem 0.5rem 0.8rem;
  }

  .menu-lightbox__btn {
    width: 34px;
    height: 34px;
  }

  .menu-lightbox__btn svg {
    width: 16px;
    height: 16px;
  }

  .menu-lightbox__btn--readout {
    min-width: 44px;
    padding: 0 0.4rem;
  }

  .menu-lightbox__stage {
    padding: 0.5rem;
  }

  .menu-lightbox__stage img {
    width: 100%;
    max-width: 100%;
    max-height: none;
  }
}

@media (prefers-reduced-motion: reduce) {
  .menu-book__shot,
  .menu-book__label {
    transition: none;
  }

  .menu-book__card:hover .menu-book__shot,
  .menu-book__card:focus-visible .menu-book__shot {
    transform: none;
  }
}

/* --- Choosing Your Order: the /menu/ decision table ------------------------
   The table the SEO/AEO/GEO audit of /menu/ asked for ("comparison or decision
   support ... Tables found: 0"). Three cuisine rows, each saying what the
   cuisine is for and what to start with, which is the decision a visitor
   opening a multi-cuisine menu is actually making.

   NOTHING HERE IS ANIMATED, so there is no reduced-motion counterpart: the
   hover row is a background change with no transition on it.

   THE WRAPPER IS THE OVERFLOW, not the page. `min-width` below is 34rem, so on
   a phone the table is wider than the screen - the wrapper takes that overflow
   so `documentElement.scrollWidth` stays at the viewport width, which is what
   .cortex/probe-menu-header.mjs and .cortex/probe-menu-seo.mjs both check for.
   Put the overflow back on the table and the phone page scrolls sideways.

   THE HEADER ROW REUSES `.eyebrow`'s TYPE rather than its class, because a
   <th> cannot carry a <span> without breaking the cell. Keep the two in step:
   if the eyebrow's tracking changes, this row is the other copy.

   THE /about/ OCCASION TABLE SHARES THIS TREATMENT under the `.answer-table`
   alias (added 2026-09-23 by the /about/ SEO/AEO/GEO pass). ONE treatment, two
   names: a value changed here changes both tables. Keep the alias SECOND in
   each selector list, so the pins in .cortex/check_about_chairman.py can keep
   anchoring on the exact text they search for. */
.menu-guide__scroll,
.answer-table__scroll {
  margin-top: clamp(1.75rem, 3vw, 2.5rem);
  overflow-x: auto;
}

.menu-guide__table,
.answer-table {
  width: 100%;
  min-width: 34rem;
  border-collapse: collapse;
  text-align: left;
}

.menu-guide__table th,
.menu-guide__table td,
.answer-table th,
.answer-table td {
  padding: 1rem 1.1rem;
  vertical-align: top;
  border-bottom: 1px solid var(--color-border);
  font-size: var(--text-sm);
  line-height: 1.6;
  color: var(--color-cream-dim);
}

.menu-guide__table thead th,
.answer-table thead th {
  padding-top: 0;
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  font-weight: 500;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  white-space: nowrap;
  color: var(--color-gold);
  border-bottom-color: rgba(200, 166, 95, 0.35);
}

.menu-guide__table tbody th,
.answer-table tbody th {
  font-weight: 500;
  color: var(--color-cream);
}

.menu-guide__table tbody tr:hover th,
.menu-guide__table tbody tr:hover td,
.answer-table tbody tr:hover th,
.answer-table tbody tr:hover td {
  background-color: rgba(200, 166, 95, 0.05);
}

.menu-guide__example {
  margin-top: clamp(1.25rem, 2vw, 1.75rem);
  max-width: 62ch;
  font-size: var(--text-base);
  color: var(--color-cream-dim);
}

/* ============================================
   CUISINE BAND (/menu/) - Indian, Arabic, Chinese
   ============================================

   ONE ANATOMY, THREE PLATES, A REAL ZIG-ZAG.

   What this replaced, and why every line below is load-bearing:

   1. THE ZIG-ZAG DID NOT EXIST. The three sections used `.grid-split` and
      `.grid-split--reverse`, and the IMAGE WAS THE FIRST CHILD IN BOTH, so
      both rendered with the plate on the LEFT. `--reverse` only swapped the
      column RATIOS (1.2fr 0.8fr -> 0.8fr 1.2fr); it never moved either child,
      because a grid places items in DOM order. Its only visible effect was to
      shrink the Arabic plate to two-thirds of the Indian one - measured
      525x612 against 787x918. A class named "reverse" that does not reverse
      survives every text diff, which is why it lasted. Here the side is
      decided by EXPLICIT grid-column placement, so it cannot drift with the
      DOM order, and the plate column holds a CONSTANT share so all three
      plates are the SAME SIZE and only the side changes.

   2. THE PLATE ALWAYS TAKES THE 0.5fr TRACK, in both directions. The flip
      layout is the MIRROR of the default, with the two track sizes swapped -
      which is exactly what `--reverse` was trying to be. Swap them back and
      the plates differ in size again, which is the fault this replaces.

      The share itself was 0.8fr in the first pass and is 0.5fr now, on the
      client's instruction: the plate was 525x700 against a copy column of 339px
      of content, which left a measured 361px of void below the copy at 1440 and
      489px at 1920. The plate now renders 285x380 and the void is bounded - see
      point 7 for the half of that fix which is NOT a column ratio.

   3. `align-items: start`, NOT center, AND the copy column STRETCHES. Centring
      opened a void above AND below the copy - measured ~230px each side on the
      Indian section, which stood 1091px tall against Arabic's 785px. Top-aligning
      killed that. The copy then took `align-self: stretch` so the signature strip
      could be pinned to its foot with `margin-top: auto`, which is what puts that
      strip's hairline rule LEVEL with the plate's bottom edge in all three
      sections. The PLATE is still top-aligned, so the centred-void fault cannot
      come back.

   4. DO NOT move any of this onto `.grid-split` ITSELF. That class is shared
      with about.html, catering.html, careers.html and index.html, and the
      column shares are load-bearing on all four. Everything here is scoped
      under `.cuisine-band`, which only /menu/ uses.

   5. `.menu-category` / `.menu-categories` are used ONLY by these three
      sections - 12 cards, all in templates/menu.html - so the restyle below has
      no blast radius. That is checked, not assumed:
      `grep -rn "menu-category" templates/` returns menu.html and nothing else.

   6. THE CATEGORIES ARE FOUR ACROSS WITH EQUAL ROW HEIGHTS, and the COUNT is
      part of the contract. They began as four stacked full-width bars (525x80
      each, a ~368px column of identical boxes). At the plate's current share the
      copy column is 1027px wide, where a 2-up grid produces 505x80 cards - a
      6.3:1 STRIP, the same "boring" complaint in a new shape - so the grid is
      four across, giving 245x104 (2.35:1). Four cards per section is exactly ONE
      row at that width, which is what makes the three copy columns the same
      height as each other. `grid-auto-rows: 1fr` equalises the cards WITHIN a
      row: a grid's default `auto` rows size to content, so one longer
      description would make its card taller than its neighbour. (Same family as
      `location-grid-equal-tracks-2026-09-17.md` and
      `band-cards-equal-size-2026-09-17.md` - when this client says "same size",
      the answer is grid tracks and the gate has to MEASURE it.)

   7. THE PLATE IS A PLAIN `width: 100%` IMAGE (.img-rounded) WITH AN HONEST
      width/height ON THE TAG, so the browser reserves its box from the
      intrinsic ratio and nothing shifts when the photograph arrives - PLUS a
      DEFINITE max-height that applies ONLY in the split layout. The cap is the
      half of the rebalance no column ratio can do: the plate is an image, so its
      height is proportional to the viewport, while the copy beside it is text and
      barely moves. Measured with no cap the void was 42px at 1440 and 111px at
      1920, still growing. The cap pins every desktop width to one plate height.

      Do NOT unscope the cap. Stacked at 1024px the plate is 973px wide, so
      `aspect-ratio: 3 / 4` asks for 973x1297 and the cap cuts it to 973x380 - a
      2.56:1 landscape band sliced out of a portrait photograph. That was
      measured on the first attempt, which is why the rule now sits inside
      `@media (min-width: 1025px)`.

      And this is NOT the `catering-figure-band-2026-09-14.md` trap, which is why
      a max-height is allowed at all here: that trap was a child with
      `height: 100%` inside a parent carrying only `max-height`, so the percentage
      resolved against `auto` and the photo was HARD-CLIPPED instead of resized.
      Here the image is `width: 100%` with `object-fit: cover`, so the cap CROPS
      the photograph - the intended behaviour - and `aspect-ratio` keeps the box
      from collapsing. At 1440 the box is 285x380, ratio 0.7500, which is the
      photograph's OWN ratio, so nothing is cropped at all.

   NO TRANSITION OR ANIMATION IS ADDED HERE, so there is no reduced-motion
   counterpart to write. The `.reveal` on each split is the existing mechanism,
   registered by main.js like every other reveal on the page. */

.cuisine-band__split {
  display: grid;
  grid-template-columns: minmax(0, 1.8fr) minmax(0, 0.5fr);
  gap: var(--space-3xl);
  align-items: start;
}

.cuisine-band__split--flip {
  grid-template-columns: minmax(0, 0.5fr) minmax(0, 1.8fr);
}

.cuisine-band__copy {
  grid-column: 1;
  grid-row: 1;
  /* A flex COLUMN, so the signature strip can be pinned to the column's foot with
     `margin-top: auto`. That is what puts its hairline rule LEVEL with the
     plate's bottom edge instead of leaving a ragged gap at a different height in
     each of the three sections. `align-self: stretch` gives it a foot to be
     pinned to, while the row keeps `align-items: start`, so the PLATE stays
     top-aligned and the centred-void fault this band was rebuilt to remove
     cannot come back. */
  display: flex;
  flex-direction: column;
  align-self: stretch;
}

.cuisine-band__plate {
  grid-column: 2;
  grid-row: 1;
}

/* The true mirror: the PLATE takes the 0.8fr track, so its rendered width is
   identical to the two sections that were not flipped. */
.cuisine-band__split--flip .cuisine-band__copy {
  grid-column: 2;
}

.cuisine-band__split--flip .cuisine-band__plate {
  grid-column: 1;
}

/* FOUR ACROSS, not two. At the plate's current share the copy column renders
   1027px wide at 1440, and a 2-up grid there produced 505x80 cards - a 6.3:1
   STRIP, which is the same "boring" complaint in a new shape (measured, and the
   reason the earlier 2-up rule was replaced rather than kept). Four across gives
   245x104, a 2.35:1 card, and with four cards per section that is exactly ONE
   row - so the three sections line up card for card.

   THE CARD COUNT IS PART OF THE CONTRACT: four cards per section is what makes
   one row, and one row is what makes the copy column's height match the plate.
   `grid-auto-rows: 1fr` still equalises the cards within a row. Pinned in
   `.cortex/check_cuisine_band.py` (the count) and measured on the wire by
   `.cortex/probe-cuisine-band.mjs` (the shape and the void). */
.cuisine-band .menu-categories {
  grid-template-columns: repeat(4, minmax(0, 1fr));
  grid-auto-rows: 1fr;
}

/* A DEFINITE CEILING ON THE PLATE, and it fixes a fault that GROWS with the
   viewport rather than sitting still.

   The plate is an image, so its height is PROPORTIONAL to the viewport; the copy
   beside it is text, so its height barely moves. Measured with no cap, the void
   below the copy was 42px at 1440 and 111px at 1920 - and it would keep growing
   on a wider screen. No column ratio can fix that, because shrinking the plate
   shrinks the whole section with it.

   The cap makes every desktop width land on the SAME plate height, so the three
   sections stay level and the void stops tracking the window width.

   IT IS SCOPED TO THE SPLIT LAYOUT ON PURPOSE, and that is not tidiness - the
   unscoped version was measured WRONG. Stacked at 1024px the plate is 973px wide,
   so `aspect-ratio: 3 / 4` asks for a 973x1297 box and the cap cuts it to
   973x380 - a 2.56:1 LANDSCAPE STRIP sliced out of a portrait photograph, which
   object-fit then crops to a thin horizontal band. Below 1025px the plate simply
   follows its own 0.75 ratio, which is already correct there.

   THIS IS NOT THE `catering-figure-band-2026-09-14.md` TRAP. That trap was a child
   with `height: 100%` inside a parent carrying only `max-height`: the percentage
   resolved against `auto`, fell back to the image's intrinsic height, and the
   photo was HARD-CLIPPED by `overflow: hidden` instead of resized. Here the image
   is `width: 100%` with `object-fit: cover`, so a capped height CROPS the
   photograph - the intended behaviour - and `aspect-ratio` keeps the box from
   collapsing when the width is fluid.

   At 1440 the box is 285x380, ratio 0.7500, which is the photograph's OWN ratio
   (1086x1448), so nothing is cropped at all. The crop only begins once the width
   would push the height past the cap, i.e. above ~1500px, and it is capped at
   ~15% of the height - inside the 8%-per-edge margin the plates were shot with. */
.cuisine-band__plate img {
  display: block;
}

@media (min-width: 1025px) {
  .cuisine-band__plate img {
    aspect-ratio: 3 / 4;
    max-height: 380px;
    object-fit: cover;
  }
}

/* THE SIGNATURE STRIP - the band's third tier, under the cards.

   It exists because the copy column had ~110px of unused height below a single
   row of cards and the plate beside it is 380px tall. It is a run-in sentence
   with a <strong> label rather than a list or a heading, for two reasons: a
   <ul> here would read as a fifth navigation block, and a heading would break the
   one-header contract that `.cortex/probe-menu-header.mjs` enforces across the
   whole page (there is a `.visually-hidden` h2 above the gallery and no other
   laid-out heading may be added near the top of the page).

   The `margin-top: auto` is deliberate and pairs with `align-items: start` on
   the split: on a phone the copy column is the only child in its row, so the
   strip sits directly under the cards, while on desktop it is pushed to the foot
   of the column, which is where the plate's bottom edge already is. Do not
   replace it with a fixed margin - the fixed margin is what leaves a gap on a
   short viewport. */
.cuisine-band__signature {
  margin-top: auto;
  padding-top: clamp(1.25rem, 2vw, 1.75rem);
  border-top: 1px solid var(--color-border);
  font-size: var(--text-sm);
  color: var(--color-cream-dim);
}

.cuisine-band__signature strong {
  color: var(--color-gold);
  font-weight: 600;
}

@media (max-width: 1024px) {
  /* Stacked - and the COPY COMES FIRST, so the heading is announced before the
     photograph it belongs to. The plate is the first child in every section's
     DOM, so the order has to be set here or the phone page opens on an image
     with no context above it. */
  .cuisine-band__split,
  .cuisine-band__split--flip {
    grid-template-columns: minmax(0, 1fr);
  }

  .cuisine-band__copy,
  .cuisine-band__split--flip .cuisine-band__copy {
    grid-column: 1;
    grid-row: 1;
  }

  .cuisine-band__plate,
  .cuisine-band__split--flip .cuisine-band__plate {
    grid-column: 1;
    grid-row: 2;
  }

  /* TWO ACROSS once the band stacks. Four across is a consequence of the WIDE
     desktop copy column; stacked, the column is the viewport, so four cards would
     be ~80px each on a phone and the descriptions would break to one word a line. */
  .cuisine-band .menu-categories {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

/* --- Careers: the "Why Join Our Team" perk band -----------------------------
   Three cards that were TEXT ONLY, with no padding band and no visual anchor,
   so each read as an empty box with a sentence in it. They keep the shared
   `.card` surface (so the radius, the hairline and the hover lift stay the
   site's own) and gain a gold icon medallion above the copy.

   Scoped to `.perks` because `.card` and `.card__body` are shared by the
   location grid, the catering band and the menu page. The medallion borrows
   its language from `.catering-item__icon` (gold at 10% on `--radius-md`) so
   the site has ONE icon treatment rather than two that almost match. */
.perks {
  /* A grid's default `auto` rows size to CONTENT, so the longest card would set
     the row and the other two would end short of it. `1fr` makes the three
     cards one height whatever the copy weighs. Same rule as the cuisine band's
     `grid-auto-rows: 1fr` and the location grid's equal tracks. */
  grid-auto-rows: 1fr;
}

.perk {
  /* Column flex so the body can grow and the medallion stays at the top. The
     card carries the padding, and `.card__body` is flattened below, so there is
     exactly ONE source of padding instead of two that half-overlap. */
  display: flex;
  flex-direction: column;
  padding: 1.6rem 1.6rem 1.9rem;
}

.perk__icon {
  width: 2.75rem;
  height: 2.75rem;
  display: flex;
  align-items: center;
  justify-content: center;
  background: rgba(200, 166, 95, 0.1);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  color: var(--color-gold);
  margin-bottom: 1.15rem;
  transition: background var(--transition-base), border-color var(--transition-base);
}

.perk__icon svg {
  width: 20px;
  height: 20px;
}

.perk:hover .perk__icon {
  background: rgba(200, 166, 95, 0.2);
  border-color: var(--color-gold);
}

/* Flatten the shared body: `.perk` already supplies the padding band, and the
   body takes the leftover height so the three paragraphs start on one line and
   the cards end flush with each other. */
.perk .card__body {
  padding: 0;
  flex: 1 1 auto;
}

.perk .card__title {
  /* The medallion is the gold element, so the title steps back to cream and the
     card reads as two levels rather than one wash of gold. `.catering-item__title`
     makes the same call beside its own gold icon. */
  color: var(--color-cream);
  font-size: var(--text-lg);
  margin-bottom: 0.55rem;
}


/* --- Contact page: two panels on one surface language ------------------------
   The page was two BARE COLUMNS of text: no card, no icon, no rhythm, and the
   info column was VERTICALLY CENTRED against the taller form. Measured at 1440,
   `.grid-2`'s `align-items: center` put the info column's first line 67px BELOW
   the form's, so the two columns read as unrelated blocks.

   THE FIX IS TWO DECLARATIONS AND A SHARED SURFACE:
   (1) `.contact-grid` is `align-items: stretch`, so both panels begin on the
       same line AND end on the same line: `center` was the 67px fault, but
       `start` left the shorter info panel floating with ~257px of void beneath
       it while the form ran on. `stretch` fixes BOTH - a grid item that
       stretches fills its row, so the two cards are one height by construction,
       not by a magic `min-height`. (Client: "both card same size need ... top to
       bottom min_height increas it".)
   (2) both panels carry `.card`'s own surface (background, hairline, radius)
       rather than a new one, so the page gains a surface WITHOUT adding a second
       card language to the site.

   THE INFO PANEL THEN HAS TO FILL ITS NEW HEIGHT, or stretching just moves the
   void inside the card. `.contact-panel` is a column flex and `.contact-rows`
   takes the leftover (`flex: 1 1 auto`), distributing its four rows from the top
   edge to the bottom edge (`align-content: space-between`). No number anywhere:
   the height comes from the taller card, whatever that turns out to be.

   SCOPED TO `.contact-*`, NOT applied to `.grid-2`/`.card` directly: `.grid-2`
   is shared by /about/, /catering/ and the homepage, and `.card` by the location
   grid, the catering band and the menu page. The row medallions borrow
   `.catering-item__icon` and `.perk__icon`'s language (gold at 10% on
   `--radius-md`) so the site keeps ONE icon treatment rather than three that
   almost match. */
.contact-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--space-2xl);
  /* `stretch`, NOT `center` and NOT `start`. See the note above: `center` opened
     the 67px offset, `start` left the shorter panel floating with ~257px of
     void under it. `stretch` makes the two cards ONE HEIGHT (and they still
     begin on the same line). */
  align-items: stretch;
}

/* A column flex so the title keeps its place and `.contact-rows` can take the
   leftover height the stretch hands the panel. */
.contact-panel {
  display: flex;
  flex-direction: column;
  background-color: var(--color-bg-card);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
  padding: clamp(1.5rem, 2.4vw, 2.25rem);
}

/* The two panel headings are <h2>, one step under the page's H1, and they are
   deliberately NOT the section-header band: the page header directly above
   already carries an H1 and a subtitle, and a second full-weight header band
   under it is the "two headers stacked" fault the client reported on /menu/
   (menu-single-header-2026-09-21.md). Sized DOWN from the base h2 for the same
   reason - these are panel titles, not page titles. */
.contact-panel__title {
  font-size: var(--text-xl);
  letter-spacing: 0;
  margin-bottom: var(--space-lg);
  padding-bottom: var(--space-md);
  border-bottom: 1px solid var(--color-border-light);
  flex: 0 0 auto;
}

/* The info rows: icon, then label and values. Divided by a hairline so the four
   groups read as one list rather than as four loose paragraphs.

   `flex: 1 1 auto` TAKES THE LEFTOVER HEIGHT the stretch hands the panel, and
   `align-content: space-between` spreads the four rows from the top edge to the
   bottom edge so the card FILLS rather than gaining void under the last row.
   The height itself is never written down - it comes from the taller form
   card, so this stays correct if the form gains or loses a field. */
.contact-rows {
  display: grid;
  gap: var(--space-lg);
  flex: 1 1 auto;
  align-content: space-between;
}

.contact-row {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  gap: 0.9rem;
  align-items: start;
}

.contact-row + .contact-row {
  padding-top: var(--space-lg);
  border-top: 1px solid var(--color-border-light);
}

.contact-row__icon {
  width: 2.4rem;
  height: 2.4rem;
  display: flex;
  align-items: center;
  justify-content: center;
  background: rgba(200, 166, 95, 0.1);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  color: var(--color-gold);
  flex: 0 0 auto;
}

.contact-row__icon svg {
  width: 18px;
  height: 18px;
}

/* A ROW LABEL, NOT A SECTION HEADING. These are <h3> for the heading ladder
   (they were <h4> under the section's <h2>, a skipped level), but the base `h3`
   rule is clamp(1.25rem, 2.5vw, 2xl) - a section-heading size. Sized down here
   so the markup can be correct AND the row can look like a row. */
.contact-row__label {
  font-size: var(--text-lg);
  font-weight: 600;
  letter-spacing: 0.02em;
  color: var(--color-gold);
  margin-bottom: 0.35rem;
}

.contact-row__value {
  font-size: var(--text-base);
  color: var(--color-cream-dim);
  line-height: 1.6;
  max-width: none;
}

.contact-row__value a {
  color: var(--color-cream);
  text-decoration: none;
  border-bottom: 1px solid color-mix(in srgb, var(--color-gold) 45%, transparent);
  padding-bottom: 1px;
  transition: color var(--transition-fast), border-color var(--transition-fast);
}

.contact-row__value a:hover,
.contact-row__value a:focus-visible {
  color: var(--color-gold);
  border-bottom-color: var(--color-gold);
}

.contact-row__link {
  display: inline-block;
  margin-top: 0.5rem;
  font-size: var(--text-base);
  color: var(--color-gold);
  text-decoration: none;
  border-bottom: 1px solid transparent;
  transition: border-color var(--transition-fast);
}

.contact-row__link:hover,
.contact-row__link:focus-visible {
  border-bottom-color: var(--color-gold);
}

/* A scoped social row. It used to borrow `.location-card__actions`, a class
   written for buttons INSIDE a location card - including an odd-last-child rule
   that made TikTok span the full width for no design reason. Three equal
   buttons is what this actually is. */
.contact-social {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 0.5rem;
  margin-top: 0.9rem;
}

.contact-social .btn {
  justify-content: center;
  padding: 0.62rem 0.75rem;
  font-size: var(--text-xs);
  letter-spacing: 0.03em;
  text-align: center;
}

/* The submit button takes the panel's full width, which used to be an inline
   `style="width: 100%"` on the element. */
.contact-submit {
  width: 100%;
  justify-content: center;
  margin-top: var(--space-sm);
}

/* --- Form states -------------------------------------------------------------
   The form used `alert()` on submit. It now validates inline: a bad field gets
   a gold hairline and a message under it, wired to the input by
   `aria-describedby`; the confirmation is a `role="status"` region so it is
   announced. Both start hidden and are switched on by a class, so with no JS
   the form still submits and neither state is left stuck on screen. */
.form-error {
  display: none;
  margin-top: 0.4rem;
  font-size: var(--text-sm);
  color: var(--color-terracotta);
}

.form-group--invalid .form-error {
  display: block;
}

.form-group--invalid .form-input,
.form-group--invalid .form-textarea {
  border-color: var(--color-terracotta);
}

.form-success {
  display: none;
  margin-top: var(--space-md);
  padding: 0.9rem 1.1rem;
  font-size: var(--text-sm);
  color: var(--color-cream);
  background: rgba(200, 166, 95, 0.1);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  max-width: none;
}

.form-success.is-visible {
  display: block;
}

/* Below the site's 768px grid breakpoint the two panels stack. `minmax(0, 1fr)`
   on the tracks above is what keeps a long phone number from forcing the panel
   wider than the viewport. */
@media (max-width: 768px) {
  .contact-grid {
    grid-template-columns: minmax(0, 1fr);
    gap: var(--space-xl);
  }
}

@media (max-width: 420px) {
  /* Three social buttons in a 350px panel leaves ~100px each, which is under
     what the labels need; two-up closes with TikTok full-width instead of
     squeezing all three. */
  .contact-social {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }

  .contact-social .btn:last-child {
    grid-column: 1 / -1;
  }
}

/* --- Branch page hero + visit panels (2026-09-23 branch hero) ---------------
   The eight location pages were "three islands in a void" at wide viewports
   (client 2026-09-23: "EACH LOCATION PAGES IN SIDE BY SIDE SOMETHING DIFFRENT
   WELL NEED DEISGN"): the header copy filled ~490px of the 1200px container,
   the visit card was a centred 544px box inside a 960px wrapper, and nothing
   on the page ever used the container's right side. The hero is now a SPLIT
   with the branch's own photo on one side, and "Visit or Order" is two equal
   panels. Plan: .cortex/branch-pages-side-by-side-plan.md (GitHub issue #1).

   THE MIRROR IS EXPLICIT PLACEMENT ON BOTH TRACKS, NEVER A TRACK SWAP.
   `.grid-split--reverse` on /menu/ was supposed to reverse a section and only
   swapped which column was WIDER, because a grid places items in DOM order
   (cuisine-band pass, 2026-09-22). So both directions below set BOTH
   grid-columns, and the photo side flips page to page
   (`.page-header--hero` = photo left, `.page-header--hero--right` = photo
   right) so no two of the eight first viewports read identically.

   ADDITIVE ONLY, and that is the zero-re-point contract: every rule scopes
   through `.page-header--hero` or `.branch-visit` and reaches shared classes
   (.container, .location-card, .location-card__*, .page-header__*) only
   through those scopes. The shared rules at 1048-1226 and 2588-2633 are
   pinned byte-untouched by .cortex/check_branch_hero_images.py (its red-test
   hashes them) - never "tidy" them.

   DELTA FROM THE PLAN, recorded here and in the memory record: the plan
   placed the photo as `grid-row: 1 / -1` across four auto rows of copy. A
   spanning item taller than its tracks INFLATES every auto track it spans
   (the extra space is distributed evenly across them), which would have
   spread the title/subtitle/note apart. One unstyled `.page-header__copy`
   wrapper keeps the copy block tight and lets `align-items: center` do
   exactly what the plan wanted: a taller photo centres against the copy
   without touching the text rules. */
.page-header__photo {
  margin: 0;
  position: relative;
  border-radius: var(--radius-lg);
  overflow: hidden;
}

.page-header__photo > img {
  display: block;
  width: 100%;
  aspect-ratio: 4 / 3;
  object-fit: cover;
}

/* The signature line: the map's ring-pin mark + the neighbourhood name in ONE
   flat caption, flush to the plate's bottom-left corner (the brand's
   figure-caption language from the catering photo, client-approved
   2026-09-14). FLAT PLATE, NOT A FADE: a fade's alpha under text moves with
   object-fit's crop and measured 5.31-7.66:1 on the catering band before it
   was flattened there. --color-ink-dark on --color-cream is the attribution
   strip's own pair (13.63:1 measured), and --color-gold-deep is the only gold
   that holds up on cream (the normal gold measures ~1.7:1 there). */
.page-header__photo > figcaption {
  position: absolute;
  left: 0;
  bottom: 0;
  display: flex;
  align-items: center;
  gap: 0.5rem;
  max-width: 100%;
  padding: 0.5rem 0.9rem;
  background: var(--color-cream);
  color: var(--color-ink-dark);
  font-family: var(--font-body);
  font-size: var(--text-xs);
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  border-top-right-radius: var(--radius-md);
}

.page-header__photo > figcaption svg {
  width: 13px;
  height: 13px;
  flex: 0 0 auto;
  color: var(--color-gold-deep);
}

@media (min-width: 641px) {
  /* A FLOOR, not a stage: a content-height hero must not inflate at tall
     viewports - that is the void this redesign removes. 26rem is the plan's
     floor and clears the copy block at every name length. */
  .page-header.page-header--hero {
    min-height: 26rem;
  }

  .page-header--hero > .container {
    display: grid;
    grid-template-columns: minmax(0, 1.15fr) minmax(0, 1fr);
    gap: 0 4rem;
    align-items: center;
  }

  /* photo LEFT (the default direction). BOTH AXES are placed explicitly -
     grid-row: 1 is LOAD-BEARING, not tidiness. With rows left to auto
     placement the sparse cursor never travels backwards, so this variant
     (copy at column 2 FIRST in DOM order, photo at column 1 second) pushed
     the photo into row 2 - measured on the four photo-left pages: 406px top
     delta against the copy, 0px overlap. DOM order silently deciding
     placement is exactly the `grid-split--reverse` trap this block exists to
     avoid, one axis later. Never drop these grid-rows. */
  .page-header--hero > .container > .page-header__copy {
    grid-column: 2;
    grid-row: 1;
  }

  .page-header--hero > .container > .page-header__photo {
    grid-column: 1;
    grid-row: 1;
  }

  /* the mirror: photo RIGHT */
  .page-header--hero.page-header--hero--right > .container > .page-header__copy {
    grid-column: 1;
    grid-row: 1;
  }

  .page-header--hero.page-header--hero--right > .container > .page-header__photo {
    grid-column: 2;
    grid-row: 1;
  }
}

/* The visit area: two equal panels. EQUAL TRACKS + STRETCH is the whole fix
   (band-cards pass, 2026-09-17): .grid-2's align-items: center is what
   floated the shorter /contact/ column 67px below its neighbour, and two
   columns that begin on different lines read as unrelated blocks.
   minmax(0, 1fr) keeps a long phone number from forcing a panel wide.
   Below 900px the panels simply stack at full width. */
@media (min-width: 900px) {
  .branch-visit {
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
    gap: var(--space-2xl);
    align-items: stretch;
  }

  .branch-visit > .location-card {
    max-width: none;
    margin: 0;
  }

  /* The five actions become one wrapping row of equal-height targets in the
     wide panel (the shared two-up grid is sized for the 435px listing cards).
     The shared `> :last-child:nth-child(odd)` full-width rule's grid-column
     is inert inside a flex row, so it cannot orphan the last button here. */
  .branch-visit .location-card__actions {
    display: flex;
    flex-wrap: wrap;
    gap: 0.75rem;
  }

  .branch-visit .location-card__actions .btn {
    flex: 1 1 auto;
  }
}

/* --- /catering/ page: the Apple-craft restructure (2026-09-23) ---------------
   Client: "/catering/ ... need restructure apply it apple style" with one real
   buffet photograph ("only use with image"). Interview: WHOLE page, and the
   page's copy is FROZEN word-for-word (pinned by check_catering_page.py).

   "APPLE STYLE" READ AS APPLE'S DESIGN DISCIPLINE INSIDE THE KING CHEF BRAND:
   generous whitespace, large optical type with size-appropriate tracking, one
   soft-deep shadow, fluid hover/press motion on the house curve (--ease-out IS
   Apple's cubic-bezier(0.16, 1, 0.3, 1); .btn:active already presses to 0.98),
   reduced-motion honoured. NOT a light apple.com re-skin (offered in the
   interview, not chosen - flagged in the pass record).

   THE PHOTO MAPPING LIVES IN EXACTLY TWO PLACES SITE-WIDE: the MASTER constant
   in .cortex/build_catering_assets.py and EXPECTED in
   .cortex/check_catering_page.py. The served file is
   img/catering/catering-spread.webp at 1500x1000 = 3:2 EXACTLY (the ratio of
   the client's own 1536x1024 buffet master, so the resize crops nothing),
   which is why this plate's object-fit: cover crops nothing and the tag's
   declared width/height can be real bytes. The plate FOLLOWS the master's
   ratio: a 4:3 crop of a 3:2 spread would slice food off both sides.

   EVERY SELECTOR HERE IS PAGE-SCOPED ON PURPOSE. .grid-split, .page-header,
   .catering-item and .cta-band are shared with the homepage catering band and
   other pages whose gates pin them (check_catering_figure,
   check_delivery_banner_scope, ...). Additive-on-shared-classes only.

   THE SPECIFICITY TRAP, defused in the 1024px block below: the scoped track
   override is (0,2,0) and the GLOBAL rule that stacks .grid-split is (0,1,0)
   inside `@media (max-width: 1024px)`. A higher-specificity track beats that
   media rule regardless of source order, so without the re-declaration the
   phone page would keep two columns. Do not "simplify" the pair away.
   --------------------------------------------------------------------------- */

.catering-header {
  padding: clamp(5.5rem, 8vw, 7.5rem) 0 var(--space-3xl);
}

.catering-header .page-header__title {
  /* apple-design: display type tightens as it grows; body stays near 0. */
  letter-spacing: -0.02em;
  max-width: 24ch;
}

.grid-split.catering-intro__split {
  /* The photograph gets the room - the product shot leads an Apple page. */
  grid-template-columns: 1fr 1.1fr;
  align-items: center;
}

.catering-intro__copy p {
  /* Reading measure, not full track width. */
  max-width: 58ch;
}

.catering-plate {
  margin: 0;
  position: relative;
  overflow: hidden;
  /* 24px: no --radius-xl token exists and Apple's plate rounding is generous.
     This literal is pinned by the gate. */
  border-radius: 24px;
  aspect-ratio: 3 / 2;
  border: 1px solid var(--color-border);
  /* One soft-deep shadow: the plate floats a step above the flat band. */
  box-shadow: 0 30px 60px -30px rgba(0, 0, 0, 0.55);
}

.catering-plate img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: transform 0.8s var(--ease-out);
}

.catering-plate:hover img {
  transform: scale(1.03);
}

.catering-grid--page {
  /* EQUAL TRACKS + 1fr ROWS = equal cards. The default `auto` row sizes to
     content, so one longer description makes its card taller (the perks and
     location-grid lesson). */
  grid-template-columns: repeat(3, minmax(0, 1fr));
  grid-auto-rows: 1fr;
}

.catering-occasions .catering-item {
  /* THE ONE PADDING SOURCE for this icon variant. The card base carries no
     padding of its own (padding lives on .catering-item__body, which the photo
     variant uses) and this page's items never had a body wrapper: their
     content sat flush against the border - the careers-perks empty-box trap. */
  padding: 1.5rem 1.6rem 1.6rem;
}

.catering-occasions .catering-item__title {
  font-size: var(--text-xl);
  letter-spacing: -0.01em;
  margin-bottom: 0.45rem;
}

.catering-cta h2 {
  letter-spacing: -0.02em;
}

.catering-cta p {
  max-width: 56ch;
}

@media (max-width: 1024px) {
  /* Re-declares the GLOBAL .grid-split stacking AT ITS OWN BREAKPOINT for the
     higher-specificity override above (see the specificity trap note). */
  .grid-split.catering-intro__split {
    grid-template-columns: 1fr;
    gap: var(--space-2xl);
  }

  .catering-grid--page {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

@media (max-width: 768px) {
  .catering-grid--page {
    grid-template-columns: 1fr;
  }
}

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

  .catering-plate:hover img {
    transform: none;
  }
}

