/* ===== Kenora — public site =====
   Brand tokens (--burgundy, --gold, --font-en …) are injected per-request by
   the renderer from the dashboard's theme settings, so this file never
   hard-codes a brand colour or typeface.

   Design language: editorial luxury. Space, restraint and typography carry
   the value signal — not effects. See docs/design-system.md.

   THE CONTRAST CONTRACT: gold (#D4AF37) scores 8.28 on black and 4.87 on
   burgundy, but only 1.82 on ivory. Gold text/rules therefore appear ONLY on
   dark grounds; on light grounds the accent role belongs to burgundy. Every
   gold rule below is scoped accordingly — please keep it that way. */

/* ===== Design tokens (fixed decisions, not brand settings) ===== */
:root {
  /* Space. Luxury reads as space; these are deliberately generous. */
  --space-2xs: 8px;
  --space-xs: 16px;
  --space-sm: 24px;
  --space-md: 40px;
  --space-lg: 64px;
  --space-xl: 96px;
  --space-2xl: 140px;
  --space-3xl: 200px;

  /* Vertical rhythm for a full-width section. */
  --sec-pad: clamp(96px, 14vw, 200px);
  --gutter: clamp(24px, 6vw, 88px);
  --measure: 68ch;

  /* Type ramp — fluid, display-led. */
  --t-display: clamp(2.6rem, 7vw, 6rem);
  --t-h2: clamp(2rem, 4.4vw, 3.5rem);
  --t-h3: clamp(1.25rem, 2vw, 1.6rem);
  --t-body: var(--base-size, 16px);
  --t-small: calc(var(--base-size, 16px) * .875);
  --t-micro: calc(var(--base-size, 16px) * .7);

  /* Motion — "Subtle" tier. Transform/opacity only. */
  --ease-out: cubic-bezier(.16, 1, .3, 1);
  --dur-fast: 200ms;
  --dur-mid: 400ms;
  --dur-slow: 600ms;

  --rule: 1px;
  --z-header: 100;
  --z-mobile-nav: 120;

  /* Radii. Soft corners are what separate a modern luxury layout from a
     stark editorial one — the previous pass used 0 everywhere and read as
     austere rather than expensive. */
  --r-sm: 10px;
  --r-md: 16px;
  --r-lg: 24px;
  --r-xl: 32px;
  --r-pill: 999px;
}

/* ===== Guest order tally =====
   A private, browser-only checklist. The fixed bar keeps one stable height;
   body padding plus the safe area keeps the final menu row reachable. */
.dish-foot {
  display: flex; align-items: center; justify-content: space-between;
  gap: var(--space-xs); margin-top: var(--space-2xs);
}
.dish-foot .price { margin-top: 0; }
.dish-add { display: flex; align-items: center; gap: 2px; margin-inline-start: auto; }
.dish-add .qty-less, .dish-add .qty { display: none; }
.dish-add.has-qty .qty-less, .dish-add.has-qty .qty { display: inline-flex; }
.qty-btn {
  appearance: none; -webkit-appearance: none;
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 44px; min-height: 44px; padding: 0;
  border: var(--rule) solid color-mix(in srgb, var(--burgundy) 30%, transparent);
  border-radius: var(--r-pill); background: none; cursor: pointer;
  font: inherit; font-size: 1.25rem; line-height: 1; color: var(--burgundy);
  transition: background var(--dur-fast) var(--ease-out);
}
.qty-btn:hover { background: color-mix(in srgb, var(--burgundy) 8%, transparent); }
.qty-btn:active { transform: scale(.96); }
.dish-add .qty-more { background: var(--burgundy); color: var(--ivory); border-color: var(--burgundy); }
.dish-add .qty {
  align-items: center; justify-content: center; min-width: 34px;
  font-family: var(--font-en); font-size: var(--t-small); font-weight: 600;
  color: var(--ink-strong, var(--black)); text-align: center;
}
.order-bar { position: fixed; inset-inline: 0; bottom: 0; z-index: 110; }
.order-bar-btn {
  appearance: none; -webkit-appearance: none;
  display: flex; align-items: center; gap: 12px; width: 100%;
  min-height: 64px; border: 0; cursor: pointer;
  --bar-pad: 16px;
  padding: var(--bar-pad) var(--gutter); padding-bottom: var(--bar-pad);
  border-radius: var(--r-lg) var(--r-lg) 0 0;
  --bar-bg: var(--burgundy); background: var(--bar-bg);
  box-shadow: 0 120px 0 0 var(--bar-bg); color: var(--ivory);
  font-family: var(--font-en); font-size: var(--t-small); font-weight: 600;
  letter-spacing: .08em; text-transform: uppercase; text-align: start;
}
html[dir="rtl"] .order-bar-btn { font-family: var(--font-ar); }
.order-count {
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 28px; height: 28px; padding-inline: 7px; border-radius: var(--r-pill);
  background: var(--gold); color: var(--black); font-size: var(--t-micro);
}
.order-bar-total { margin-inline-start: auto; font-size: var(--t-small); }
body.has-order-bar {
  padding-bottom: calc(var(--order-bar-h, 64px) + var(--space-sm) + env(safe-area-inset-bottom));
}
.order-scrim {
  position: fixed; inset: 0; z-index: 118;
  background: color-mix(in srgb, var(--black) 55%, transparent);
}
.order-sheet {
  position: fixed; inset-inline: 0; bottom: 0; z-index: 120;
  max-height: 82vh; overflow-y: auto; overscroll-behavior: contain;
  padding: var(--space-sm) var(--gutter) calc(var(--space-md) + env(safe-area-inset-bottom));
  background: var(--ivory); color: var(--black);
  border-radius: var(--r-lg) var(--r-lg) 0 0;
}
.order-sheet-head { display: flex; align-items: center; justify-content: space-between; }
.order-sheet-head h2 { font-size: var(--t-h3); font-weight: 400; }
.order-close {
  appearance: none; -webkit-appearance: none;
  min-width: 44px; min-height: 44px; border: 0; background: none;
  font-size: 1.6rem; line-height: 1; color: var(--black); cursor: pointer;
}
.order-line {
  display: grid; grid-template-columns: minmax(0, 1fr) auto auto; align-items: center;
  gap: var(--space-xs); padding-block: 12px;
  border-bottom: var(--rule) solid color-mix(in srgb, var(--black) 10%, transparent);
}
.order-line-name { min-width: 0; font-size: var(--t-small); line-height: 1.4; }
.order-line-cat {
  display: block; margin-top: 2px; font-family: var(--font-en);
  font-size: var(--t-micro); letter-spacing: .1em; text-transform: uppercase;
  color: color-mix(in srgb, var(--black) 62%, var(--ivory));
}
html[dir="rtl"] .order-line-cat { font-family: var(--font-ar); }
.order-line-price, .order-total-value {
  font-family: var(--font-en); font-weight: 600; color: var(--burgundy); white-space: nowrap;
}
.order-line-price { font-size: var(--t-small); }
.order-total-value { font-size: var(--t-h3); }
.order-empty, .order-unpriced, .order-note {
  font-size: var(--t-small); color: color-mix(in srgb, var(--black) 62%, var(--ivory));
  margin-top: var(--space-xs);
}
.order-total {
  display: flex; align-items: baseline; justify-content: space-between; margin-top: var(--space-sm);
  font-family: var(--font-display-en); font-size: var(--t-h3);
}
html[dir="rtl"] .order-total { font-family: var(--font-display-ar); }
#orderSheet .order-clear {
  margin-top: var(--space-sm); background: none; color: var(--burgundy);
  border: var(--rule) solid color-mix(in srgb, var(--burgundy) 38%, transparent);
}
#orderSheet .order-clear:hover { background: color-mix(in srgb, var(--burgundy) 8%, transparent); border-color: var(--burgundy); }
@media (max-width: 520px) {
  .order-line { grid-template-columns: minmax(0, 1fr) auto; }
  .order-line-price { grid-column: 1; }
  .order-line > .dish-add { grid-column: 2; grid-row: 1 / span 2; }
}
@media (prefers-reduced-motion: reduce) {
  .qty-btn { transition: none; }
  .qty-btn:active { transform: none; }
}

/* Surfaces derived from the brand colours. A "panel" is a lifted card on a
   dark ground: a hint of burgundy warmth over black, with a gold hairline.
   These are what the reference layout builds every card from. */
:root {
  --deep: color-mix(in srgb, var(--burgundy) 14%, var(--black));
  --deep-2: color-mix(in srgb, var(--burgundy) 26%, var(--black));
  --panel: color-mix(in srgb, var(--burgundy) 20%, var(--black));
  --panel-hi: color-mix(in srgb, var(--burgundy) 32%, var(--black));
  --hairline: color-mix(in srgb, var(--gold) 20%, transparent);
  --hairline-hi: color-mix(in srgb, var(--gold) 42%, transparent);
}

* { box-sizing: border-box; margin: 0; padding: 0; }
html { scroll-behavior: smooth; -webkit-text-size-adjust: 100%; }

body {
  font-family: var(--font-en);
  font-size: var(--base-size, 16px);
  color: var(--black);
  background: var(--ivory);
  line-height: 1.7;
  overflow-x: hidden;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  font-kerning: normal;
}
html[dir="rtl"] body { font-family: var(--font-ar); }

/* Arabic is cursive: the letters of a word join, and the join is part of the
   letterform rather than a gap between two of them. `letter-spacing` opens
   every one of those joins and the connecting stroke drawn across the gap
   reads as a dash -- الحجـــز where the word is الحجز. There is no amount of
   it that is safe, so no Arabic on this site carries any, whatever the Latin
   rule beside it asks for. See CLAUDE.md and docs/gotchas.md.

   `!important` and the universal selector, deliberately: this is a property of
   the script, not a decision about one component, and the failure is silent --
   it looks like a font problem, not a spacing one. Rules for Latin text keep
   being written with tracking, as they should be, and every one of them would
   otherwise have to remember to undo itself here. To make an Arabic word
   wider, set it larger; the joins stay.

   The cost is the handful of Latin fragments on an Arabic page -- EN on the
   language switch, a figure beside a unit -- losing their tracking. That is a
   smaller price than a broken word. */
html[dir="rtl"] *,
html[dir="rtl"] *::before,
html[dir="rtl"] *::after { letter-spacing: normal !important; }

img { max-width: 100%; display: block; }
a { color: inherit; text-decoration: none; }

/* Headings use the display face. This single rule is the largest visual
   change on the site: a serif at scale is what separates "fine dining" from
   "web template". */
h1, h2, h3, h4 {
  font-family: var(--font-display-en);
  font-weight: 400;
  letter-spacing: var(--heading-ls, .01em);
  line-height: 1.12;
  text-wrap: balance;
}
html[dir="rtl"] h1,
html[dir="rtl"] h2,
html[dir="rtl"] h3,
html[dir="rtl"] h4 { font-family: var(--font-display-ar); line-height: 1.35; }

p { text-wrap: pretty; }

:focus-visible { outline: 2px solid var(--gold); outline-offset: 3px; }
.sec--light :focus-visible { outline-color: var(--burgundy); }

::selection { background: var(--burgundy); color: var(--ivory); }

/* The editorial micro-label. Wide tracking is the whole effect — it must stay
   uppercase and small for the letterspacing to read as intentional. */
.eyebrow {
  display: block;
  font-family: var(--font-en);
  font-size: var(--t-micro);
  letter-spacing: .32em;
  text-transform: uppercase;
  color: var(--gold);
  font-weight: 600;
  margin-bottom: var(--space-sm);
}
html[dir="rtl"] .eyebrow { font-family: var(--font-ar); letter-spacing: .12em; }
.sec--light .eyebrow { color: var(--burgundy); } /* gold fails contrast here */
/* The burgundy ground is a gradient whose lightest stop is --burgundy-lift.
   Plain gold measures only 3.60 against it — below the 4.5 needed for small
   text — so every gold TEXT role on burgundy steps up to gold-soft (5.09).
   Large display type and decorative rules stay on plain gold: 3.60 already
   clears the 3:1 bar those are held to. */
.sec--burgundy .eyebrow,
.sec--burgundy .sec-index,
.sec--burgundy .rte a,
.sec--burgundy .menu-card .cat,
.sec--burgundy .menu-card .price,
.sec--burgundy .contact-col a:hover { color: var(--gold-soft); }

/* Editorial section index — 01 / 02 / 03. Latin numerals stay LTR even in an
   RTL page, which is correct practice for Arabic set in this style. */
.sec-index {
  display: block;
  font-family: var(--font-display-en);
  font-size: var(--t-small);
  letter-spacing: .2em;
  color: var(--gold);
  opacity: 1;
  margin-bottom: var(--space-xs);
  direction: ltr;
  unicode-bidi: isolate;
}
/* LTR numerals, but on the heading's side: `direction: ltr` alone also moves
   the block's start edge, which parked the index at the far left of an Arabic
   heading that sits on the right. */
html[dir="rtl"] .sec-index { text-align: right; }
.sec--light .sec-index { color: var(--burgundy); opacity: 1; }

/* Hairline rules carry the structure — no boxes, no shadows. */
.hairline {
  display: block; height: var(--rule); border: 0;
  background: currentColor; opacity: .18;
}

/* ===== Scroll reveal =====
   Content is VISIBLE by default. The hidden state is gated behind
   html.anim-ready, which a tiny inline bootstrap in <head> sets only when JS
   is running and motion is allowed — and which it removes again if main.js
   never initialises. So no-JS, a blocked script, a JS error or reduced motion
   all render the full page rather than a blank one. */
html.anim-ready .reveal { opacity: 0; transform: translateY(14px); }
html.anim-ready .reveal.in {
  opacity: 1; transform: none;
  transition: opacity var(--dur-slow) var(--ease-out),
              transform var(--dur-slow) var(--ease-out);
}

/* ===== Header ===== */
.page-lang-toggle {
  position: fixed;
  inset-block-start: max(14px, env(safe-area-inset-top));
  inset-inline-end: 18px;
  z-index: var(--z-header);
  display: inline-grid;
  place-items: center;
  min-width: 44px;
  min-height: 44px;
  border: 1px solid var(--gold);
  border-radius: 999px;
  background: var(--burgundy);
  color: var(--ivory);
  font-size: 13px;
  font-weight: 700;
  letter-spacing: .12em;
  text-decoration: none;
}
.page-lang-toggle:focus-visible {
  outline: 3px solid var(--ivory);
  outline-offset: 3px;
}

.site-header {
  position: absolute; top: 0; left: 0; right: 0; z-index: var(--z-header);
  height: var(--header-h);
  display: flex; align-items: center;
  transition: background var(--dur-mid) var(--ease-out),
              border-color var(--dur-mid) var(--ease-out);
  border-bottom: var(--rule) solid transparent;
}
.site-header.is-sticky { position: fixed; }
/* A very see-through white -- 22%, so the page reads through it as a veil
   rather than as a bar. The blur is what stops that veil turning into a smear
   of whatever scrolls under it. Background only: the mark, the nav, the two
   pills and the hairline are all deliberately untouched. */
.site-header.scrolled {
  background: color-mix(in srgb, #fff 22%, transparent);
  backdrop-filter: blur(14px) saturate(1.2);
  border-bottom-color: color-mix(in srgb, var(--gold) 22%, transparent);
}
/* Nav split around a centred logo badge. The grid keeps the badge optically
   centred regardless of how long the link labels are on either side. */
.header-inner {
  width: 100%; max-width: 1440px; margin: 0 auto;
  padding-inline: var(--gutter);
  display: grid; align-items: center; gap: var(--space-sm);
  grid-template-columns: 1fr auto 1fr auto;
  position: relative;
}
.nav-start { justify-content: flex-end; }
.nav-end { justify-content: flex-start; }

/* A bare wrapper, not a badge. The gold-edged tile that used to sit behind the
   mark is gone at every width; the logo stands on its own.
   It also took the brand block over the header: 55px of logo plus 10px of
   padding top and bottom and a hairline came to 77px inside a 64px bar, so the
   tile overhung the band by 6px at each edge. Without it the mark is 55px and
   sits inside the header with room to spare. */
.brand-badge {
  display: grid; place-items: center;
  background: none; border: 0; padding: 0; border-radius: 0;
  transition: opacity var(--dur-fast) var(--ease-out);
}
/* The link still needs to answer a hover now that there is no tile to light
   up, so the mark itself lifts slightly instead. */
.brand-mark:hover .brand-badge {
  opacity: .78;
}
.brand-badge img { width: auto; display: block; }
/* min-height keeps the logo link at the 44px touch minimum even when the
   logo image itself is set smaller in the dashboard. */
/* min-width/height keep the logo link at the 44px touch minimum even when the
   logo image is set smaller in the dashboard, or when the wordmark beside it
   is hidden at phone widths. */
.brand-mark {
  display: flex; align-items: center; gap: 14px; flex-shrink: 0;
  min-height: 44px; min-width: 44px; flex-direction: column;
}
.brand-mark img { width: auto; }
.wordmark { display: flex; flex-direction: column; line-height: 1; text-align: center; }
.wordmark span:first-child {
  font-family: var(--font-en);
  font-size: .78rem; font-weight: 600; letter-spacing: .34em; color: var(--ink);
}
html[dir="rtl"] .wordmark span:first-child { font-family: var(--font-ar); letter-spacing: .1em; }
.wordmark span:last-child {
  font-size: .52rem; letter-spacing: .26em; color: var(--gold);
  margin-top: 6px; text-transform: uppercase;
}

.main-nav { display: flex; align-items: center; gap: var(--space-md); }
.nav-link {
  font-size: var(--t-small); letter-spacing: .12em; color: var(--ink);
  font-weight: 400; text-transform: uppercase; position: relative; padding: 8px 0;
}
.nav-link::after {
  content: ''; position: absolute; inset-inline-start: 0; bottom: 0;
  height: var(--rule); width: 0; background: var(--gold);
  transition: width var(--dur-mid) var(--ease-out);
}
.nav-link:hover::after, .nav-link:focus-visible::after { width: 100%; }

.header-actions { display: flex; align-items: center; gap: var(--space-xs); }
/* The white counterpart to the gold Reservation pill. They sit side by side in
   the header and have to read as a pair -- a bordered rectangle beside a filled
   pill reads as a button next to a leftover input. Same shape at every width;
   the mobile block only adjusts the sizing it shares with .btn-reserve. */
.lang-toggle {
  display: inline-flex; align-items: center; justify-content: center;
  font-size: var(--t-micro); font-weight: 600; letter-spacing: .18em;
  text-transform: uppercase;
  background: var(--ivory); color: var(--deep);
  border: var(--rule) solid var(--ivory); border-radius: var(--r-pill);
  /* 48px matches .btn-reserve exactly. At 44 the two pills sat side by side
     at visibly different heights, which is the mismatch the pill shape was
     meant to remove. */
  padding: 0 18px; min-height: 48px; cursor: pointer;
  white-space: nowrap;
  transition: background var(--dur-fast) var(--ease-out),
              border-color var(--dur-fast) var(--ease-out),
              color var(--dur-fast) var(--ease-out);
}
.lang-toggle:hover { background: var(--ivory); border-color: var(--ivory); color: var(--deep); }

/* ===== Buttons =====
   Pill-shaped with a warm gold fill. The reserve button is the site's single
   primary action, so only it gets the solid treatment. */
.btn-reserve, .btn-outline, .btn-dark {
  display: inline-flex; align-items: center; justify-content: center;
  font-family: var(--font-en);
  font-weight: 600; font-size: var(--t-micro); letter-spacing: .2em;
  text-transform: uppercase; white-space: nowrap;
  padding: 16px 34px; min-height: 48px; border-radius: var(--r-pill);
  transition: background var(--dur-fast) var(--ease-out),
              color var(--dur-fast) var(--ease-out),
              border-color var(--dur-fast) var(--ease-out),
              transform var(--dur-fast) var(--ease-out);
}
.btn-reserve:active, .btn-outline:active, .btn-dark:active { transform: scale(.97); }
html[dir="rtl"] .btn-reserve,
html[dir="rtl"] .btn-outline,
html[dir="rtl"] .btn-dark { font-family: var(--font-ar); letter-spacing: .08em; }

.btn-reserve {
  background: linear-gradient(140deg, var(--gold-soft), var(--gold));
  color: var(--black);
}
.btn-reserve:hover { background: var(--gold-soft); }
.btn-outline {
  border: var(--rule) solid color-mix(in srgb, var(--ink) 38%, transparent);
  color: var(--ink);
}
.btn-outline:hover { border-color: var(--gold); color: var(--gold); }
.sec--light .btn-outline { border-color: color-mix(in srgb, var(--black) 28%, transparent); color: var(--black); }
.sec--light .btn-outline:hover { border-color: var(--burgundy); color: var(--burgundy); }
.sec--burgundy .btn-outline:hover { border-color: var(--gold-soft); color: var(--gold-soft); }
.btn-dark { background: var(--black); color: var(--ink); }
.btn-dark:hover { background: var(--burgundy); }
.btn-row { display: flex; gap: var(--space-xs); flex-wrap: wrap; margin-top: var(--space-md); }
.sec-inner--narrow .btn-row, .hero-content .btn-row { justify-content: center; }

.menu-toggle {
  display: none; flex-direction: column; gap: 6px; background: none; border: 0;
  cursor: pointer; padding: 12px; min-width: 48px; min-height: 48px;
  align-items: center; justify-content: center;
}
.menu-toggle span {
  width: 26px; height: var(--rule); background: var(--ink);
  transition: transform var(--dur-mid) var(--ease-out), opacity var(--dur-mid) var(--ease-out);
}

.mobile-nav {
  position: fixed; inset-block: 0; inset-inline-end: 0;
  width: min(75%, 420px); background: var(--black); z-index: var(--z-mobile-nav);
  display: flex; flex-direction: column; align-items: flex-start; justify-content: center;
  gap: var(--space-sm); padding: var(--space-xl) var(--gutter);
  transform: translateX(100%); transition: transform var(--dur-slow) var(--ease-out);
  visibility: hidden;
  overflow-y: auto; overscroll-behavior: contain;
}
html[dir="rtl"] .mobile-nav { transform: translateX(-100%); }
.mobile-nav.open, html[dir="rtl"] .mobile-nav.open { transform: translateX(0); visibility: visible; }
.mobile-nav-scrim {
  position: fixed; inset: 0; z-index: calc(var(--z-mobile-nav) - 1);
  background: color-mix(in srgb, var(--black) 62%, transparent);
  opacity: 0; visibility: hidden;
  transition: opacity var(--dur-slow) var(--ease-out), visibility var(--dur-slow);
}
.mobile-nav-scrim.open { opacity: 1; visibility: visible; }
.mobile-nav a {
  display: flex; align-items: center; justify-content: flex-start;
  font-family: var(--font-display-en); text-align: start;
  font-size: var(--menu-size, clamp(1.5rem, 5.5vw, 2.1rem)); color: var(--ink); font-weight: 400;
  /* The display face's line box lands ~43.5px at phone sizes — half a pixel
     under the touch minimum. Pin it rather than rely on line-height. */
  min-height: 48px; padding-inline: var(--space-xs);
}
html[dir="rtl"] .mobile-nav a { font-family: var(--font-display-ar); }
.mobile-nav .btn-reserve {
  margin-top: var(--space-sm); font-family: var(--font-en);
  font-size: var(--t-micro); align-self: flex-start;
}
html[dir="rtl"] .mobile-nav .btn-reserve { font-family: var(--font-ar); }
.mobile-close {
  position: absolute; top: var(--space-sm); inset-inline-end: var(--gutter);
  background: none; border: 0; color: var(--ink); font-size: 2rem; cursor: pointer;
  line-height: 1; min-width: 48px; min-height: 48px;
}

/* ===== Hero =====
   Two modes. `.hero--photo` is the cinematic treatment: a full-bleed
   photograph under a scrim, which is what makes the page read as expensive
   from the first second. `.hero--pattern` is the fallback when no photo is
   set, using the brand's own arc/ripple motif. */
/* Deliberately NOT 100svh. A full-height hero pushed the buttons off screen:
   measured at 1138px tall in a 602px viewport, with the CTA 440px below the
   fold. The hero's job is to introduce the restaurant AND show the reserve
   button, so it is sized to its copy with a ceiling, and grows only if the
   content genuinely needs more. */
.hero {
  /* Phones and tablets: a full screen. `svh` rather than `vh` deliberately --
     svh is the viewport with the browser chrome showing, so the hero fills the
     screen on load instead of running under the URL bar and pushing the CTA
     out of reach. No floor and no ceiling: a viewport shorter than this simply
     gets a hero as tall as its copy. Desktop is also 100svh, below. */
  position: relative;
  /* Two declarations on purpose, in this order.
     `svh` is the viewport assuming the browser chrome is at its LARGEST. On
     iOS Safari the URL bar has a large state and a compact one, and if the
     chrome is already partly compact at load, the visible area is taller than
     svh -- which left a strip of the next section showing above the toolbar.
     `dvh` is the CURRENT viewport and tracks the chrome as it resizes, so it
     fills whatever is actually on screen. svh stays first as the fallback for
     engines without dvh. */
  min-height: 100svh;
  min-height: 100dvh;
  background: var(--black);
  display: flex; align-items: center; overflow: hidden; perspective: 1400px;
}

.hero-media { position: absolute; inset: 0; z-index: 0; }
.hero-media picture, .hero-media img {
  width: 100%; height: 100%; object-fit: cover; display: block;
}
/* A slow drift keeps the still photograph feeling alive without asking the
   visitor to do anything. Paused under reduced motion. */
.hero--photo .hero-media img {
  animation: heroDrift 26s ease-in-out infinite alternate;
  transform-origin: center 55%;
}
@keyframes heroDrift { from { transform: scale(1.04); } to { transform: scale(1.12); } }

/* A centred photo puts copy across unpredictable pixels, so its author control
   is a bounded uniform treatment (65–90%). The editorial layout below uses a
   fixed directional panel because its copy position is known. */
.hero-scrim {
  position: absolute; inset: 0;
  background: color-mix(in srgb, var(--black) calc(var(--scrim, .70) * 100%), transparent);
}
.hero--editorial .hero-scrim {
  background:
    linear-gradient(to var(--hero-copy-edge, right),
      color-mix(in srgb, var(--black) calc(var(--scrim, .55) * 130%), transparent) 0%,
      color-mix(in srgb, var(--black) calc(var(--scrim, .55) * 88%), transparent) 42%,
      transparent 82%),
    linear-gradient(to bottom,
      color-mix(in srgb, var(--black) 55%, transparent) 0%, transparent 30%,
      transparent 62%, color-mix(in srgb, var(--black) 88%, transparent) 100%);
}
html[dir="rtl"] .hero--editorial { --hero-copy-edge: left; }

/* With a full-bleed photograph the pattern layer would fight the image. */
.hero--editorial .hero-scene, .hero--centered.hero--photo .hero-scene {
  opacity: .28; mix-blend-mode: soft-light;
}

/* ===== Hero: floating plate =====
   Copy one side, the dish unframed on a gradient ground the other. This is
   the treatment that does not depend on the photograph happening to have
   empty space where the headline goes. */
.hero--plate {
  background:
    radial-gradient(90% 70% at 78% 42%, var(--deep-2) 0%, transparent 60%),
    linear-gradient(160deg, var(--black) 0%, var(--deep) 52%, var(--black) 100%);
}
.hero-plate-grid {
  position: relative; z-index: 3;
  width: 100%; max-width: 1440px; margin: 0 auto;
  padding: calc(var(--header-h) + var(--space-lg)) var(--gutter) var(--space-xl);
  display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1.02fr);
  gap: var(--space-lg) var(--space-xl); align-items: center;
}
.hero--plate .hero-content {
  padding: 0; text-align: start; align-items: flex-start; max-width: none;
}
.hero--plate .hero-content h1 { max-width: 14ch; }
.hero--plate .tagline-sub { margin-inline: 0; }
.hero--plate .btn-row { justify-content: flex-start; }
.hero--plate .hero-content .btn-row::before { left: 0; transform: none; }
html[dir="rtl"] .hero--plate .hero-content .btn-row::before { left: auto; right: 0; }

.hero-plate { position: relative; display: grid; place-items: center; }
.hero-plate-img {
  /* height:auto is load-bearing. The <img> carries width/height attributes to
     reserve layout space; without height:auto the attribute height wins, and
     aspect-ratio is ignored entirely — which rendered a 336×804 slab here. */
  position: relative; width: 100%; height: auto; aspect-ratio: 1; object-fit: cover;
  border-radius: var(--r-pill);
  border: var(--rule) solid var(--hairline-hi);
  animation: platedrift 18s ease-in-out infinite alternate;
}
@keyframes platedrift {
  from { transform: translateY(-8px) rotate(-1deg); }
  to   { transform: translateY(8px) rotate(1deg); }
}
.hero-plate picture { display: block; width: 100%; position: relative; }
.hero-scene {
  position: absolute; inset: 0; transform-style: preserve-3d;
  transition: transform var(--dur-slow) var(--ease-out);
}
.hero-pattern {
  position: absolute; inset: -12%; opacity: .10;
  background: url('/patterns/pattern-on-black.png') repeat; background-size: 460px;
  will-change: transform;
}
.hero-pattern.p2 { opacity: .16; background-size: 680px; }

/* Vignette: pulls focus to the centred lockup and guarantees text contrast
   over the pattern without darkening the pattern itself. */
.hero-scene::after {
  content: ''; position: absolute; inset: 0;
  background: radial-gradient(ellipse at center,
    transparent 30%, color-mix(in srgb, var(--black) 82%, transparent) 100%);
}

.hero-content {
  position: relative; z-index: 3; width: 100%; max-width: 1100px; margin: 0 auto;
  padding: calc(var(--header-h) + var(--space-xl)) var(--gutter) var(--space-2xl);
  text-align: center; display: flex; flex-direction: column; align-items: center;
}

/* Editorial hero: copy anchored to the start edge over the darkened side of
   the photograph, headline running large. */
.hero--editorial .hero-content {
  max-width: 1440px; text-align: start; align-items: flex-start;
  /* The old 2xl bottom padding alone was 140px, which on a laptop viewport
     was most of the reason the CTA fell below the fold. */
  padding-block: calc(var(--header-h) + var(--space-md)) var(--space-lg);
}
.hero--editorial .hero-copy { max-width: 58ch; }
.hero--editorial .hero-logo { margin-bottom: var(--space-sm); }
.hero--editorial .hero-content h1 { max-width: 15ch; }
.hero--editorial .tagline-sub { margin-inline: 0; }
.hero--editorial .btn-row { justify-content: flex-start; }
.hero--editorial .hero-content .btn-row::before { left: 0; transform: none; }
html[dir="rtl"] .hero--editorial .hero-content .btn-row::before { left: auto; right: 0; }
.hero-logo { height: auto; margin-bottom: var(--space-md); opacity: .96; }
.hero-content h1 {
  color: var(--ink); font-size: var(--t-display);
  max-width: 18ch; line-height: 1.05; margin: 0 0 var(--space-sm);
  font-weight: 400;
}
.hero-ar {
  font-family: var(--font-display-ar); color: var(--gold);
  font-size: var(--t-h3); margin-bottom: var(--space-sm);
  direction: rtl;
}
.tagline-sub {
  color: var(--ink-dim); max-width: 52ch; font-size: var(--t-body);
  line-height: 1.8;
}

/* Gold hairline under the hero lockup — a single decisive stroke. */
.hero-content .btn-row::before {
  content: ''; position: absolute; top: calc(var(--space-md) * -1); left: 50%;
  transform: translateX(-50%);
  width: 56px; height: var(--rule); background: var(--gold); opacity: .6;
}
.hero-content .btn-row { position: relative; margin-top: var(--space-xl); }

.scroll-hint {
  position: absolute; bottom: var(--space-md); left: 50%; transform: translateX(-50%);
  color: var(--ink-dim); font-size: var(--t-micro); letter-spacing: .28em;
  text-transform: uppercase; display: flex; flex-direction: column;
  align-items: center; gap: var(--space-2xs); z-index: 3;
}
.scroll-hint .line {
  width: var(--rule); height: 40px;
  background: linear-gradient(var(--gold), transparent);
  animation: scrollpulse 2.4s var(--ease-out) infinite;
}
@keyframes scrollpulse { 0%, 100% { opacity: .25; } 50% { opacity: 1; } }

/* ===== Generic section shell ===== */
.sec { position: relative; overflow: hidden; padding: var(--sec-pad) var(--gutter); }
.sec--light { background: var(--ivory); color: var(--black); }
/* Dark sections are a deep burgundy-black gradient rather than flat black —
   the saturated ground is what makes gold and photography glow against it. */
.sec--dark {
  background:
    radial-gradient(120% 80% at 50% 0%, var(--deep-2) 0%, transparent 62%),
    linear-gradient(180deg, var(--deep) 0%, var(--black) 100%);
  color: var(--ink);
}
.sec--burgundy {
  background:
    radial-gradient(110% 75% at 50% 0%, var(--burgundy-lift) 0%, transparent 60%),
    linear-gradient(180deg, var(--burgundy) 0%, var(--burgundy-deep) 100%);
  color: var(--ink);
}
.sec--dark h2, .sec--burgundy h2 { color: var(--ink); }
.sec--dark p, .sec--burgundy p { color: color-mix(in srgb, var(--ink) 78%, transparent); }
.sec--light p { color: var(--black); }

.pattern-bg {
  position: absolute; inset: 0; opacity: .05;
  background-repeat: repeat; background-size: 420px;
}
.sec--dark .pattern-bg, .sec--burgundy .pattern-bg { opacity: .09; }

.sec-inner { position: relative; z-index: 1; max-width: 1280px; margin: 0 auto; }
.sec-inner--narrow { max-width: 720px; text-align: center; }
.sec h2 { font-size: var(--t-h2); margin: 0; font-weight: 400; }
.sec-inner--narrow h2 { margin-bottom: var(--space-sm); }
.sec-inner--narrow > p { font-size: var(--t-body); }

/* Asymmetric editorial header. Centring every heading was the previous
   build's strongest "template" tell — this splits heading and supporting
   copy into an off-balance two-column arrangement that collapses cleanly. */
.sec-head {
  display: grid; grid-template-columns: minmax(0, 1.1fr) minmax(0, 1fr);
  gap: var(--space-md) var(--space-xl);
  align-items: end;
  margin-bottom: var(--space-xl);
  padding-bottom: var(--space-md);
  border-bottom: var(--rule) solid currentColor;
  border-color: color-mix(in srgb, currentColor 15%, transparent);
}
.sec-head > *:only-child { grid-column: 1 / -1; }
/* A category page's h1 wears the section heading's size, not the hero's. */
.sec .menu-cat-intro h1 { font-size: var(--t-h2); margin: 0; font-weight: 400; }
.menu-cat-lede { margin: var(--space-xs) 0 0; font-size: var(--t-body); line-height: 1.8; max-width: var(--measure); }
/* While the category heading shows, the one visible block is that category:
   its own title would repeat the h1 directly below it. */
.menu-cat-intro:not([hidden]) ~ .menu-rows .menu-cat-title { display: none; }
.sec-head-aside {
  font-size: var(--t-small); line-height: 1.8; max-width: 44ch;
  align-self: end;
}

/* Long-form copy */
.rte { font-size: var(--t-body); max-width: var(--measure); }
.sec-inner--narrow .rte { margin-inline: auto; }
.rte p + p { margin-top: 1.4em; }
.rte ul, .rte ol { margin: 1.4em 0; padding-inline-start: 1.4em; }
.rte li + li { margin-top: .5em; }
.rte a { color: var(--burgundy); text-decoration: underline; text-underline-offset: 4px; }
.sec--dark .rte a { color: var(--gold); }
.sec--burgundy .rte a { color: var(--gold-soft); }
.rte .gold { color: var(--gold); }
.sec--light .rte .gold { color: var(--burgundy); } /* contrast contract */
.sec--burgundy .rte .gold { color: var(--gold-soft); }
.rte .muted { opacity: 1; }
.rte .lead { font-size: 1.25em; line-height: 1.6; }

/* Editorial drop cap on the opening paragraph of a narrative section. */
.sec--text .rte > p:first-of-type::first-letter {
  font-family: var(--font-display-en);
  float: inline-start; font-size: 3.6em; line-height: .82;
  padding-inline-end: .09em; padding-top: .04em;
  color: var(--burgundy);
}
.sec--dark.sec--text .rte > p:first-of-type::first-letter,
.sec--burgundy.sec--text .rte > p:first-of-type::first-letter { color: var(--gold); }
/* Arabic has no drop-cap tradition and the glyphs connect — never apply it. */
html[dir="rtl"] .sec--text .rte > p:first-of-type::first-letter {
  float: none; font-size: inherit; padding: 0; color: inherit; line-height: inherit;
}

/* ===== Pinned dish showcase ===== */
.featured { background: var(--black); position: relative; }
.featured-head {
  padding: var(--sec-pad) var(--gutter) 0; position: relative; z-index: 2;
  max-width: 1280px; margin: 0 auto;
}
.featured-head h2 { color: var(--ink); font-size: var(--t-h2); font-weight: 400; }
.pin-wrap { position: relative; }
.pin-stage {
  position: sticky; top: 0; height: 100svh;
  display: flex; align-items: center; justify-content: center; overflow: hidden;
}
.dish-frame {
  position: absolute; inset: 0; display: flex; align-items: center; justify-content: center;
  flex-direction: column; opacity: 0; transform: scale(1.04);
  transition: opacity var(--dur-slow) var(--ease-out), transform 900ms var(--ease-out);
  padding: 0 var(--gutter); text-align: center;
}
.dish-frame.active { opacity: 1; transform: scale(1); }
.dish-photo {
  width: min(400px, 68vw); aspect-ratio: 4 / 5; border-radius: var(--r-xl);
  margin-bottom: var(--space-md); position: relative; overflow: hidden;
  border: var(--rule) solid color-mix(in srgb, var(--gold) 30%, transparent);
}
.dish-photo img { width: 100%; height: 100%; object-fit: cover; }
.dish-frame h3 {
  color: var(--ink); font-size: clamp(1.6rem, 3.4vw, 2.4rem);
  margin-bottom: var(--space-2xs); font-weight: 400;
}
.dish-frame p { color: var(--ink-dim); font-size: var(--t-small); max-width: 48ch; }
.pin-dots {
  position: absolute; inset-inline-end: var(--gutter); top: 50%;
  transform: translateY(-50%); display: flex; flex-direction: column;
  gap: var(--space-xs); z-index: 4;
}
.pin-dots span {
  width: 18px; height: var(--rule);
  background: color-mix(in srgb, var(--ink) 28%, transparent);
  transition: background var(--dur-mid) var(--ease-out), width var(--dur-mid) var(--ease-out);
}
.pin-dots span.active { background: var(--gold); width: 34px; }

/* ===== Menu =====
   A printed menu, not a grid of cards. No boxes, no radius, no shadow — the
   dish name and its leader rule do all the work. */
.menu-grid { display: grid; gap: var(--space-md) var(--space-xl); }
.menu-grid.cols-2 { grid-template-columns: repeat(2, 1fr); }
.menu-grid.cols-3 { grid-template-columns: repeat(3, 1fr); }
.menu-grid.cols-4 { grid-template-columns: repeat(4, 1fr); }

.menu-card {
  background: none; border: 0; border-radius: 0; padding: 0;
  padding-bottom: var(--space-md);
  border-bottom: var(--rule) solid color-mix(in srgb, currentColor 14%, transparent);
  display: flex; flex-direction: column;
}
/* With a photograph the card becomes image-led: the picture carries it and
   the rule underneath is dropped, since the image already bounds the block. */
.menu-card.has-img { border-bottom: 0; padding-bottom: 0; }
.menu-card-img {
  margin: 0 0 var(--space-sm); aspect-ratio: 4 / 5; overflow: hidden;
  border-radius: var(--r-lg);
  background: color-mix(in srgb, currentColor 6%, transparent);
  position: relative;
}
.menu-card-img picture { display: block; width: 100%; height: 100%; }
.menu-card-img img {
  width: 100%; height: 100%; object-fit: cover;
  transition: transform 900ms var(--ease-out), filter var(--dur-slow) var(--ease-out);
}
/* A slight desaturation at rest, resolving on hover — the photograph feels
   lit rather than pasted in. */
.menu-card-img img { filter: saturate(.92) contrast(1.04); }
.menu-card:hover .menu-card-img img { transform: scale(1.06); filter: saturate(1) contrast(1.06); }
/* Warm gradient foot so dark-section cards sit on the ground rather than
   ending in a hard edge. */
.sec--dark .menu-card-img::after, .sec--burgundy .menu-card-img::after {
  content: ''; position: absolute; inset-inline: 0; bottom: 0; height: 42%;
  background: linear-gradient(transparent, color-mix(in srgb, var(--black) 72%, transparent));
  pointer-events: none;
}

.menu-card .cat {
  font-family: var(--font-en);
  font-size: var(--t-micro); letter-spacing: .24em; text-transform: uppercase;
  color: var(--burgundy); font-weight: 600; margin-bottom: var(--space-2xs);
}
html[dir="rtl"] .menu-card .cat { font-family: var(--font-ar); letter-spacing: .08em; }
.sec--dark .menu-card .cat { color: var(--gold); }
.sec--burgundy .menu-card .cat { color: var(--gold-soft); }

.menu-card h3 {
  font-size: var(--t-h3); margin: 0 0 var(--space-2xs); font-weight: 400;
  line-height: 1.25;
}
.sec--dark .menu-card h3, .sec--burgundy .menu-card h3 { color: var(--ink); }
.menu-card p {
  font-size: var(--t-small); color: var(--black); margin-bottom: var(--space-sm);
  flex: 1; line-height: 1.75;
}
.sec--dark .menu-card p { color: var(--ink-dim); }
.sec--burgundy .menu-card p { color: var(--ink); }
/* Raw/cooked label. Burgundy, never gold: these sit on the ivory menu ground
   where gold scores 1.82 and fails contrast outright (see docs/gotchas.md).
   On dark and burgundy grounds the gold-soft token is safe and is used instead. */
.menu-card .prep {
  display: inline-block; margin-inline-start: 10px;
  font-family: var(--font-en);
  font-size: var(--t-micro); letter-spacing: .18em; text-transform: uppercase;
  color: var(--burgundy); border: var(--rule) solid currentColor;
  padding: 3px 9px; border-radius: var(--r-pill); vertical-align: middle;
  white-space: nowrap;
}
.sec--dark .menu-card .prep, .sec--burgundy .menu-card .prep { color: var(--gold-soft); }
/* Beside the name the mark is punctuation -- "Dragon - Raw" -- so it takes the
   heading's own type. It used to be set apart here with a 10px indent and a
   micro size, which is the badge treatment in a place that reads as a name. */

.menu-card .price {
  font-family: var(--font-en);
  font-size: var(--t-small); color: var(--burgundy); font-weight: 600;
  letter-spacing: .08em;
  font-variant-numeric: tabular-nums; /* prices must not jitter between rows */
}
.sec--dark .menu-card .price { color: var(--gold); }
.sec--burgundy .menu-card .price { color: var(--gold-soft); }

.menu-cat + .menu-cat { margin-top: var(--space-2xl); }
/* The rule under a category name sits 16px below the name, and now the same
   16px above the first dish. It was 64px of margin here plus the row's own
   24px of padding -- 88px below against 16 above, so the rule read as
   belonging to the heading rather than separating the two. */
.menu-cat-title {
  font-size: clamp(1.4rem, 2.6vw, 2rem); font-weight: 400;
  margin-bottom: 0; padding-bottom: var(--space-xs);
  border-bottom: var(--rule) solid color-mix(in srgb, currentColor 20%, transparent);
  display: flex; align-items: baseline; gap: var(--space-sm);
}
/* Arabic descenders reach lower than the Latin serif's, so the same padding
   leaves the rule visibly tighter under an Arabic name. 22, not the 21 this
   was under Amiri: measured ink-bottom to rule, Amiri sat 24.23px clear at 21
   and Cairo sits 23.23px, so the number moves with the typeface to keep the
   gap where it was tuned.
   Measure these after `await document.fonts.ready`. Taken while the webfont is
   still swapping, the fallback's metrics are 2px out and the correction lands
   wrong -- which is how the first pass at this number happened. */
html[dir="rtl"] .menu-cat-title { padding-bottom: 22px; }
.sec--menu .btn-row { margin-top: var(--space-xl); justify-content: center; }

/* ===== Three-up feature row =====
   Alternating bordered panels and photographs. The panels are lifted
   surfaces with a gold hairline; the photo column is full-bleed inside the
   same radius so the row reads as one object. */
.trio-grid {
  display: grid; grid-template-columns: repeat(3, 1fr);
  gap: var(--space-sm); align-items: stretch;
}
.trio-panel, .trio-photo {
  border-radius: var(--r-lg); overflow: hidden; margin: 0;
  min-height: clamp(280px, 30vw, 400px);
}
.trio-panel {
  background: linear-gradient(165deg, var(--panel-hi), var(--panel));
  border: var(--rule) solid var(--hairline);
  padding: var(--space-md);
  display: flex; flex-direction: column; gap: var(--space-xs);
  transition: border-color var(--dur-mid) var(--ease-out),
              transform var(--dur-mid) var(--ease-out);
}
.trio-panel:hover { border-color: var(--hairline-hi); transform: translateY(-4px); }
.sec--light .trio-panel {
  background: linear-gradient(165deg, var(--ivory), var(--ivory-deep));
  border-color: color-mix(in srgb, var(--black) 12%, transparent);
}
.trio-panel h3 { font-size: var(--t-h3); font-weight: 400; color: var(--ink); }
.sec--light .trio-panel h3 { color: var(--black); }
.trio-panel p { font-size: var(--t-small); line-height: 1.8; }

/* Gold icon badge. Circular, hairline-edged, currentColor stroke. */
.badge {
  width: 52px; height: 52px; border-radius: var(--r-pill);
  display: grid; place-items: center; flex: none;
  border: var(--rule) solid var(--hairline-hi);
  background: color-mix(in srgb, var(--gold) 10%, transparent);
  color: var(--gold); margin-bottom: var(--space-2xs);
}
.sec--light .badge { color: var(--burgundy); border-color: color-mix(in srgb, var(--burgundy) 30%, transparent); }
.badge .ico { width: 24px; height: 24px; }

.trio-photo { position: relative; }
.trio-photo picture { display: block; height: 100%; }
.trio-photo img {
  width: 100%; height: 100%; object-fit: cover;
  transition: transform 900ms var(--ease-out);
}
.trio-photo:hover img { transform: scale(1.05); }

/* ===== Dish spotlight =====
   The floating plate: an unframed photograph on the section ground with a
   soft gold halo behind it, copy alongside. */
.spot-grid {
  display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1.05fr);
  gap: var(--space-xl) var(--space-2xl); align-items: center;
}
.sec--spotlight.is-flipped .spot-copy { order: 2; }
.sec--spotlight.is-flipped .spot-media { order: 1; }
.spot-copy .rte { max-width: 50ch; }
.spot-media { position: relative; display: grid; place-items: center; }
.spot-halo {
  position: absolute; inset: 6% 6% 10%;
  border-radius: var(--r-pill);
  background: radial-gradient(circle at 50% 50%,
    color-mix(in srgb, var(--gold) 26%, transparent) 0%,
    color-mix(in srgb, var(--burgundy) 18%, transparent) 45%,
    transparent 70%);
  filter: blur(38px);
}
.spot-media picture { position: relative; display: block; width: 100%; }
.spot-img {
  /* See .hero-plate-img — height:auto is required for aspect-ratio to win
     over the img's height attribute. */
  width: 100%; height: auto; aspect-ratio: 1; object-fit: cover;
  border-radius: var(--r-pill);
  border: var(--rule) solid var(--hairline);
  position: relative;
}

.spot-points { list-style: none; margin: var(--space-md) 0 0; padding: 0; display: grid; gap: var(--space-xs); }
.spot-points li { display: flex; gap: var(--space-xs); align-items: center; }
.pt-thumb {
  width: 64px; height: 64px; flex: none; border-radius: var(--r-md);
  overflow: hidden; border: var(--rule) solid var(--hairline);
}
.pt-thumb picture, .pt-thumb img { width: 100%; height: 100%; object-fit: cover; display: block; }
.pt-text { display: flex; flex-direction: column; gap: 2px; }
.pt-text strong {
  font-size: var(--t-small); font-weight: 600; color: var(--ink);
  letter-spacing: .04em;
}
.sec--light .pt-text strong { color: var(--black); }
.pt-text span { font-size: var(--t-small); opacity: 1; line-height: 1.6; }

/* ===== Image + text split ===== */
.split-grid {
  display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1.15fr);
  gap: var(--space-xl) var(--space-2xl); align-items: center;
}
/* `is-flipped` reorders visually only; DOM order keeps copy first so the
   reading and tab order stay correct in both directions. */
.sec--split.is-flipped .split-copy { order: 2; }
.sec--split.is-flipped .split-shots { order: 1; }
.split-copy .rte { max-width: 52ch; }
.split-copy h2 { font-size: var(--t-h2); }
.split-shots { display: grid; gap: var(--space-sm); }
.split-shots.shots-2 { grid-template-columns: repeat(2, 1fr); }
.split-shots.shots-2 .split-shot:first-child { margin-top: var(--space-lg); }
.split-shot { position: relative; overflow: hidden; margin: 0; border-radius: var(--r-lg); }
.split-shot picture { display: block; }
.split-shot img {
  width: 100%; height: auto; aspect-ratio: 3 / 4; object-fit: cover;
  /* Interior shots put the ceiling at the top and the lit room lower down;
     a centred crop lands on the dark half and reads as an empty box. */
  object-position: center 62%;
  transition: transform 900ms var(--ease-out);
}
.split-shot:hover img { transform: scale(1.05); }
.split-shot figcaption {
  position: absolute; inset-inline: 0; bottom: 0;
  padding: var(--space-md) var(--space-sm) var(--space-xs);
  background: linear-gradient(transparent, color-mix(in srgb, var(--black) 85%, transparent));
  color: var(--ink); font-size: var(--t-micro);
  letter-spacing: .2em; text-transform: uppercase;
}

/* ===== Gallery ===== */
.gal-grid { display: grid; gap: var(--space-sm); }
.gal-grid.cols-2 { grid-template-columns: repeat(2, 1fr); }
.gal-grid.cols-3 { grid-template-columns: repeat(3, 1fr); }
.gal-grid.cols-4 { grid-template-columns: repeat(4, 1fr); }
.gal-item { position: relative; overflow: hidden; border-radius: var(--r-lg); }
.gal-item img {
  width: 100%; aspect-ratio: 3 / 4; object-fit: cover;
  transition: transform var(--dur-slow) var(--ease-out);
}
.gal-item:hover img { transform: scale(1.04); }
.gal-item figcaption {
  position: absolute; inset-inline: 0; bottom: 0; padding: var(--space-md) var(--space-sm) var(--space-xs);
  background: linear-gradient(transparent, rgb(0 0 0 / .78));
  color: var(--ivory); font-size: var(--t-small);
}

/* ===== Contact ===== */
.contact-grid {
  display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: var(--space-xl);
}
.contact-col address {
  font-style: normal; font-size: var(--t-body); margin-bottom: var(--space-sm);
  line-height: 1.9;
}
.contact-col > p { font-size: var(--t-body); }
.contact-col a { text-decoration: underline; text-underline-offset: 4px; }
.contact-col a:hover { color: var(--burgundy); }
.sec--dark .contact-col a:hover { color: var(--gold); }
.sec--burgundy .contact-col a:hover { color: var(--gold-soft); }
.hours-row {
  display: flex; justify-content: space-between; gap: var(--space-sm);
  padding: var(--space-xs) 0;
  border-bottom: var(--rule) solid color-mix(in srgb, currentColor 14%, transparent);
  font-size: var(--t-small);
  font-variant-numeric: tabular-nums;
}
.map-wrap { margin-top: var(--space-lg); overflow: hidden; border-radius: var(--r-lg); }
.map-wrap iframe { width: 100%; height: 440px; border: 0; display: block; filter: grayscale(1) contrast(.95); }
.map-wrap:hover iframe { filter: none; transition: filter var(--dur-slow) var(--ease-out); }

/* ===== Divider ===== */
.sec--divider { display: flex; align-items: center; justify-content: center; overflow: visible; }
.sec--divider.space-sm { padding: var(--space-md) var(--gutter); }
.sec--divider.space-md { padding: var(--space-lg) var(--gutter); }
.sec--divider.space-lg { padding: var(--space-xl) var(--gutter); }
.sec--divider .rule { width: min(140px, 30vw); height: var(--rule); background: currentColor; opacity: .4; }
.sec--dark .rule, .sec--burgundy .rule { background: var(--gold); opacity: .7; }

/* ===== CTA band ===== */
.sec--cta .sec-inner--narrow h2 { font-size: var(--t-h2); }

/* ===== Footer ===== */
/* Burgundy, not black: the footer is the last thing on the page and the brand's
   primary colour closes it. Ivory on burgundy measures 9.17:1; gold-soft, used
   on hover, measures 6.15. Plain gold is 4.87 here and is not used for text. */
.site-footer {
  background: var(--burgundy); color: var(--ink);
  /* 24, not 64. Each row is already a 44px touch target wrapped around ~22px
     of glyph or text, so the rows carry most of their own breathing room; 64px
     on top of that made the footer 271px on a phone against the reference's
     ~160. */
  padding: var(--space-sm) var(--gutter);
  /* The page often ends with a burgundy CTA band, and flat burgundy under a
     burgundy gradient reads as one block. A hairline is enough to separate
     them without introducing a second colour. */
  border-top: var(--rule) solid color-mix(in srgb, var(--ink) 16%, transparent);
  position: relative;
}
/* The mirror of the banner's scrim: that one holds black at the TOP of the
   image and fades down, so the header band is the darkest part; this one is
   clear at the top and deepens toward the bottom, so the page closes darker
   than it opens.

   An overlay rather than a gradient background, for two reasons. The footer's
   background colour is a chrome setting, so anything written as `background`
   here is one dashboard edit away from being replaced -- an overlay composites
   over whatever colour it ends up being. And the border-top stays visible:
   inset: 0 addresses the padding box, so it does not paint over the hairline.

   38% at the foot lands on roughly --burgundy-deep, which is the value
   .sec--burgundy already ends its own gradient on -- so the footer closes on
   the same note a burgundy section does, rather than inventing a second dark.
   Darkening only ever improves the ivory text on it: 9.17 to about 12. */
.site-footer::before {
  content: ''; position: absolute; inset: 0; z-index: 0;
  background: linear-gradient(to bottom,
    transparent 0%,
    color-mix(in srgb, var(--black) 38%, transparent) 100%);
  pointer-events: none;
}

/* Two rows on a wide screen, three on a phone -- the grid areas do the
   reordering, so the markup keeps the order a screen reader should hear.
   Mobile:  icons / links / copyright, centred.
   Desktop: links and icons share a row, copyright below. */
.footer-inner {
  /* Above the scrim. Without this the grid sits in the same stacking context
     at z-index auto and the overlay paints over the links and the icons. */
  position: relative; z-index: 1;
  max-width: 1280px; margin: 0 auto;
  display: grid; justify-items: center; gap: 4px;
  grid-template-areas: "icons" "links" "copy";
  text-align: center;
}
.footer-icons { grid-area: icons; }
.footer-links { grid-area: links; }
.footer-copy { grid-area: copy; }

.footer-icons {
  display: flex; flex-wrap: wrap; align-items: center; justify-content: center;
  gap: var(--space-2xs);
}
/* 44px of hit area with the glyph at 22, and the negative inline margin keeps
   the row optically flush with the text above and below it. */
.footer-icons a {
  display: inline-grid; place-items: center;
  width: 44px; height: 44px; margin-inline: -3px;
  color: var(--ink);
  transition: color var(--dur-fast) var(--ease-out);
}
.footer-icons a:hover { color: var(--gold-soft); }
.footer-icons .ico { width: 22px; height: 22px; }
/* Two of the set are solid rather than outlined, as in the reference.
   CSS beats the `fill="none"` presentation attribute icon() writes.
   The star and the handset keep their stroke: the round joins soften their
   corners, where `fill` alone gives hard ones. The book drops its stroke,
   because the gap between its two leaves is 1.6 units wide and a 1.4 stroke
   would close it up. */
.footer-icons .ico-reviews .ico,
.footer-icons .ico-phone .ico { fill: currentColor; }
.footer-icons .ico-pdf .ico { fill: currentColor; stroke: none; }
/* Part of a glyph, not all of it: the WhatsApp bubble stays an outline while
   the handset inside it is solid, which is how the mark is drawn.
   NOT scoped to the footer, unlike the four rules above it. Those say what a
   particular icon looks like in a particular row; this one says what the
   WhatsApp mark IS. Scoped, it left the menu page's venue card drawing the
   bubble and skipping the handset -- a plain speech balloon that read as a
   chat link, not as WhatsApp. Anywhere the glyph appears, the handset fills. */
.ico .solid { fill: currentColor; }

.footer-links {
  display: flex; flex-wrap: wrap; align-items: center; justify-content: center;
  gap: 0 var(--space-md);
}
.footer-links a {
  display: inline-flex; align-items: center; min-height: 44px;
  font-size: var(--t-small); font-weight: 600; color: var(--ink);
  transition: color var(--dur-fast) var(--ease-out);
}
.footer-links a:hover { color: var(--gold-soft); }

.footer-copy {
  font-size: var(--t-small);
  color: color-mix(in srgb, var(--ink) 78%, transparent);
}

@media (min-width: 1025px) {
  .footer-inner {
    grid-template-columns: 1fr auto;
    grid-template-areas: "links icons" "copy copy";
    justify-items: start; align-items: center;
    text-align: start; gap: var(--space-2xs) var(--space-md);
  }
  .footer-icons { justify-self: end; }
  .footer-links { justify-content: flex-start; }
}

/* The footer the renderer actually emits.
   `.footer-inner` above is the older three-area layout and is kept because a
   published snapshot rendered by an older build still uses it. What ships now
   is .footer-grid > .footer-brand + .footer-col, closed by .footer-bottom, and
   none of it had any rules at all -- the markup changed without the stylesheet,
   so the whole footer rendered as unstyled flow content under the scrim.

   z-index is not optional here. .site-footer::before is an absolutely
   positioned overlay at z-index 0; without a stacking context above it the
   gradient paints over the text and the links. */
.footer-grid,
.footer-bottom {
  position: relative; z-index: 1;
  max-width: 1280px; margin-inline: auto;
}
.footer-grid {
  display: grid; gap: var(--space-sm);
  justify-items: center; text-align: center;
}
.footer-brand { display: grid; justify-items: center; gap: var(--space-2xs); }
.footer-brand img { height: 44px; width: auto; }
.footer-brand p {
  margin: 0; font-size: var(--t-small);
  color: color-mix(in srgb, var(--ink) 78%, transparent);
  max-width: 34ch;
}
.footer-col { display: grid; justify-items: center; gap: 2px; }
.footer-col h4 {
  margin: 0 0 2px; font-size: var(--t-small); font-weight: 700;
  letter-spacing: .04em; color: var(--ink);
}
/* Arabic is cursive -- letter-spacing breaks the joins. See docs/gotchas.md. */
html[dir="rtl"] .footer-col h4 { letter-spacing: 0; }
.footer-col p { margin: 0; font-size: var(--t-small);
  color: color-mix(in srgb, var(--ink) 78%, transparent); }
.footer-col a {
  display: inline-flex; align-items: center; min-height: 44px;
  font-size: var(--t-small); font-weight: 600; color: var(--ink);
  transition: color var(--dur-fast) var(--ease-out);
}
.footer-col a:hover { color: var(--gold-soft); }

/* Same 44px hit area and 22px glyph as the old icon row. */
.social-row {
  display: flex; flex-wrap: wrap; align-items: center; justify-content: center;
  gap: var(--space-2xs);
}
.social-row a {
  display: inline-grid; place-items: center;
  width: 44px; height: 44px; margin-inline: -3px;
  color: var(--ink);
  transition: color var(--dur-fast) var(--ease-out);
}
.social-row a:hover { color: var(--gold-soft); }
.social-row .ico { width: 22px; height: 22px; }
/* The star and the handset are solid in this set; the book drops its stroke,
   because the gap between its leaves is narrower than the stroke width. */
.social-row .ico-reviews .ico,
.social-row .ico-phone .ico { fill: currentColor; }
.social-row .ico-pdf .ico { fill: currentColor; stroke: none; }

.footer-bottom {
  display: flex; flex-wrap: wrap; justify-content: center; gap: 0 var(--space-sm);
  margin-top: var(--space-xs); text-align: center;
  font-size: var(--t-small);
  color: color-mix(in srgb, var(--ink) 78%, transparent);
}

@media (min-width: 1025px) {
  .footer-grid {
    grid-template-columns: 1.4fr 1fr 1fr;
    justify-items: start; align-items: start;
    text-align: start; gap: var(--space-md);
  }
  .footer-brand,
  .footer-col { justify-items: start; }
  .social-row { justify-content: flex-start; }
  .footer-bottom { justify-content: space-between; text-align: start; }
}

/* ===== Responsive ===== */
@media (max-width: 1024px) {
  .main-nav { display: none; }
  .menu-toggle { display: flex; }
  /* Badge moves back to the leading edge once the split nav is gone. */
  .header-inner { grid-template-columns: auto 1fr; justify-content: space-between; }
  .brand-mark { flex-direction: row; }
  .wordmark { text-align: start; }
  .header-actions { justify-self: end; }
  /* Two panels share the top row and the photograph runs full width beneath.
     Without `order` the photo lands between them and strands a panel beside
     an empty cell. */
  .trio-grid { grid-template-columns: 1fr 1fr; }
  .trio-panel { order: 1; }
  .trio-photo { order: 2; grid-column: 1 / -1; min-height: 260px; aspect-ratio: 16 / 7; }
  .hero-plate-grid { grid-template-columns: 1fr; gap: var(--space-md); text-align: start; }
  .hero-plate { max-width: 460px; margin-inline: auto; order: -1; }
  .spot-grid { grid-template-columns: 1fr; gap: var(--space-lg); }
  .sec--spotlight.is-flipped .spot-copy,
  .sec--spotlight.is-flipped .spot-media { order: 0; }
  .spot-media { max-width: 560px; margin-inline: auto; }
  .split-grid { grid-template-columns: 1fr; gap: var(--space-lg); }
  .sec--split.is-flipped .split-copy,
  .sec--split.is-flipped .split-shots { order: 0; }
  .split-shots.shots-2 .split-shot:first-child { margin-top: 0; }
  .sec-head { grid-template-columns: 1fr; align-items: start; }
  .sec-head-aside { max-width: var(--measure); }
  .menu-grid.cols-3, .menu-grid.cols-4 { grid-template-columns: repeat(2, 1fr); }
  .gal-grid.cols-4 { grid-template-columns: repeat(3, 1fr); }
  .contact-grid { grid-template-columns: 1fr; gap: var(--space-lg); }
}

@media (max-width: 680px) {
  /* The header must fit badge + language + Reservation + hamburger inside
     375px. At full desktop padding the pill alone is 176px and pushes the
     hamburger off-screen, so the button tightens rather than being hidden —
     Reservation stays reachable at every width by design. */
  .header-actions { gap: var(--space-2xs); min-width: 0; }
  .site-header .btn-reserve {
    padding-inline: 16px; letter-spacing: .1em; font-size: .62rem;
  }
  .lang-toggle { padding-inline: 10px; letter-spacing: .1em; }
  .menu-toggle { padding: 12px 6px; min-width: 44px; }

  .trio-grid { grid-template-columns: 1fr; }
  .trio-panel, .trio-photo { min-height: 0; }
  .trio-photo { aspect-ratio: 4 / 3; }
  .menu-grid, .menu-grid.cols-2, .menu-grid.cols-3, .menu-grid.cols-4 { grid-template-columns: 1fr; }
  .gal-grid.cols-3, .gal-grid.cols-4 { grid-template-columns: repeat(2, 1fr); }
  .wordmark { display: none; }
  .btn-row { flex-direction: column; width: 100%; }
  .btn-row a { width: 100%; }
  .pin-dots { inset-inline-end: var(--space-xs); }
  .hero-content { padding-bottom: var(--space-xl); }
  /* A drop cap at phone width eats a third of the column. */
  .sec--text .rte > p:first-of-type::first-letter { font-size: 2.8em; }
}

/* ===== Reduced motion =====
   Everything renders in its final state. The pinned sequence unpins into a
   plain stacked list so no content is trapped behind a scroll interaction. */
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after {
    animation-duration: .01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .01ms !important;
    scroll-behavior: auto !important;
  }
  .hero-scene, .hero-pattern, .scroll-hint .line { transform: none !important; }
  .hero--photo .hero-media img { animation: none !important; transform: scale(1.04) !important; }
  .hero-plate-img { animation: none !important; transform: none !important; }
  .trio-panel:hover, .trio-photo:hover img { transform: none !important; }
  .menu-card:hover .menu-card-img img, .split-shot:hover img { transform: none !important; }
  /* Belt and braces: the bootstrap already skips .anim-ready under reduced
     motion, but if the class is ever set anyway, content still shows. */
  .reveal, html.anim-ready .reveal { opacity: 1 !important; transform: none !important; }
  .dish-frame {
    opacity: 1 !important; transform: none !important;
    position: relative; inset: auto;
    padding-block: var(--space-lg);
  }
  .pin-stage { position: relative; height: auto; display: block; padding: var(--space-md) 0; }
  .pin-track { display: none; }
  .pin-dots { display: none; }
  .menu-card:hover .menu-card-img img, .gal-item:hover img { transform: none; }
}

/* ===== Menu browser (/menu) =====
   One dish per row, a sticky category jump bar and a search box. */

/* The filter sets el.hidden. Author CSS outranks the UA stylesheet, so any
   display rule on these elements would defeat it silently -- the same bug that
   once left the dashboard boot spinner covering a rendered page. Keep this. */
[hidden] { display: none !important; }

.sr-only {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}

/* Sticky under the site header. --header-h already exists, so no magic number. */
.menu-tools {
  position: sticky; top: var(--header-h); z-index: 5;
  background: var(--ivory);
  padding-block: var(--space-xs);
  margin-bottom: var(--space-md);
  border-bottom: var(--rule) solid color-mix(in srgb, var(--ink) 12%, transparent);
}
.sec--dark .menu-tools, .sec--burgundy .menu-tools { background: transparent; }

.cat-pills {
  display: flex; gap: 8px; overflow-x: auto; overscroll-behavior-x: contain;
  -webkit-overflow-scrolling: touch; scrollbar-width: none;
  padding-bottom: 4px;
}
.cat-pills::-webkit-scrollbar { display: none; }

/* Burgundy, never gold: these sit on ivory, where gold scores 1.82. */
.pill {
  flex: 0 0 auto; display: inline-flex; align-items: center;
  min-height: 44px; padding: 0 18px; border-radius: var(--r-pill);
  font-family: var(--font-en); font-size: var(--t-micro);
  letter-spacing: .12em; text-transform: uppercase; white-space: nowrap;
  color: var(--burgundy); border: var(--rule) solid currentColor;
  background: transparent; cursor: pointer;
  transition: background var(--dur-fast) var(--ease-out),
              color var(--dur-fast) var(--ease-out);
}
html[dir="rtl"] .pill { font-family: var(--font-ar); letter-spacing: 0; }
.pill:hover { background: color-mix(in srgb, var(--burgundy) 8%, transparent); }
.pill.is-active { background: var(--burgundy); color: var(--ivory); }
.sec--dark .pill, .sec--burgundy .pill { color: var(--gold-soft); }
.sec--dark .pill.is-active, .sec--burgundy .pill.is-active {
  background: var(--gold-soft); color: var(--deep);
}

/* The field is on the ivory ground of the menu page, and --ink is a dimmed
   ivory: as an outline in --ink it was a 1.1:1 border around near-invisible
   text, so the search read as a stray word rather than a control. It now sits
   on its own sunk ground with dark text and a filled icon disc. */
.menu-search { position: relative; }
.menu-search input {
  width: 100%; min-height: 56px;
  padding-inline: 22px calc(44px + var(--space-sm));
  font-family: inherit; font-size: var(--t-body); color: var(--black);
  background: color-mix(in srgb, var(--black) 6%, var(--ivory));
  border: var(--rule) solid transparent;
  border-radius: var(--r-pill);
}
.menu-search input::placeholder { color: var(--black); opacity: 1; }
.menu-search input:focus-visible {
  outline: none; border-color: var(--burgundy);
  background: color-mix(in srgb, var(--black) 3%, var(--ivory));
}
/* Decorative: the list filters as you type, so there is nothing to submit. */
.menu-search-ico {
  position: absolute; inset-block: 0; inset-inline-end: 8px; margin-block: auto;
  width: 40px; height: 40px; border-radius: 50%;
  display: grid; place-items: center; pointer-events: none;
  background: var(--ivory); color: var(--burgundy);
}
.menu-search-ico .ico { width: 20px; height: 20px; }
.menu-search input[type="search"]::-webkit-search-cancel-button { margin-inline-end: 4px; }

.menu-cat-block { scroll-margin-top: calc(var(--header-h) + 96px); }
.menu-cat-block.is-loose {
  padding-top: var(--space-xs);
  border-top: var(--rule) solid color-mix(in srgb, currentColor 20%, transparent);
}
/* Category blocks are separated by a flex gap, not by a margin on the second
   of two adjacent siblings. `display: none` takes an element out of the layout
   but NOT out of the selector: `.menu-cat-block + .menu-cat-block` still
   matched a visible block that followed a hidden one, so every category except
   the very first opened with 96px of empty space under the Back button. A flex
   gap is only drawn between items that actually render. */
.menu-rows { display: flex; flex-direction: column; gap: var(--space-xl); }
/* Matches the padding above the rule. Only the first row: every row after it
   is separated from its neighbour by the full 24px and a hairline. */
.menu-cat-block .dish-row:first-of-type { padding-top: var(--space-xs); }

/* One dish per row. No photo block at all when there is no photograph -- a
   full-width placeholder repeated across the 30 dishes still awaiting a shoot
   reads as broken rather than as a menu. */
.dish-row {
  padding-block: var(--space-sm);
  border-bottom: var(--rule) solid color-mix(in srgb, var(--ink) 12%, transparent);
}
/* A real <button>, not a div with a click handler: it is a control, so it has
   to be reachable by keyboard and announced as one. That means resetting the
   UA chrome -- a button brings its own border, padding, background and font,
   and a centred inline-block layout that would have shrunk the photo to its
   intrinsic width inside the grid column. */
/* A real <button>, not a div with a click handler: opening the photo is an
   action, so it has to be reachable by keyboard and announced as one. That
   means resetting the UA chrome -- a button brings its own appearance, border,
   padding, background and font, and a centred inline-block layout that would
   have shrunk the photo to its intrinsic width inside the grid column. */
.dish-row-img {
  appearance: none; -webkit-appearance: none;
  display: block; width: 100%; padding: 0; border: 0; background: none;
  font: inherit; color: inherit; text-align: inherit; cursor: zoom-in;
  margin-bottom: var(--space-xs); border-radius: var(--r-lg); overflow: hidden;
}
/* 4:3, the same shape the category card uses, so one exported file fits both
   without a second crop. It is also what this image is already given at 780px
   and up, so the dish photo is now 4:3 at every width. */
.dish-row-img img {
  display: block; width: 100%; height: auto; aspect-ratio: 4 / 3;
  object-fit: cover;
}
.dish-row-top {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: var(--space-xs);
}
.dish-row-top h3 { margin: 0; font-size: var(--t-h3); font-weight: 400; line-height: 1.25; }
/* The same warm grey the description uses, one step quieter than the dish
   name -- as in the reference, where the count sits opposite the name and
   reads as a footnote to it.
   It was --ink at 55%, which is a dimmed IVORY: written for a dark ground and
   left behind when this row moved onto the ivory panel. It measured 1.04:1
   there. Nobody noticed because every dish had calories = 0 and the line never
   rendered; it does now. */
.dish-kcal {
  flex: 0 0 auto; font-family: var(--font-en);
  font-size: var(--t-micro); letter-spacing: .1em;
  color: var(--black);
}
.dish-row-body > p {
  margin: 6px 0 0; font-size: var(--t-small); line-height: 1.7;
  color: var(--black);
}
.dish-row .price {
  display: inline-block; margin-top: var(--space-2xs);
  font-family: var(--font-en); font-weight: 600; font-size: var(--t-small);
  color: var(--burgundy);
}

/* Side choices -- the rice on the hibachi plates. Shaped like the allergen
   chips below rather than like the Eat/Drink tabs: these belong to one dish,
   not to the page, and a second row of 44px pills inside every dish row would
   read as navigation. They are still real buttons, so they carry the touch
   minimum in padding and a visible pressed state. */
.dish-opts {
  display: flex; flex-wrap: wrap; gap: 8px;
  margin-top: var(--space-2xs);
}
.dish-opt {
  appearance: none; -webkit-appearance: none;
  font-family: var(--font-en); font-size: var(--t-micro);
  letter-spacing: .1em; text-transform: uppercase;
  color: var(--burgundy); background: transparent;
  border: var(--rule) solid currentColor; border-radius: var(--r-pill);
  padding: 9px 14px; min-height: 36px; cursor: pointer;
  transition: background var(--dur-fast) var(--ease-out),
              color var(--dur-fast) var(--ease-out);
}
html[dir="rtl"] .dish-opt { font-family: var(--font-ar); letter-spacing: 0; }
.dish-opt:hover { background: color-mix(in srgb, var(--burgundy) 8%, transparent); }
/* Filled, not merely outlined-in-bold: the whole point of the control is that
   the guest can see at a glance which figure the count is now showing. */
.dish-opt.is-on { background: var(--burgundy); color: var(--ivory); }
.sec--dark .dish-opt, .sec--burgundy .dish-opt { color: var(--gold-soft); }
.sec--dark .dish-opt.is-on, .sec--burgundy .dish-opt.is-on {
  background: var(--gold-soft); color: var(--deep);
}

/* Allergen chips. Burgundy text, never gold, and never emoji. */
.dish-allergens {
  display: flex; flex-wrap: wrap; align-items: center; gap: 6px;
  margin-top: var(--space-2xs);
}
.allergen-label {
  font-size: var(--t-micro); letter-spacing: .1em; text-transform: uppercase;
  color: var(--black);
}
.allergen {
  font-size: var(--t-micro); letter-spacing: .06em;
  color: var(--burgundy); border: var(--rule) solid currentColor;
  border-radius: var(--r-pill); padding: 2px 9px;
}
.sec--dark .allergen, .sec--burgundy .allergen { color: var(--gold-soft); }

.menu-empty {
  padding: var(--space-lg) 0; text-align: center; font-size: var(--t-body);
  color: var(--black);
}

/* No two-column variant. The menu renders in one 560px column at every width
   (see .sec--browser below), so a 260px photo beside the copy would be laid
   out against a viewport the menu no longer fills. The base rules above are
   the phone's stacked row, and they are now the only row. */

/* A page that does not open with a dark hero (e.g. /menu) must not leave the
   header transparent: its ivory nav text would land on the ivory ground at
   1.03:1 and vanish, taking the language toggle with it. Give it the scrolled
   treatment from the start -- same dark ground, so contrast returns to ~15:1. */
/* :where() keeps this a default rather than an override. The dashboard's
   "Surface treatment" writes `.site-header{background:...}` into the page's
   own <style>, and at (0,2,1) this rule outranked it -- so the privacy
   policy and terms pages, which have no hero, ignored every surface an
   operator chose. Zero-specificity context still beats the bare
   `.site-header` above by source order, and a deliberate choice now wins. */
:where(body.page-no-hero) .site-header {
  background: color-mix(in srgb, #fff 22%, transparent);
  backdrop-filter: blur(14px) saturate(1.2);
  border-bottom-color: color-mix(in srgb, var(--gold) 22%, transparent);
}

/* ===== Dish photo viewer =====
   Tap a dish photograph and it opens over the page: the menu dims behind it,
   the photo sits full-width and vertically centred, and one control closes it.

   `position: fixed` with inset 0 rather than a height: on iOS the visual
   viewport shrinks as the URL bar collapses, and a vh-based overlay is either
   short of the bottom or scrolls. Fixed insets track the layout viewport,
   which is the box the photo should fill.

   The scrim is deliberately heavy. A dish photograph opened over a menu of
   other dish photographs has to read as a layer, not as a bigger card. */
.photo-viewer {
  position: fixed; inset: 0; z-index: var(--z-mobile-nav);
  background: color-mix(in srgb, var(--black) 88%, transparent);
  opacity: 0;
  transition: opacity var(--dur-mid) var(--ease-out);
}
.photo-viewer.open { opacity: 1; }
/* The photo fills the overlay and object-fit does the fitting, rather than
   `width: 100%` with a `max-height` cap. That version overflowed: a 4:3 shot
   at 1707px wide is 1280px tall, the percentage max-height did not clamp it,
   and the picture ran off the bottom of a 769px screen. Filling a box that is
   already exactly the viewport takes the guesswork out -- `contain` centres
   the image and letterboxes the rest, which is the scrim, which is the dimmed
   menu behind it. */
.photo-viewer-img {
  position: absolute; inset: 0;
  width: 100%; height: 100%; object-fit: contain;
  transform: scale(.985);
  transition: transform var(--dur-mid) var(--ease-out);
}
.photo-viewer.open .photo-viewer-img { transform: none; }
/* Over a photograph whose corner could be any colour, so it carries its own
   dark disc rather than trusting the image behind it. */
.photo-viewer-close {
  position: absolute; top: var(--space-xs); inset-inline-end: var(--gutter);
  /* The photo comes after this in the DOM and now covers the whole overlay, so
     without a stacking order it would paint straight over the control. */
  z-index: 1;
  display: grid; place-items: center;
  width: 44px; height: 44px; border-radius: 50%;
  background: color-mix(in srgb, var(--black) 55%, transparent);
  color: var(--ink); border: 0; cursor: pointer;
  transition: background var(--dur-fast) var(--ease-out);
}
.photo-viewer-close:hover { background: color-mix(in srgb, var(--black) 78%, transparent); }
.photo-viewer-close .ico { width: 22px; height: 22px; }
/* The page must not scroll behind the overlay. On <html>, not <body>: body
   already carries overflow-x: hidden, which makes it the scroll container on
   this site, and locking the wrong one does nothing. */
html.viewer-open { overflow: hidden; }

@media (prefers-reduced-motion: reduce) {
  .photo-viewer, .photo-viewer-img { transition: none; }
  .photo-viewer-img { transform: none; }
}

/* ===== Menu page: the header goes with the page =====
   On /menu the header scrolls away instead of riding down the screen. It is
   still `position: absolute` over the banner at the top, so the opening view
   and the banner scrim are unchanged -- it simply does not switch to `fixed`.

   Scoped by `body.page-menu`, written by the renderer from the requested page,
   because a menu browser section can be placed on any page from the dashboard;
   keying off the section would take the home page's header with it.

   The Eat/Drink tabs and the search field go with it -- nothing at the top of
   this page parks any more, so the guest scrolls into an uninterrupted list of
   dishes. The bar keeps its background and its hairline: the fill is now the
   same ivory it sits on, and the rule is the line under the search field.

   The Back button is the deliberate exception. It stays sticky, because it is
   the one control that has to be reachable from anywhere inside a category.
   Its offset drops from `--header-h + 8` to 8: with no fixed header to clear,
   the old figure parked it 64px down the screen with dish rows sliding
   through the gap above it. Same reasoning for the anchor offset. */
body.page-menu .site-header.is-sticky { position: absolute; }
/* And no painted bar on the way out. The header leaves as the page scrolls, but
   main.js still adds `.scrolled` past 40px, so for the ~24px between that and
   the header clearing the top edge the white veil flashed on and then left with
   it. Nothing to paint over here anyway: while the header is still on screen it
   is over the banner, which carries its own scrim. */
/* :where() for the same reason as page-no-hero above: at (0,3,1) this beat
   the chosen surface, so /menu reverted to transparent the moment a guest
   scrolled 40px. */
:where(body.page-menu) .site-header.scrolled {
  background: transparent;
  backdrop-filter: none;
  border-bottom-color: transparent;
}
/* No width-dependent header treatment here any more. The banner is full bleed
   at every width again, so the header sits over a dark photograph exactly as it
   does on a phone, and the transparent treatment is correct everywhere. */
body.page-menu .menu-tools { position: static; }
body.page-menu .menu-back { top: var(--space-2xs); }
body.page-menu .menu-cat-block { scroll-margin-top: var(--space-xl); }

/* ===== Menu page: banner, panel, venue card =====
   The reference layout opens with a full-width banner, then lifts a rounded
   panel over its bottom edge so the corners cut into the image. The section
   therefore drops its own padding and the panel carries the gutter instead --
   otherwise the banner would sit inset from both edges. */
/* The panel owns every edge once there is a banner. The section kept its own
   `--sec-pad` bottom padding, which sat under the panel's -- 96 + 96 on a phone
   and 200 + 200 at 1920 -- so the last category floated 200 to 408px above the
   footer. */
.sec--browser.has-banner { padding-block: 0; padding-inline: 0; }
/* clip, not hidden. `.sec` sets `overflow: hidden`, which makes it a scroll
   container and silently disables `position: sticky` for everything inside --
   so the Eat/Drink bar has never actually parked under the header on a long
   menu; it just scrolled away. `clip` cuts the overflow exactly the same way
   without creating a scroll container, so sticky works again. Where it is not
   supported it computes to `visible`, which bleeds a little rather than
   breaking. */
.sec--browser { overflow: clip; }

/* ===== One column, every width, full bleed =====
   The menu reads the same on a wide desktop as it does on the phone a seated
   guest is holding: one column of category bands, the banner full width above
   them, stacked dish rows inside. A menu is scanned, not laid out -- widening
   it into three square cards and a two-column dish row produced a different
   page at every breakpoint and a second photographic crop to match.

   Nothing is capped and nothing is centred: the panel fills the page at every
   width, so the layout is the phone's and the width is the screen's. */
.sec--browser {
  /* The phone's gutter, pinned, so the content reaches the edges the way it
     does on a phone rather than sitting inside a desktop's 88px of air.
     Scoped here, so the header and every other section keep the fluid one. */
  --gutter: 24px;
}
/* .sec-inner would otherwise cap the panel's contents at 1280. */
.sec--browser .sec-inner { max-width: none; }
.menu-banner {
  position: relative; overflow: hidden;
  /* Wide and short. Taller and the menu itself is pushed off a phone screen,
     which is the one thing a seated guest opened the page for.
     The header is fixed and sits over the top of this image, so the floor has
     to clear it: at 16/6 on a 390px screen the banner is 146px, the header
     eats 64 and the panel overlaps 26, leaving 56px of visible photograph. */
  /* Sized from the header rather than by aspect-ratio. An aspect-ratio box
     with an auto width re-derives that width from the ratio once max-height
     clamps the height, so the banner stopped 127px short of the right edge at
     1264px wide. What matters here is how much photograph clears the header,
     so state that directly. */
  /* 65% of what it was -- the whole height scaled, not a fixed number taken
     off the offset. Written as a multiplier because --header-h is 88px on a
     desktop and 64 below 1025, so one subtracted constant would have been a
     different percentage at each of the three sizes this renders at. */
  height: calc((var(--header-h) + 130px) * .65);
  background: var(--deep);
}
/* A scrim under the header, the same job the hero's `scrim` setting does for
   its photograph. The header is transparent with ivory text until it scrolls,
   and measured against this banner the nav's worst case was 1.62:1 -- the lit
   doorway on the right of the shot. Holding 62% black through the header band
   and fading it out below takes that to 7.57:1 while leaving the rest of the
   image alone. It is not the Style tab's darkening control: that one dims the
   whole banner and starts at zero. */
.menu-banner::before {
  content: ''; position: absolute; inset-inline: 0; top: 0; z-index: 1;
  height: calc(var(--header-h) + 40px);
  background: linear-gradient(to bottom,
    color-mix(in srgb, var(--black) 62%, transparent) 0,
    color-mix(in srgb, var(--black) 62%, transparent) calc(var(--header-h) * .75),
    transparent 100%);
  pointer-events: none;
}
/* The darkening layer is always present at zero opacity, so the Style tab only
   has to change one number. It paints after the image because it comes later
   in the stacking order, and takes no pointer events.

   Zero, not .68. The migration left the value at .68 while this comment still
   said zero, which painted 68% black over the whole banner and turned the
   burgundy band above the menu panel almost black. Nothing asks for it: the
   section's bannerDim and bannerTone are both unset. Nav contrast over the
   banner is the ::before scrim's job, above, and that is untouched. */
.menu-banner::after {
  content: ''; position: absolute; inset: 0;
  background: var(--black); opacity: 0; pointer-events: none;
}
.menu-banner-img,
.menu-banner picture {
  position: absolute; inset: 0; display: block;
  width: 100%; height: 100%; object-fit: cover;
}
.has-banner .menu-panel {
  position: relative; z-index: 1;
  margin-top: -26px;
  border-start-start-radius: var(--r-xl);
  border-start-end-radius: var(--r-xl);
  background: var(--ivory);
  color: var(--black);
}
/* The panel only takes over the section's padding when there is a banner to
   overlap. Without one the section keeps its own padding and this wrapper is
   inert -- otherwise a snapshot with no banner got both, and the live page
   gained a screenful of whitespace from a code deploy alone. */
.has-banner .menu-panel {
  padding-inline: var(--gutter);
  /* 24 at the foot, the same gap the search field leaves above the first
     category card, so the menu closes on the footer as evenly as it opens. */
  padding-block: var(--space-md) var(--space-sm);
}

/* The venue card: name, how to reach the place, and the Google listing.
   Every row is optional -- the address, phone and social accounts are still
   outstanding, and a line reading "Phone: TBD" to a seated guest is worse than
   no phone line at all. */
/* Spacing here is a compromise the reference layout does not have to make.
   Its meta line, icon row and reviews link sit about 5px apart because none of
   them is a 44px touch target -- ours all are (CLAUDE.md), and three stacked
   44px rows leave 22px of dead air between each pair of text baselines.
   The negative margins pull the rows back to roughly the reference rhythm. The
   boxes overlap by 14px in the middle, so every control still measures its full
   44px; a tap in the overlap band is ambiguous between two adjacent links,
   which is the price of the tighter layout. */
.venue { margin-bottom: 0; }
.venue-name {
  font-size: clamp(1.9rem, 5vw, 2.6rem); font-weight: 400; line-height: 1.15;
  margin: 0;
}
.venue-meta {
  display: flex; flex-wrap: wrap; align-items: center;
  gap: 0 var(--space-xs); margin-bottom: 0;
}
.venue-bit {
  display: inline-flex; align-items: center; gap: 8px;
  font-size: var(--t-small); color: var(--black);
}
a.venue-bit { min-height: 44px; }
a.venue-bit:hover { color: var(--burgundy); }
.venue-bit .ico { width: 17px; height: 17px; flex-shrink: 0; opacity: .75; }

.venue-social {
  display: flex; flex-wrap: wrap; align-items: center; gap: 0;
  margin-top: -14px;
}
.venue-social a {
  display: inline-grid; place-items: center;
  /* 44 is the touch minimum; the icon is 22 and the rest is hit area. */
  width: 44px; height: 44px; margin-inline-start: -10px;
  font-size: var(--t-small); color: var(--black);
}
.venue-social a:first-child { margin-inline-start: -10px; }
.venue-social a:hover { color: var(--burgundy); }
.venue-social .ico { width: 22px; height: 22px; }

/* flex, not inline-flex: as an inline box it sits inside an anonymous line box
   whose half-leading added 6px above and 16px below, which no margin here could
   see. Block-level with a shrink-to-fit width behaves and keeps the link the
   width of its own text. */
/* The 44px touch target lives entirely in padding, and the padding is cancelled
   by an equal negative margin -- so the box this contributes to the layout is
   exactly its text box, while the tappable area stays 44px.
   That matters because the rendered text box is not the same height in both
   languages: 16.2px in English against 25.6px in Arabic, at the same font size.
   With a fixed 44px box the space below the text differed by 4.7px between the
   two, so no single margin below could give the same visible gap in both. With
   the box hugging the text, one margin does. */
.venue-reviews {
  display: inline-flex; align-items: center; gap: 7px;
  min-height: 44px; font-size: var(--t-small); color: var(--black);
}
.venue-reviews:hover { color: var(--burgundy); }
.venue-reviews .ico { width: 15px; height: 15px; opacity: .8; }

/* Rhythm down the page, matching the reference: the details card, then a clear
   gap to the group tabs, a smaller one to the search, and a larger one before
   the menu itself starts. */
/* The menu page has no section header in the reference layout: the details card
   runs straight into the Eat/Drink tabs. The editorial rule and its 96px of air
   belong to sections that open with a heading, so they are dropped here rather
   than left to depend on the eyebrow and heading both happening to be empty --
   which is a content value, and content values come back. */
.sec--browser .sec-head {
  border-bottom: 0;
  padding-bottom: 0;
  margin-bottom: var(--space-xs);
  gap: var(--space-2xs) var(--space-xl);
}

/* The menu's own label: "Asian Restaurant" over "Menu". It is the one h2 that
   names the page, so it has to exist in the markup, but it sits under the
   venue's h1 and above a guest's first category -- it labels the column, it
   does not open it. The shared --t-h2 is sized for editorial section openers
   and set "Menu" at 56px on a desktop, larger than KENORA above it, and the
   default eyebrow gap and 96px .sec-head air pushed the first category ~100px
   down a phone. Everything here keeps the column's single ~24px rhythm instead.
   The size is a --t-h2 default on the section, not a font-size on the h2, so
   the Style tab's heading-size override (#id, written on the same variable)
   still wins. */
.sec--browser { --t-h2: clamp(1.6rem, 3vw, 2rem); }
.sec--browser .venue + .sec-head { margin-top: var(--space-sm); }
/* The same Montserrat half-leading the tools rule below gives back, plus the
   eyebrow's own: measured text box to text box, 24px read as 39.9 in English
   and 32.9 in Arabic. 18 brings English to 33.9, the reviews-to-pills gap the
   column had before it carried a heading. */
body.lang-en .sec--browser .venue + .sec-head { margin-top: 18px; }
.sec--browser .sec-head .eyebrow { margin-bottom: var(--space-2xs); }
/* It is the top of the menu, already on screen: fading it in would animate one
   line of a panel whose every other row is static. The hidden state is only
   ever added under html.anim-ready, so this removes a state, never content. */
html.anim-ready .sec--browser .sec-head.reveal { opacity: 1; transform: none; }

/* Inside a category: no search field, and the tabs scroll away so that only
   the Back button stays pinned. Keeping the bar sticky here would park tabs,
   search and Back together -- about 170px of chrome on a 390x844 phone, for a
   page whose entire job is showing dishes. */
.menu-tools.in-category { position: static; }
.menu-tools.in-category .menu-search { display: none; }
/* With the search field gone, the tabs' bottom margin -- which exists to
   separate them from it -- is dead space above Back. */
.menu-tools.in-category .menu-groups { margin-bottom: 0; }
/* 26, not 24: the display serif's glyphs overshoot their line box by about 2px
   at the top, so a 24px margin reads as 22 against the category name's caps.
   The number is a property of the typeface, not of the layout, and Arabic used
   to need its own -- 31px, because Amiri overshoots by nearly 5. Cairo does
   not overshoot, so Arabic takes the same 26 as English: measured from Back to
   the category name's ink, that is the 33px Amiri produced at 31. */
.menu-tools.in-category + .menu-back { margin-bottom: 26px; }

/* The pills and the search sit close under the details, as in the reference.
   Both rows carry their own 44px+ padding, so the written gap is small -- and
   negative directly under the venue card, whose last row is a 44px link. The
   menu's own heading, when it has one, sits between the two and takes the same
   rhythm, so the tools follow it exactly as they follow the card. */
/* 8, not 24: the sticky bar already carries 16px of bottom padding, which
   reads as space whether it is parked or not. */
.sec--browser .menu-tools { margin-top: var(--space-sm); margin-bottom: var(--space-2xs); }
/* All three gaps down this column are one value, ~24px: Google reviews to the
   Eat/Drink pills, the pills to the search field, and the search field to the
   first category card. The last of those is the bar's own 16px bottom padding
   plus 8px of collapsed margin, so the other two are written to match it.

   .venue-reviews contributes only its text box to the layout (its touch target
   is padding cancelled by margin), so 24px here is 24px of visible space below
   the text in both languages. The bar has no top padding in flow; .is-stuck
   adds it from main.js only while it is parked under the header. */
.sec--browser .venue + .menu-tools,
.sec--browser .sec-head + .menu-tools { margin-top: var(--space-sm); padding-block: 0 var(--space-xs); }
/* The eye measures from the glyphs, and a font's glyph box is not its line box.
   Montserrat at 14px draws 16.2px inside a 22.4px line, leaving 3.1px of half
   leading below the text; Cairo draws 25.6px in the same line and overflows it.
   So the same 24px margin reads as 27.8px in English and 23.1px in Arabic. The
   Latin side gives that leading back. */
body.lang-en .sec--browser .venue + .menu-tools { margin-top: 20px; }
/* Sticking moves 8px of padding from the bottom to the top rather than adding
   it: the block padding totals 16px either way, so the box height never changes
   and nothing below it jumps.
   Compensating with margin-bottom instead did not work -- that margin collapses
   with .cat-cards' margin-top, so dropping it from 8 to 0 left the collapsed
   value at 8 and the page still shifted by the 8px the padding added.
   The selector also has to match the rule above it: `.menu-tools.is-stuck`
   alone scores (0,2,0) against that rule's (0,3,0) and lost silently. */
.sec--browser .venue + .menu-tools.is-stuck,
.sec--browser .sec-head + .menu-tools.is-stuck { padding-block: var(--space-2xs); }
.sec--browser .menu-groups { margin-bottom: var(--space-sm); }
/* 8, and the sticky bar's 16px of bottom padding makes up the rest. These are
   adjacent siblings, so the two margins collapse to the larger -- setting only
   the bar's margin-bottom left this one at 24 and changed nothing.
   .menu-rows deliberately does NOT carry this. It always renders, holding
   nothing but hidden blocks while the category cards are showing, so its top
   margin was 8px of phantom space between the last card and the footer. Where
   it does need separating -- under Back, or under the tools while searching --
   the margin above it is larger and wins the collapse anyway. */
.sec--browser .cat-cards { margin-top: var(--space-2xs); }

/* Desktop sizing. This block has to sit AFTER the base rules above: the
   earlier `@media (min-width: 780px)` block in this file comes before them,
   and at equal specificity the later declaration wins regardless of the media
   query -- so putting these there silently did nothing. */
/* The taller banner, deeper overlap and larger search field were sized for a
   full-width desktop menu. In one 560px column they are the phone's numbers at
   every width, which is the whole point of the column. */

/* ===== Menu groups (Eat / Drink) and category cards ===== */

.menu-groups { display: flex; gap: 10px; }
.group-tab {
  display: inline-flex; align-items: center; min-height: 44px; padding: 0 22px;
  border-radius: var(--r-pill);
  font-family: var(--font-en); font-size: var(--t-small); font-weight: 600;
  letter-spacing: .08em; white-space: nowrap;
  color: var(--burgundy); border: var(--rule) solid currentColor;
  background: transparent; cursor: pointer;
  transition: background var(--dur-fast) var(--ease-out), color var(--dur-fast) var(--ease-out);
}
html[dir="rtl"] .group-tab { font-family: var(--font-ar); letter-spacing: 0; }
.group-tab:hover { background: color-mix(in srgb, var(--burgundy) 8%, transparent); }
.group-tab.is-active { background: var(--burgundy); color: var(--ivory); }
.sec--dark .group-tab, .sec--burgundy .group-tab { color: var(--gold-soft); }
.sec--dark .group-tab.is-active, .sec--burgundy .group-tab.is-active {
  background: var(--gold-soft); color: var(--deep);
}

/* Category cards: photograph with the name overlaid, borrowed from the first
   photographed dish in the category. A category with no photographed dish gets
   a plain burgundy card — honest, and it upgrades itself when a photo lands. */
.cat-cards { display: grid; gap: var(--space-sm); }
/* 5:2 on a phone. The height used to be pinned at 132px while the width
   followed the screen, so the card was 2.2:1 at 360px and 5.1:1 at 779 -- no
   exported photograph could fit that, only survive it. 5:2 is the shape it
   already had at 390px (326.7x132), so the phone layout is unchanged to the
   eye while the crop becomes a number a photographer can be given.
   Desktop is square; see the min-width: 780px block. */
.cat-card {
  position: relative; display: block; overflow: hidden;
  /* Stated as a ratio rather than left to a min-height and a fluid width. The
     column is now a fixed 560px, so 5:2 is a single number a photographer can
     be given, and it is the shape the phone already had at 390px. */
  border-radius: var(--r-lg); min-height: 132px; aspect-ratio: 5 / 2;
  background: var(--burgundy); color: var(--ivory);
  display: grid; place-items: center; text-align: center;
}
/* picture() wraps static assets in <picture> but serves KV uploads as a bare
   <img>. Only the <img> carried position:absolute, so a card backed by a static
   asset kept an in-flow <picture> that added a grid row and pushed the name
   down — 117px against 78px on the cards beside it. Both must leave the flow. */
.cat-card picture { position: absolute; inset: 0; display: block; }
.cat-card-img {
  position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover;
}
/* Scrim so the overlaid name stays legible over any photograph. Ivory on this
   is well clear of AA; the name is never gold, which would fail on a light
   photo. */
.cat-card.has-img::after {
  content: ''; position: absolute; inset: 0;
  background: linear-gradient(180deg,
    color-mix(in srgb, var(--black) 22%, transparent),
    color-mix(in srgb, var(--black) 68%, transparent));
}
.cat-card-name {
  position: relative; z-index: 1;
  font-family: var(--font-en); font-size: var(--t-small); font-weight: 600;
  letter-spacing: .22em; text-transform: uppercase; color: var(--ivory);
  padding: 0 var(--space-sm);
}
html[dir="rtl"] .cat-card-name { font-family: var(--font-ar); letter-spacing: .04em; }

/* Sticky, so it travels down the category with the guest -- the one control
   that has to stay reachable once the tabs have scrolled away. Filled rather
   than transparent because dish rows pass underneath it; ivory on the ivory
   panel, so the fill is invisible until it is doing its job.
   flex + fit-content rather than inline-flex: an inline-level sticky box is
   positioned against its line box, which is not what is wanted here. */
.menu-back {
  display: flex; width: fit-content; align-items: center; gap: 8px;
  min-height: 44px; padding: 0 18px; margin-bottom: var(--space-sm);
  border-radius: var(--r-pill);
  font-family: inherit; font-size: var(--t-micro); letter-spacing: .12em;
  text-transform: uppercase; color: var(--burgundy);
  background: var(--ivory); border: var(--rule) solid currentColor; cursor: pointer;
  position: sticky; top: calc(var(--header-h) + var(--space-2xs)); z-index: 6;
}
html[dir="rtl"] .menu-back span[aria-hidden] { transform: scaleX(-1); display: inline-block; }
.sec--dark .menu-back, .sec--burgundy .menu-back { color: var(--gold-soft); }

/* One category per line at every width, so the 5:2 band is the only crop a
   photographer is ever given. The square three-up desktop variant is gone with
   the full-width menu it was drawn for. */

/* ===== Hero: fit the CTA above the fold =====
   Measured before this block, at a 1278x602 viewport: hero 972px tall with the
   buttons 306px below the fold. The drivers were the headline (89px type over
   three lines = 282px) and a 96px margin above the button row. The hero has to
   show the reserve button without a scroll, so the display type is scoped
   smaller here than in section headings and the rhythm is tightened. */
.hero-content h1 {
  font-size: clamp(2.1rem, 4.4vw, 3.6rem);
  max-width: 20ch;
}
.hero--editorial .hero-content h1 { max-width: 18ch; }
.hero-content .hero-logo { margin-bottom: var(--space-sm); }
.hero-content .eyebrow { margin-bottom: var(--space-2xs); }
.hero-content .hero-ar { margin-bottom: var(--space-xs); }
.hero-content .btn-row { margin-top: var(--space-sm); }

/* Short viewports — a laptop with browser chrome, or a phone in landscape —
   need more than proportional type scaling, so the fixed spacing tightens too.
   Without this the button row lands just under the fold on a 602px viewport. */
@media (max-height: 760px) {
  .hero-content h1 { font-size: clamp(1.9rem, 3.4vw, 2.6rem); }
  .hero-content .hero-logo { width: 76px !important; margin-bottom: var(--space-xs); }
  .hero--editorial .hero-content {
    padding-block: calc(var(--header-h) + var(--space-sm)) var(--space-md);
  }
  .hero-content .tagline-sub { font-size: var(--t-small); }
  .hero-content .btn-row { margin-top: var(--space-xs); }
}

/* Wide desktop: centre the nav-and-badge cluster on the page.
   The actions leave the grid and pin to the end edge, and BOTH sides reserve
   the same width for them — otherwise the cluster looks centred while the last
   nav link runs underneath the buttons, measured at a 31px overlap of
   "Contact Us" and the language toggle. Below 1200px there is not enough room
   to reserve both sides, so the header keeps its four-column layout with the
   cluster sitting left of centre. */
@media (min-width: 1200px) {
  .header-inner {
    grid-template-columns: 1fr auto 1fr;
    /* Reserve is deliberately LESS than the buttons' ~250px. Reserving the full
       width made the nav columns collapse to exactly the links' width, so the
       last link ended flush against the buttons at 0px clearance every time.
       At 200px the columns are wider than the links, the links sit at the start
       of their column, and real space opens up before the buttons. The reserve
       is symmetric, so the cluster stays centred either way. */
    padding-inline: calc(var(--gutter) + 200px);
  }
  /* The reserve costs the nav columns width, so the links tighten to match.
     At the default 40px gap the last link cleared the buttons by 1px, which is
     one label change away from colliding. */
  .site-header .main-nav { gap: var(--space-sm); }
  .header-actions {
    position: absolute;
    /* Pinned nearer the edge than the page gutter. The nav links are anchored
       to the centred badge, so their right edge is fixed by their own width --
       widening or narrowing the reserve cannot move them. The only way to open
       clearance is to push the buttons outward, and at this width the gutter is
       ~76px of space doing nothing. */
    inset-inline-end: var(--space-sm);
    top: 50%; transform: translateY(-50%);
  }
}

/* Phones and tablets in landscape: the viewport is shorter than the hero's
   floor, so drop the floor entirely and let the copy set the height. Scoped
   away from desktop, which wants the full-screen treatment below. */
@media (max-height: 760px) and (max-width: 1023px) {
  /* Nothing to point at on a small screen. */
  .hero .scroll-hint { display: none; }
}

/* Landscape phones only. 72svh of a ~390px viewport is smaller than the copy,
   so the proportional height buys nothing and a floor would push the button
   off screen. Let the content set the height instead. */
@media (max-height: 520px) and (max-width: 1023px) {
  .hero { min-height: 0; }
}

/* Desktop: full-screen hero. The copy is short enough since the logo, accent
   line and second button came out that it sits comfortably centred with the
   CTA well above the fold — which is what made the earlier full-height hero
   unusable, not the height itself. Phones keep the clamp above: 100svh there
   would put the button under the fold again on a small screen. */
@media (min-width: 1024px) {
  .hero { min-height: 100svh; }
}

/* Editorial scrim, retuned for the short hero.
   The previous one stacked a heavy directional wash over a vertical one that
   put 88% black along the bottom edge. At full height that read as depth; at
   583px it collapsed onto itself and the photograph rendered essentially
   black — verified by toggling the scrim off, which showed a bright, busy
   image underneath.

   This photograph has no negative space: food fills the frame edge to edge,
   which is why the floating-plate layout existed. Making an editorial layout
   work with it means an explicit dark panel on the copy edge that clears
   completely by the halfway mark, so the drinks and trays on the far side stay
   visible. The vertical wash is reduced to a top band for the header only. */
.hero--editorial .hero-scrim {
  background:
    linear-gradient(to var(--hero-copy-edge, right),
      color-mix(in srgb, var(--black) 92%, transparent) 0%,
      color-mix(in srgb, var(--black) 82%, transparent) 26%,
      color-mix(in srgb, var(--black) 45%, transparent) 46%,
      transparent 66%),
    linear-gradient(to bottom,
      color-mix(in srgb, var(--black) 62%, transparent) 0%,
      transparent 26%);
}

/* The hero headline carries its own line break: the copy reads
   "Crafting Asia," / "One Plate at a Time", and where that break falls is an
   editorial decision, not something to leave to the column width. pre-line
   honours a newline in the content while still collapsing runs of spaces, so
   the break lives in the database rather than in markup. Headings without a
   newline (the Arabic one) wrap normally. */
.hero-content h1 { white-space: pre-line; }

/* Hero focal point. The photograph is 3:2 and the hero is far wider than that,
   so `cover` crops top and bottom — and centring the crop put the food in the
   bottom third with half the frame empty. Biasing downward lifts the spread
   into view while keeping the banquette above it as the dark ground the
   headline sits on. Adjust this one value if the hero photograph changes. */
.hero--editorial .hero-media img { object-position: 50% 68%; }

/* Mirror the hero photograph on the Arabic page.
   The editorial scrim puts its dark panel on the copy edge, which flips with
   direction — so English shows the right half of the photograph and Arabic
   shows the left. This photograph has its food on the right and the banquette
   on the left, which left the Arabic hero looking empty while English looked
   full. Mirroring rebalances it. The image carries no text or logo, so it
   mirrors cleanly; a future hero photograph containing either would need this
   rule removed.

   Applied through a second keyframe rather than a plain transform, because the
   drift animation also writes `transform` and would otherwise overwrite it. */
@keyframes heroDriftFlip {
  from { transform: scaleX(-1) scale(1.04); }
  to   { transform: scaleX(-1) scale(1.12); }
}
html[dir="rtl"] .hero--editorial .hero-media img { animation-name: heroDriftFlip; }
@media (prefers-reduced-motion: reduce) {
  html[dir="rtl"] .hero--editorial .hero-media img {
    animation: none; transform: scaleX(-1);
  }
}

/* Mobile hero scrim.
   The editorial scrim is a horizontal panel: dark on the copy edge, clear by
   66%. That works when the copy occupies a third of a wide viewport. On a
   phone the copy spans nearly the full width, so the "clear" side sits
   directly under the body paragraph and the text crosses bright food — the
   headline stayed legible on dark banquette while the paragraph did not.
   Weight it vertically instead: lighter through the middle so the photograph
   still reads, heavier at top and bottom where the header and copy sit. */
@media (max-width: 780px) {
  .hero--editorial .hero-scrim {
    background:
      linear-gradient(to bottom,
        color-mix(in srgb, var(--black) 68%, transparent) 0%,
        color-mix(in srgb, var(--black) 40%, transparent) 32%,
        color-mix(in srgb, var(--black) 50%, transparent) 64%,
        color-mix(in srgb, var(--black) 74%, transparent) 100%);
  }
}

/* ===== Mobile header and hero controls =====
   Sized against the reference layout: a chunkier hamburger, both header
   controls as pills, and a hero button that is not full-bleed. Phone only —
   the desktop header is unchanged. */
@media (max-width: 1024px) {
  /* Hamburger: short, thick, tight. The default is three hairlines 26px wide,
     which reads as a faint scratch at phone size rather than a control. */
  /* --header-h is written into an inline <style> AFTER the stylesheet link, so
     a plain `:root` override here would lose on source order. `html:root` is
     one specificity point higher and wins regardless. The reference header is
     60 CSS px, measured at a 390px viewport, against our 88.

     Ours is 64, not 60, and that is deliberate. The reference header element
     measures 60 but its background is transparent -- it has no visible edge,
     so the bar reads as tall as whatever it sits over. Ours is a solid band
     with a hard bottom edge, and its logo badge is 56px against the
     reference's 46px logo, so the same 60px leaves the badge almost touching
     both edges and the bar reads shorter than the reference's. 64 puts the
     visible band at the same ~95 device pixels the reference occupies on the
     same phone. Measure the occupied area, not the element. */
  html:root { --header-h: 64px; }

  /* Hamburger. The reference icon is compact and blocky -- roughly 5:1 width
     to thickness -- where ours was nearer 9:1 and read as three long strokes.
     Shorter, thicker, tighter, with barely-rounded ends. The 44x44 tap target
     is kept via padding, so only the glyph shrinks. */
  /* Hamburger, matched to the reference glyph: thick bars with gaps narrower
     than the bars themselves (roughly 0.7x, where ours were 1.1x), and a
     middle bar about two-thirds width sitting on the inline start edge.
     align-self rather than a margin, so it flips with direction in Arabic.
     The 44x44 tap target is held by padding — only the glyph is this small. */
  /* Hamburger, measured off the reference close-up rather than eyeballed.
     Two proportions define that glyph:
       - bars run about 4:1 length to thickness, where ours were nearer 7:1,
       - and the whole icon is essentially SQUARE in its bounding box
         (3 bars + 2 gaps adds up to about its own width), where ours was wide
         and flat at roughly 0.8:1.
     20px wide with 4.5px bars and 3px gaps gives 19.5px of height against 20px
     of width. The middle bar stays two-thirds width on the inline start edge,
     via align-self so it mirrors in Arabic. Corners are barely rounded — the
     reference reads as square-cut at this size. The 44x44 tap target is held
     by padding, so only the glyph is this small. */
  .menu-toggle { gap: 3px; padding: 12px; min-width: 44px; min-height: 44px; }
  .menu-toggle span {
    width: 20px; height: 4.5px; border-radius: 1px;
    background: var(--ivory);
  }
  /* The middle bar is the SAME length as the other two — it is simply nudged
     toward the inline end edge: right in English, left in Arabic. A transform
     rather than a margin, so it shifts the bar without changing the flex box's
     size or re-centring the glyph, and the RTL rule mirrors it explicitly. */
  .menu-toggle span:nth-child(2) { transform: translateX(4px); }
  html[dir="rtl"] .menu-toggle span:nth-child(2) { transform: translateX(-4px); }

  /* Both header controls share the pill shape and the fill treatment at every
     width; on a phone they also have to share a height and tighten together. */
  .site-header .btn-reserve,
  .site-header .lang-toggle {
    border-radius: var(--r-pill);
    /* Equal height, or they read as two unrelated controls rather than a pair.
       .btn-reserve carries its own block padding and a 48px floor, which came
       out 10px taller than the toggle.
       39px, 15px regular, no tracking and no uppercase: the reference button
       measured 136x39 at 15px/400 with 24px of side padding, and this is that
       button. The toggle keeps a tighter inline padding because it holds two
       letters, not a word. */
    min-height: 39px;
    padding-block: 0;
    font-size: 15px;
    font-weight: 400;
    letter-spacing: normal;
    text-transform: none;
    padding-inline: 14px;
    /* The hit area is extended past the visual box below, so it needs a
       containing block. */
    position: relative;
  }
  .site-header .btn-reserve { padding-inline: 24px; }
  /* 39px is under the 44px touch minimum this project holds itself to, and the
     reference does not. Rather than fatten the button, an invisible overlay
     takes the tap area to 45 while the pill stays exactly 39. */
  .site-header .btn-reserve::after,
  .site-header .lang-toggle::after {
    content: ''; position: absolute; inset: -3px 0;
  }
  /* The hero CTA sits to the copy edge at its own width, as in the reference,
     instead of stretching the full column. Other button rows on the page keep
     the full-width stacking, which suits them. */
  .hero .btn-row { width: auto; align-items: flex-start; }
  .hero .btn-row a { width: auto; padding-inline: 34px; min-height: 52px; }
}

/* Small phones. The badge, both pills and the hamburger together overran a
   360px viewport by 27px, and the language pill slid underneath the logo —
   an overlap, not a wrap, so nothing visibly broke except the logo. The
   controls tighten rather than hide: Reservation has to stay reachable at
   every width. */
@media (max-width: 379px) {
  .header-actions { gap: 6px; }
  .site-header .btn-reserve,
  .site-header .lang-toggle {
    padding-inline: 10px;
    font-size: .64rem;
    letter-spacing: .04em;
  }
}

/* Mobile hero: sit the copy low in the frame.
   Centred vertically, the CTA landed around 71% down the hero; the reference
   puts it near the bottom, at roughly 90%. Bottom-aligning the block moves the
   headline down with it, which is how the reference reads too. */
@media (max-width: 1024px) {
  /* --vvh is written by main.js from the measured visual viewport. iOS Safari
     reports svh and dvh that do not match what is actually on screen: with the
     URL bar part-collapsed the visible area is taller than svh, and dvh did not
     close the gap either -- a strip of the next section stayed visible above
     the toolbar. Measuring is the only thing that is exact. dvh remains the
     fallback for a dead script.

     Exactly one viewport. Not lvh and not any multiple of it: lvh is the
     viewport with the chrome RETRACTED, so it runs past the screen's bottom
     edge while the toolbar is showing.

     The consequence, accepted deliberately: Safari's toolbar floats over a
     strip that the viewport excludes but the page still paints into, so a
     sliver of the next section shows behind the toolbar. Covering that strip
     requires being taller than the viewport, which is not what this is for. */
  .hero { min-height: var(--vvh, 100dvh); align-items: flex-end; }

  .hero--editorial .hero-content {
    /* Fixed padding. The hero is exactly one viewport tall, so its bottom IS
       the visible bottom and there is nothing to compensate for. A padding
       that varied with the viewport is what made the CTA drift up the screen
       while scrolling. */
    padding-block: calc(var(--header-h) + var(--space-md)) var(--space-lg);
  }
  /* The hint is pinned to the bottom centre of the hero, which is exactly
     where the CTA now sits -- they overlapped, with "SCROLL" printed through
     the button. The copy occupying the lower frame is itself the cue that
     there is more below. */
  .hero .scroll-hint { display: none; }
}
