/* ==========================================================================
   Passion Kitchen — sample site
   ==========================================================================

   DIRECTION — "night service". Warm dark, colour, curves.

   Third pass, and the one Leo asked for. The history matters, because each
   version failed in a way this one has to avoid:

   v1  near-black + one bright orange, expanded caps, six saturated icon tiles,
       pill buttons and shadows. Read cheap and cartoony.
   v2  putty ground, low-contrast serif, square edges, forest green. Read fancy
       and creamy — correct for a restaurant guide, wrong for a Jamaican
       takeaway on Blackstock Road.
   v3  this. Dark and colourful like v1, curved like v1, with the scrolling
       dish rail back, but built with the discipline v1 was missing.

   The specific things that made v1 cheap, and what is done instead:

   · SIX SATURATED ICON TILES IN SIX COLOURS → two colours, assigned by meaning.
     Flame for the three ways to order, green for the three ways to host. Colour
     that carries information rather than colour as decoration.
   · GENERIC RADII EVERYWHERE → one scale, used consistently: 14px on controls,
     22px on cards, pill only on small tags. Curved, not bubbly.
   · EXPANDED BLACK CAPS → Plus Jakarta Sans at 700, sentence case, moderate
     sizes. Warm and modern without the fast-food-chain shout.
   · BRIGHT ORANGE AS LARGE FILLS WITH WHITE TEXT → their orange stays exactly
     as they chose it (#EA580C) and the text on it is near-black, which measures
     5.4:1 instead of 3.6:1 and looks more considered besides.
   · SLATE-BLACK → the ground is a warm cocoa-black. A blue-black reads SaaS
     dashboard; a warm one reads food. It is the cheapest single change here and
     probably the most effective.

   Photography is the only other colour on the page, at full saturation, in
   rounded frames — see _images.ps1.

   Mobile first: every base rule is the 390px design.
   Colour pairs are measured in _contrast.mjs, never judged by eye.
   ========================================================================== */

@font-face {
  font-family: 'Jakarta';
  font-style: normal;
  font-weight: 400 800;
  font-display: swap;
  src: url('fonts/jakarta-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
    U+02DC, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

/* ---------- tokens ---------- */
:root {
  /* warm cocoa-black, not slate */
  --ink:    #16100D;
  --ink-2:  #1F1712;
  --ink-3:  #2A2019;
  --line:   #3A2E25;
  --line-2: #4E3E31;   /* decorative card borders */
  --field-line: #826751;   /* a control boundary needs 3:1 on every surface it sits on */

  --cream:  #F8F2E9;
  --muted:  #C4B6A7;
  --dim:    #A2937F;

  --flame:    #EA580C;
  --flame-lt: #FB9042;
  --green:    #15803D;
  --green-lt: #55C47E;
  --gold-lt:  #E5BE68;
  --sky:      #38BDF8;   /* their fourth colour: delivery, and the stall outdoors */
  --sky-dim:  rgba(56, 189, 248, .16);

  --r-sm:  10px;
  --r:     14px;
  --r-lg:  22px;
  --r-xl:  28px;
  --pill:  999px;

  --gut: 20px;
  --band: 72px;
  --maxw: 1180px;

  --e:      cubic-bezier(.22, .61, .36, 1);
  --e-slow: cubic-bezier(.16, 1, .3, 1);

  --focus: var(--flame-lt);
}
@media (min-width: 700px)  { :root { --gut: 34px; --band: 96px; } }
@media (min-width: 1040px) { :root { --gut: 48px; --band: 116px; } }

/* ---------- reset ---------- */
*, *::before, *::after { box-sizing: border-box; }
* { margin: 0; }
html { -webkit-text-size-adjust: 100%; scroll-behavior: smooth; }
@media (prefers-reduced-motion: reduce) { html { scroll-behavior: auto; } }

body {
  background: var(--ink);
  color: var(--cream);
  font-family: 'Jakarta', ui-sans-serif, system-ui, -apple-system, 'Segoe UI', sans-serif;
  font-size: 16px;
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
  overflow-x: clip;
}
body.locked { overflow: hidden; }

img, svg { display: block; max-width: 100%; }
img { height: auto; }
button, input, select, textarea { font: inherit; color: inherit; }
button { background: none; border: 0; cursor: pointer; }
a { color: inherit; }
ul, ol { list-style: none; padding: 0; }
[hidden] { display: none !important; }

:focus-visible { outline: 2px solid var(--focus); outline-offset: 3px; border-radius: 4px; }
a, button, input, select, textarea { touch-action: manipulation; }

.skip {
  position: absolute; left: 12px; top: -100px; z-index: 200;
  background: var(--flame); color: var(--ink);
  padding: 12px 18px; border-radius: var(--r); font-weight: 700; text-decoration: none;
}
.skip:focus { top: 12px; }

.vh { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }

/* ---------- layout ---------- */
.wrap { width: 100%; max-width: var(--maxw); margin-inline: auto; padding-inline: var(--gut); }
.band { padding-block: var(--band); position: relative; }
.band-2 { background: var(--ink-2); }
.band-warm {
  background:
    radial-gradient(120% 80% at 12% 0%, rgba(234, 88, 12, .1), transparent 62%),
    var(--ink);
}

/* the faintest grain — large flat dark areas band badly on cheap phone screens */
.band::after, .foot::after, .hero::after {
  content: ''; position: absolute; inset: 0; pointer-events: none;
  opacity: .3; mix-blend-mode: overlay;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='180' height='180'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.9' numOctaves='2' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='180' height='180' filter='url(%23n)' opacity='.3'/%3E%3C/svg%3E");
}

/* ---------- type ---------- */
h1, h2, h3, h4 { font-weight: 700; line-height: 1.12; letter-spacing: -.025em; text-wrap: balance; }
h1 { font-size: clamp(2.1rem, 6.6vw, 3.5rem); line-height: 1.05; letter-spacing: -.032em; }
h2 { font-size: clamp(1.7rem, 4.6vw, 2.5rem); letter-spacing: -.03em; }
h3 { font-size: clamp(1.1rem, 2.6vw, 1.28rem); letter-spacing: -.018em; }

.label {
  display: inline-block;
  font-size: .72rem; font-weight: 700;
  letter-spacing: .16em; text-transform: uppercase;
  color: var(--flame-lt);
}
.label-green { color: var(--green-lt); }

.lead { font-size: 1.03rem; color: var(--muted); max-width: 48ch; }
p + p { margin-top: 1em; }

.band-head { display: grid; gap: 16px; margin-bottom: 40px; }
.band-head.center { justify-items: center; text-align: center; }
.band-head.center .lead { max-width: 46ch; }
@media (min-width: 900px) {
  .band-head.center { grid-template-columns: minmax(0, 40rem); justify-content: center; }
}
.band-head h2 { margin-top: 14px; }
@media (min-width: 900px) {
  .band-head { grid-template-columns: minmax(0, 1.2fr) minmax(0, 1fr); gap: 52px; align-items: end; }
}

/* ---------- buttons ---------- */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 9px;
  min-height: 50px; padding: 13px 24px;
  border-radius: var(--r);
  background: var(--flame); color: var(--ink);
  font-size: .96rem; font-weight: 700; letter-spacing: -.01em; text-decoration: none;
  border: 1px solid var(--flame);
  transition: background .3s var(--e), border-color .3s var(--e), transform .3s var(--e-slow);
}
.btn:hover { background: var(--flame-lt); border-color: var(--flame-lt); transform: translateY(-1px); }
.btn:active { transform: none; }
.btn[disabled] { opacity: .4; pointer-events: none; }

.btn-line { background: none; color: var(--cream); border-color: var(--field-line); }
.btn-line:hover { background: var(--ink-3); border-color: var(--dim); color: var(--cream); }

.btn-green { background: var(--green-lt); border-color: var(--green-lt); color: var(--ink); }
.btn-green:hover { background: #6ED193; border-color: #6ED193; }

.btn-sm { min-height: 44px; padding: 10px 18px; font-size: .9rem; }
.btn-block { width: 100%; }

.lnk {
  display: inline-flex; align-items: center; gap: 8px;
  font-weight: 700; font-size: .95rem; text-decoration: none; color: var(--flame-lt);
}
.lnk .arw { transition: transform .4s var(--e-slow); }
.lnk:hover .arw { transform: translateX(4px); }
.arw { font-style: normal; }

/* ---------- header ---------- */
.nav {
  position: sticky; top: 0; z-index: 90;
  background: rgba(22, 16, 13, .86);
  backdrop-filter: saturate(150%) blur(14px);
  -webkit-backdrop-filter: saturate(150%) blur(14px);
  border-bottom: 1px solid transparent;
  transition: border-color .4s var(--e);
}
.nav.stuck { border-bottom-color: var(--line); }
.nav-in { display: flex; align-items: center; gap: 16px; height: 66px; }
@media (min-width: 900px) { .nav-in { height: 78px; } }

.brand { display: flex; align-items: center; gap: 9px; text-decoration: none; margin-right: auto; }
.brand-name { font-size: 1.02rem; font-weight: 800; letter-spacing: -.02em; white-space: nowrap; }
.brand-name i { font-style: normal; color: var(--flame-lt); }

.nav-links { display: none; }
@media (min-width: 900px) {
  .nav-links { display: flex; align-items: center; gap: 6px; }
  .nav-links a {
    padding: 9px 14px; border-radius: var(--pill);
    text-decoration: none; font-size: .93rem; font-weight: 500; color: var(--muted);
    transition: color .3s, background .3s;
  }
  .nav-links a:hover, .nav-links a[aria-current="page"] { color: var(--cream); background: var(--ink-3); }
}

.nav-actions { display: flex; align-items: center; gap: 10px; }
/* The call button beside the burger. It was the basket until collection
   ordering came out on 14 September 2026; ringing is how a collection order is
   placed now, so it keeps the same place and the same shape. */
.call-btn {
  display: flex; align-items: center; gap: 8px;
  min-height: 44px; padding: 9px 15px; border-radius: var(--pill);
  border: 1px solid var(--field-line); text-decoration: none;
  font-size: .9rem; font-weight: 600; color: var(--cream);
  transition: border-color .3s, background .3s;
}
.call-btn:hover { border-color: var(--dim); background: var(--ink-3); }
.call-btn svg { width: 16px; height: 16px; stroke: currentColor; fill: none; }

.burger { width: 44px; height: 44px; display: grid; place-items: center; margin-right: -8px; }
@media (min-width: 900px) { .burger { display: none; } }
.burger span { display: block; width: 19px; height: 2px; border-radius: 2px; background: var(--cream); transition: transform .45s var(--e-slow), opacity .2s; }
.burger span + span { margin-top: 5px; }
body.nav-open .burger span:nth-child(1) { transform: translateY(7px) rotate(45deg); }
body.nav-open .burger span:nth-child(2) { opacity: 0; }
body.nav-open .burger span:nth-child(3) { transform: translateY(-7px) rotate(-45deg); }

.navsheet {
  position: fixed; inset: 66px 0 0; z-index: 85;
  background: var(--ink);
  padding: 22px var(--gut) 34px;
  display: flex; flex-direction: column; justify-content: space-between; gap: 28px;
  opacity: 0; visibility: hidden; transform: translateY(-8px);
  transition: opacity .35s var(--e), transform .45s var(--e-slow), visibility 0s .35s;
}
body.nav-open .navsheet { opacity: 1; visibility: visible; transform: none; transition: opacity .35s var(--e), transform .45s var(--e-slow), visibility 0s 0s; }
@media (min-width: 900px) { .navsheet { display: none; } }
.navsheet li + li { border-top: 1px solid var(--line); }
.navsheet a { display: block; padding: 17px 0; text-decoration: none; font-size: 1.5rem; font-weight: 700; letter-spacing: -.025em; }
.navsheet-foot { border-top: 1px solid var(--line); padding-top: 20px; display: grid; gap: 6px; }
.navsheet-foot a { font-size: 1.2rem; font-weight: 700; text-decoration: none; color: var(--flame-lt); }
.navsheet-foot p { font-size: .9rem; color: var(--muted); }

/* ---------- hero ----------
   A single full-bleed panel rather than a copy column beside a tall photo. The
   split version left a big dead gap above the copy, because the text was being
   vertically centred against a 1050px image. Here the photograph IS the hero and
   the words sit on it.

   Contrast is handled by the scrim, not by hope: the gradient reaches near-solid
   ink under every line of copy, so the text measures the same as it does on the
   page ground. If the photograph is ever swapped, check that first. */
.hero { position: relative; padding-block: 20px 40px; }
@media (min-width: 900px) { .hero { padding-block: 26px 64px; } }

.hero-panel {
  position: relative; isolation: isolate;
  border-radius: var(--r-xl); overflow: hidden;
  min-height: 520px;
  display: flex; align-items: flex-end;
  padding: 28px 24px;
}
@media (min-width: 700px)  { .hero-panel { min-height: 560px; padding: 40px 38px; } }
@media (min-width: 1040px) { .hero-panel { min-height: 620px; padding: 56px 52px; } }

.hero-bg {
  position: absolute; inset: 0; z-index: -2;
  width: 100%; height: 100%; object-fit: cover; object-position: 58% 50%;
}
.hero-panel::before {
  content: ''; position: absolute; inset: 0; z-index: -1;
  background: linear-gradient(
    to top,
    rgba(22, 16, 13, .97) 0%,
    rgba(22, 16, 13, .93) 30%,
    rgba(22, 16, 13, .70) 55%,
    rgba(22, 16, 13, .30) 78%,
    rgba(22, 16, 13, .10) 100%);
}
@media (min-width: 800px) {
  .hero-panel { align-items: center; }
  .hero-panel::before {
    background: linear-gradient(
      100deg,
      rgba(22, 16, 13, .97) 0%,
      rgba(22, 16, 13, .95) 44%,
      rgba(22, 16, 13, .80) 58%,
      rgba(22, 16, 13, .28) 80%,
      rgba(22, 16, 13, .06) 100%);
  }
}

.hero-copy { position: relative; max-width: 34rem; }
.hero-copy > * + * { margin-top: 18px; }
.hero h1 { max-width: 13ch; }

.chip-row { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; }
.chip-info {
  display: inline-flex; align-items: center; gap: 8px;
  padding: 7px 14px; border-radius: var(--pill);
  background: rgba(31, 23, 18, .72);
  border: 1px solid rgba(248, 242, 233, .14);
  backdrop-filter: blur(8px); -webkit-backdrop-filter: blur(8px);
  font-size: .82rem; color: var(--muted);
}
.chip-info b { color: var(--cream); font-weight: 600; }
.chip-info .stars { display: inline-flex; gap: 2px; }
.chip-info .stars svg { width: 11px; height: 11px; fill: var(--gold-lt); }
.open-dot {
  width: 7px; height: 7px; border-radius: 50%; background: var(--green-lt); flex: none;
  box-shadow: 0 0 0 0 rgba(85, 196, 126, .55); animation: pulse 2.8s infinite;
}
@keyframes pulse {
  0%   { box-shadow: 0 0 0 0 rgba(85, 196, 126, .5); }
  70%  { box-shadow: 0 0 0 8px rgba(85, 196, 126, 0); }
  100% { box-shadow: 0 0 0 0 rgba(85, 196, 126, 0); }
}
/* Shut: still a dot, but not a beating one. site.js sets this from the hours
   in London time, so the pulse means "the door is open now" rather than "this
   page has a green dot on it". */
.open-dot.shut { background: var(--dim); animation: none; box-shadow: none; }

.hero-actions { display: flex; flex-wrap: wrap; gap: 12px; }

/* the price of the dish actually in the photograph, bottom-right on wide screens */
.hero-note {
  position: absolute; right: 24px; bottom: 24px;
  display: none; align-items: center; gap: 12px;
  padding: 11px 16px; border-radius: var(--r-lg);
  background: rgba(22, 16, 13, .78);
  border: 1px solid rgba(248, 242, 233, .12);
  backdrop-filter: blur(10px); -webkit-backdrop-filter: blur(10px);
  font-size: .84rem; color: var(--muted);
}
@media (min-width: 1040px) { .hero-note { display: inline-flex; right: 32px; bottom: 32px; } }
.hero-note b { display: block; color: var(--cream); font-weight: 700; font-size: .92rem; }
.hero-note .p { font-size: 1.05rem; font-weight: 800; color: var(--flame-lt); font-variant-numeric: tabular-nums; }

/* ---------- the dish rail — what people liked ---------- */
.rail {
  display: grid; grid-auto-flow: column; grid-auto-columns: 74%;
  gap: 14px; overflow-x: auto; overscroll-behavior-x: contain;
  scroll-snap-type: x mandatory;
  padding-inline: var(--gut); margin-inline: calc(var(--gut) * -1);
  padding-bottom: 6px; scrollbar-width: none;
}
.rail::-webkit-scrollbar { display: none; }
.rail > * { scroll-snap-align: start; }
.rail::after { content: ''; width: 1px; }
@media (min-width: 620px)  { .rail { grid-auto-columns: 42%; } }
@media (min-width: 1000px) { .rail { grid-auto-columns: 27%; } }

.dish {
  display: flex; flex-direction: column;
  border-radius: var(--r-lg); overflow: hidden;
  background: var(--ink-2); border: 1px solid var(--line);
  transition: border-color .4s var(--e), transform .45s var(--e-slow);
}
.dish:hover { border-color: var(--line-2); transform: translateY(-3px); }
.dish-img { position: relative; aspect-ratio: 4 / 5; overflow: hidden; background: var(--ink-3); }
.dish-img img { width: 100%; height: 100%; object-fit: cover; transition: transform 1s var(--e-slow); }
.dish:hover .dish-img img { transform: scale(1.04); }
.dish-price {
  position: absolute; top: 12px; right: 12px;
  padding: 6px 12px; border-radius: var(--pill);
  background: rgba(22, 16, 13, .84);
  backdrop-filter: blur(8px); -webkit-backdrop-filter: blur(8px);
  border: 1px solid rgba(255, 255, 255, .1);
  font-size: .88rem; font-weight: 700; color: var(--flame-lt);
  font-variant-numeric: tabular-nums;
}
.dish-tag {
  position: absolute; top: 12px; left: 12px;
  padding: 5px 11px; border-radius: var(--pill);
  background: var(--gold-lt); color: var(--ink);
  font-size: .68rem; font-weight: 700; letter-spacing: .06em; text-transform: uppercase;
}
.dish-body { padding: 16px 16px 18px; display: flex; flex-direction: column; gap: 7px; flex: 1; }
.dish-body h3 { font-size: 1.06rem; }
.dish-body p {
  font-size: .875rem; color: var(--muted); flex: 1;
  display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical; overflow: hidden;
}

/* Sold out came out on 16 September 2026, at Leo's instruction. There was a
   "Sold out today" pill on a dish and a dimmed, dashed card behind it, set by
   a switch in the editor and corrected on every page load. The site no longer
   says anything about a dish being off, here or anywhere. */
.dish.is-out .dish-img img { transform: none; }


.rail-hint { margin-top: 16px; font-size: .82rem; color: var(--dim); }
@media (min-width: 1000px) { .rail-hint { display: none; } }

/* ---------- services ---------- */
.svc-grid { display: grid; gap: 12px; }
@media (min-width: 620px)  { .svc-grid { grid-template-columns: repeat(2, 1fr); } }
@media (min-width: 1000px) { .svc-grid { grid-template-columns: repeat(3, 1fr); gap: 16px; } }

/* THREE CARDS IN A TWO-COLUMN GRID LEAVE AN ORPHAN, and between 620px and
   1000px that is exactly what this is. A half-width card sitting alone beside
   nothing reads as a broken grid rather than as a third thing — the same fault
   .gal-m-full exists to avoid on the gallery. The last of three spans both
   columns instead; :nth-child(3):last-child means it only fires when there are
   exactly three, so a fourth card added later needs no CSS change. */
@media (min-width: 620px) and (max-width: 999px) {
  .svc-grid > :nth-child(3):last-child { grid-column: span 2; }
}

.svc {
  display: block; width: 100%; text-align: left; font: inherit; color: inherit;
  text-decoration: none;
  padding: 24px 22px 22px; border-radius: var(--r-lg);
  background: var(--ink-2); border: 1px solid var(--line);
  transition: border-color .4s var(--e), transform .45s var(--e-slow), background .4s;
}
.svc:hover { border-color: var(--line-2); background: var(--ink-3); transform: translateY(-3px); }
.svc-ic {
  width: 42px; height: 42px; border-radius: var(--r); display: grid; place-items: center;
  margin-bottom: 16px;
  background: rgba(234, 88, 12, .14); color: var(--flame-lt);
  box-shadow: inset 0 0 0 1px rgba(251, 144, 66, .22);
}
/* two colours, assigned by meaning: flame for ordering, green for the ways to
   be hosted. Colour that says something, rather than three of them.
   .svc.deliver was the third — the cool accent for the one service that
   happened somewhere else — and went with the Delivery card on
   16 September 2026, when six cards became three. */
.svc.host .svc-ic {
  background: rgba(21, 128, 61, .16); color: var(--green-lt);
  box-shadow: inset 0 0 0 1px rgba(85, 196, 126, .22);
}
.svc-ic svg { width: 20px; height: 20px; stroke: currentColor; fill: none; }
.svc h3 { margin-bottom: 6px; }
.svc p { font-size: .92rem; color: var(--muted); }
.svc-go {
  margin-top: 14px; display: inline-flex; align-items: center; gap: 7px;
  font-size: .84rem; font-weight: 700; color: var(--flame-lt);
}
.svc.host .svc-go { color: var(--green-lt); }
.svc-go .arw { transition: transform .4s var(--e-slow); }
.svc:hover .svc-go .arw { transform: translateX(4px); }

/* ---------- split sections ---------- */
.split { display: grid; gap: 34px; align-items: center; }
@media (min-width: 900px) { .split { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: 60px; } }
@media (min-width: 900px) { .split-wide { grid-template-columns: minmax(0, .88fr) minmax(0, 1.12fr); } }

figure img { width: 100%; border-radius: var(--r-lg); }
figcaption { margin-top: 12px; font-size: .82rem; color: var(--dim); }

.pull { font-size: clamp(1.4rem, 3.6vw, 2rem); font-weight: 700; line-height: 1.24; letter-spacing: -.03em; max-width: 20ch; }
.pull em { font-style: normal; color: var(--flame-lt); }

/* ---------- reviews ----------
   One row on mobile, swiped rather than stacked — three tall stacked cards is a
   lot of scrolling for something people skim. Google's own mark sits beside the
   stars so it is obvious at a glance where the reviews came from. */
.rv-grid {
  display: grid; grid-auto-flow: column; grid-auto-columns: 82%;
  gap: 12px; overflow-x: auto; overscroll-behavior-x: contain;
  scroll-snap-type: x mandatory;
  padding-inline: var(--gut); margin-inline: calc(var(--gut) * -1);
  padding-bottom: 6px; scrollbar-width: none;
}
.rv-grid::-webkit-scrollbar { display: none; }
.rv-grid > * { scroll-snap-align: start; }
.rv-grid::after { content: ''; width: 1px; }
@media (min-width: 620px) { .rv-grid { grid-auto-columns: 48%; } }
@media (min-width: 900px) {
  .rv-grid {
    grid-auto-flow: row; grid-template-columns: repeat(3, 1fr);
    overflow: visible; padding-inline: 0; margin-inline: 0; gap: 16px;
  }
  .rv-grid::after { display: none; }
}

.rv {
  padding: 24px 22px; border-radius: var(--r-lg);
  background: var(--ink-2); border: 1px solid var(--line);
  display: flex; flex-direction: column; gap: 14px;
}
.rv-top { display: flex; align-items: center; gap: 10px; }
.rv .stars { display: flex; gap: 3px; }
.rv .stars svg { width: 13px; height: 13px; fill: var(--gold-lt); }
.g-mark { width: 15px; height: 15px; flex: none; }
.rv blockquote { font-size: .99rem; line-height: 1.55; flex: 1; }
.rv footer { font-size: .82rem; color: var(--dim); }
.rv-badge {
  display: inline-flex; align-items: center; gap: 10px; margin-top: 22px;
  padding: 11px 18px; border-radius: var(--pill);
  background: var(--ink-2); border: 1px solid var(--line);
  font-size: .88rem; color: var(--muted);
}
.rv-badge b { color: var(--cream); font-weight: 800; font-size: 1.1rem; }
.rv-hint { margin-top: 14px; font-size: .82rem; color: var(--dim); }
@media (min-width: 900px) { .rv-hint { display: none; } }

/* ---------- pop-ups ----------
   Kept deliberately small. It is one fact — where and when — plus a link for
   the updates. It does not need a panel of its own. */
.popups { text-align: center; }
.popups .band-head { justify-items: center; }
.pop-next {
  display: inline-flex; flex-wrap: wrap; align-items: center; justify-content: center;
  gap: 10px 18px; margin: 4px auto 0;
  padding: 16px 24px; border-radius: var(--pill);
  background: var(--ink-2); border: 1px solid rgba(56, 189, 248, .28);
  font-size: .96rem; color: var(--muted);
}
.pop-next b { color: var(--cream); font-weight: 700; }
.pop-next .sky { color: var(--sky); font-weight: 700; }
.pop-note { margin-top: 16px; font-size: .84rem; color: var(--dim); }

/* The stalls themselves, written from the events table by _snapshot.mjs and
   dropping off on their own once the date passes.

   An ordered list, because the order IS the information — the next one first.
   Date before title, for the same reason: somebody scanning this is asking
   "when can I get to one", not "what is it called". */
.evts { display: grid; gap: 12px; margin-top: 30px; text-align: left; }
@media (min-width: 780px) { .evts { grid-template-columns: 1fr 1fr; } }

.evt {
  padding: 22px 22px 20px;
  border-radius: var(--r-lg); background: var(--ink-2);
  border: 1px solid var(--line); border-left: 3px solid var(--sky);
}
.evt-when {
  display: flex; flex-wrap: wrap; align-items: baseline; gap: 4px 10px;
  font-size: .82rem; font-weight: 700; letter-spacing: .04em;
  color: var(--sky); text-transform: uppercase;
}
.evt-when .evt-time { color: var(--dim); font-weight: 600; letter-spacing: 0; text-transform: none; }
.evt h3 { margin-top: 9px; font-size: 1.08rem; }
.evt-where { margin-top: 5px; font-size: .9rem; color: var(--muted); }
.evt-note { margin-top: 9px; font-size: .86rem; color: var(--dim); line-height: 1.55; }
/* In the date row, pushed right, and free to wrap onto its own line. Pinned to
   the card's corner it overlapped a long time range at 390px. */
.evt-kind {
  margin-left: auto;
  padding: 4px 10px; border-radius: var(--pill);
  background: var(--sky-dim); color: var(--sky);
  font-size: .68rem; font-weight: 800; letter-spacing: .1em; text-transform: uppercase;
}

/* One-off closures, in the notice on the find-us page. */
.closed-days { margin-top: 8px; display: grid; gap: 5px; }
.closed-days li { list-style: none; }
.closed-days b { color: var(--cream); font-weight: 700; }

/* ---------- forms ---------- */
.field { display: grid; gap: 7px; }
.field > label, .lbl-t { font-size: .72rem; font-weight: 700; letter-spacing: .14em; text-transform: uppercase; color: var(--dim); }

.inp {
  width: 100%; min-height: 50px; padding: 13px 15px;
  border-radius: var(--r); border: 1px solid var(--field-line);
  background: var(--ink); color: var(--cream);
  font-size: 1rem;
  transition: border-color .3s var(--e), background .3s;
}
.inp::placeholder { color: var(--dim); }
.inp:hover { border-color: var(--dim); }
.inp:focus { outline: none; border-color: var(--flame); background: var(--ink-3); }
.inp:focus-visible { outline: 2px solid var(--focus); outline-offset: 2px; }
textarea.inp { min-height: 100px; resize: vertical; line-height: 1.55; }
select.inp {
  appearance: none; padding-right: 42px;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23A2937F' stroke-width='2' stroke-linecap='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E");
  background-repeat: no-repeat; background-position: right 14px center; background-size: 17px;
}
/* The catering headcount is the only number field on the public site. Chrome
   and Safari paint the spinner in the browser's own colours, which on a
   near-black field is a pale block in the corner belonging to no part of this
   design — the same reason select.inp draws its own arrow rather than using
   the native one. This field is filled in by typing or by the phone keypad. */
input[type="number"].inp { appearance: textfield; -moz-appearance: textfield; }
input[type="number"].inp::-webkit-outer-spin-button,
input[type="number"].inp::-webkit-inner-spin-button { appearance: none; -webkit-appearance: none; margin: 0; }

.inp:disabled, .inp[disabled] { opacity: .45; cursor: not-allowed; border-style: dashed; }

.row2 { display: grid; gap: 14px; }
@media (min-width: 560px) { .row2 { grid-template-columns: 1fr 1fr; } }

.err { font-size: .85rem; color: #FF9B7A; margin-top: 2px; }
.inp.bad { border-color: #FF9B7A; }
.form-note { font-size: .82rem; color: var(--dim); }

.chips { display: flex; flex-wrap: wrap; gap: 8px; }
.chip {
  min-height: 44px; padding: 10px 16px; border-radius: var(--pill);
  border: 1px solid var(--field-line); font-size: .9rem; font-weight: 600;
  transition: border-color .3s, background .3s, color .3s;
}
.chip:hover { border-color: var(--dim); }
.chip[aria-pressed="true"] { background: var(--flame); border-color: var(--flame); color: var(--ink); }


/* ---------- catering ---------- */
/* .cat-list is gone — see the eighth pass at the foot of this file. */

.panel {
  background: var(--ink-2); border: 1px solid var(--line);
  border-radius: var(--r-xl); padding: 26px 22px;
  display: grid; gap: 16px;
}
@media (min-width: 560px) { .panel { padding: 34px 30px; } }
.sent { display: grid; gap: 12px; justify-items: center; text-align: center; padding: 22px 0; }
.sent-ic { width: 52px; height: 52px; border-radius: 50%; display: grid; place-items: center; background: var(--green-lt); color: var(--ink); }
.sent-ic svg { width: 24px; height: 24px; stroke: currentColor; fill: none; }

/* ---------- visit ---------- */
.visit-lines { display: grid; gap: 10px; }
.visit-lines > div {
  display: grid; grid-template-columns: 20px 1fr; gap: 13px;
  padding: 15px 0; border-bottom: 1px solid var(--line);
}
.visit-lines svg { width: 18px; height: 18px; margin-top: 4px; stroke: var(--flame-lt); fill: none; }
.visit-lines dt { font-size: .72rem; letter-spacing: .14em; text-transform: uppercase; color: var(--dim); }
.visit-lines dd { font-size: 1.03rem; margin-top: 3px; }
.visit-lines a { text-decoration: none; }
.visit-lines a:hover { color: var(--flame-lt); }

.hours { margin-top: 26px; border-radius: var(--r-lg); background: var(--ink-2); border: 1px solid var(--line); padding: 8px 18px; }
.hours div { display: flex; justify-content: space-between; gap: 16px; padding: 11px 0; font-size: .95rem; }
.hours div + div { border-top: 1px solid var(--line); }
.hours dt { color: var(--muted); }
.hours dd { font-variant-numeric: tabular-nums; }
.hours div.today dt, .hours div.today dd { color: var(--flame-lt); font-weight: 600; }
.hours div.today dt::after { content: ' · today'; font-weight: 400; }

/* ---------- footer ---------- */
.foot { position: relative; background: var(--ink-2); border-top: 1px solid var(--line); padding-block: 56px 28px; }
.foot-grid { display: grid; gap: 34px; }
@media (min-width: 760px) { .foot-grid { grid-template-columns: 1.5fr 1fr 1fr; gap: 44px; } }
.foot h4 { font-size: .72rem; font-weight: 700; letter-spacing: .14em; text-transform: uppercase; color: var(--dim); margin-bottom: 16px; }
.foot ul { display: grid; gap: 11px; }
.foot a { text-decoration: none; font-size: .94rem; color: var(--muted); transition: color .3s; }
.foot a:hover { color: var(--flame-lt); }
.foot-brand p { font-size: .94rem; color: var(--muted); max-width: 32ch; margin-top: 16px; }
.foot-base {
  margin-top: 44px; padding-top: 20px; border-top: 1px solid var(--line);
  display: flex; flex-wrap: wrap; gap: 10px 22px; justify-content: space-between;
  font-size: .82rem; color: var(--dim);
}

/* ---------- chips that are chosen ----------
   The basket sheet, the sides picker and the toast lived here until collection
   ordering came out on 14 September 2026. What survives is the catering form's
   dish chips.

   "What would you like on the table?" on /catering: the menu's own categories
   as quiet sub-heads, and the site's chips under each. Chosen is said by more
   than the colour: a tick comes in with the fill. */
.cat-dishes { gap: 8px; }
.cat-dishes .form-note { margin-bottom: 4px; }
.cat-h { margin-top: 10px; font-size: .84rem; font-weight: 700; color: var(--muted); }
.cat-dishes .chip { display: inline-flex; align-items: center; gap: 8px; min-height: 44px; padding: 9px 14px; font-size: .86rem; color: var(--cream); }
.cat-dishes .chip[aria-pressed="true"] { color: var(--ink); }
.cat-dishes .chip[aria-pressed="true"]::before {
  content: ""; width: 10px; height: 5px; margin-top: -3px;
  border: solid currentColor; border-width: 0 0 2px 2px; transform: rotate(-45deg);
}

/* ---------- unfinished work, made impossible to ship ----------
   A page that is still waiting on Tyrell says so, in his own site's voice,
   rather than sitting empty or carrying invented filler. _preflight.mjs fails
   the build while any [data-todo] remains, so none of this can reach a live
   domain quietly. Deliberately loud: dashed rule, flame label, no attempt to
   look like finished copy. */
.todo-panel {
  border: 1px dashed var(--field-line); border-radius: var(--r-lg);
  padding: 22px 20px; display: grid; gap: 12px; background: var(--ink-2);
}
@media (min-width: 700px) { .todo-panel { padding: 28px 26px; } }
.todo-panel::before {
  content: 'Not finished yet'; justify-self: start;
  font-size: .72rem; font-weight: 800; letter-spacing: .12em; text-transform: uppercase;
  color: var(--flame-lt);
}
.todo { font-size: .93rem; line-height: 1.6; color: var(--muted); }
.todo code { font-family: ui-monospace, 'Cascadia Mono', Menlo, monospace; font-size: .88em; color: var(--cream); }
[data-todo] .todo-panel, .field[data-todo] { border-radius: var(--r); }
.field[data-todo] .todo { margin-top: 8px; }

/* ---------- reveal ---------- */
.rv-up { opacity: 0; transform: translateY(16px); }
.rv-up.in { opacity: 1; transform: none; transition: opacity .8s var(--e), transform .85s var(--e-slow); }
.rv-up.d1.in { transition-delay: .07s; }
.rv-up.d2.in { transition-delay: .14s; }
.rv-up.d3.in { transition-delay: .21s; }
.rv-up.d4.in { transition-delay: .28s; }
html:not(.js) .rv-up { opacity: 1; transform: none; }

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .01ms !important; animation-iteration-count: 1 !important;
    transition-duration: .01ms !important;
  }
  .rv-up { opacity: 1; transform: none; }
}

/* ==========================================================================
   Menu page
   ========================================================================== */
.mhead { padding-block: 34px 30px; }
@media (min-width: 900px) { .mhead { padding-block: 54px 38px; } }
.mhead h1 { max-width: 13ch; margin-top: 14px; }
.mhead .lead { margin-top: 18px; }

.mnav { position: sticky; top: 66px; z-index: 60; background: rgba(22, 16, 13, .92); backdrop-filter: blur(14px); -webkit-backdrop-filter: blur(14px); border-block: 1px solid var(--line); }
@media (min-width: 900px) { .mnav { top: 78px; } }
.mnav-row { display: flex; gap: 8px; overflow-x: auto; scrollbar-width: none; padding: 12px var(--gut); max-width: var(--maxw); margin-inline: auto; }
.mnav-row::-webkit-scrollbar { display: none; }
.mnav a {
  flex: none; white-space: nowrap; text-decoration: none;
  padding: 9px 16px; border-radius: var(--pill);
  font-size: .87rem; font-weight: 600; color: var(--muted);
  border: 1px solid transparent;
  transition: color .3s, background .3s, border-color .3s;
}
.mnav a:hover { color: var(--cream); border-color: var(--field-line); }
.mnav a.here { background: var(--flame); color: var(--ink); border-color: var(--flame); }

.mcat { padding-block: 44px; scroll-margin-top: 132px; }
.mcat + .mcat { border-top: 1px solid var(--line); }
.mcat-head { display: flex; align-items: baseline; gap: 14px; flex-wrap: wrap; margin-bottom: 22px; }
.mcat-head p { font-size: .9rem; color: var(--dim); }

.mlist { display: grid; gap: 10px; }
@media (min-width: 780px) { .mlist { grid-template-columns: 1fr 1fr; gap: 12px; } }

.mitem {
  display: grid; grid-template-columns: 72px minmax(0, 1fr) auto; gap: 4px 14px;
  align-items: center;
  padding: 12px; border-radius: var(--r-lg);
  background: var(--ink-2); border: 1px solid var(--line);
  transition: border-color .35s var(--e), background .35s;
}
.mitem:hover { border-color: var(--line-2); background: var(--ink-3); }
.mitem img { grid-row: 1 / span 2; width: 72px; height: 72px; border-radius: var(--r); object-fit: cover; }
.mitem-ph {
  grid-row: 1 / span 2; width: 72px; height: 72px; border-radius: var(--r);
  display: grid; place-items: center;
  background: linear-gradient(150deg, rgba(234, 88, 12, .22), rgba(21, 128, 61, .18));
  color: var(--flame-lt);
}
.mitem-ph svg { width: 22px; height: 22px; stroke: currentColor; fill: none; }
.mitem h3 { font-size: 1rem; }
.mitem p { grid-column: 2; font-size: .85rem; color: var(--muted); line-height: 1.45; }
/* The allergens under the description: quieter than it, never hidden. */
/* margin-top 2px, not the 1em that "p + p" gives every paragraph on the site:
   it belongs to the description above it, not a new paragraph. The photograph
   and the price column span the third row too. */
.mitem .mitem-al { margin-top: 2px; font-size: .8rem; color: var(--dim); }
.mitem:has(.mitem-al) > img,
.mitem:has(.mitem-al) > .mitem-ph,
.mitem:has(.mitem-al) > .mitem-r { grid-row: 1 / span 3; }
.mitem-r { grid-column: 3; grid-row: 1 / span 2; display: flex; flex-direction: column; align-items: flex-end; gap: 8px; }
.mitem-price { font-weight: 800; font-size: 1rem; color: var(--flame-lt); font-variant-numeric: tabular-nums; }

.mnote {
  margin-top: 26px; padding: 18px 20px; border-radius: var(--r-lg);
  background: var(--ink-2); border: 1px solid var(--line);
  font-size: .88rem; color: var(--muted);
  display: grid; grid-template-columns: 20px 1fr; gap: 12px;
}
.mnote svg { width: 18px; height: 18px; margin-top: 2px; stroke: var(--gold-lt); fill: none; }

/* How to order, at the top of /menu: ring for collection, or the two platforms. */
.order-ways b { color: var(--cream); }
.order-ways a:not(.btn) { color: var(--flame-lt); font-weight: 700; }
.order-ways .order-ways-btns { margin-top: 14px; display: flex; flex-wrap: wrap; gap: 10px; justify-content: center; }

/* The pop-up band is one line of information, so it should not take a full
   band's worth of height doing it. */
.popups { padding-block: calc(var(--band) * .58); }
.popups .band-head { margin-bottom: 26px; }

/* ==========================================================================
   Fourth pass
   ========================================================================== */

/* ---------- hero: centred copy, sitting in the bottom third ----------
   Centring the copy means it can no longer hide behind a left-to-right scrim,
   so the panel is bottom-aligned instead: the copy sits where the gradient is
   near-solid and the top half of the photograph stays open.

   The numbers are measured, not guessed. Over the brightest pixel in this
   photograph the secondary text needs the scrim at .92 or more to clear 4.5:1
   — at .72 it measured 2.5:1. Re-check with _contrast.mjs if the photo changes. */
.hero-panel { align-items: flex-end; }
@media (min-width: 800px) { .hero-panel { align-items: flex-end; } }
.hero-panel::before {
  background: linear-gradient(
    to top,
    rgba(22, 16, 13, .985) 0%,
    rgba(22, 16, 13, .965) 34%,
    rgba(22, 16, 13, .92)  46%,
    rgba(22, 16, 13, .62)  62%,
    rgba(22, 16, 13, .30)  80%,
    rgba(22, 16, 13, .16) 100%) !important;
}
.hero-copy {
  max-width: 40rem; margin-inline: auto; text-align: center;
}
.hero h1 { max-width: 18ch; margin-inline: auto; }
.hero .lead { margin-inline: auto; }
.chip-row, .hero-actions { justify-content: center; }
.hero-note { display: none !important; }

/* ---------- services: same six, roughly half the height ----------
   Every service still has its own card and its own link, because the brief
   asked for all six and because "private hire" is genuinely news to someone
   who only knew them as a takeaway. What went is the wasted space: the icon
   moved beside the title instead of above it, the copy is one line, and the
   separate "go" link folded into an arrow on the heading. */
.svc-grid { display: grid; gap: 10px; }
@media (min-width: 620px)  { .svc-grid { grid-template-columns: repeat(2, 1fr); } }
@media (min-width: 1000px) { .svc-grid { grid-template-columns: repeat(3, 1fr); gap: 12px; } }

.svc {
  display: grid; grid-template-columns: 38px minmax(0, 1fr);
  gap: 3px 14px; align-items: start;
  padding: 18px 20px 19px;
  border-radius: var(--r-lg);
  background: var(--ink-2); border: 1px solid var(--line);
  text-decoration: none; width: 100%; text-align: left; font: inherit; color: inherit;
  transition: border-color .35s var(--e), background .35s var(--e), transform .4s var(--e-slow);
}
.svc:hover { border-color: var(--line-2); background: var(--ink-3); transform: translateY(-2px); }
.svc-ic {
  grid-row: 1 / span 2; align-self: center;
  width: 38px; height: 38px; border-radius: var(--r-sm);
  display: grid; place-items: center; margin: 0;
}
.svc-ic svg { width: 18px; height: 18px; }
.svc h3 {
  display: flex; align-items: baseline; gap: 8px;
  font-size: 1.04rem; margin: 0;
  transition: color .3s;
}
.svc h3 .arw { font-size: .86rem; color: var(--flame-lt); transition: transform .4s var(--e-slow); }
.svc.host h3 .arw { color: var(--green-lt); }
.svc.deliver h3 .arw { color: var(--sky); }
.svc:hover h3 .arw { transform: translateX(4px); }
.svc p { font-size: .88rem; color: var(--muted); }
.svc-go { display: none; }

/* ---------- find us: no photograph, no hours, three short facts ---------- */
.visit-row { display: grid; gap: 10px; }
@media (min-width: 760px) { .visit-row { grid-template-columns: repeat(3, 1fr); gap: 12px; } }
.visit-card {
  display: grid; grid-template-columns: 20px minmax(0, 1fr); gap: 4px 13px;
  padding: 18px 20px; border-radius: var(--r-lg);
  background: var(--ink-2); border: 1px solid var(--line);
}
.visit-card svg { grid-row: 1 / span 2; align-self: center; width: 18px; height: 18px; stroke: var(--flame-lt); fill: none; }
.visit-card dt { font-size: .7rem; font-weight: 700; letter-spacing: .14em; text-transform: uppercase; color: var(--dim); }
.visit-card dd { font-size: .98rem; }
.visit-card a { text-decoration: none; }
.visit-card a:hover { color: var(--flame-lt); }

/* ---------- footer: the opening hours live here now ---------- */
@media (min-width: 760px) { .foot-grid { grid-template-columns: 1.5fr 1fr 1fr 1fr; gap: 36px; } }
.foot-hours { display: grid; gap: 9px; }
.foot-hours div { display: flex; justify-content: space-between; gap: 14px; font-size: .92rem; color: var(--muted); }
.foot-hours dd { font-variant-numeric: tabular-nums; color: var(--cream); }
.foot-hours div.today dt, .foot-hours div.today dd { color: var(--flame-lt); }
.foot-open {
  display: inline-flex; align-items: center; gap: 8px; margin-top: 12px;
  font-size: .82rem; color: var(--muted);
}

/* In the footer the hours are a note, not a panel: no card, no borders, and no
   " · today" suffix — the line underneath already says it, and in a narrow
   column the suffix wrapped the day range onto two lines. */
.foot-hours { background: none; border: 0; padding: 0; margin-top: 0; }
.foot-hours div { padding: 0; border: 0 !important; }
.foot-hours div.today dt::after { content: none; }

/* ==========================================================================
   Fifth pass — photographic grounds
   ==========================================================================

   After the hero the page was a long run of flat cocoa-black: seven bands of
   the same two surfaces, with every photograph locked inside a card. It read
   as plain, and the fix is not more colour — it is material. Four bands now
   sit on a photograph, alternating with the flat ones so the rhythm is
   photo / flat / photo rather than a wall of texture.

   Which band gets which, and why:

     #services   charred wood      the only bought image on the site (see below)
     #menu-band  six of their own dishes, edge to edge
     #catering   their jerk chicken, blurred past recognition
     #visit      their own shopfront, blurred but still legibly a shop
     .mhead      the charred wood again, on the menu page

   THREE RULES, and breaking any one of them undoes the work:

   1. EVERY PLATE IS BAKED. The darkening, the blur and the edge fades are in
      the JPEG, not in a CSS scrim or an opacity. _images.ps1 measures the
      99.5th-percentile relative luminance of each finished file into
      _plates.json, and _contrast.mjs checks the real text colours against
      those numbers — so contrast here is a measurement of the shipped bytes
      rather than a guess about a composite. Never add an `opacity` or a scrim
      to one of these bands to "tune" it: change the multiplier in _images.ps1
      and re-run it, or the check stops describing the page.

   2. THE ONLY BOUGHT IMAGE IS A TEXTURE, NEVER FOOD AND NEVER A PLACE. Every
      other photograph on this site is Passion Kitchen's own, and the arrival
      notice says so. Charred wood cannot be mistaken for a claim about their
      kitchen, their food or their premises; a stock plate of jerk chicken
      could, and would make that notice untrue. If another bought image is ever
      added, it has to clear the same bar.

   3. EACH PLATE FADES TO ITS OWN BAND COLOUR, top and bottom. The baked fade
      does most of it; the linear-gradient in each rule below guarantees it at
      any viewport, because `cover` crops the baked fade off a short wide band.
      Without it a photograph ends in a hard horizontal line partway down the
      section, which reads as a broken image rather than a design.

   The plates are background-images rather than <img> elements on purpose: they
   are decorative, they carry no information, and an empty alt on four images
   is four more things to get wrong. The cost is that they are not lazily
   loaded — which is why bg-menu.jpg, the only heavy one, is 69KB and the other
   three are 15–38KB between them. */

:root {
  --ink-0:  rgba(22, 16, 13, 0);
  --ink2-0: rgba(31, 23, 18, 0);
}

/* --- charcoal, under the six ways to eat --- */
.plate-char {
  background-color: var(--ink);
  background-image:
    linear-gradient(to bottom, var(--ink), var(--ink-0) 110px,
                    var(--ink-0) calc(100% - 110px), var(--ink)),
    radial-gradient(120% 80% at 12% 0%, rgba(234, 88, 12, .13), rgba(234, 88, 12, 0) 62%),
    url('img/bg-char.jpg');
  background-size: auto, auto, cover;
  background-position: center;
  background-repeat: no-repeat;
}

/* --- the menu band: their own food, full bleed, with the copy on a card ---
   This is the one plate that stays bright, because it is the one plate with no
   text on it. The card is opaque, so the photograph underneath can carry full
   colour instead of being crushed to a texture — which is the whole reason for
   putting the copy in a card rather than straight on the band.

   ⚠ "BRIGHT" IS NOT "UNBOUNDED", which is what it had quietly become. Until
   12 September this ground was six Uber Eats tray shots butted edge to edge,
   with six visible vertical seams, at a p99.5 luminance of 0.1525 against
   0.038–0.046 for every other ground on the site — three times brighter than
   its neighbours, on the widest band on the page. It is now a single frame of
   Tyrell's own plated spread at 0.1110: still the brightest, still sharp,
   still the point of the band, and no longer a different page. */
.band-menu {
  padding-block: calc(var(--band) * 1.15);
  background-color: var(--ink);
  background-image:
    linear-gradient(to bottom, var(--ink), var(--ink-0) 90px,
                    var(--ink-0) calc(100% - 90px), var(--ink)),
    url('img/bg-menu.jpg');
  background-size: auto, cover;
  background-position: center;
  background-repeat: no-repeat;
}
.mb-card {
  display: grid; gap: 13px; justify-items: start;
  max-width: 33rem;
  padding: 28px 24px 30px;
  border-radius: var(--r-xl);
  background: var(--ink-2);
  border: 1px solid var(--line-2);
}
@media (min-width: 700px) { .mb-card { padding: 36px 34px 38px; gap: 15px; } }
.mb-card h2 { font-size: clamp(1.5rem, 4vw, 2.1rem); }
.mb-card .lead { font-size: .98rem; }
.mb-card .btn { margin-top: 6px; }

/* --- their jerk chicken, blurred to a warm wash --- */
.plate-plate {
  background-color: var(--ink-2);
  background-image:
    linear-gradient(to bottom, var(--ink-2), var(--ink2-0) 120px,
                    var(--ink2-0) calc(100% - 120px), var(--ink-2)),
    url('img/bg-plate.jpg');
  background-size: auto, cover;
  background-position: center;
  background-repeat: no-repeat;
}

/* --- their own shopfront. The one band where the subject of the
       photograph and the heading above it are the same thing. --- */
.plate-shop {
  background-color: var(--ink-2);
  background-image:
    linear-gradient(to bottom, var(--ink-2), var(--ink2-0) 120px,
                    var(--ink2-0) calc(100% - 120px), var(--ink-2)),
    url('img/bg-shop.jpg');
  background-size: auto, cover;
  background-position: center 38%;
  background-repeat: no-repeat;
}

/* --- the menu page's header gets the same charcoal, so the two pages read as
       one site. Short band, so the fade distances are shorter too. --- */
.mhead {
  background-color: var(--ink);
  background-image:
    linear-gradient(to bottom, var(--ink), var(--ink-0) 55px,
                    var(--ink-0) calc(100% - 55px), var(--ink)),
    url('img/bg-char.jpg');
  background-size: auto, cover;
  background-position: center;
  background-repeat: no-repeat;
  padding-block: 52px 44px;
}
@media (min-width: 900px) { .mhead { padding-block: 72px 54px; } }


/* ==========================================================================
   Seventh pass — the horizontal rails stopped hijacking the page
   ==========================================================================

   Reported by Leo: "sometimes I try scrolling down the page and it ends up
   swiping them to the sides." Two separate causes, one per input device, and
   fixing either alone leaves half the bug in place.

   TOUCH — `scroll-snap-type: x mandatory`. Mandatory snapping has no rest
   position between cards, so the moment a mostly-vertical swipe picks up a few
   pixels of horizontal drift, the browser locks the gesture to x and commits a
   whole 74%-of-viewport card across. The drift is normal (nobody swipes a
   perfectly straight line with a thumb); the amplification is the bug.
   `proximity` keeps the snap where it is useful — let go near a card and it
   tidies up — and lets a stray few pixels spring back instead of turning into
   a page-width jump.

   POINTER — see the wheel guard in site.js. Chrome and Firefox retarget a
   VERTICAL wheel onto the horizontal axis whenever the box under the cursor
   scrolls horizontally and not vertically, so on a laptop the page simply does
   not move while the pointer is over the rail. There is no CSS for that one.

   What was deliberately NOT done: `touch-action: pan-y` on the rail would end
   the sideways swipe altogether, and `overflow: visible` above 1000px would
   turn the rail into a static grid on desktop. The rail is the signature of
   this page (§2 of the README) — it stays a rail at every width, it just
   behaves now. */

.rail, .rv-grid { scroll-snap-type: x proximity; }

/* The headline is two sentences, so each is its own block and each wraps on its
   own. Without this the line breaks land mid-sentence ("Authentic Jamaican /
   Cuisine. Cooked / With Love.") and the full stop reads as a typo. `max-width`
   goes to 18ch on both breakpoints because the title is 43 characters, not the
   30 the 13ch cap was set for. `text-wrap: balance` is inherited from the
   global h1 rule and does the rest. */
.hero h1 > span { display: block; }
.hero h1 { max-width: 18ch; }

/* ==========================================================================
   Eighth pass — the catering claims came out
   ==========================================================================

   Leo removed the four-item tick list under the catering heading, and the tail
   of the lead paragraph with it:

     "…you will get a written quote back, not a game of phone tag."
     · A menu built around your guests, not a fixed package
     · Vegetarian and mild options always available
     · The whole restaurant to yourself, if that suits better
     · One form, one reply — no back and forth on WhatsApp

   Right call, and the reason is worth keeping: every one of those is a service
   promise Passion Kitchen has never published anywhere. A written quote by
   return, vegetarian options *always*, the whole room on request — they are all
   plausible and none of them is his. The same rule that keeps invented prices
   and invented pop-up dates off this page applies to invented service levels,
   and it is easier to miss here because a bullet list of reassurances reads
   like copy rather than like a claim.

   What is left is what he actually told us: the occasions, and what to send.
   `.cat-list` and its rules are deleted rather than commented out — the markup
   is gone, so the CSS was dead. */


/* ==========================================================================
   The small print — /legal
   ==========================================================================

   A page nobody reads for pleasure and somebody reads under pressure: an
   allergy, a wrong order, a question about what we keep. So it is set for
   SCANNING and not for beauty — short measure, real headings, definition
   lists where the shape of the content is a list of pairs, and no decoration
   competing with the words. The one indulgence is the measure itself.        */
.legal { max-width: 72ch; }
.legal-sec + .legal-sec { margin-top: 52px; padding-top: 44px; border-top: 1px solid var(--line); }
.legal-sec { scroll-margin-top: 96px; }

.legal h2 { font-size: 1.5rem; }
.legal h3 {
  margin-top: 28px; font-size: .78rem; font-weight: 800;
  letter-spacing: .14em; text-transform: uppercase; color: var(--flame-lt);
}
.legal h2 + p, .legal h3 + p, .legal h3 + div, .legal h3 + dl, .legal h3 + ul { margin-top: 12px; }
.legal p { color: var(--muted); line-height: 1.75; }
.legal p + p { margin-top: 12px; }
.legal b { color: var(--cream); font-weight: 700; }

.legal-list { margin-top: 12px; display: grid; gap: 9px; padding-left: 20px; }
.legal-list li { color: var(--muted); line-height: 1.7; }
.legal-list li::marker { color: var(--flame); }

/* Pairs, so a definition list rather than paragraphs pretending to be one. */
.legal-dl { display: grid; gap: 16px; margin-top: 14px; }
.legal-dl dt { color: var(--cream); font-weight: 700; }
.legal-dl dd { margin-top: 4px; color: var(--muted); line-height: 1.7; }
@media (min-width: 720px) {
  .legal-dl div { display: grid; grid-template-columns: 15rem 1fr; gap: 18px; align-items: baseline; }
  .legal-dl dd { margin-top: 0; }
}

/* The allergen notice on /legal. It is the thing to read before ordering with
   an allergy, so it gets body text and a boundary round it rather than the
   quietest colour on the site. A safety notice must not be the faintest
   paragraph on the page. */
.legal-note {
  margin-top: 14px; padding: 18px 20px;
  border-radius: var(--r-lg);
  background: var(--ink); border: 1px solid var(--gold-lt);
}
.legal-note p { font-size: 1rem; color: var(--muted); line-height: 1.75; }
.legal-note p b { color: var(--gold-lt); }
.legal-updated { margin-top: 34px; font-size: .85rem; color: var(--dim); }

/* ---------- the food hygiene rating, in the footer ----------
   The number is set as a figure rather than an image because the FSA's badge
   artwork is an official mark and a lookalike would be misrepresenting it.
   Green because a 4 is good news; the class carries the number so a future
   re-inspection cannot leave a red rating sitting on a green ground. */
.foot-fhrs {
  display: flex; align-items: center; gap: 12px;
  margin-top: 30px; padding-top: 22px; border-top: 1px solid var(--line);
  font-size: .85rem; color: var(--muted); line-height: 1.6;
}
.fhrs-n {
  flex: none; width: 32px; height: 32px; border-radius: var(--r-sm);
  display: inline-flex; align-items: center; justify-content: center;
  background: var(--green); color: var(--cream);
  font-weight: 800; font-size: 1.05rem; font-variant-numeric: tabular-nums;
}

.foot-legal { display: flex; flex-wrap: wrap; gap: 6px 16px; }

/* ---------- the gallery ----------
   Tyrell's own photographs and video, and the first place on the site where
   the pictures ARE the content rather than an illustration beside it.

   THE TILE SIZES CARRY MEANING, which is the only reason they vary. A uniform
   grid would say every photograph here is worth the same, and they are not:
   the two of him and the plated food are what somebody deciding where to eat
   actually wants, and the street and the room are context. Big tile, small
   tile — that is the whole system, and it is why there is no caption strip
   fighting the photographs for room.

   Six columns with a 110px row unit, and every tile spans whole rows. It
   looks like masonry and is not: masonry needs measuring, and this is a grid
   whose spans were chosen so that each row's tiles come out the same height.
   `span 4` is 482px tall, `span 3` is 358px, and each column span has a width
   that pairs with one of them at close to the photograph's own shape — so
   `object-fit: cover` is trimming edges, never reframing a subject.

   BELOW 760px IT IS TWO COLUMNS — Leo's call, 13 September. It used to drop to
   one column at the photograph's natural shape, and thirteen tiles stacked one
   under another made /gallery a very long scroll of single pictures. Now:

   - a portrait tile (`.tall`, every clip and every 4:5 photograph) takes one
     column, at 4:5, so a row of two always comes out level;
   - a landscape tile (anything not `.tall`) takes the full width at 3:2,
     because a 16:9 room at 172px wide is the thumbnail nobody can read;
   - `.gal-m-full` puts a portrait tile across the full width too. It is how an
     odd one out is avoided: a lone half-width tile next to nothing reads as a
     broken grid. Add it wherever a run of portrait tiles is an odd number.

   The desktop spans below are untouched. */
.gal { display: grid; gap: 14px; grid-template-columns: 1fr; }
@media (max-width: 759.98px) {
  .gal { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 10px; }
  .gal-item:not(.tall), .gal-item.gal-m-full { grid-column: 1 / -1; }
  /* The pill's label would cover half of a 172px tile. It stays as the
     button's accessible name; only the paint goes. */
  .gal-item:not(.gal-m-full) .vtoggle { padding: 6px 9px; }
  .gal-item:not(.gal-m-full) .vt-l { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }
}

/* The home page's pop-up photograph, on its own since the clip came off. */
.pop-shot { max-width: 880px; margin: 30px auto 0; }
.pop-shot img { aspect-ratio: 16 / 9; object-fit: cover; }

.gal-item {
  position: relative; overflow: hidden;
  border-radius: var(--r-lg);
  background: var(--ink-3); border: 1px solid var(--line);
  aspect-ratio: 3 / 2;
  transition: border-color .4s var(--e), transform .45s var(--e-slow);
}
.gal-item.tall { aspect-ratio: 4 / 5; }
.gal-item:hover { border-color: var(--line-2); transform: translateY(-3px); }

/* height:100% rather than the aspect-ratio doing it, because on desktop the
   grid row span sets the height and the aspect-ratio is off. */
.gal-item > img,
.gal-item > video { display: block; width: 100%; height: 100%; object-fit: cover; }

/* The video gets no hover zoom and the photographs do. A tile you can press
   should not also be drifting under your finger, and the play control belongs
   to the browser rather than to us. */
.gal-item > img { transition: transform 1s var(--e-slow); }
.gal-item:hover > img { transform: scale(1.04); }

/* ---------- the stop button on a looping clip ----------
   The clips autoplay muted, so WCAG 2.2.2 wants a way to stop them: anything
   that moves for more than five seconds needs one, and a loop never stops on
   its own. This is it.

   It ships `hidden` and site.js reveals it, because it is only needed in the
   branch where site.js has taken the native controls away. With no JavaScript,
   reduced motion, or a data-saver on, the clip keeps `controls` and this stays
   hidden — one control on a clip, never two.

   It doubles as the "this one moves" badge the grid needs anyway: it is the
   only mark distinguishing a clip from a photograph at a glance. */
.vtoggle {
  position: absolute; top: 12px; left: 12px; z-index: 2;
  display: inline-flex; align-items: center; gap: 7px;
  min-height: 34px; padding: 6px 13px 6px 11px;
  border-radius: var(--pill);
  background: rgba(22, 16, 13, .84);
  backdrop-filter: blur(8px); -webkit-backdrop-filter: blur(8px);
  border: 1px solid rgba(255, 255, 255, .14);
  color: var(--cream); cursor: pointer;
  font: inherit; font-size: .68rem; font-weight: 700;
  letter-spacing: .06em; text-transform: uppercase;
  transition: background .3s var(--e), border-color .3s var(--e);
}
/* The visible pill is 34px so it does not swamp a 352px tile, but the thing
   you can hit is 50 — the target rule is about the finger, not the paint. */
.vtoggle::after { content: ''; position: absolute; inset: -8px; }
.vtoggle:hover { background: rgba(22, 16, 13, .96); border-color: var(--dim); }
.vtoggle svg { width: 12px; height: 12px; fill: currentColor; }
.vtoggle .vt-play { display: none; }
.vtoggle.is-paused .vt-pause { display: none; }
.vtoggle.is-paused .vt-play { display: block; }

@media (min-width: 760px) {
  .gal { grid-template-columns: repeat(6, 1fr); grid-auto-rows: 110px; }
  .gal-item, .gal-item.tall { aspect-ratio: auto; }
  .gal-c2 { grid-column: span 2; }
  .gal-c3 { grid-column: span 3; }
  .gal-c4 { grid-column: span 4; }
  .gal-r3 { grid-row: span 3; }
  .gal-r4 { grid-row: span 4; }
  /* 352x606, which is 0.58 — a phone video is 9:16, or 0.5625. The row of
     clips is the one place the grid gets taller, and it is taller because
     that is the shape the footage actually is. */
  .gal-r5 { grid-row: span 5; }
}

@media (prefers-reduced-motion: reduce) {
  .gal-item, .gal-item > img { transition: none; }
  .gal-item:hover { transform: none; }
  .gal-item:hover > img { transform: none; }
}

/* ---------- a figure that is a video ----------
   Used where a page wants one clip beside its copy rather than a grid of
   them: same frame as `figure img`, so the caption and the radius match.

   ⚠ `height: auto` IS LOAD-BEARING and is the whole reason this rule exists
   rather than just borrowing `figure img`. The reset at the top of this file
   says `img { height: auto }` and says nothing about video, so a <video
   width="1024" height="576"> takes the height attribute as a literal 576px
   box while CSS sets the width to 100% — and the poster letterboxes inside a
   black panel four times too tall. It looks like a broken encode rather than
   a missing line of CSS. Same trap as the checkout inputs needing `class="inp"`:
   a new kind of element does not inherit a stylesheet's vocabulary just
   because the element next to it does. */
figure video {
  width: 100%; height: auto; display: block;
  border-radius: var(--r-lg); background: var(--ink-3);
}

/* ---------- notes to US, not to him ----------
   `[data-todo]` marks a section that is waiting on Tyrell. Some of those notes
   are written to the studio — "this list comes out of the events table",
   "blocked on the Dropbox" — and he should not read them over our shoulder
   when he is sent a preview. So they are HIDDEN BY DEFAULT, and the default is
   the safe way round: a note added later is hidden without anybody
   remembering, and one that a customer genuinely must see has to say so.

   THEY STAY IN THE MARKUP, deliberately. `_preflight.mjs --live` counts them
   and refuses to launch while any remain, and deleting the markup would delete
   the gate along with it.

   `data-todo="public"` is the opt-in for a waiting notice a customer must
   see. There were two, the allergen notices on /order and /legal; since
   15 September 2026 there are none — /order is gone and the /legal notice is
   permanent copy — but the opt-in stays for the next one. */
[data-todo] { display: none; }
[data-todo="public"] { display: block; }

/* On localhost they come back, because they are the working checklist. Keyed
   on the hostname rather than a query parameter: a dev flag that can be typed
   into his domain is a backdoor, and this one cannot be true in production.
   The dashed outline is a reminder that nobody else can see this. */
html.local [data-todo] { display: block; }
html.local [data-todo]:not([data-todo="public"]) {
  outline: 1px dashed var(--flame); outline-offset: 8px;
}

/* ---------- every text card is centred — Leo's call, 13 September ----------
   A box holding words — the menu card, the six ways to eat, the reviews, the
   three facts under Find us, the menu notes, the allergen notices, the pop-up
   listings — centres everything inside it. Kept here as one block, after the
   rules it overrides, so the decision reads as one decision rather than eight
   edits scattered through the file.

   Where a card put its icon BESIDE the text (.svc, .visit-card, .mnote), the
   icon moves above it: centred text next to a left-hand icon reads as
   misaligned rather than centred.

   DELIBERATELY NOT HERE: the checkout and catering forms (.panel), whose
   labels belong against their fields; the dish cards and menu rows, whose
   price and button sit in rows; and the totals and hours tables, which are
   label-and-value pairs. Those are layouts, not boxes of text. */
.mb-card { justify-items: center; text-align: center; }

.svc {
  grid-template-columns: minmax(0, 1fr); justify-items: center;
  text-align: center; gap: 6px; padding: 20px 20px 21px;
}
.svc-ic { grid-row: auto; margin-bottom: 4px; }
.svc h3 { justify-content: center; }

.visit-card { grid-template-columns: minmax(0, 1fr); justify-items: center; text-align: center; gap: 4px; }
.visit-card svg { grid-row: auto; margin-bottom: 6px; }

.rv { text-align: center; align-items: center; }
.rv-top { justify-content: center; }

.mnote { grid-template-columns: minmax(0, 1fr); justify-items: center; text-align: center; gap: 8px; }
.mnote svg { margin-top: 0; }

.legal-note { text-align: center; }

.evts { text-align: center; }
.evt-when { justify-content: center; }
.evt-kind { margin-left: 0; }

/* ---------- the map on /find-us ----------
   Drawn by _map.mjs out of OpenStreetMap data and written into the page as an
   inline SVG, so the streets are coloured here by the same tokens as
   everything else and no request leaves the page (a Google embed sets cookies
   on everybody who scrolls past it). The drawing is streets only. The pin,
   the station dots and every label are HTML laid over it by percentage, so
   they stay the same size on a phone as on a desktop while the drawing
   scales.

   Quiet on purpose: the only colour on it is Blackstock Road and the pin, so
   the answer to "where is it" is the first thing the eye lands on. Every
   label sits on a near-opaque --ink chip, which is what lets _contrast.mjs
   measure it: text over a road of unknown colour cannot be checked, text on a
   chip can. */
.map-fig { position: relative; margin-top: 26px; }

/* The OpenStreetMap credit: the data licence's one condition. Small and quiet
   in the corner rather than a caption underneath (Leo, 13 September), but it
   stays — and _map.mjs writes it, so it cannot be forgotten on a rebuild. */
.map-credit {
  position: absolute; right: 8px; bottom: 8px; z-index: 2;
  padding: 2px 7px; border-radius: var(--pill); background: rgba(22, 16, 13, .9);
  font-size: .6rem; line-height: 1.4; color: var(--dim); text-decoration: none;
}
.map-credit:hover { color: var(--cream); text-decoration: underline; }

.map {
  --map-green: rgba(21, 128, 61, .20);   /* --green over --ink */
  --map-water: rgba(56, 189, 248, .16);  /* --sky-dim */
  --map-home:  rgba(234, 88, 12, .72);   /* --flame over --ink */
  --map-chip:  rgba(22, 16, 13, .9);     /* --ink */
  position: relative; aspect-ratio: 4 / 3; overflow: hidden; isolation: isolate;
  background: var(--ink); border: 1px solid var(--line-2); border-radius: var(--r-lg);
}
.map-art { position: absolute; inset: 0; display: block; width: 100%; height: 100%; }
.map-art path { fill: none; stroke-linecap: round; stroke-linejoin: round; vector-effect: non-scaling-stroke; }
.map-ground { fill: var(--ink); }
.map-art .map-green   { fill: var(--map-green); stroke: none; }
.map-art .map-water   { fill: var(--map-water); stroke: none; }
.map-art .map-stadium { fill: var(--ink-3); stroke: var(--line-2); stroke-width: 1; }
.map-art .map-rail    { stroke: var(--line); stroke-width: 1.2; stroke-dasharray: 5 4; stroke-linecap: butt; }
.map-art .map-minor   { stroke: var(--line); stroke-width: 1.6; }
.map-art .map-major   { stroke: var(--line-2); stroke-width: 3.2; }
.map-art .map-home-road { stroke: var(--map-home); stroke-width: 4.5; }
@media (min-width: 900px) {
  .map-art .map-minor { stroke-width: 2; }
  .map-art .map-major { stroke-width: 4; }
  .map-art .map-home-road { stroke-width: 5.5; }
}

.map-lbl {
  position: absolute; left: var(--x); top: var(--y); z-index: 1;
  display: inline-flex; align-items: center; gap: 6px; white-space: nowrap;
  padding: 3px 8px; border-radius: var(--pill); background: var(--map-chip);
  font-size: .72rem; font-weight: 600; line-height: 1.3; color: var(--muted);
}
/* A station: the dot is the point, the name reads off to its right. */
.map-stn { transform: translate(-11px, -50%); padding-left: 5px; color: var(--cream); }
.map-stn::before {
  content: ""; flex: none; width: 10px; height: 10px; border-radius: 50%;
  background: var(--ink); box-shadow: inset 0 0 0 2.5px var(--cream);
}
/* A centred label is kept off the edges, so a landmark near the frame is
   named rather than cut in half on a phone. */
.map-place, .map-road { left: clamp(64px, var(--x), calc(100% - 64px)); }
.map-place { transform: translate(-50%, -50%); color: var(--dim); letter-spacing: .08em; text-transform: uppercase; font-size: .62rem; }
.map-road  { transform: translate(-50%, -50%) rotate(var(--r)); color: var(--flame-lt); }

/* The door: the pin's tip is the point, the name sits beside its head. */
.map-home {
  transform: translate(-14px, -100%); padding: 0; background: none; gap: 4px; align-items: flex-start;
  font-size: .86rem; font-weight: 700; color: var(--cream);
}
.map-home svg { flex: none; width: 28px; height: 36px; filter: drop-shadow(0 3px 6px rgba(0, 0, 0, .5)); }
.map-home path { fill: var(--flame); stroke: var(--ink); stroke-width: 1.5; }
.map-home circle { fill: var(--cream); }
.map-home b { margin-top: 4px; padding: 3px 9px; border-radius: var(--pill); background: var(--map-chip); border: 1px solid var(--line-2); font-weight: 700; }
