/* ============================================================
   THE NUTMEG GROUP
   Tokens read off the supplied brand assets.

   The wordmark is white type with peach and teal layers offset
   behind it — a misregistered-print effect. Two rules follow:

   1. The logo lives on DARK. Header, hero and feature bands are
      teal, and the wordmark is used as supplied, never recoloured.
   2. Peach has under 2:1 contrast on the off-white ground, so it
      is never type or a button on light surfaces. Teal does that.
      Peach is for dark grounds only.

   A third rule was added with the restaurant pages:

   3. Per-venue accent colours (--accent / --accent-deep /
      --accent-light, set per page from _data/restaurants.js) dress
      only the page belonging to that restaurant. The header, the
      footer and every shared surface stay house teal and peach, so
      the group identity never dissolves into five sub-brands.
   ============================================================ */
:root {
  --teal: #22424D;
  --teal-deep: #142A32;
  --teal-mute: #4A6570;
  --peach: #FFB98F;
  --peach-soft: #FFD9C2;
  --shell: #F7F3EE;
  --surface: #FFFFFF;

  --line: rgba(34, 66, 77, .14);
  --line-dark: rgba(255, 255, 255, .16);

  /* Muted white TEXT on a dark ground — the footer's sub-lines, the form
     hints, the timeline numbers. Not a decorative value: it is the floor
     that still clears WCAG AA at small sizes.

     On --teal-deep (#142A32) the ratios are:
         .40 -> 3.61   fails
         .45 -> 4.20   fails
         .50 -> 4.81   passes, with nothing to spare
         .55 -> 5.52   what this is

     Everything here is 11–13px, the smallest type on the site, so it takes
     the headroom rather than the minimum. Anything quieter than this is
     not "subtle", it is unreadable to someone who needs the contrast —
     which is most people, on a phone, outdoors. */
  --on-dark-mute: rgba(255, 255, 255, .55);

  /* Fallbacks. Restaurant pages override these three on their own
     wrapper; everywhere else "accent" simply means house teal. */
  --accent: #22424D;
  --accent-deep: #22424D;
  --accent-light: #FFB98F;

  --display: "Archivo", "Helvetica Neue", Helvetica, sans-serif;
  --body: "Work Sans", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;

  /* An editorial serif, used sparingly and only where the site is
     speaking rather than labelling: hero sub-lines, restaurant
     taglines, pull quotes, the founder's letter. Archivo remains the
     brand display face for every heading. */
  --serif: "Fraunces", Georgia, "Times New Roman", serif;
  /* The same typeface frozen at a display optical size. Its own family,
     so a text-size request can never be matched to it or the reverse -
     see tools/instance_fonts.py. */
  --serif-display: "Fraunces Display", "Fraunces", Georgia, "Times New Roman", serif;

  --wrap: 1200px;

  /* The page gutter, as a token rather than a number repeated per
     component. The swipe rails bleed past it by exactly this much to
     reach the screen edge, so the two can never drift apart. */
  --gutter: 32px;

  /* One easing curve for everything that moves, so the site has a
     single sense of weight rather than a different one per component. */
  --ease: cubic-bezier(.22, .61, .36, 1);
}

*,
*::before,
*::after {
  box-sizing: border-box;
}

html {
  scroll-behavior: smooth;
  /* Anchor targets clear the fixed header instead of hiding beneath it. */
  scroll-padding-top: 96px;
}

body {
  margin: 0;
  background: var(--shell);
  color: var(--teal);
  font-family: var(--body);
  font-size: 17px;
  line-height: 1.65;
  -webkit-font-smoothing: antialiased;
  /* `clip`, not `hidden`. Both stop a stray wide element scrolling the page
     sideways, but `hidden` makes the body a scroll container, and iOS Safari
     then loses track of where the bottom of the screen is while its toolbar
     collapses and expands — the fixed booking/membership bar was left
     floating mid-screen with page content showing beneath it. `hidden`
     stays as the fallback for browsers without `clip`. */
  overflow-x: hidden;
  overflow-x: clip;
}

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

a {
  color: inherit;
  text-decoration: none;
}

ul {
  list-style: none;
  margin: 0;
  padding: 0;
}

address {
  font-style: normal;
}

.wrap {
  max-width: var(--wrap);
  margin: 0 auto;
  padding: 0 var(--gutter);
}

h1,
h2,
h3,
h4 {
  font-family: var(--display);
  font-weight: 700;
  line-height: 1.04;
  letter-spacing: -.025em;
  margin: 0 0 .5em;
}

h1 {
  font-size: clamp(2.9rem, 6.5vw, 5.4rem);
}

h2 {
  font-size: clamp(2rem, 3.8vw, 3.2rem);
}

h3 {
  font-size: 1.4rem;
  letter-spacing: -.015em;
}

p {
  margin: 0 0 1em;
}

.lede {
  font-size: 1.18rem;
  line-height: 1.6;
  max-width: 58ch;
  color: var(--teal-mute);
}

/* Opening paragraph of a long read — a size up, and set in the serif. */
.lede-lg {
  font-family: var(--serif);
  font-size: 1.34rem;
  line-height: 1.5;
  color: var(--teal);
  margin-bottom: 1.1em;
}

.band-dark .lede,
.band-teal .lede {
  color: rgba(255, 255, 255, .75);
}

/* Small caps label. No leading rule, no middle dots — the wordmark
   already carries rules above and below "GROUP", so repeating the
   device everywhere would dilute it. */
.label {
  font-family: var(--display);
  font-size: .72rem;
  font-weight: 600;
  letter-spacing: .22em;
  text-transform: uppercase;
  color: var(--teal-mute);
  margin: 0 0 18px;
}

.band-dark .label,
.band-teal .label {
  color: var(--peach);
}

.inline-link {
  color: var(--teal);
  border-bottom: 1px solid currentColor;
  white-space: nowrap;
}

.inline-link:hover {
  color: var(--accent-deep);
}

/* On the photographed hero, --teal is nearly unreadable against the image
   — every other .inline-link on the site sits on a plain light band, so
   this never showed up until the contact page put one in its hero lede.
   Matched to the "Contact" eyebrow label above it, which this same
   gradient was already tuned to clear AA for (see .page-head.with-image
   ::before). Hover goes to white rather than darker, since darkening is
   the wrong direction on a dark ground. */
.page-head.with-image .inline-link {
  color: var(--peach);
}

.page-head.with-image .inline-link:hover {
  color: #fff;
}

/* ---------- Utilities ---------- */
.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;
}

.skip-link {
  position: absolute;
  left: 16px;
  top: -60px;
  z-index: 300;
  background: var(--peach);
  color: var(--teal-deep);
  font-family: var(--display);
  font-size: .8rem;
  font-weight: 600;
  letter-spacing: .08em;
  text-transform: uppercase;
  padding: 12px 20px;
  transition: top .2s var(--ease);
}

.skip-link:focus {
  top: 16px;
}

/* ---------- Buttons ---------- */
.btn {
  display: inline-block;
  font-family: var(--display);
  font-size: .82rem;
  font-weight: 600;
  letter-spacing: .1em;
  text-transform: uppercase;
  padding: 17px 32px;
  border: 1px solid transparent;
  cursor: pointer;
  transition: background .22s var(--ease), color .22s var(--ease),
    border-color .22s var(--ease), transform .22s var(--ease);
}

.btn:hover {
  transform: translateY(-2px);
}

.btn-sm {
  padding: 11px 20px;
  font-size: .74rem;
}

.btn-solid {
  background: var(--teal);
  color: #fff;
}

.btn-solid:hover {
  background: var(--teal-deep);
}

.btn-line {
  border-color: var(--teal);
  color: var(--teal);
}

.btn-line:hover {
  background: var(--teal);
  color: #fff;
}

/* The venue button. --accent-deep is the darkened variant precisely so
   white type on it clears AA; never swap this for the raw --accent. */
.btn-accent {
  background: var(--accent-deep);
  color: #fff;
}

.btn-accent:hover {
  background: var(--teal-deep);
}

/* On dark, peach carries the action — teal would vanish. */
.btn-peach {
  background: var(--peach);
  color: var(--teal-deep);
}

.btn-peach:hover {
  background: var(--peach-soft);
}

.btn-ghost {
  border-color: rgba(255, 255, 255, .5);
  color: #fff;
  backdrop-filter: blur(2px);
}

.btn-ghost:hover {
  background: #fff;
  color: var(--teal-deep);
}

/* ============================================================
   SCROLL PROGRESS
   ============================================================ */
.scroll-progress {
  position: fixed;
  inset: 0 0 auto 0;
  height: 2px;
  z-index: 200;
  pointer-events: none;
}

.scroll-progress i {
  display: block;
  height: 100%;
  background: var(--peach);
  transform: scaleX(0);
  transform-origin: 0 50%;
}

/* ============================================================
   HEADER
   ============================================================ */
.site-header {
  position: fixed;
  inset: 0 0 auto 0;
  z-index: 100;
  padding: 40px 0;
  transition: background .3s var(--ease), padding .3s var(--ease);
}

.site-header .wrap {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 24px;
}

.site-header.stuck {
  background: var(--teal-deep);
  padding: 20px 0;
}

/* The mark is an anchor wrapping the image, not a bare image: it is the
   thing people reach for when they want to get home. Positioning and the
   rotation both stay on .header-icon so initHeader keeps writing to one
   element, and the <img> simply fills it. */
.header-icon {
  position: absolute;
  left: 24px;
  top: 50%;
  display: block;
  width: 84px;
  height: 84px;
  transform: translateY(-50%) rotate(0deg);
  will-change: transform;
  transition: width .3s var(--ease), height .3s var(--ease);
}

.header-icon img {
  display: block;
  width: 100%;
  height: 100%;
}

.site-header.stuck .header-icon {
  width: 56px;
  height: 56px;
}

/* The icon is positioned against the header itself, not the centred .wrap,
   so that it bleeds toward the left edge. That means it collides with the
   wordmark on every viewport narrow enough that .wrap is not already inset
   past it — below roughly 1360px the two were simply drawn on top of each
   other. The wordmark gets pushed clear rather than the icon being moved,
   because the icon sitting near the edge is the point of it. */
@media (max-width: 1360px) {
  .site-header .wrap {
    padding-left: 124px;
  }
}

/* 1080px, not 900px. The bar carries seven items since booking joined it
   and stops fitting below roughly 1080 — past that the wordmark was being
   squeezed and "Book a table" pushed into the row beneath. This breakpoint
   and the burger's below are a pair and must stay in step. */
@media (max-width: 1080px) {
  .header-icon {
    width: 56px;
    height: 56px;
    left: 16px;
  }

  .site-header.stuck .header-icon {
    width: 44px;
    height: 44px;
  }

  .site-header .wrap {
    padding-left: 84px;
  }

  .site-header.stuck .wrap {
    padding-left: 72px;
  }
}

/* The .brand anchor is the flex item, not the image inside it — putting
   this on the image alone let the anchor keep shrinking and clip the
   wordmark to a smudge. It is the one thing in the bar that must not give
   way when the nav runs out of room. */
.brand {
  flex-shrink: 0;
}

.brand img {
  height: 30px;
  width: auto;
}

.nav ul {
  display: flex;
  align-items: center;
  gap: 34px;
}

.nav > ul > li > a,
.mega-toggle {
  font-family: var(--display);
  font-size: .78rem;
  font-weight: 600;
  letter-spacing: .12em;
  text-transform: uppercase;
  color: #fff;
  padding-bottom: 3px;
  position: relative;
  /* "Book a table" and "Our Story" both wrap mid-phrase when the bar runs
     out of room, which reads as broken rather than as crowded. They stay on
     one line; the burger breakpoint below decides when the bar gives up. */
  white-space: nowrap;
}

.mega-toggle {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  background: none;
  border: 0;
  cursor: pointer;
  font-family: var(--display);
}

.mega-toggle .chev {
  width: 9px;
  height: 6px;
  transition: transform .22s var(--ease);
}

.mega-toggle[aria-expanded="true"] .chev {
  transform: rotate(180deg);
}

.nav > ul > li > a::after,
.mega-toggle::after {
  content: "";
  position: absolute;
  left: 0;
  bottom: 0;
  width: 0;
  height: 1.5px;
  background: var(--peach);
  transition: width .22s var(--ease);
}

.nav > ul > li > a:hover::after,
.nav > ul > li > a[aria-current="page"]::after,
.mega-toggle:hover::after,
.mega-toggle.is-current::after,
.mega-toggle[aria-expanded="true"]::after {
  width: 100%;
}

.nav .nav-join {
  background: var(--peach);
  color: var(--teal-deep);
  padding: 10px 20px;
}

.nav .nav-join::after {
  display: none;
}

.nav .nav-join:hover {
  background: var(--peach-soft);
}

/* ============================================================
   MEGA-MENU
   Five venues, each with its photograph — the point being that you
   choose a restaurant by looking at it, not by reading a list.
   ============================================================ */
.has-mega {
  position: static;
}

.mega {
  position: absolute;
  left: 0;
  right: 0;
  top: 100%;
  background: var(--teal-deep);
  border-top: 1px solid var(--line-dark);
  padding: 34px 0 38px;
  opacity: 0;
  transform: translateY(-10px);
  transition: opacity .26s var(--ease), transform .26s var(--ease);
}

.mega.open {
  opacity: 1;
  transform: none;
}

.mega-inner {
  max-width: var(--wrap);
  margin: 0 auto;
  padding: 0 32px;
  display: grid;
  grid-template-columns: repeat(5, 1fr);
  gap: 22px;
}

.mega-card {
  display: block;
}

.mega-thumb {
  display: block;
  aspect-ratio: 4 / 3;
  overflow: hidden;
  background: var(--teal);
  margin-bottom: 14px;
}

.mega-thumb img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: transform .6s var(--ease), filter .35s var(--ease);
  filter: saturate(.85);
}

.mega-card:hover .mega-thumb img {
  transform: scale(1.07);
  filter: saturate(1.05);
}

.mega-name {
  display: block;
  font-family: var(--display);
  font-weight: 700;
  font-size: 1.02rem;
  color: #fff;
  letter-spacing: -.01em;
  line-height: 1.2;
}

.mega-card:hover .mega-name {
  color: var(--accent);
}

.mega-area {
  display: block;
  font-size: .82rem;
  color: var(--on-dark-mute);
  margin-top: 3px;
}

/* The closing row of the menu. The rule and the spacing moved here from
   .mega-all when booking joined it — two links each drawing their own
   border read as two rows of one thing rather than one row of two. */
.mega-foot {
  grid-column: 1 / -1;
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: 10px 26px;
  padding-top: 22px;
  margin-top: 4px;
  border-top: 1px solid var(--line-dark);
}

.mega-all {
  font-family: var(--display);
  font-size: .76rem;
  font-weight: 600;
  letter-spacing: .14em;
  text-transform: uppercase;
  color: var(--peach);
}

.mega-all span {
  display: inline-block;
  transition: transform .22s var(--ease);
}

.mega-all:hover span {
  transform: translateX(5px);
}

/* ---------- Burger and the mobile drawer ---------- */
.burger {
  display: none;
  background: none;
  border: 0;
  cursor: pointer;
  width: 28px;
  height: 18px;
  position: relative;
  padding: 0;
}

.burger span {
  position: absolute;
  left: 0;
  right: 0;
  height: 2px;
  background: #fff;
  transition: transform .28s var(--ease), opacity .28s var(--ease);
}

.burger span:nth-child(1) {
  top: 0;
}

.burger span:nth-child(2) {
  top: 8px;
}

.burger span:nth-child(3) {
  top: 16px;
}

.menu-open .burger span:nth-child(1) {
  transform: translateY(8px) rotate(45deg);
}

.menu-open .burger span:nth-child(2) {
  opacity: 0;
}

.menu-open .burger span:nth-child(3) {
  transform: translateY(-8px) rotate(-45deg);
}

/* Seven items rather than six since booking joined the nav, so the gaps
   have to close earlier than they used to. */
@media (max-width: 1200px) {
  .nav ul {
    gap: 22px;
  }
}

@media (max-width: 1040px) {
  .mega-inner {
    grid-template-columns: repeat(3, 1fr);
  }
}

/* Paired with the header block above — see the note there. */
@media (max-width: 1080px) {
  .burger {
    display: block;
    z-index: 10;
  }

  .nav {
    position: fixed;
    inset: 0;
    background: var(--teal-deep);
    display: flex;
    /* NOT align-items: center. Centring a child taller than its scroll
       container splits the overflow across both ends, and the half above the
       start edge cannot be scrolled back to. Auto margins on the child (below)
       centre the same way but collapse to zero when space is negative. The two
       rules only work as a pair. */
    align-items: flex-start;
    justify-content: center;
    overflow-y: auto;
    opacity: 0;
    visibility: hidden;
    transition: opacity .28s var(--ease);
  }

  /* The other half of the pair above: centres the menu in both axes while
     it fits, yields to the scroll container when it does not. */
  .nav > ul {
    margin: auto;
  }

  .menu-open .nav {
    opacity: 1;
    visibility: visible;
  }

  .nav ul {
    flex-direction: column;
    gap: 6px;
    text-align: center;
    padding: 110px 20px 60px;
  }

  /* Pad the links out to a comfortable touch target rather than relying on
     the gap between them: the gap separates them visually but is not part of
     the tappable area, so the actual targets were about 20px tall against a
     44px guideline. Padding replaces most of the gap, so the spacing looks
     the same and the whole row is now tappable. */
  .nav > ul > li > a,
  .mega-toggle {
    font-size: 1rem;
    display: block;
    width: 100%;
    padding: 13px 0;
  }

  .mega-toggle {
    display: flex;
    justify-content: center;
  }

  /* The venue rows in the drawer get the same treatment. */
  .mega-card {
    padding: 10px 0;
  }

  .site-header.menu-open {
    background: var(--teal-deep);
  }

  /* The drawer is a fixed overlay covering the header's own background, so
     the lifted wordmark had nothing behind it and links scrolled visibly
     underneath. This paints the band they sit on. */
  .site-header.menu-open::before {
    content: "";
    position: absolute;
    inset: 0;
    background: var(--teal-deep);
    z-index: 9;
  }

  /* Lift the wordmark above the drawer so the menu still says whose site
     this is. The icon gets z-index ALONE — it is already absolute, and adding
     position: relative put its 56px back into flow, growing the header past
     the drawer's top padding and hiding the first menu item. */
  .site-header.menu-open .brand {
    position: relative;
    z-index: 10;
  }

  .site-header.menu-open .header-icon {
    z-index: 10;
  }

  /* In the drawer the mega-menu becomes a plain in-flow list — a
     photo grid inside a scrolling overlay is more trouble than it
     is worth on a phone. */
  .mega {
    position: static;
    padding: 22px 0 0;
    border: 0;
    background: none;
    opacity: 1;
    transform: none;
  }

  .mega-inner {
    grid-template-columns: 1fr;
    gap: 14px;
    padding: 0;
  }

  .mega-thumb {
    display: none;
  }

  .mega-name {
    font-size: .95rem;
  }

  .mega-all {
    padding-top: 16px;
  }
}

/* ============================================================
   HERO
   ============================================================ */
.hero {
  position: relative;
  min-height: 100svh;
  display: flex;
  align-items: flex-end;
  background: var(--teal-deep);
  overflow: hidden;
}

.hero-slides {
  position: absolute;
  inset: 0;
}

.hero-slide {
  position: absolute;
  inset: 0;
  opacity: 0;
  transition: opacity 1.4s var(--ease);
}

.hero-slide.on {
  opacity: 1;
}

.hero-slide img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  /* The animation's end state, as the resting value. An inactive slide sits
     here, so when the active slide finishes its zoom and hands over, nothing
     jumps: the outgoing image fades out from exactly where it stopped. */
  transform: scale(1.13);
}

/* The slow push in, on the active slide only. `linear` because an eased zoom
   visibly stalls near the end, and 5.6s because that is HOLD in main.js —
   the zoom must finish precisely as the crossfade begins. Change one and you
   must change the other. */
.hero-slide.on img {
  animation: kenburns 5.6s linear forwards;
}

@keyframes kenburns {
  from {
    transform: scale(1.02);
  }

  to {
    transform: scale(1.13);
  }
}

.hero::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 1;
  background:
    /* Text protection for the copy column, which is left-aligned and never
       wider than ~16ch. Without it the headline lands on whatever the
       photograph happens to be doing — over the bright food slides that was
       white type on pale yellow, with the peach offset disappearing into
       the food. */
    linear-gradient(100deg,
      rgba(20, 42, 50, .92) 0%,
      rgba(20, 42, 50, .74) 30%,
      rgba(20, 42, 50, .32) 55%,
      rgba(20, 42, 50, 0) 76%),
    linear-gradient(180deg,
      rgba(20, 42, 50, .55) 0%,
      rgba(20, 42, 50, .28) 26%,
      rgba(20, 42, 50, .66) 62%,
      rgba(20, 42, 50, .94) 100%);
}

.hero .wrap {
  position: relative;
  z-index: 2;
  padding-bottom: 130px;
  padding-top: 160px;
  width: 100%;
}

/* The one serif heading on the site, and deliberately so.

   Everything else uses Archivo, but a heavy sans set over food photography
   read as a poster rather than as a restaurant group. Fraunces is already
   loaded for the italic line directly beneath this, so the pairing is the
   same family in two voices rather than a third typeface.

   Weight 400, not 700: at this size the scale carries the line, and a high
   contrast serif set bold loses the thin strokes that make it look
   expensive.

   The display cut comes from its own font file, not from the browser. This
   used to set font-optical-sizing: auto and trust the browser to pick the
   display cut from a variable font; Chrome did, WebKit did not, and every
   iPhone drew this line as a heavy black face. Fraunces Display is frozen at
   a display optical size by tools/instance_fonts.py, so there is no choice
   left for any browser to make. */
.hero h1 {
  color: #fff;
  font-family: var(--serif-display);
  font-weight: 400;
  /* Both inherited values are tuned for Archivo. A serif needs a little
     more room: -.025em closes the counters, and 1.04 collides ascenders
     with the descenders of the line above. */
  letter-spacing: -.012em;
  line-height: 1.08;
  /* The floor governs on phones, where 6.8vw is far below it. Set lower
     than the sans it replaced: a serif at 48px pushed this headline to four
     lines on a 390px screen, and the forced break above means the two
     halves each wrap on their own. */
  font-size: clamp(2.6rem, 6.8vw, 5.8rem);
  /* The break itself is forced in the markup (see the riseIn filter), so
     this is only a cap for very wide viewports, not the thing deciding
     where the line ends. */
  max-width: 24ch;
  /* Not an effect — the scrim below already carries legibility. This is
     insurance for the hairline strokes over the brighter food slides, at a
     radius large enough that it reads as depth rather than as a shadow. */
  text-shadow: 0 2px 30px rgba(20, 42, 50, .55);
}

.hero-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 14px;
  margin-top: 32px;
}

/* ---------- Word-by-word headline reveal ----------
   The words are wrapped at build time by the `riseIn` filter, each in a
   clipping span, so they lift up from behind a mask. The unwrapped text
   is repeated in a .sr-only sibling for screen readers, which otherwise
   read a heading split into spans one word per pause. */
.rise {
  display: inline;
}

.rise-w {
  display: inline-block;
  overflow: hidden;
  vertical-align: bottom;
  /* Room for descenders, which a tight overflow:hidden would clip. */
  padding-bottom: .12em;
  margin-bottom: -.12em;
}

.rise-w>span {
  display: inline-block;
}

.js .rise-w>span {
  transform: translateY(105%);
  animation: riseWord .9s var(--ease) forwards;
  animation-delay: calc(var(--i) * 70ms + 120ms);
}

@keyframes riseWord {
  to {
    transform: none;
  }
}

/* ---------- Hero name ticker ---------- */
.hero-ticker {
  position: absolute;
  z-index: 2;
  right: 32px;
  bottom: 130px;
  text-align: right;
  height: 3.4em;
  display: none;
}

@media (min-width: 1100px) {
  .hero-ticker {
    display: block;
  }
}

.hero-tick {
  position: absolute;
  right: 0;
  bottom: 0;
  white-space: nowrap;
  font-family: var(--display);
  font-weight: 700;
  font-size: 1.05rem;
  letter-spacing: -.01em;
  color: #fff;
  opacity: 0;
  transform: translateY(8px);
  /* Out quickly, with no delay. */
  transition: opacity .25s var(--ease), transform .25s var(--ease);
}

.hero-tick.on {
  opacity: 1;
  transform: none;
  /* In more slowly, and only once the outgoing name has cleared — the two
     are stacked in the same place, so overlapping them is just a pile. */
  transition: opacity .45s var(--ease) .25s, transform .45s var(--ease) .25s;
}

.hero-tick em {
  display: block;
  font-style: normal;
  font-family: var(--body);
  font-weight: 400;
  font-size: .82rem;
  color: var(--peach);
  letter-spacing: .06em;
  margin-top: 2px;
}

/* ---------- Scroll cue ---------- */
.scroll-cue {
  position: absolute;
  z-index: 2;
  left: 50%;
  bottom: 42px;
  transform: translateX(-50%);
  width: 22px;
  height: 36px;
  border: 1px solid rgba(255, 255, 255, .5);
  border-radius: 12px;
}

.scroll-cue span {
  position: absolute;
  left: 50%;
  top: 8px;
  width: 3px;
  height: 6px;
  margin-left: -1.5px;
  border-radius: 2px;
  background: var(--peach);
  animation: cue 1.9s var(--ease) infinite;
}

@keyframes cue {

  0%,
  100% {
    transform: translateY(0);
    opacity: 1;
  }

  60% {
    transform: translateY(13px);
    opacity: .2;
  }
}

@media (max-width: 640px) {
  .scroll-cue {
    display: none;
  }
}

/* ============================================================
   MARQUEE
   ============================================================ */
/* Teal-deep, not teal: the ribbon sits under the hero's scrim and reads as
   its foot. The membership band below steps up to the lighter teal, so the
   page lightens into itself rather than going dark/light/dark. */
.marquee {
  background: var(--teal-deep);
  color: #fff;
  overflow: hidden;
  padding: 18px 0;
  border-top: 1px solid rgba(255, 255, 255, .08);
}

.marquee-track {
  display: flex;
  align-items: center;
  gap: 34px;
  width: max-content;
  animation: slide 42s linear infinite;
}

/* The list is emitted twice, so the track can travel exactly half its own
   width and restart with the seam off-screen. */
@keyframes slide {
  to {
    transform: translateX(-50%);
  }
}

.marquee span {
  font-family: var(--display);
  font-weight: 700;
  font-size: 1.05rem;
  letter-spacing: .16em;
  text-transform: uppercase;
  white-space: nowrap;
  color: rgba(255, 255, 255, .82);
}

.marquee img {
  width: 26px;
  height: 26px;
  opacity: .5;
  flex: none;
}

/* ============================================================
   PAGE HEAD — interior pages
   Same dark ground; .with-image is an explicit per-page opt-in.
   ============================================================ */
.page-head {
  background: var(--teal-deep);
  color: #fff;
  padding: 190px 0 90px;
}

.page-head h1 {
  color: #fff;
  max-width: 18ch;
}

/* Given a photograph, behave like one. This used to sit the type on a
   flat block: 190px of padding and a veil that never went below 74%, so
   the picture underneath was texture rather than an image. The venue
   heroes have always bottomed out at 24% and read as photography — the
   difference was not deliberate, it just never got looked at.

   Height comes from the viewport now, with the padding kept as a floor for
   short screens. Deliberately short of the 92svh the venue heroes take:
   an inner page should have presence without competing with them. */
.page-head.with-image {
  position: relative;
  /* Two floors, and the head takes whichever is greater. 62svh keeps it
     proportional to the window, which is what makes it read as a hero. The
     620px is what stops it being sized by its own contents: below roughly a
     1000px-tall window the svh figure falls under what the fullest head
     needs, and the heads with a jump strip or a button then grew past the
     ones without — /catering/ ran 55px taller than /contact/ on a 900px
     laptop. 620px clears the tallest of them, so every head in the nav is
     the same height at every window size and moving between them stops
     shifting the page. The phone block sets its own, flat, figure. */
  min-height: max(62svh, 620px);
  display: flex;
  align-items: flex-end;
  /* The photograph comes in as two custom properties set inline by each
     page: --head-bg (2000px) and --head-bg-sm (1200px). A CSS background
     has no srcset, so the media query below is what keeps a phone from
     downloading the desktop file. 1200px rather than a phone's own width
     because `cover` on a tall, narrow head sizes the image by its HEIGHT —
     a 620px-tall head needs a ~930px-wide image — and the heavy overlay
     below hides the rest of the difference. */
  background-image: var(--head-bg);
  background-size: cover;
  background-position: center;
  overflow: hidden;
}

/* Two layers, the same shape the venue heroes use, so the site reads as
   one system. The vertical one is light through the middle where nothing
   sits and heavy at the foot where the label and h1 do; the horizontal one
   anchors the left column, and is what keeps the peach label above AA: on
   the vertical layer alone the peach measures 4.40:1 against a worst-case
   white photograph, just under the 4.5:1 AA needs. With both it is 5.03:1
   at the wrap edge. White h1 clears comfortably either way. */
.page-head.with-image::before {
  content: "";
  position: absolute;
  inset: 0;
  background:
    linear-gradient(180deg,
      rgba(20, 42, 50, .55) 0%,
      rgba(20, 42, 50, .34) 32%,
      rgba(20, 42, 50, .78) 72%,
      rgba(20, 42, 50, .95) 100%),
    linear-gradient(90deg, rgba(20, 42, 50, .5) 0%, transparent 62%);
}

@media (max-width: 700px) {
  .page-head.with-image {
    background-image: var(--head-bg-sm, var(--head-bg));
  }
}

.page-head.with-image .wrap {
  position: relative;
  z-index: 1;
  /* A flex child would otherwise shrink to its content. */
  width: 100%;
}

/* ============================================================
   BANDS
   ============================================================ */
/* 112px top and bottom put 224px of empty band between every section. On a
   1440x900 laptop that was around a fifth of the homepage's whole height
   spent on nothing, which reads as a long page rather than a calm one. 88px
   still gives each band clear air without the scroll it was costing. */
section {
  padding: 88px 0;
}

.band-dark {
  background: var(--teal-deep);
  color: #fff;
}

.band-dark h2,
.band-dark h3 {
  color: #fff;
}

.band-teal {
  /* The group's own lattice rather than flat teal. The artwork is drawn on
     #22424D — the same --teal these bands used — so it drops in without
     shifting the colour, and the motif is low enough in contrast that type
     over it reads exactly as it did.

     background-color stays as the ground: if the image is slow, blocked or
     404s, the band is the teal it always was rather than bare white.
     --brand-pattern is set on <body> from _data/images.js. */
  background-color: var(--teal);
  background-image: var(--brand-pattern);
  background-repeat: repeat;
  /* Half the artwork's native width: the motif reads as a pattern rather
     than as noise, and stays the same size on every band and every screen
     (which `cover` would not — it would rescale per band height). */
  background-size: 1200px auto;
  color: #fff;
}

.band-teal h2,
.band-teal h3 {
  color: #fff;
}

.band-surface {
  background: var(--surface);
}

.split {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 76px;
  align-items: center;
}

.split.flip .media {
  order: 2;
}

.split .media {
  aspect-ratio: 4 / 3;
  overflow: hidden;
}

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

@media (max-width: 900px) {
  .split {
    grid-template-columns: 1fr;
    gap: 40px;
  }

  .split.flip .media {
    order: 1;
  }
}

.head-row {
  display: flex;
  justify-content: space-between;
  align-items: flex-end;
  gap: 28px;
  flex-wrap: wrap;
  margin-bottom: 44px;
}

.head-row h2 {
  margin-bottom: 0;
}

/* ---------- Image wipe ----------
   The picture is uncovered from the bottom up as it enters the viewport,
   rather than simply fading. Applied to the container so a parallax
   transform on the <img> inside is left free to do its own job. */
.js .wipe {
  clip-path: inset(0 0 100% 0);
  transition: clip-path 1.1s var(--ease);
}

.js .reveal.seen .wipe,
.js .wipe.seen {
  clip-path: inset(0 0 0 0);
}

/* ============================================================
   FIGURES STRIP
   ============================================================ */
.figures {
  padding: 70px 0;
}

.figure-row {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 40px;
}

@media (max-width: 820px) {
  .figure-row {
    grid-template-columns: 1fr;
    gap: 30px;
  }
}

.figure-row li {
  border-top: 1px solid var(--line-dark);
  padding-top: 22px;
}

.fig {
  display: block;
  font-family: var(--display);
  font-weight: 800;
  font-size: clamp(3.2rem, 6vw, 4.6rem);
  line-height: .95;
  letter-spacing: -.04em;
  color: var(--peach);
}

.fig-label {
  display: block;
  margin-top: 10px;
  color: rgba(255, 255, 255, .78);
  max-width: 26ch;
}

/* ============================================================
   MEMBERSHIP — the loyalty programme's front door
   ============================================================ */
.membership {
  position: relative;
  overflow: hidden;
}


.membership .wrap {
  position: relative;
  z-index: 1;
}

.membership-grid {
  display: grid;
  grid-template-columns: 1.15fr .85fr;
  gap: 72px;
  align-items: center;
}

@media (max-width: 900px) {
  .membership-grid {
    grid-template-columns: 1fr;
    gap: 40px;
  }
}

.big-figure {
  font-family: var(--display);
  font-weight: 800;
  font-size: clamp(5rem, 13vw, 9rem);
  line-height: .9;
  letter-spacing: -.04em;
  color: var(--peach);
  margin: 0;
}

.big-figure.sm {
  font-size: clamp(2.6rem, 5vw, 3.4rem);
  color: var(--accent-deep);
}

/* Sits under the Join button, deliberately quiet — it is a reassurance
   for the person who wants to check before joining, not a second CTA. */
.member-terms {
  margin: 14px 0 0;
  font-size: .86rem;
}

.member-terms a {
  color: rgba(255, 255, 255, .6);
  border-bottom: 1px solid rgba(255, 255, 255, .25);
  padding-bottom: 1px;
}

.member-terms a:hover {
  color: #fff;
  border-bottom-color: currentColor;
}

.member-points {
  margin: 28px 0 0;
}

.member-points li {
  padding: 14px 0;
  border-top: 1px solid var(--line-dark);
  color: rgba(255, 255, 255, .8);
  font-size: .98rem;
}

/* ============================================================
   RESTAURANTS — full-width alternating showcase, not a grid.
   Five items don't divide evenly into a fixed-column grid without an
   awkward empty cell in the last row. A stacked alternating list (the same
   device as .split) sidesteps that and gives each restaurant room. A
   1 + 2x2 arrangement was tried and reverted: shorter, but not the look
   the group wants.

   Below 900px this becomes a swipe rail — see the RAIL block.
   ============================================================ */
.restaurant-list {
  display: flex;
  flex-direction: column;
}

.restaurant-row {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 76px;
  align-items: center;
  padding: 64px 0;
  border-top: 1px solid var(--line);
}

.restaurant-list .restaurant-row:last-child {
  border-bottom: 1px solid var(--line);
}

.restaurant-row.flip .r-media {
  order: 2;
}

@media (max-width: 900px) {
  .restaurant-row {
    grid-template-columns: 1fr;
    gap: 32px;
    padding: 48px 0;
  }

  .restaurant-row.flip .r-media {
    order: 1;
  }
}

.r-media {
  position: relative;
  display: block;
  aspect-ratio: 4 / 3;
  overflow: hidden;
  background: var(--teal-deep);
}

.r-media img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: transform .9s var(--ease);
}

.restaurant-row:hover .r-media img {
  transform: scale(1.06);
}

/* A bar in the restaurant's own colour, drawn on hover. The only place
   the venue accent appears on the homepage — enough to teach the
   colour, not enough to fracture the group's palette. */
.r-media::after {
  content: "";
  position: absolute;
  left: 0;
  bottom: 0;
  height: 5px;
  width: 0;
  background: var(--accent-light);
  transition: width .55s var(--ease);
}

.restaurant-row:hover .r-media::after {
  width: 100%;
}

.r-index {
  font-family: var(--display);
  font-weight: 700;
  font-size: .78rem;
  letter-spacing: .18em;
  text-transform: uppercase;
  color: var(--teal-mute);
  margin-bottom: 14px;
}

.restaurant-row h3 {
  font-size: clamp(1.6rem, 3vw, 2.2rem);
  margin-bottom: 14px;
}

.restaurant-row h3 a:hover {
  color: var(--accent-deep);
}

/* The blurb, and only the blurb. `.restaurant-row p` on its own also
   matches .r-index and .r-award — both are paragraphs inside this row —
   and at one class plus one element it outweighs their own single-class
   rules, so it was quietly restating both at body size and colour with
   26px under them. The index line above each name has been rendering at
   1.02rem rather than the .78rem it sets for itself. :not([class]) picks
   the one paragraph that has no rules of its own. */
.restaurant-row p:not([class]) {
  color: var(--teal-mute);
  font-size: 1.02rem;
  max-width: 46ch;
  margin-bottom: 26px;
}

.restaurant-row .foot {
  display: flex;
  align-items: center;
  gap: 24px;
}

/* The name is dropped from the visible label at every width, not just in the
   swipe cards. "Explore Nutmeg Street Kitchen" is nearly twice the length of
   "Explore Nadu", and .foot is a nowrap flex row, so the longest name did not
   simply wrap itself — it over-subscribed the row and flex shrank every item,
   breaking "Menu" and "Book a table" across two lines each on that one card.
   The h3 with the restaurant's name sits directly above, so "Explore" alone is
   unambiguous on screen, and the aria-label still reads the full phrase. */
.restaurant-row .more-name {
  display: none;
}

.restaurant-row .more {
  font-family: var(--display);
  font-size: .78rem;
  font-weight: 600;
  letter-spacing: .12em;
  text-transform: uppercase;
  color: var(--teal);
  border-bottom: 1px solid transparent;
  padding-bottom: 2px;
}

.restaurant-row .more:hover {
  color: var(--accent-deep);
  border-bottom-color: currentColor;
}

/* The menu link reads as the same kind of thing as "Explore" beside it —
   a plain caps link — rather than a second chip competing with the booking
   one. Two solid chips in a card foot is the same mistake the header made
   with two pills. The outbound arrow is in the markup, the same way
   .venue-site does it, rather than a CSS ::after. */
.restaurant-row .menu-link {
  font-family: var(--display);
  white-space: nowrap;
  font-size: .78rem;
  font-weight: 600;
  letter-spacing: .12em;
  text-transform: uppercase;
  color: var(--accent-deep);
  border-bottom: 1px solid transparent;
  padding-bottom: 2px;
}

.restaurant-row .menu-link:hover {
  border-bottom-color: currentColor;
}

/* Booking, as a small solid chip in the venue's own darkened accent. It is
   set apart from the "Explore" link beside it because the two do different
   things — one stays on this site, one leaves it — and a second underlined
   caps link would have read as a pair of equals. */
.restaurant-row .book {
  font-family: var(--display);
  /* An action label, so it is never a wrapping opportunity: it keeps its
     width and the flexible text links beside it give way instead. */
  white-space: nowrap;
  flex-shrink: 0;
  font-size: .74rem;
  font-weight: 600;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: #fff;
  background: var(--accent-deep);
  padding: 11px 18px;
  transition: background .2s var(--ease), transform .2s var(--ease);
}

.restaurant-row .book:hover {
  background: var(--teal-deep);
  transform: translateY(-2px);
}

/* A filled teal disc with the mark reversed out, the same circular badge
   the venue pages use for their social links. It was a 19px line glyph at
   70% opacity, which read as disabled next to the "Explore" link beside it
   and was less than half the 44px touch target a thumb needs. */
.restaurant-row .ig {
  display: grid;
  place-items: center;
  width: 38px;
  height: 38px;
  border-radius: 50%;
  background: var(--teal);
  color: var(--shell);
  transition: background .2s var(--ease), transform .2s var(--ease);
}

.restaurant-row .ig:hover {
  background: var(--accent-deep);
  transform: translateY(-2px);
}

.restaurant-row .ig svg {
  width: 19px;
  height: 19px;
}

/* ============================================================
   POSTER LIST — /restaurants/
   Full-bleed bands. The group's photography is its strongest asset,
   so on the page that exists to choose a restaurant it gets the
   entire width rather than a card-sized crop.
   ============================================================ */
.poster-list {
  padding: 0;
}

.poster {
  position: relative;
  display: flex;
  align-items: flex-end;
  min-height: 82vh;
  overflow: hidden;
  background: var(--teal-deep);
  color: #fff;
}

.poster-media {
  position: absolute;
  inset: 0;
}

.poster-media img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  filter: saturate(.9);
  transition: filter .6s var(--ease);
}

.poster:hover .poster-media img {
  filter: saturate(1.08);
}

/* Scrim tinted with the venue's own colour, so each band feels like it
   belongs to that restaurant without recolouring the photograph.

   pointer-events is load-bearing, not tidiness. The band used to be one
   big <a> and the scrim sat harmlessly on top of it. Now the link is the
   .poster-media beneath, and a pseudo-element painted over it would
   intercept every click on the photograph. */
.poster::after {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  background:
    linear-gradient(180deg,
      rgba(20, 42, 50, .34) 0%,
      rgba(20, 42, 50, .26) 34%,
      rgba(20, 42, 50, .78) 70%,
      rgba(20, 42, 50, .95) 100%),
    linear-gradient(100deg, color-mix(in srgb, var(--accent) 55%, transparent) 0%, transparent 62%);
}

.poster-body {
  position: relative;
  z-index: 1;
  width: 100%;
  max-width: var(--wrap);
  margin: 0 auto;
  padding: 0 32px 76px;
}

.poster.flip {
  justify-content: flex-end;
  text-align: right;
}

.poster.flip .poster-body {
  display: flex;
  flex-direction: column;
  align-items: flex-end;
}

.poster.flip .poster-actions {
  justify-content: flex-end;
}

.poster-no,
.poster-area,
.poster-cta {
  display: block;
  font-family: var(--display);
  font-weight: 600;
  letter-spacing: .18em;
  text-transform: uppercase;
  font-size: .74rem;
}

.poster-no {
  color: var(--accent-light);
  margin-bottom: 16px;
}

/* An <h2> since the band stopped being one anonymous link — margin is
   reset because the global heading rule adds half an em below it. */
.poster-name {
  display: block;
  margin: 0;
  font-family: var(--display);
  font-weight: 700;
  font-size: clamp(2.6rem, 7vw, 5.2rem);
  line-height: .98;
  letter-spacing: -.035em;
}

.poster-name a {
  color: inherit;
}

.poster-area {
  color: rgba(255, 255, 255, .7);
  letter-spacing: .14em;
  margin-top: 14px;
}

.poster-blurb {
  display: block;
  max-width: 46ch;
  color: rgba(255, 255, 255, .82);
  margin-top: 18px;
  font-size: 1.02rem;
}

/* Book first, read second: on the page whose job is choosing a restaurant,
   the visitor who has already chosen should not have to go through the
   detail page to reach a booking form. */
.poster-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 14px 24px;
  margin-top: 28px;
}

.poster-cta {
  color: var(--accent-light);
  margin-top: 26px;
  letter-spacing: .12em;
}

/* Inside the actions row the row itself carries the spacing. */
.poster-actions .poster-cta {
  margin-top: 0;
}

.poster-cta span {
  display: inline-block;
  transition: transform .25s var(--ease);
}

.poster:hover .poster-cta span {
  transform: translateX(6px);
}

@media (max-width: 820px) {
  .poster {
    min-height: 64vh;
  }

  .poster.flip {
    text-align: left;
    justify-content: flex-start;
  }

  .poster.flip .poster-body {
    align-items: flex-start;
  }

  .poster.flip .poster-actions {
    justify-content: flex-start;
  }

  /* The three text actions are 20px of type, which is well under the 44px
     a thumb needs — the same problem the Instagram badge and the nav links
     were fixed for. Padding replaces most of the row gap rather than being
     added to it, so the spacing looks unchanged and the whole row becomes
     tappable instead of just the words. */
  .poster-actions {
    row-gap: 0;
  }

  .poster-actions .poster-cta {
    padding: 13px 0;
  }

  .poster-body {
    padding-bottom: 48px;
  }
}

/* Quick-jump chips on the restaurants page head. */
.venue-jump {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  margin-top: 34px;
}

.venue-jump a {
  display: inline-flex;
  align-items: baseline;
  gap: 9px;
  border: 1px solid var(--line-dark);
  padding: 10px 18px;
  font-family: var(--display);
  font-size: .78rem;
  font-weight: 600;
  letter-spacing: .06em;
  color: #fff;
  transition: border-color .22s var(--ease), background .22s var(--ease);
}

.venue-jump a span {
  font-family: var(--body);
  font-weight: 400;
  font-size: .74rem;
  color: var(--on-dark-mute);
}

.venue-jump a:hover {
  border-color: var(--accent-light);
  background: rgba(255, 255, 255, .06);
}

/* ============================================================
   VENUE PAGES
   ============================================================ */
.venue-hero {
  position: relative;
  min-height: 92svh;
  display: flex;
  align-items: flex-end;
  background: var(--teal-deep);
  overflow: hidden;
  padding: 0;
}

.kenburns {
  position: absolute;
  inset: 0;
}

.kenburns img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  animation: kenburns 22s var(--ease) forwards;
}

.venue-hero::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 1;
  background:
    linear-gradient(180deg,
      rgba(20, 42, 50, .55) 0%,
      rgba(20, 42, 50, .24) 28%,
      rgba(20, 42, 50, .74) 64%,
      rgba(20, 42, 50, .96) 100%),
    linear-gradient(90deg, color-mix(in srgb, var(--accent) 52%, transparent) 0%, transparent 60%);
}

.venue-hero-inner {
  position: relative;
  z-index: 2;
  width: 100%;
  padding-top: 170px;
  padding-bottom: 120px;
}

.venue-eyebrow {
  display: flex;
  align-items: center;
  gap: 16px;
  flex-wrap: wrap;
  color: var(--accent-light);
}

.venue-no {
  font-size: 1.1rem;
  font-weight: 800;
  letter-spacing: -.02em;
  color: #fff;
}

.venue-no span {
  font-size: .74rem;
  font-weight: 600;
  letter-spacing: .1em;
  color: rgba(255, 255, 255, .5);
}

.venue-title {
  color: #fff;
  max-width: 14ch;
}

.venue-tagline {
  font-family: var(--serif);
  font-style: italic;
  font-size: clamp(1.15rem, 2.1vw, 1.6rem);
  line-height: 1.4;
  color: rgba(255, 255, 255, .85);
  max-width: 34ch;
  margin-top: 18px;
}

.venue-hero .btn-accent {
  background: var(--accent-light);
  color: var(--teal-deep);
}

.venue-hero .btn-accent:hover {
  background: #fff;
}

/* ---------- Sticky venue bar ---------- */
.venue-bar {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 90;
  /* Solid, with no backdrop blur. At 96% opacity the blur was invisible,
     and a backdrop-filter on a fixed element is a known source of WebKit
     repositioning glitches while the page scrolls. */
  background: var(--teal-deep);
  border-top: 3px solid var(--accent-light);
  transform: translateY(100%);
  /* Slid out of sight is not the same as gone. Without `visibility` the
     bar's booking link stays in the tab order while the bar is off-screen,
     and because the bar is `position: fixed` the browser has nowhere to
     scroll it into view — so a keyboard user tabbing down a venue page hit
     two invisible stops. The delayed visibility transition lets the slide
     finish before it is hidden. */
  visibility: hidden;
  transition: transform .4s var(--ease), visibility 0s linear .4s;
}

.venue-bar.on {
  transform: none;
  visibility: visible;
  transition: transform .4s var(--ease), visibility 0s;
}

.venue-bar .wrap {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 20px;
  padding-top: 13px;
  /* The bar is the one fixed thing at the bottom of the screen, so it is
     where a home indicator or a rounded corner would land. The fallback is
     0px, which is what every browser that does not set these reports, so
     this is the ordinary 13px everywhere else.

     The viewport meta deliberately does NOT carry viewport-fit=cover. With
     the default, iOS insets the layout viewport itself and the bar already
     clears the indicator, so these are belt and braces. Turning cover on
     would push the page under the notch in landscape and every fixed
     element on the site — the header included — would need the same
     treatment before it was safe. */
  padding-bottom: calc(13px + env(safe-area-inset-bottom, 0px));
  padding-left: calc(var(--gutter) + env(safe-area-inset-left, 0px));
  padding-right: calc(var(--gutter) + env(safe-area-inset-right, 0px));
}

/* ---------- The group bar, homepage only ----------
   Phones only: on a desktop homepage the header is always in view and
   already carries "Book a table", so a second fixed action would be the
   same link twice on one screen. The restaurant pages keep theirs at every
   width because their booking link is venue-specific and the header's is
   not. */
.venue-bar-group {
  display: none;
}

@media (max-width: 720px) {
  .venue-bar-group {
    display: block;
  }

  .has-group-bar .site-footer {
    padding-bottom: 110px;
  }
}

/* "Join the membership" is a long label for a 320px screen, and at full
   tracking it wrapped to two lines and took the figure beside it with it.
   Tightening the letter-spacing keeps both on one line down to the
   narrowest phones still in use, rather than dropping the "10% off food"
   that is the entire reason to tap it. */
@media (max-width: 380px) {
  .venue-bar-group .btn {
    /* The smaller type costs height as well as width, so the vertical
       padding goes back up to hold the 44px touch target. */
    padding: 13px 14px;
    letter-spacing: .04em;
    font-size: .68rem;
  }

  .venue-bar-group .venue-bar-name {
    font-size: .92rem;
  }
}

.venue-bar-name {
  font-family: var(--display);
  font-weight: 700;
  font-size: 1rem;
  /* The body's 1.65 is for paragraphs. When "15% off your first visit"
     wraps on a phone, that left the two lines looking like separate items. */
  line-height: 1.25;
  color: #fff;
  letter-spacing: -.01em;
}

/* The button never wraps: a two-line uppercase label reads as two buttons.
   The name beside it gives way instead, which it can do gracefully. */
.venue-bar .btn {
  white-space: nowrap;
  flex-shrink: 0;
}

.venue-bar-name em {
  display: block;
  font-style: normal;
  font-family: var(--body);
  font-weight: 400;
  font-size: .78rem;
  color: var(--on-dark-mute);
}

.venue-bar-actions {
  display: flex;
  align-items: center;
  gap: 20px;
}

/* Was `> a:first-child`, which meant "Directions" only because it happened
   to be the only plain link in the bar. Menu joined it, so the plain links
   carry a class and the button is the one identified by its own. */
.venue-bar-actions .vb-link {
  font-family: var(--display);
  font-size: .74rem;
  font-weight: 600;
  letter-spacing: .12em;
  text-transform: uppercase;
  color: rgba(255, 255, 255, .8);
}

.venue-bar-actions .vb-link:hover {
  color: var(--accent-light);
}

.venue-bar .btn-accent {
  background: var(--accent-light);
  color: var(--teal-deep);
}

@media (max-width: 560px) {
  .venue-bar-name em {
    display: none;
  }

  /* Both plain links go, not just one: on a phone the bar is down to the
     venue's name and the one button, which is all it has room to say. */
  .venue-bar-actions .vb-link {
    display: none;
  }
}

/* ---------- Story ---------- */
.venue-story-grid {
  display: grid;
  grid-template-columns: 300px 1fr;
  gap: 80px;
  align-items: start;
}

@media (max-width: 900px) {
  .venue-story-grid {
    grid-template-columns: 1fr;
    gap: 44px;
  }
}

.venue-facts {
  position: sticky;
  top: 120px;
}

@media (max-width: 900px) {
  .venue-facts {
    position: static;
  }
}

.venue-facts dl {
  margin: 0;
  border-top: 2px solid var(--accent);
}

.venue-facts dl>div {
  padding: 18px 0;
  border-bottom: 1px solid var(--line);
}

.venue-facts dt {
  font-family: var(--display);
  font-size: .68rem;
  font-weight: 600;
  letter-spacing: .2em;
  text-transform: uppercase;
  color: var(--teal-mute);
  margin-bottom: 5px;
}

.venue-facts dd {
  margin: 0;
  font-size: 1rem;
}

.venue-site {
  display: inline-block;
  margin-top: 22px;
  font-family: var(--display);
  font-size: .76rem;
  font-weight: 600;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--accent-deep);
  border-bottom: 1px solid currentColor;
  padding-bottom: 2px;
}

.venue-prose p {
  max-width: 62ch;
}

/* Pull quote in the restaurant's own voice — the serif, at size, with a
   rule in the venue colour. */
.pull {
  margin: 44px 0 0;
  padding-left: 30px;
  border-left: 3px solid var(--accent);
  font-family: var(--serif);
  font-size: clamp(1.35rem, 2.6vw, 1.9rem);
  line-height: 1.35;
  letter-spacing: -.01em;
  color: var(--teal);
  max-width: 24ch;
}

/* ---------- Gallery ----------
   The column count is set explicitly from the number of photographs rather
   than left to auto-fit. auto-fit sized the track to whatever fitted the
   container — four columns on a wide screen — so a gallery of four, with
   the first spanning the full width, left the last column of the second row
   empty. The galleries only ever hold two, three or four images, so saying
   which is which is simpler than any amount of nth-child arithmetic. */
.gal {
  display: grid;
  gap: 20px;
  grid-template-columns: 1fr;
}

.gal-item {
  margin: 0;
}

.gal-btn {
  display: block;
  width: 100%;
  padding: 0;
  border: 0;
  background: var(--teal);
  cursor: zoom-in;
  overflow: hidden;
  aspect-ratio: 4 / 3;
}

.gal-btn img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: transform .8s var(--ease), filter .4s var(--ease);
  filter: saturate(.9);
}

.gal-btn:hover img {
  transform: scale(1.06);
  filter: saturate(1.05);
}

@media (min-width: 720px) {
  .gal-2 {
    grid-template-columns: repeat(2, 1fr);
  }

  .gal-3 {
    grid-template-columns: repeat(3, 1fr);
  }

  /* Four images: the first takes the full width as a lead shot and the
     remaining three sit beneath it, one per column. A flat run of four
     equal tiles reads as a contact sheet, not a room. */
  .gal-4 {
    grid-template-columns: repeat(3, 1fr);
  }

  .gal-4 .gal-item:first-child {
    grid-column: 1 / -1;
  }

  .gal-4 .gal-item:first-child .gal-btn {
    aspect-ratio: 21 / 9;
  }
}

/* ---------- Visit ---------- */
.venue-visit-grid {
  display: grid;
  grid-template-columns: 1.05fr .95fr;
  gap: 84px;
  align-items: start;
}

@media (max-width: 900px) {
  .venue-visit-grid {
    grid-template-columns: 1fr;
    gap: 48px;
  }
}

@media (max-width: 480px) {
  .cr-icon {
    width: 36px;
    height: 36px;
  }

  .cr-icon .icon {
    width: 17px;
    height: 17px;
  }

  .contact-rows a {
    gap: 14px;
  }

  /* Full-width buttons read better than two half-width ones that wrap. */
  .venue-visit-actions .btn {
    flex: 1 1 100%;
    text-align: center;
  }
}

/* Base icon sizing. Every icon inherits currentColor, so it takes the
   colour of whatever it sits inside without a modifier class. */
.icon {
  width: 20px;
  height: 20px;
  flex: none;
}

/* ---------- Contact rows ----------
   Icon, then the kind of thing, then the value. The whole row is the link:
   a bigger target than an underlined phone number, which matters because
   this panel is mostly used on a phone, standing outside. */
.contact-rows {
  margin: 30px 0;
  border-top: 1px solid var(--line);
}

.contact-rows li {
  border-bottom: 1px solid var(--line);
}

.contact-rows a {
  display: flex;
  align-items: center;
  gap: 18px;
  padding: 16px 4px;
  transition: padding-left .28s var(--ease);
}

.contact-rows a:hover {
  padding-left: 12px;
}

.cr-icon {
  display: grid;
  place-items: center;
  width: 42px;
  height: 42px;
  flex: none;
  border-radius: 50%;
  color: #fff;
  background: var(--accent-deep);
  transition: transform .28s var(--ease);
}

.contact-rows a:hover .cr-icon {
  transform: scale(1.07);
}

.cr-text {
  min-width: 0;
}

.cr-label {
  display: block;
  font-family: var(--display);
  font-size: .64rem;
  font-weight: 600;
  letter-spacing: .2em;
  text-transform: uppercase;
  color: var(--teal-mute);
  margin-bottom: 3px;
}

.cr-value {
  display: block;
  font-size: 1.02rem;
  line-height: 1.45;
  /* Long email addresses must not push the panel wider than its column. */
  overflow-wrap: anywhere;
}

.contact-rows a:hover .cr-value {
  color: var(--accent-deep);
}

/* Directions and booking, side by side under the contact rows. */
.venue-visit-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 12px;
}

/* ---------- Social / external, as icon buttons ---------- */
.social-label {
  font-family: var(--display);
  font-size: .64rem;
  font-weight: 600;
  letter-spacing: .2em;
  text-transform: uppercase;
  color: var(--teal-mute);
  margin: 34px 0 12px;
}

.venue-social {
  display: flex;
  gap: 12px;
}

.venue-social a {
  display: grid;
  place-items: center;
  width: 46px;
  height: 46px;
  border: 1px solid var(--line);
  border-radius: 50%;
  color: var(--teal);
  background: rgba(255, 255, 255, .55);
  transition: background .24s var(--ease), color .24s var(--ease),
    border-color .24s var(--ease), transform .24s var(--ease);
}

.venue-social a:hover {
  background: var(--accent-deep);
  border-color: var(--accent-deep);
  color: #fff;
  transform: translateY(-3px);
}

.venue-social .icon {
  width: 21px;
  height: 21px;
}

/* The clock sits inline with the "Opening times" label. */
.venue-visit .label .icon {
  width: 15px;
  height: 15px;
  vertical-align: -2px;
  margin-right: 7px;
}

.hours {
  border-top: 1px solid var(--line);
}

.hours li {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 20px;
  padding: 15px 4px;
  border-bottom: 1px solid var(--line);
  transition: background .2s var(--ease);
}

.hours li:hover {
  background: rgba(255, 255, 255, .45);
}

/* ---------- Today's row ----------
   This used to be deliberately NOT done: a static page has no idea what day
   it is, and a highlight arriving a beat after load reads as a glitch.

   It is done now because the live status line needs Bristol's clock anyway,
   and the glitch is handled rather than accepted. The row snaps rather than
   fades - transition: none, since .hours li carries a 0.2s background
   transition that would otherwise animate it in, which is the glitch made
   worse - and main.js is deferred, so it runs as parsing ends, at or before
   first paint on these pages.

   If it ever does read as a glitch, delete the .is-today rules. The status
   line above the list says the same thing and cannot flash, because it
   starts empty and simply appears. */

/* Restaurant pages: roomy rows, so the row fills and takes a marker in the
   venue's colour. */
.hours li.is-today {
  transition: none;
  background: var(--surface);
  box-shadow: inset 3px 0 0 var(--accent-deep);
}

.hours li.is-today .h-days {
  color: var(--accent-deep);
}

/* /book: compact rows with no side padding, inside a card that is already
   white - a marker would sit on the first letter and a white fill would not
   show. So weight and colour carry it, and weight matters most: Nutmeg's
   accent (#1A4553) is all but the body teal (#22424D), so on that card a
   colour change alone is invisible. */
.bk-hours li.is-today {
  font-weight: 600;
  color: var(--accent-deep);
}

/* ---------- Live opening status ----------
   Filled by initStatus in main.js. Hidden while empty: without JavaScript
   there is no clock to read, and saying nothing beats a stale "Open now".
   Open is shown in the venue's own --accent-deep - the version of the
   accent chosen to pass contrast as text on a light ground - and closed in
   the muted teal, so the difference is legible without relying on colour:
   the words say it either way. */
.status {
  display: flex;
  align-items: center;
  gap: 9px;
  margin: 12px 0 0;
  font-family: var(--display);
  font-size: .8rem;
  font-weight: 600;
  letter-spacing: .02em;
  color: var(--teal-mute);
}

.status:empty {
  display: none;
}

.status.is-open {
  color: var(--accent-deep);
}

.status-dot {
  flex: none;
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: currentColor;
  opacity: .55;
}

.status.is-open .status-dot {
  opacity: 1;
}

/* A slow ring on "open", so it reads as live rather than printed. Motion
   only where it is welcome: with reduced motion the dot simply stays still,
   which is a complete state rather than a half-finished one. */
@media (prefers-reduced-motion: no-preference) {
  .status.is-open .status-dot {
    animation: status-pulse 2.4s ease-out infinite;
  }
}

@keyframes status-pulse {
  0% { box-shadow: 0 0 0 0 color-mix(in srgb, currentColor 45%, transparent); }
  70%, 100% { box-shadow: 0 0 0 7px transparent; }
}

.h-days {
  font-family: var(--display);
  font-weight: 600;
  font-size: .9rem;
  letter-spacing: .02em;
}

.h-time {
  text-align: right;
  font-size: .96rem;
}

.h-time em {
  display: block;
  font-style: normal;
  font-size: .78rem;
  color: var(--teal-mute);
}

.hours-missing {
  color: var(--teal-mute);
  max-width: 44ch;
}

.hours-missing a {
  color: var(--accent-deep);
  border-bottom: 1px solid currentColor;
}

.venue-member {
  margin-top: 34px;
  padding: 24px 26px;
  background: rgba(255, 255, 255, .6);
  border-left: 3px solid var(--accent);
}

.venue-member .big-figure.sm {
  line-height: 1;
}

.venue-member p:last-child {
  color: var(--teal-mute);
  max-width: 34ch;
  margin: 6px 0 0;
}

.venue-member a {
  color: var(--accent-deep);
  border-bottom: 1px solid currentColor;
}

/* ---------- Next restaurant ---------- */
.venue-next {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 54vh;
  overflow: hidden;
  background: var(--teal-deep);
  color: #fff;
  text-align: center;
}

.venue-next img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  opacity: .5;
  transition: opacity .6s var(--ease);
}

.venue-next:hover img {
  opacity: .68;
}

.venue-next::after {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(180deg, rgba(20, 42, 50, .7), rgba(20, 42, 50, .8));
}

.venue-next .wrap {
  position: relative;
  z-index: 1;
}

.venue-next .label {
  color: var(--accent-light);
}

.venue-next-name {
  display: block;
  font-family: var(--display);
  font-weight: 700;
  font-size: clamp(2.4rem, 6vw, 4.4rem);
  line-height: 1;
  letter-spacing: -.035em;
}

.venue-next-area {
  display: block;
  margin-top: 14px;
  font-family: var(--display);
  font-size: .78rem;
  font-weight: 600;
  letter-spacing: .18em;
  text-transform: uppercase;
  color: rgba(255, 255, 255, .72);
}

.venue-next-area span {
  display: inline-block;
  transition: transform .25s var(--ease);
}

.venue-next:hover .venue-next-area span {
  transform: translateX(7px);
}

/* ============================================================
   LIGHTBOX
   ============================================================ */
/* The `hidden` attribute must be restored explicitly here. The browser's
   default `[hidden] { display: none }` comes from the UA stylesheet, and ANY
   author rule setting `display` beats it — so `display: flex` below left the
   closed lightbox laid over the whole viewport at z-index 300, invisible at
   opacity 0 but still swallowing every click on the page. That broke the
   header, the booking buttons and the next-restaurant link on all five
   restaurant pages, which are the only pages that build a lightbox.
   Any hidden-by-default element that sets its own `display` needs this. */
.lightbox[hidden] {
  display: none;
}

.lightbox {
  position: fixed;
  inset: 0;
  z-index: 300;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 5vh 6vw;
  background: rgba(11, 24, 29, .96);
  opacity: 0;
  transition: opacity .25s var(--ease);
}

.lightbox.on {
  opacity: 1;
}

/* A stage of fixed size, so the frame never changes between images.
   Without it the <img> collapsed the instant its src was swapped and
   snapped back when the next file decoded — with the dialog sized by its
   contents, that resized the whole thing on every click. The stage holds
   the size; the picture inside it is what changes. */
.lb-stage {
  width: 100%;
  height: 90svh;
  display: flex;
  align-items: center;
  justify-content: center;
}

.lightbox img {
  max-width: 100%;
  max-height: 100%;
  width: auto;
  height: auto;
  object-fit: contain;
  /* Faded in once decoded, so a swap is a crossfade rather than a blank
     frame. Under reduced motion the blanket rule makes this instant,
     which is the correct degradation — there is no half-finished state. */
  opacity: 0;
  transition: opacity .22s var(--ease);
}

.lightbox img.ready {
  opacity: 1;
}

.lightbox button {
  position: absolute;
  background: none;
  border: 0;
  color: #fff;
  cursor: pointer;
  font-size: 2.4rem;
  line-height: 1;
  padding: 14px 20px;
  opacity: .75;
  transition: opacity .2s var(--ease);
}

.lightbox button:hover {
  opacity: 1;
}

.lb-close {
  top: 10px;
  right: 12px;
  font-size: 2.8rem;
}

.lb-prev {
  left: 4px;
  top: 50%;
  transform: translateY(-50%);
}

.lb-next {
  right: 4px;
  top: 50%;
  transform: translateY(-50%);
}

.lb-count {
  position: absolute;
  bottom: 18px;
  left: 0;
  right: 0;
  text-align: center;
  font-family: var(--display);
  font-size: .76rem;
  letter-spacing: .18em;
  color: rgba(255, 255, 255, .6);
  margin: 0;
}

/* ============================================================
   PEOPLE
   ============================================================ */
.people {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 28px;
}

@media (max-width: 800px) {
  .people {
    grid-template-columns: 1fr;
    max-width: 460px;
  }
}

.person {
}

.person .p-media {
  overflow: hidden;
  margin-bottom: 20px;
}

.person img {
  width: 100%;
  aspect-ratio: 4 / 5;
  object-fit: cover;
  transition: transform .8s var(--ease);
}

.person:hover img {
  transform: scale(1.04);
}

.person h3 {
  margin-bottom: 2px;
  font-size: 1.25rem;
}

.person .role {
  font-family: var(--display);
  font-size: .72rem;
  font-weight: 600;
  letter-spacing: .18em;
  text-transform: uppercase;
  color: var(--teal-mute);
}

.letter {
  max-width: 68ch;
}

.letter p {
  font-size: 1.08rem;
}

/* The opening paragraph of the founder's letter, set larger and in the
   serif — it is the one piece of first-person writing on the site. */
.letter p:first-of-type {
  font-family: var(--serif);
  font-size: 1.32rem;
  line-height: 1.55;
}

.sign {
  font-family: var(--display);
  font-weight: 700;
  font-size: 1.3rem;
  margin-top: 34px;
  letter-spacing: -.02em;
}

.sign small {
  display: block;
  font-size: .72rem;
  font-weight: 600;
  letter-spacing: .18em;
  text-transform: uppercase;
  color: var(--teal-mute);
  margin-top: 6px;
}

/* ============================================================
   TIMELINE — Our Story
   ============================================================ */
.timeline {
  position: relative;
  display: grid;
  grid-template-columns: repeat(5, 1fr);
  gap: 0;
  margin-top: 20px;
  /* The reset at the top of this file only covers `ul`, so this <ol> kept
     its native numbering and indent — rendering "1. 01", "2. 02" and so on
     against the 01-05 labels in the markup. It stays an <ol> because the
     order is the meaning of the component. */
  list-style: none;
  padding: 0;
}

@media (max-width: 900px) {
  .timeline {
    grid-template-columns: 1fr;
  }
}

.tl-item {
  position: relative;
  padding: 30px 26px 30px 0;
  border-top: 1px solid var(--line-dark);
}

/* The lit rule, drawn over the items' own top borders and filled as the
   section travels up the viewport. --tl-progress comes from the single
   scroll pipeline in main.js.

   IT DEFAULTS TO 1. With no JS, or under reduced motion, the line is simply
   there and fully drawn. A default of 0 would leave the timeline looking
   half-built on exactly the browsers least able to finish it — the same
   rule the reveal styles follow. */
.timeline::after {
  content: "";
  position: absolute;
  left: 0;
  top: 0;
  width: 100%;
  height: 2px;
  background: var(--peach);
  transform: scaleX(var(--tl-progress, 1));
  transform-origin: left center;
}

@media (max-width: 900px) {
  /* One column below this width, so the rule runs DOWN through the markers
     rather than across them. 4px puts a 2px line through the centre of a
     9px dot sitting at left: 0. */
  .timeline::after {
    left: 4px;
    top: 0;
    width: 2px;
    height: 100%;
    transform: scaleY(var(--tl-progress, 1));
    transform-origin: center top;
  }

  /* The rule now runs down the left edge, so the text steps in to clear
     it. Without this the line was drawn straight through the first letter
     of every number, name and year. */
  .tl-item {
    padding: 30px 0 30px 28px;
  }
}

/* The marker sits on the rule, one per venue, in that venue's colour, and
   comes up to full strength as the line reaches it.

   Five items, so each is passed at --i/5 of the way along. opacity clamps
   to 0-1 by itself, which is why there is no clamp() here: item 4 sits at
   -4 until the line is four fifths along, and 1 shortly after. Both
   custom properties inherit — --tl-progress from .timeline, --i from the
   item's own style attribute. */
.tl-item::before {
  content: "";
  position: absolute;
  left: 0;
  top: -5px;
  width: 9px;
  height: 9px;
  border-radius: 50%;
  background: var(--accent-light);
  opacity: calc(var(--tl-progress, 1) * 5 - var(--i));
}

/* The restaurant name leads and the year is a quiet footnote beneath it.
   It used to be the other way round, with the year set large — which only
   worked while every venue had one. Four of the five do not publish an
   opening year, so the component now reads correctly with the date line
   absent. */
.tl-no {
  display: block;
  font-family: var(--display);
  font-weight: 700;
  font-size: .72rem;
  letter-spacing: .18em;
  color: var(--on-dark-mute);
  margin-bottom: 10px;
}

.tl-name {
  display: block;
  font-family: var(--display);
  font-weight: 700;
  font-size: 1.35rem;
  line-height: 1.15;
  letter-spacing: -.02em;
  color: #fff;
}

.tl-item a:hover .tl-name {
  color: var(--accent-light);
}

.tl-year {
  display: block;
  font-family: var(--display);
  font-size: .72rem;
  font-weight: 600;
  letter-spacing: .16em;
  text-transform: uppercase;
  color: var(--accent-light);
  margin-top: 10px;
}

.tl-area {
  display: block;
  font-size: .86rem;
  color: var(--on-dark-mute);
  margin-top: 2px;
}

/* ============================================================
   SERVICES (catering)
   ============================================================ */
.service {
  display: grid;
  grid-template-columns: 90px 1fr 210px;
  gap: 34px;
  padding: 46px 0;
  border-top: 1px solid var(--line);
  align-items: start;
}

.service:last-of-type {
  border-bottom: 1px solid var(--line);
}

.service .n {
  font-family: var(--display);
  font-weight: 700;
  font-size: 1rem;
  color: var(--teal-mute);
}

.service p {
  color: var(--teal-mute);
  max-width: 62ch;
  margin-bottom: 0;
}

@media (max-width: 820px) {
  .service {
    grid-template-columns: 1fr;
    gap: 16px;
  }
}

/* ============================================================
   TESTIMONIALS
   Five now, one per restaurant, so the grid is three-then-two rather
   than the old fixed pair.
   ============================================================ */
.quotes {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(290px, 1fr));
  /* The row gap has to clear the coloured rule that tops each quote, or it
     sits immediately under the previous quote's credit and reads as a
     divider between them rather than a heading for the next one. */
  gap: 40px 40px;
}

/* Credits sit under their quote, not pinned to the card foot: with five
   quotes of very different lengths, pinning opened a chasm between a short
   quote and its name. The coloured top rule already aligns the row. */
.quote {
  margin: 0;
  padding: 20px 0 0;
  /* The rule above each quote is that restaurant's own colour — the same
     cue used on the homepage rows and the venue pages. */
  border-top: 3px solid var(--accent-light);
  display: flex;
  flex-direction: column;
}

.quote .stars {
  color: var(--peach);
  letter-spacing: .2em;
  font-size: .78rem;
  margin: 0 0 12px;
}

.quote blockquote {
  margin: 0 0 14px;
  font-family: var(--serif);
  font-weight: 400;
  /* This band is 221 words — over a third of the homepage's total — and at
     1.16rem the three columns were only ~38 characters a line, which turns
     five ordinary reviews into five dense slabs. A touch smaller widens the
     measure rather than narrowing it, because the column width is fixed. */
  font-size: 1.08rem;
  line-height: 1.45;
  color: #fff;
  letter-spacing: -.01em;
}

.quote figcaption {
  font-size: .8rem;
  color: var(--on-dark-mute);
}

.q-venue {
  display: block;
  font-family: var(--display);
  font-size: .72rem;
  font-weight: 600;
  letter-spacing: .18em;
  text-transform: uppercase;
  color: var(--peach);
  margin-bottom: 5px;
}

.q-venue:hover {
  color: #fff;
}

/* The source is a link to the review it was taken from: the claim is
   checkable rather than asserted. */
.q-src a {
  color: rgba(255, 255, 255, .75);
  border-bottom: 1px solid rgba(255, 255, 255, .3);
}

.q-src a:hover {
  color: #fff;
}

/* ---------- Reviews: the group's rating beside one quote ----------
   Adapted from the Bombil site. Mobile-first: stacked, and the five quotes
   stay the swipe rail they already were. Only with room does the rating sit
   beside them and initReviews show one at a time. */
.reviews {
  display: grid;
  /* minmax(0, ...), not the default track. A grid track will not shrink
     below its item's minimum content width, and on a phone that item holds
     the five-card swipe rail - so the column stretched to fit the rail and
     the whole homepage scrolled sideways (537px on a 390px screen, which
     also pushed the sticky Join button off the edge). The desktop columns
     below are guarded the same way. */
  grid-template-columns: minmax(0, 1fr);
  gap: 40px;
}

/* .big-figure supplies the type - the same face, size and peach as the
   membership's 10%, so the homepage's two headline numbers read as a pair. */
.reviews-stars {
  display: inline-block;
  margin: 14px 0 0;
  font-size: 1.35rem;
  letter-spacing: .12em;
  /* Filled to --fill (94% of the row for 4.7) rather than five solid stars.
     The gradient runs through the glyphs via background-clip, so the unfilled
     part of the last star stays faint. It is approximate, not exact: the row's
     width includes the letter-spacing after the last star, which pushes the
     edge to about 80% through it rather than 70%. Exact would mean a separate
     element per star, which is not worth it for a row that is aria-hidden
     while the words beside it state the figure precisely. */
  background: linear-gradient(90deg,
      var(--peach) var(--fill, 100%),
      rgba(255, 255, 255, .22) var(--fill, 100%));
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}

.reviews-from {
  margin: 12px 0 0;
  max-width: 24ch;
  font-size: .92rem;
  line-height: 1.5;
  color: var(--on-dark-mute);
}

/* Built by initReviews; only shown where it can rotate. */
.reviews-tabs {
  display: none;
}

.reviews-tabs button {
  font-family: var(--display);
  font-size: .72rem;
  font-weight: 600;
  letter-spacing: .14em;
  text-transform: uppercase;
  padding: 9px 14px;
  border: 1px solid var(--line-dark);
  background: transparent;
  color: rgba(255, 255, 255, .72);
  cursor: pointer;
  transition: color .2s var(--ease), border-color .2s var(--ease), background .2s var(--ease);
}

.reviews-tabs button:hover {
  color: #fff;
  border-color: var(--accent-light);
}

/* The chosen restaurant takes its own colour, the same accent as the rule
   above its quote - a colour means the same restaurant everywhere. */
.reviews-tabs button[aria-pressed="true"] {
  color: var(--teal-deep);
  background: var(--accent-light);
  border-color: var(--accent-light);
}

@media (min-width: 901px) {
  .reviews {
    grid-template-columns: minmax(0, 5fr) minmax(0, 7fr);
    gap: 72px;
    align-items: start;
  }

  .is-rotating .quotes {
    grid-template-columns: 1fr;
    gap: 0;
  }

  /* All five share one grid cell, so the band is always the height of the
     longest and the page never jumps as a short quote replaces a long one.
     visibility follows opacity out, so a hidden quote's links cannot be
     tabbed into. */
  .is-rotating .quote {
    grid-area: 1 / 1;
    opacity: 0;
    visibility: hidden;
    transition: opacity .5s var(--ease), visibility 0s linear .5s;
  }

  .is-rotating .quote.is-active {
    opacity: 1;
    visibility: visible;
    transition: opacity .5s var(--ease);
  }

  /* One quote has the width three used to share, so it can be set larger. */
  .is-rotating .quote blockquote {
    font-size: clamp(1.25rem, 1.7vw, 1.55rem);
    line-height: 1.4;
  }

  /* Above the quote, as a tab bar sits above what it selects. Below it they
     floated: the shared cell is as tall as the LONGEST quote, so a short one
     left a gap between itself and the controls. Up here that spare height
     falls at the foot of the band, where it reads as ordinary padding. */
  .reviews-tabs {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
    margin-bottom: 30px;
  }
}

@media (prefers-reduced-motion: reduce) {
  .is-rotating .quote,
  .is-rotating .quote.is-active {
    transition: none;
  }
}

/* ---------- Signature dishes ----------
   Four across, then two; below 900px the shared .rail takes over as a
   swipe. That rule is LATER in this file, which is what lets its
   display:flex replace this grid - keep these above it. */
.dishes {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 24px;
  list-style: none;
  margin: 0;
  padding: 0;
}

@media (max-width: 1040px) {
  .dishes {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

/* The still and the clip share one box; the clip sits on top, invisible
   until it is genuinely playing, so a declined or slow play() never shows
   a blank or a spinner - just the photograph. */
.dish-media {
  position: relative;
  aspect-ratio: 4 / 5;
  overflow: hidden;
  background: var(--line);
}

.dish-media img,
.dish-media video {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.dish-media video {
  opacity: 0;
  transition: opacity .35s var(--ease);
}

.dish.is-playing .dish-media video {
  opacity: 1;
}

.dish-name {
  margin: 14px 0 4px;
  font-size: 1.05rem;
}

.dish-note {
  margin: 0;
  font-size: .9rem;
  line-height: 1.5;
  color: var(--teal-mute);
}

/* ---------- Awards ---------- */
.venue-awards {
  margin: 40px 0 0;
}

.venue-awards li {
  padding: 18px 0 18px 22px;
  border-left: 3px solid var(--accent);
  max-width: 46ch;
}

.aw-title {
  display: block;
  font-family: var(--display);
  font-weight: 700;
  font-size: 1.05rem;
  letter-spacing: -.01em;
}

.aw-body {
  display: block;
  font-family: var(--display);
  font-size: .72rem;
  font-weight: 600;
  letter-spacing: .16em;
  text-transform: uppercase;
  color: var(--accent-deep);
  margin-top: 6px;
}

.aw-body a {
  border-bottom: 1px solid currentColor;
  padding-bottom: 1px;
}

.aw-detail {
  display: block;
  font-size: .92rem;
  color: var(--teal-mute);
  margin-top: 8px;
}

/* The same recognition, compressed to a single line on the homepage row. */
.r-award {
  font-family: var(--display);
  font-size: .72rem;
  font-weight: 600;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--accent-deep);
  margin: -6px 0 14px;
}

/* ---------- The venue's own review, on its page ---------- */
/* ============================================================
   VENUE REVIEWS

   Replaces the single inline review that used to sit in the prose
   column. Up to four per restaurant now, so they need their own band
   — four stacked inside a narrow prose column would bury the story.

   auto-fit, not a fixed column count: the number of quotes varies by
   venue on purpose (see the note in _data/restaurants.js about not
   padding to four), and a restaurant with one review worth printing
   has to look deliberate rather than like a broken four-up grid.
   ============================================================ */
.vr-agg {
  margin: 0;
  font-size: .9rem;
  color: var(--teal-mute);
}

.vr-score {
  font-family: var(--display);
  font-size: 1.5rem;
  font-weight: 700;
  /* --accent-deep, not --accent: the base accents are mid-tones and fail
     AA as type on the light ground. Same rule as everywhere else here. */
  color: var(--accent-deep);
  margin-right: 8px;
}

.vr-count a {
  color: var(--accent-deep);
  border-bottom: 1px solid currentColor;
}

.vr-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(258px, 1fr));
  gap: 44px 40px;
}

.vr-item {
  margin: 0;
  padding: 24px 0 0;
  /* The venue's own colour, same cue as the homepage quotes. Safe as a
     rule even though it is not safe as type. */
  border-top: 3px solid var(--accent);
  display: flex;
  flex-direction: column;
}

.vr-item .stars {
  color: var(--accent-deep);
  letter-spacing: .2em;
  font-size: .78rem;
  margin: 0 0 12px;
}

.vr-item blockquote {
  margin: 0 0 16px;
  font-family: var(--serif);
  font-size: 1.12rem;
  line-height: 1.45;
  color: var(--teal);
  /* Push the credit down so credits line up across a row of quotes of
     different lengths — the same device the homepage band uses. */
  flex: 1;
}

.vr-item figcaption {
  font-size: .82rem;
  color: var(--teal-mute);
}

.vr-who {
  display: block;
  font-family: var(--display);
  font-size: .72rem;
  font-weight: 600;
  letter-spacing: .18em;
  text-transform: uppercase;
  color: var(--accent-deep);
  margin-bottom: 5px;
}

.vr-item figcaption a {
  color: inherit;
  border-bottom: 1px solid var(--line);
}

.vr-item figcaption a:hover {
  color: var(--accent-deep);
  border-bottom-color: currentColor;
}

/* ============================================================
   FORMS
   ============================================================ */
.form-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 76px;
  align-items: start;
}

@media (max-width: 900px) {
  .form-grid {
    grid-template-columns: 1fr;
    gap: 44px;
  }
}

.form-grid>div>img {
  width: 100%;
  aspect-ratio: 3 / 4;
  object-fit: cover;
}

.pair {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 18px;
}

@media (max-width: 520px) {
  .pair {
    grid-template-columns: 1fr;
  }
}

.field {
  margin-bottom: 20px;
}

.field label {
  display: block;
  font-size: .82rem;
  color: var(--teal-mute);
  margin-bottom: 7px;
}

.field input,
.field select,
.field textarea {
  width: 100%;
  padding: 14px 16px;
  font-family: var(--body);
  font-size: 16px;
  border: 1px solid var(--line);
  background: var(--surface);
  color: var(--teal);
  transition: border-color .2s var(--ease);
}

.field input:focus,
.field select:focus,
.field textarea:focus {
  border-color: var(--teal);
}

.field textarea {
  min-height: 140px;
  resize: vertical;
}

:focus-visible {
  outline: 2px solid var(--teal);
  outline-offset: 2px;
}

.band-dark :focus-visible,
.band-teal :focus-visible,
.hero :focus-visible,
.venue-hero :focus-visible,
.poster :focus-visible,
.venue-bar :focus-visible,
.page-head :focus-visible,
.site-header :focus-visible,
.site-footer :focus-visible {
  outline-color: var(--peach);
}

.form-note {
  font-size: .85rem;
  color: var(--teal-mute);
  margin-top: 10px;
}

.form-note.is-warning {
  color: var(--teal);
  font-weight: 500;
}

/* The enquiry forms' honeypot field. Moved off-screen rather than hidden
   with display:none, which some bots check for and skip. The markup also
   takes it out of the tab order and the accessibility tree, so no person
   can reach it -- unlike .sr-only, which exists to be read aloud. */
.form-trap {
  position: absolute;
  left: -10000px;
  width: 1px;
  height: 1px;
  overflow: hidden;
}

.notice {
  display: flex;
  align-items: center;
  gap: 16px;
  border: 1px solid var(--line);
  background: var(--surface);
  padding: 30px 34px;
}

.notice .dot {
  width: 9px;
  height: 9px;
  border-radius: 50%;
  background: var(--peach);
  flex: none;
}

.notice p {
  margin: 0;
}

/* Contact page — the five venues as a compact reference block. */
/* Five restaurants, so the column count is stated rather than left to
   auto-fit. At --wrap (1200px less two 32px gutters = 1136px) a
   minmax(220px, 1fr) auto-fit resolves to four columns, which put Nutmeg,
   Nadu, Kal Dosa and Nutmeg Street Kitchen in a row and left Bombil alone
   underneath — the same odd-number problem the homepage showcase and the
   /restaurants index both avoid by not using a grid at all.

   The steps go 5 -> 3 and deliberately skip 4: with five items, four
   columns is the one count that always strands a single card. Three gives
   3+2, which reads as intended rather than as a mistake.

   Below 900px there is no grid at all -- .rail takes over, the same swipe
   the homepage restaurant cards and quotes use, so five addresses do not
   become five screens of scrolling. That rule is later in this file and
   sets display:flex, which is why nothing here needs to undo the grid. */
.contact-venues {
  display: grid;
  grid-template-columns: repeat(5, 1fr);
  gap: 24px;
}

@media (max-width: 1040px) {
  .contact-venues {
    grid-template-columns: repeat(3, 1fr);
    gap: 30px;
  }
}

.cv {
  border-top: 2px solid var(--accent);
  padding-top: 18px;
}

.cv h3 {
  font-size: 1.15rem;
  margin-bottom: 8px;
}

.cv h3 a:hover {
  color: var(--accent-deep);
}

.cv address {
  font-size: .92rem;
  color: var(--teal-mute);
  line-height: 1.6;
}

.cv .cv-tel {
  display: inline-block;
  margin-top: 10px;
  font-family: var(--display);
  font-weight: 600;
  font-size: .86rem;
  color: var(--accent-deep);
}

/* ============================================================
   WHAT'S ON — /whats-on/ and the homepage band

   Built on the same list device as /menus/ and /book/ rather than a
   fourth layout. Deliberately the plainest of the three: an offer is
   a fact with a date on it, and dressing it up as editorial makes a
   two-week promotion look like a permanent part of the site.
   ============================================================ */
.wo-list {
  display: grid;
  gap: 24px;
}

.wo {
  display: grid;
  grid-template-columns: 300px 1fr;
  gap: 32px;
  align-items: center;
  background: var(--surface);
  border-left: 3px solid var(--accent);
}

.wo-media {
  align-self: stretch;
  overflow: hidden;
}

.wo-media img {
  width: 100%;
  height: 100%;
  min-height: 200px;
  object-fit: cover;
}

.wo-body {
  padding: 26px 32px 26px 0;
}

/* No media column means the body should not keep the gutter it left. */
.wo:not(:has(.wo-media)) {
  grid-template-columns: 1fr;
}

.wo:not(:has(.wo-media)) .wo-body {
  padding: 26px 32px;
}

.wo-meta {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
  font-family: var(--display);
  font-size: .74rem;
  font-weight: 600;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--teal-mute);
  margin-bottom: 10px;
}

/* "On now" earns colour; "Coming up" does not — a thing happening today
   should not look the same as a thing happening in six weeks. */
.wo-status {
  padding: 4px 10px;
  font-size: .68rem;
  letter-spacing: .12em;
}

.wo-status-on {
  background: var(--accent-deep);
  color: #fff;
}

.wo-status-upcoming {
  border: 1px solid var(--line);
  color: var(--teal-mute);
}

.wo-body h2 {
  font-size: 1.6rem;
  margin-bottom: 8px;
}

.wo-blurb {
  color: var(--teal-mute);
  max-width: 54ch;
  margin-bottom: 20px;
}

/* ---------- Empty state ---------- */
.wo-empty {
  max-width: 54ch;
}

.wo-empty-links {
  display: flex;
  flex-wrap: wrap;
  gap: 10px 18px;
  margin: 22px 0 0;
}

.wo-empty-links a {
  font-family: var(--display);
  font-size: .76rem;
  font-weight: 600;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--teal);
  border-bottom: 1px solid var(--line);
  padding: 12px 0;
}

.wo-empty-links a:hover {
  border-bottom-color: currentColor;
}

/* ---------- Archive ---------- */
.wo-past {
  margin-top: 56px;
  padding-top: 28px;
  border-top: 1px solid var(--line);
}

.wo-past ul {
  margin-top: 14px;
}

.wo-past li {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 6px 14px;
  padding: 10px 0;
  border-bottom: 1px solid var(--line);
  color: var(--teal-mute);
  font-size: .92rem;
}

.wo-past-when {
  font-family: var(--display);
  font-size: .74rem;
  font-weight: 600;
  letter-spacing: .1em;
  text-transform: uppercase;
  min-width: 13ch;
}

.wo-past-tag {
  margin-left: auto;
  font-family: var(--display);
  font-size: .68rem;
  font-weight: 600;
  letter-spacing: .12em;
  text-transform: uppercase;
  color: var(--teal-mute);
  opacity: .7;
}

/* ---------- Homepage band ---------- */
.wo-rail {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 26px;
  margin-top: 40px;
}

.wo-card a {
  display: block;
}

.wo-card-media {
  display: block;
  overflow: hidden;
  margin-bottom: 14px;
}

.wo-card-media img {
  width: 100%;
  aspect-ratio: 4 / 3;
  object-fit: cover;
  transition: transform .5s var(--ease);
}

.wo-card a:hover .wo-card-media img {
  transform: scale(1.04);
}

.wo-card-meta {
  display: block;
  font-family: var(--display);
  font-size: .72rem;
  font-weight: 600;
  letter-spacing: .12em;
  text-transform: uppercase;
  color: var(--teal-mute);
  margin-bottom: 6px;
}

.wo-card-title {
  display: block;
  font-family: var(--display);
  font-weight: 700;
  font-size: 1.24rem;
  line-height: 1.2;
  color: var(--teal);
}

.wo-card a:hover .wo-card-title {
  color: var(--accent-deep);
}

@media (max-width: 900px) {
  .wo-rail {
    grid-template-columns: 1fr 1fr;
  }
}

@media (max-width: 820px) {
  .wo {
    grid-template-columns: 1fr;
    gap: 0;
  }

  .wo-media img {
    min-height: 190px;
    max-height: 230px;
  }

  .wo-body,
  .wo:not(:has(.wo-media)) .wo-body {
    padding: 22px 22px 26px;
  }

  .wo-past-tag {
    margin-left: 0;
  }
}

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

/* ============================================================
   MENUS PAGE — /menus/

   Built on the same list device as /book/, but the rows are taller and
   led by the photograph: choosing where to eat is a decision made on
   the food, and this is the one page where the picture is the argument.
   The row carries no opening times — someone here is choosing a
   kitchen, not planning an arrival.
   ============================================================ */
.menu-list {
  display: grid;
  gap: 28px;
}

.mn {
  display: grid;
  grid-template-columns: 340px 1fr;
  gap: 36px;
  align-items: center;
  background: var(--surface);
  border-left: 3px solid var(--accent);
}

.mn-media {
  display: block;
  align-self: stretch;
  overflow: hidden;
}

.mn-media img {
  width: 100%;
  height: 100%;
  min-height: 250px;
  object-fit: cover;
  transition: transform .5s var(--ease);
}

.mn:hover .mn-media img {
  transform: scale(1.04);
}

.mn-body {
  padding: 30px 36px 30px 0;
}

.mn-index {
  font-family: var(--display);
  font-size: .72rem;
  font-weight: 600;
  letter-spacing: .18em;
  text-transform: uppercase;
  color: var(--accent-deep);
  margin-bottom: 8px;
}

.mn-body h2 {
  font-size: 1.9rem;
  margin-bottom: 6px;
}

.mn-body h2 a:hover {
  color: var(--accent-deep);
}

.mn-cuisine {
  font-family: var(--display);
  font-size: .76rem;
  font-weight: 600;
  letter-spacing: .12em;
  text-transform: uppercase;
  color: var(--teal-mute);
  margin-bottom: 12px;
}

.mn-blurb {
  color: var(--teal-mute);
  max-width: 52ch;
  margin-bottom: 0;
}

.mn-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 14px 24px;
  margin-top: 26px;
}

.mn-visit,
.mn-allergens {
  font-family: var(--display);
  font-size: .76rem;
  font-weight: 600;
  letter-spacing: .12em;
  text-transform: uppercase;
  color: var(--accent-deep);
  border-bottom: 1px solid transparent;
  padding-bottom: 2px;
}

.mn-visit:hover,
.mn-allergens:hover {
  border-bottom-color: currentColor;
}

.mn-visit span {
  display: inline-block;
  transition: transform .25s var(--ease);
}

.mn-visit:hover span {
  transform: translateX(5px);
}

/* The "no menu published" case. Deliberately plain text with a phone
   number in it rather than anything that looks like a button — there is
   nothing here to press, and dressing it up as an action would be the
   empty link this page exists to avoid. */
.mn-none {
  flex: 1 1 100%;
  margin: 0;
  font-size: .94rem;
  color: var(--teal-mute);
}

.mn-none a {
  color: var(--accent-deep);
  border-bottom: 1px solid var(--line);
}

@media (max-width: 1080px) {
  .mn {
    grid-template-columns: 260px 1fr;
    gap: 28px;
  }
}

@media (max-width: 820px) {
  .mn {
    grid-template-columns: 1fr;
    gap: 0;
  }

  .mn-media img {
    min-height: 210px;
    max-height: 260px;
  }

  .mn-body {
    padding: 24px 24px 28px;
  }

  .mn-actions .btn {
    flex: 1 1 100%;
    text-align: center;
  }
}

/* ============================================================
   BOOKING PAGE — /book/

   A list rather than a grid, for the same reason /restaurants/ is:
   five items never sit neatly in a grid. Each row is a decision — the
   photograph to recognise it by, the address and hours to judge it on,
   and the booking link. Deliberately plainer than the poster bands:
   this page is a chooser, not a showcase, and someone on it has already
   been persuaded.
   ============================================================ */
.booking-list {
  display: grid;
  gap: 28px;
}

.bk {
  display: grid;
  grid-template-columns: 240px 1fr;
  gap: 32px;
  align-items: center;
  background: var(--surface);
  border-left: 3px solid var(--accent);
}

.bk-media {
  display: block;
  align-self: stretch;
  overflow: hidden;
}

.bk-media img {
  width: 100%;
  height: 100%;
  min-height: 210px;
  object-fit: cover;
  transition: transform .5s var(--ease);
}

.bk:hover .bk-media img {
  transform: scale(1.04);
}

/* Two columns on a wide row: who and where on the left, when on the
   right, actions across the foot. A single column left most of a
   1200px row empty next to a 40ch list of opening times. */
.bk-body {
  display: grid;
  grid-template-columns: minmax(0, 1.15fr) minmax(0, 1fr);
  align-items: start;
  column-gap: 44px;
  padding: 28px 32px 28px 0;
}

.bk-who,
.bk-when {
  min-width: 0;
}

.bk-when-head {
  display: flex;
  align-items: center;
  gap: 8px;
  font-family: var(--display);
  font-size: .72rem;
  font-weight: 600;
  letter-spacing: .16em;
  text-transform: uppercase;
  color: var(--teal-mute);
  margin-bottom: 10px;
}

.bk-when-head svg {
  width: 16px;
  height: 16px;
}

.bk-actions,
.bk-note {
  grid-column: 1 / -1;
}

.bk-index {
  font-family: var(--display);
  font-size: .72rem;
  font-weight: 600;
  letter-spacing: .18em;
  text-transform: uppercase;
  color: var(--accent-deep);
  margin-bottom: 8px;
}

.bk-body h2 {
  font-size: 1.7rem;
  margin-bottom: 6px;
}

.bk-body h2 a:hover {
  color: var(--accent-deep);
}

.bk-area {
  font-family: var(--display);
  font-size: .76rem;
  font-weight: 600;
  letter-spacing: .12em;
  text-transform: uppercase;
  color: var(--teal-mute);
  margin-bottom: 10px;
}

.bk-address {
  font-size: .94rem;
  color: var(--teal-mute);
  margin-bottom: 0;
}

.bk-hours {
  font-size: .88rem;
  color: var(--teal-mute);
}

.bk-hours li {
  display: flex;
  justify-content: space-between;
  gap: 18px;
  padding: 3px 0;
  border-bottom: 1px solid var(--line);
}

.bk-hours li:last-child {
  border-bottom: 0;
}

.bk-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 14px 22px;
  margin-top: 26px;
}

.bk-tel {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  font-family: var(--display);
  font-weight: 600;
  font-size: .86rem;
  color: var(--accent-deep);
}

.bk-tel:hover {
  color: var(--teal-deep);
}

.bk-tel svg {
  width: 17px;
  height: 17px;
}

.bk-note {
  font-size: .86rem;
  color: var(--teal-mute);
  margin: 14px 0 0;
}

@media (max-width: 1080px) {
  /* The two inner columns are the first thing to give: the hours table
     needs its full width more than the row needs to be short. */
  .bk-body {
    grid-template-columns: 1fr;
    row-gap: 20px;
  }
}

@media (max-width: 820px) {
  .bk {
    grid-template-columns: 1fr;
    gap: 0;
  }

  .bk-media img {
    min-height: 200px;
    max-height: 240px;
  }

  .bk-body {
    padding: 24px 24px 26px;
  }

  /* Full-width tap target, and a button that is never narrower than the
     restaurant name sitting above it. */
  .bk-actions .btn {
    flex: 1 1 100%;
    text-align: center;
  }
}

/* ============================================================
   CATERING — the practical facts and the enquiry form
   ============================================================ */

/* A definition list rather than prose: someone comparing venues is
   scanning for four answers, not reading a paragraph that contains them. */
.facts {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(230px, 1fr));
  gap: 30px;
  margin: 0;
}

.facts dt {
  font-family: var(--display);
  font-size: .74rem;
  font-weight: 600;
  letter-spacing: .14em;
  text-transform: uppercase;
  color: var(--accent-deep);
  padding-bottom: 10px;
  border-bottom: 2px solid var(--line);
  margin-bottom: 12px;
}

.facts dd {
  margin: 0;
  color: var(--teal-mute);
  font-size: .96rem;
}

.facts dd a {
  color: var(--teal);
  border-bottom: 1px solid var(--line);
  padding-bottom: 1px;
}

.facts dd a:hover {
  border-bottom-color: currentColor;
}

/* The enquiry form sits on the dark band, so the fields invert. Everything
   else about them — sizing, focus, the .pair grid — is shared with the
   contact form rather than restated. */
.enquiry-form {
  max-width: 760px;
}

.enquiry-form label {
  color: rgba(255, 255, 255, .82);
}

.enquiry-form .hint {
  font-family: var(--body);
  font-weight: 400;
  text-transform: none;
  letter-spacing: 0;
  font-size: .82rem;
  color: var(--on-dark-mute);
}

.enquiry-form input,
.enquiry-form select,
.enquiry-form textarea {
  background: rgba(255, 255, 255, .06);
  border-color: var(--line-dark);
  color: #fff;
  /* This form sits on a dark band, and the browser does not know that.
     Without it, the parts of a control the browser draws itself are still
     drawn for a light page: the date field's dd/mm/yyyy, its calendar
     button, and the dropdown list a <select> opens. Declaring the scheme
     is what makes those follow the form rather than fight it. */
  color-scheme: dark;
}

/* The options in a dropdown inherit the select's own colours, and this
   select's background is a translucent white over a dark band. In the popup
   that translucency composites against the list's own light backdrop, so
   the options were effectively white text on white -- "Type of event" and
   "Which restaurant" both opened to a list nobody could read. The popup is
   drawn by the browser, not the page, so it needs a solid colour of its own
   rather than the see-through one that works in place. */
.enquiry-form select option {
  background: var(--teal-deep);
  color: #fff;
}

/* The calendar button is a dark glyph by default, which on this background
   is invisible. Inverted rather than replaced so it stays the browser's own
   control. Not what was reported, but the same cause. */
.enquiry-form input[type="date"]::-webkit-calendar-picker-indicator {
  filter: invert(1);
  opacity: .75;
  cursor: pointer;
}

.enquiry-form input[type="date"]::-webkit-calendar-picker-indicator:hover {
  opacity: 1;
}

.enquiry-form textarea::placeholder {
  color: var(--on-dark-mute);
}

.enquiry-form input:focus,
.enquiry-form select:focus,
.enquiry-form textarea:focus {
  border-color: var(--peach);
}

/* The date and number pickers render their own controls in the browser's
   colours, which are black on a dark ground by default. */
.enquiry-form input[type="date"],
.enquiry-form input[type="number"] {
  color-scheme: dark;
}

/* A date input is four tab stops, not one: day, month, year, then the
   browser's own calendar button. The first three match :focus-visible and
   get the ring; the fourth lives in the browser's shadow DOM and does not,
   so the ring vanished while focus was still inside the field. Focus that
   disappears is worse than focus that is merely subtle.
   
   :focus-within holds the ring for all four. It also shows on a mouse
   click, which :focus-visible exists to avoid — an acceptable trade on one
   field, and the alternative is a keyboard user losing their place. */
.enquiry-form input[type="date"]:focus-within {
  outline: 2px solid var(--peach);
  outline-offset: 2px;
}

.enquiry-form .form-note {
  color: rgba(255, 255, 255, .6);
}

.enquiry-form .form-note a {
  color: var(--peach);
  border-bottom: 1px solid currentColor;
}

/* Confirmation. The warning variant already exists for the contact form;
   this is its counterpart, and the two must never look alike. On a light
   ground (the contact form) that is teal with a tick, since the warning is
   teal without one; the peach below is only legible on the dark band the
   catering form sits on. */
.form-note.is-ok {
  color: var(--teal);
  font-weight: 600;
}

.form-note.is-ok::before {
  content: "\2713\00a0";
}

.enquiry-form .form-note.is-ok {
  color: var(--peach-soft);
}

.enquiry-form button[disabled] {
  opacity: .6;
  cursor: progress;
}

/* The service rows gained a fourth entry and longer CTA labels; on a
   phone the button needs the full width rather than trailing the text. */
@media (max-width: 800px) {
  .service .btn {
    width: 100%;
    text-align: center;
  }
}

/* ============================================================
   FOOTER
   ============================================================ */
.site-footer {
  background: var(--teal-deep);
  color: #fff;
  padding: 84px 0 30px;
}

.foot-top {
  display: grid;
  grid-template-columns: 1.2fr 1.3fr .8fr;
  gap: 52px;
  padding-bottom: 52px;
  border-bottom: 1px solid var(--line-dark);
}

@media (max-width: 900px) {
  .foot-top {
    grid-template-columns: 1fr;
    gap: 44px;
  }
}

.foot-brand img {
  height: 34px;
  width: auto;
  margin-bottom: 18px;
}

.foot-brand p {
  color: rgba(255, 255, 255, .6);
  max-width: 34ch;
  font-size: .95rem;
}

.foot-head {
  font-family: var(--display);
  font-size: .7rem;
  font-weight: 600;
  letter-spacing: .2em;
  text-transform: uppercase;
  color: var(--peach);
  margin: 0 0 18px;
}

.foot-venues li {
  padding: 9px 0;
  border-bottom: 1px solid rgba(255, 255, 255, .08);
}

.foot-venues a {
  font-family: var(--display);
  font-weight: 600;
  font-size: .92rem;
  color: #fff;
}

.foot-venues a:hover {
  color: var(--peach);
}

.foot-venues span {
  display: block;
  font-size: .8rem;
  color: var(--on-dark-mute);
}

.foot-nav a {
  font-family: var(--display);
  font-size: .78rem;
  font-weight: 600;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: rgba(255, 255, 255, .65);
  display: block;
  padding: 6px 0;
}

.foot-nav a:hover {
  color: var(--peach);
}

.foot-bottom {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 18px;
  padding-top: 26px;
  font-size: .82rem;
  color: var(--on-dark-mute);
}

/* The legal links and the staff link all sit on the copyright line and read
   as one muted row — inherit the row colour rather than the link colour. */
.foot-bottom a {
  color: inherit;
}

.foot-bottom a:hover {
  color: var(--peach);
}

/* Only the staff link is pushed to the far end, away from the legal pair. */
.foot-staff-link {
  margin-left: auto;
}

/* ============================================================
   REVEAL
   Gated on .js, set in <head> before first paint. Without JavaScript
   none of this applies and the page renders as ordinary content.
   ============================================================ */
.js .reveal {
  opacity: 0;
  transform: translateY(22px);
  transition: opacity .75s var(--ease), transform .75s var(--ease);
  /* Declared here rather than per-component: the `transition` shorthand above
     resets transition-delay, so any delay set on a lower-specificity rule
     would be silently wiped. Elements opt in by setting --i. */
  transition-delay: calc(var(--i, 0) * 80ms);
}

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

/* ============================================================
   REDUCED MOTION
   Everything decorative stops. Note this cannot rely on the blanket
   `animation-duration: .01ms` trick alone: a marquee or a Ken Burns
   pass forced to finish instantly leaves the element parked at its
   END state, which for the marquee means the track sitting half
   off-screen. Those are switched off explicitly instead.
   ============================================================ */
@media (prefers-reduced-motion: reduce) {
  html {
    scroll-behavior: auto;
  }

  *,
  *::before,
  *::after {
    animation-duration: .01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .01ms !important;
  }

  .marquee-track,
  .hero-slide.on img,
  .kenburns img,
  .scroll-cue span,
  .js .rise-w>span {
    animation: none !important;
    transform: none !important;
  }

  .btn:hover {
    transform: none;
  }

  .js .wipe {
    clip-path: none;
  }
}

/* The sticky booking bar covers the foot of the viewport on restaurant
   pages, so the footer is padded clear of it there. */
.has-venue-bar .site-footer {
  padding-bottom: 110px;
}

/* ============================================================
   RAIL — a horizontal swipe list, phones and tablets.

   The restaurant showcase and the testimonials read well stacked on a wide
   screen and badly on a narrow one, where five items become most of the
   page's height for a fifth of its words.

   One class does both. Cards sit at 80% width so the next is visibly cut
   off: that peek is the only affordance, which is why the band bleeds to
   the screen edge and the scrollbar is hidden. The breakpoint matches where
   .restaurant-row drops to one column — that collapse is what makes the
   stack tall.
   ============================================================ */
@media (max-width: 900px) {
  .rail {
    display: flex;
    flex-direction: row;
    overflow-x: auto;
    overscroll-behavior-x: contain;
    -webkit-overflow-scrolling: touch;
    scroll-snap-type: x mandatory;
    gap: 14px;
    /* Bleed past .wrap's 20px gutter so cards run to the screen edge,
       then put the gutter back as padding so the first card still lines
       up with the heading above it. */
    margin: 0 calc(var(--gutter) * -1);
    padding: 0 var(--gutter) 4px;
    /* Snap positions sit at each card's leading edge, so with mandatory
       snapping the browser immediately scrolls the gutter away on load and
       the first card lands flush against the screen edge. scroll-padding
       moves the snap line inboard by the same amount, which is the whole
       reason the property exists. */
    scroll-padding: 0 var(--gutter);
    scrollbar-width: none;
  }

  .rail::-webkit-scrollbar {
    display: none;
  }

  /* 80% leaves a slice of the next card showing, which is the only thing
     telling anyone there is more to swipe to. The cap stops a card from
     growing to 600px-odd on a tablet, where two-up reads better than one
     enormous card and a sliver. */
  .rail > * {
    flex: 0 0 80%;
    max-width: 420px;
    scroll-snap-align: start;
  }

  /* Reveal and wipe observe the viewport, which a card parked off to the
     right never intersects — left alone, cards two onwards sit at opacity 0
     with their images clipped to nothing. The rail reveals; its contents
     arrive with it. */
  .js .rail > .reveal {
    opacity: 1;
    transform: none;
    transition-delay: 0s;
  }

  .js .rail .wipe {
    clip-path: none;
    transition: none;
  }

  /* ---------- Showcase rows as cards ----------

     A row and a card are not the same object. Stacked full width, the row
     can be as tall as its copy needs and its four actions sit comfortably
     in a line. Cut to 80% of a phone, laid side by side in a snap rail,
     both of those stop being true: blurbs of different lengths put every
     card's actions at a different height, and roughly 380px of buttons in
     a 300px card simply ran off the edge with the Instagram disc squashed
     out of round at the end of it. Swiping through five of those is the
     disorder — nothing lines up between one card and the next.

     So the card is built as a card. Image, a fixed allowance of copy, then
     the actions on the floor of every card at the same height, because the
     rail already stretches them all to match the tallest. */
  .rail .restaurant-row {
    grid-template-columns: 1fr;
    grid-template-rows: auto 1fr;
    align-content: stretch;
    gap: 14px;
    padding: 0;
    border: 0;
  }

  /* Rows are stated rather than auto-placed. The alternating showcase
     flips every second row's image to the other side with `order`, and
     `order` still decides auto-placement here — so on those rows the copy
     took the auto row and the picture took the 1fr one, which put the
     image underneath the text on two of the five cards and left their
     actions floating in the middle. A card is a card whichever row it
     came from; the flip is a device for the full-width list only. */
  .rail .r-media,
  .rail .restaurant-row.flip .r-media {
    grid-row: 1;
    order: 0;
  }

  .rail .restaurant-row > div {
    grid-row: 2;
    display: flex;
    flex-direction: column;
  }

  /* The rules that separated stacked rows have no job between cards that
     are already separated by a gap. */
  .restaurant-list.rail .restaurant-row:last-child {
    border-bottom: 0;
  }

  /* Three lines for every card, whatever the blurb's length. The clamp is
     what makes the cards comparable at a glance; the full text is one tap
     away on the restaurant's own page, which is what the card is for. */
  .rail .restaurant-row p:not([class]) {
    margin-bottom: 18px;
    display: -webkit-box;
    -webkit-box-orient: vertical;
    -webkit-line-clamp: 3;
    line-clamp: 3;
    overflow: hidden;
  }

  /* One line, and it stops competing with the name directly under it. */
  .rail .r-award {
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
  }

  /* Pushed to the foot of the card, so the actions form one straight line
     across the whole rail instead of stepping up and down with the copy. */
  .rail .restaurant-row .foot {
    margin-top: auto;
    display: grid;
    grid-template-columns: 1fr auto;
    align-items: center;
    gap: 14px 16px;
  }

  /* Booking is the card's one job, so it gets the full width and a real
     thumb target rather than a chip competing for room in a line. */
  .rail .restaurant-row .book {
    grid-row: 1;
    grid-column: 1 / -1;
    text-align: center;
    padding: 14px 18px;
  }

  /* Every position is stated rather than left to auto-placement: with a
     card this narrow the two text links have to stack, and auto-placement
     orders them by which rule happens to make a column definite, not by
     the order they are written in.

     Instagram keeps its disc and sits alongside both of them, centred, at
     44px — it was the item being pushed off the card edge, and at 38px it
     was under the touch target anyway. */
  /* "Explore" alone in a card: the name is the heading two lines above it,
     and spelling it out again is what made the longest of the five wrap.
     The link's accessible name still carries it. */
  .rail .restaurant-row .more {
    grid-row: 2;
    grid-column: 1;
    justify-self: start;
    white-space: nowrap;
  }

  .rail .restaurant-row .menu-link {
    grid-row: 3;
    grid-column: 1;
    justify-self: start;
  }

  .rail .restaurant-row .ig {
    grid-row: 2 / span 2;
    grid-column: 2;
    align-self: center;
    width: 44px;
    height: 44px;
  }

  /* ---------- Quotes as cards ---------- */
  .quotes.rail {
    gap: 14px;
  }

  .quotes.rail .quote {
    padding: 18px 0 0;
  }
}

/* ============================================================
   SMALL SCREENS
   ============================================================ */
@media (max-width: 640px) {
  section {
    padding: 56px 0;
  }

  :root {
    --gutter: 20px;
  }

  /* The stuck header is the one people actually live with — it is on
     screen for the whole scroll, where the tall unstuck state only ever
     sits over the hero. 70px of a 844px viewport is worth trimming. */
  .site-header.stuck {
    padding: 14px 0;
  }

  /* 56px of air between a section's heading and its content is a desktop
     interval; on a phone it is most of a thumb-swipe past nothing. */
  .head-row {
    margin-bottom: 26px;
  }

  /* 1.18rem over a 350px column is barely 30 characters a line, so three
     sentences become a six-line block and every lede reads as a wall.
     Dropping a little brings the measure back without shrinking the type
     to something anyone has to work at. */
  .lede {
    font-size: 1.04rem;
    line-height: 1.55;
  }

  .figures {
    padding: 48px 0;
  }

  .figure-row {
    gap: 16px;
  }

  .figure-row li {
    padding-top: 14px;
  }

  .member-points li {
    padding: 10px 0;
  }

  /* ---- Page heads, on a phone ----

     Two problems, one cause. The head is sized by its content here, so
     every page's was a different height: an h1 that wrapped to two lines
     on one page and one on the next, a lede of one sentence against three,
     a jump strip of five chips wrapping to four rows. Moving between nav
     items, the whole page shifted under the thumb — the thing the four
     inner heads were levelled for once already, undone by the pages built
     since.

     Worse, on /menus/ the content added up to more than the screen. The
     head filled the viewport with nothing below it, so the five chips in
     it looked like the menus themselves rather than a way into the list
     underneath that nobody could see was there.

     A fixed 480px fixes both. It is a pixel figure, not a viewport one, on
     purpose: svh varies with the handset and with whether the browser
     chrome is showing, which is exactly the drift being removed. Every
     head now ends in the same place on every page and every phone, and
     480px leaves the next section visibly under it on the shortest screen
     still in use. The rules below keep the content inside that budget. */
  .page-head {
    padding: 112px 0 56px;
  }

  .page-head.with-image {
    min-height: 480px;
  }

  /* 2.9rem is the h1 clamp's floor, which puts "Our restaurants" and
     "Offers and events" onto two lines and "Our menus" onto one. At 2.4rem
     all of them fit on one, so the heads stop differing by a whole line of
     display type. */
  .page-head h1 {
    font-size: 2.4rem;
  }

  /* ---- The footer, on a phone ----

     Three columns side by side is 592px on a desktop, two thirds of a
     screen. Stacked into one it was 1301px — nearly two full phone screens
     of scrolling after the page has finished, which is what made it feel
     like it would not end. Nothing is removed: the fourteen links and five
     addresses are exactly what someone who has scrolled this far is looking
     for. They are laid out for the width instead.

     The touch targets below stay at their full size. They are the reason
     the stacked footer is tall, but shrinking them back to 20px to save
     scroll would trade a real problem for a cosmetic one. */
  .site-footer {
    padding: 56px 0 24px;
  }

  .foot-top {
    gap: 32px;
    padding-bottom: 32px;
  }

  .foot-brand img {
    height: 28px;
    margin-bottom: 14px;
  }

  /* Fourteen short labels down a single column is 420px of footer for two
     words a row. Two columns halves it and reads better besides — this is
     a list to scan, not to read. */
  .foot-nav ul,
  .foot-venues ul {
    display: grid;
    grid-template-columns: 1fr 1fr;
    column-gap: 18px;
  }

  .foot-nav a {
    padding: 11px 0;
  }

  .foot-venues li {
    padding: 4px 0;
  }

  .foot-venues a {
    display: block;
    padding: 8px 0 2px;
  }

  /* Legal pair and staff link, all to a real touch size. The staff link
     drops its auto margin: in a row that wraps, "push to the far end" just
     strands it alone on a line of its own. */
  .foot-bottom {
    gap: 4px 18px;
    padding-top: 18px;
  }

  .foot-bottom a {
    padding: 10px 0;
  }

  .foot-staff-link {
    margin-left: 0;
  }

  /* The jump chips wrap to three or four rows at this width, which is most
     of the head spent on a list you can also see below. One swipeable row
     instead — the same device the showcase rail uses, bleeding to the
     screen edge so the cut-off chip is the affordance. */
  .venue-jump {
    flex-wrap: nowrap;
    overflow-x: auto;
    overscroll-behavior-x: contain;
    scroll-snap-type: x proximity;
    margin: 24px calc(var(--gutter) * -1) 0;
    padding: 0 var(--gutter) 2px;
    scroll-padding: 0 var(--gutter);
    scrollbar-width: none;
  }

  .venue-jump::-webkit-scrollbar {
    display: none;
  }

  .venue-jump li {
    flex: 0 0 auto;
    scroll-snap-align: start;
  }

  .hero .wrap {
    padding-bottom: 84px;
  }

  .venue-hero-inner {
    padding-top: 140px;
    padding-bottom: 96px;
  }

  /* Note: extra footer clearance for the sticky booking bar is handled by
     .has-venue-bar, which only the restaurant pages carry. Adding it here
     too would put dead space under every other page's footer. */
}

/* ============================================================
   PHONES
   Most visitors arrive on a handset, so these are corrections to the
   desktop layout rather than an afterthought.
   ============================================================ */
@media (max-width: 820px) {

  /* The hero scrims are left-anchored on desktop, which protects a copy
     column occupying the left third and leaves the photograph alone on the
     right. On a phone the copy spans the full width, so that wash covers
     nothing that matters. Both heroes switch to a full-width, bottom-weighted
     scrim instead — same job, different shape. */
  .hero::after {
    background: linear-gradient(180deg,
        rgba(20, 42, 50, .5) 0%,
        rgba(20, 42, 50, .34) 24%,
        rgba(20, 42, 50, .8) 56%,
        rgba(20, 42, 50, .96) 100%);
  }

  .venue-hero::after {
    background:
      linear-gradient(180deg,
        rgba(20, 42, 50, .5) 0%,
        rgba(20, 42, 50, .32) 22%,
        rgba(20, 42, 50, .82) 56%,
        rgba(20, 42, 50, .97) 100%),
      /* The venue tint moves to the foot of the image, where the name sits. */
      linear-gradient(180deg,
        transparent 45%,
        color-mix(in srgb, var(--accent) 42%, transparent) 100%);
  }

  /* Full-height heroes waste a phone screen: the fold arrives before any
     content does. Shorter, and the copy sits closer to the foot. */
  .hero,
  .venue-hero {
    min-height: 86svh;
  }

  .venue-eyebrow {
    gap: 6px 12px;
  }

  /* Same problem as the heroes: the angled wash was cut for a wide band and
     leaves the copy sitting on the photograph at this width. */
  .poster::after {
    background:
      linear-gradient(180deg,
        rgba(20, 42, 50, .45) 0%,
        rgba(20, 42, 50, .3) 28%,
        rgba(20, 42, 50, .86) 60%,
        rgba(20, 42, 50, .97) 100%),
      linear-gradient(180deg,
        transparent 48%,
        color-mix(in srgb, var(--accent) 42%, transparent) 100%);
  }
}

@media (max-width: 560px) {

  /* Two buttons of different widths, stacked, look like a mistake. Equal
     full-width blocks are also a far easier thumb target. */
  .hero-actions {
    gap: 10px;
  }

  .hero-actions .btn {
    flex: 1 1 100%;
    text-align: center;
  }

  /* The headline is the tallest thing on the page; give it back some room. */
  .hero .wrap {
    padding-bottom: 64px;
  }

  .venue-hero-inner {
    padding-bottom: 72px;
  }

  .venue-title {
    max-width: none;
  }

  /* Poster bands: the name was sized for a wide band and crowded the blurb. */
  .poster-name {
    font-size: clamp(2.2rem, 11vw, 3.2rem);
  }

  .poster-blurb {
    font-size: .96rem;
  }

  /* Section rhythm — top and bottom padding on every band adds up to a lot
     of scrolling past nothing on a small screen. */
  section {
    padding: 48px 0;
  }

  .figures {
    padding: 44px 0;
  }

  .figure-row {
    gap: 14px;
  }

  /* Three stacked figures at 3rem were 528px — most of a screen for three
     numbers and nine words. */
  .fig {
    font-size: 2.5rem;
  }

  /* The next-restaurant teaser is a full band for one line of text. */
  .venue-next {
    min-height: 42vh;
  }

  /* Quotes sat with a lot of air between them once stacked. */
  .quotes {
    gap: 34px;
  }

  /* Galleries: a single column of 4:3 crops is a long scroll. Two up reads
     as a contact sheet in the good sense at this size.

     Which image spans the full width depends on the count, so that no row
     is left half empty: three images lead with a wide one and pair the rest,
     four sit as an even 2x2, two sit side by side. */
  .gal-2,
  .gal-3,
  .gal-4 {
    grid-template-columns: repeat(2, 1fr);
    gap: 10px;
  }

  .gal-3 .gal-item:first-child {
    grid-column: 1 / -1;
  }

  .gal-3 .gal-item:first-child .gal-btn {
    aspect-ratio: 16 / 10;
  }

  /* Four is even on its own — the desktop lead image would strand the last
     one in a row by itself. */
  .gal-4 .gal-item:first-child {
    grid-column: auto;
  }

  .gal-4 .gal-item:first-child .gal-btn {
    aspect-ratio: 4 / 3;
  }

  /* Lightbox arrows were sitting under the thumb rather than beside it. */
  .lightbox {
    padding: 8vh 4vw;
  }

  .lb-prev,
  .lb-next {
    top: auto;
    bottom: 6px;
    transform: none;
    font-size: 2.6rem;
  }

  .lb-prev {
    left: 8px;
  }

  .lb-next {
    right: 8px;
  }
}

/* ============================================================
   LEGAL PAGES — membership terms, privacy notice

   Deliberately the plainest thing on the site. These pages exist to be
   read and quoted back at us, so they get a measure that suits reading,
   headings that step down rather than shout, and no motion at all.
   ============================================================ */

/* A shorter head than the photographic pages. Same teal ground, so it still
   belongs to the group, but it does not pretend to be an editorial spread. */
.page-head.compact {
  padding: 150px 0 60px;
}

.page-head.compact h1 {
  font-size: clamp(2.2rem, 4vw, 3.4rem);
}

.legal {
  max-width: 72ch;
}

.legal p,
.legal li {
  max-width: 68ch;
  line-height: 1.7;
}

/* Numbered section headings, at body-adjacent size. h2's display clamp is
   built for section titles on a marketing page and is far too loud here. */
.legal h2 {
  font-size: 1.25rem;
  letter-spacing: -.01em;
  margin: 48px 0 14px;
}

/* A sub-heading within a numbered section. Must stay smaller than .legal h2
   above -- the global h3 is 1.4rem, which would outrank its own section. */
.legal h3 {
  font-size: 1.05rem;
  letter-spacing: -.005em;
  margin: 28px 0 10px;
}

.legal ul {
  margin: 0 0 1em;
  padding-left: 22px;
}

.legal li {
  margin-bottom: .5em;
}

.legal-updated {
  font-family: var(--display);
  font-size: .72rem;
  font-weight: 600;
  letter-spacing: .22em;
  text-transform: uppercase;
  color: var(--teal-mute);
  margin: 0 0 28px;
}

/* An unanswered detail, marked so it reads as unfinished rather than as
   text someone wrote on purpose. */
.legal-todo {
  border-left: 3px solid var(--peach);
  padding-left: 16px;
  color: var(--teal-mute);
  font-style: italic;
}

.legal-foot {
  margin-top: 56px;
  padding-top: 26px;
  border-top: 1px solid var(--line);
  color: var(--teal-mute);
}
