/* ============================================================
   MUSTANG GROUP
   Paper ground, Zodiak over Switzer, editorial plates.
   Light rework: the venues are daytime places, marinas, a
   hotel, waterfront restaurants, so the page reads that way.
   ============================================================ */

:root{
  --paper:          #FCFBF9;   /* page ground, barely warm white */
  --paper-deep:     #F4F1EC;   /* raised or tinted panels, used sparingly */
  --ink:            #1A1E21;   /* primary text */
  --ink-soft:       #4A5459;   /* secondary text */
  --ink-faint:      #646D6C;   /* captions and meta, darkened to clear 4.5:1 on both grounds */
  --line:           #E2DFD8;   /* hairlines */

  --peach:          #F0AE8B;   /* brand accent, rules and marks and dots only */
  --peach-deep:     #C8825F;   /* peach a couple of steps down, for the pill's
                                  pressed state and the takeover flood's middle
                                  wave. It was already in use as a hard-coded
                                  fallback inside var(--peach-deep,#C8825F) and
                                  had no declaration to fall back FROM; phase 7
                                  gave it one rather than adding a second use of
                                  a token that did not exist. Same hex, so
                                  nothing that already referenced it moved. */
  --peach-text:     #9A5C38;   /* accent as text, darkened to clear 4.5:1 on both grounds */

  --rubys:          #B23B4A;
  --ocean:          #4E8FA8;
  --hamble:         #699883;
  --grumpy:         #E26D1D;
  --grand:          #AD8A40;
  --ennios:         #889558;

  --measure:        62ch;
  --gutter:         clamp(22px, 5vw, 84px);
  --rule:           1px solid var(--line);

  --ease:           cubic-bezier(.16,.84,.34,1);
  --slow:           600ms;

  /* Phase 4 adds two more curves. Both are pure ease-outs with no overshoot,
     so the page's motion still reads as one family; they differ only in how
     front-loaded they are, and each has ONE job.

     --ease-grow is Banana Wharf's own ease-out-quart, lifted verbatim from
     banana-wharf-demo/app/globals.css (--ease-out-quart) because Kirsty
     asked for the venue tiles to grow "like the Banana Wharf menu", and the
     curve is most of what that feel actually is. Measured off the live BW
     build: their hovered menu card is 56% of the way through its move at
     106ms, 83% at 206ms, and spends the last 300ms settling the final 15%.
     Instant commitment, long soft landing. Used for every growth/scale move.

     --ease-exp is the standard expo-out, kept for the takeover's single
     decisive entrance and the accordion, where the move should feel
     committed the moment it starts rather than eased into. */
  --ease-grow:      cubic-bezier(.25,1,.5,1);
  --ease-exp:       cubic-bezier(.16,1,.3,1);
}

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

html{-webkit-text-size-adjust:100%;scrollbar-gutter:stable;overflow-x:clip}
/* the reserved gutter above keeps this the same page width whether or not
   a scrollbar is actually drawn, so flipping overflow to hidden below (the
   booking dialog's scroll lock) never shifts the page by a scrollbar's width */
html.scroll-lock{overflow:hidden}

body{
  margin:0;
  background:var(--paper);
  color:var(--ink);
  font-family:'Switzer','Switzer-fallback',-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;
  font-size:clamp(15.5px,.55vw + 14px,17px);
  line-height:1.6;
  font-weight:400;
  -webkit-font-smoothing:antialiased;
  overflow-x:clip;   /* round 6, item 2a: overflow-x:hidden on html/body breaks
                        position:sticky children on iOS Safari; clip does not
                        create a scroll container, so .masthead's sticky
                        positioning survives. Belt-and-braces with the
                        .mg-backdrop/.foot clips (item 1), which remove the
                        actual overflow source. */
}

h1,h2,h3,.display{
  font-family:'Zodiak','Zodiak-fallback',Georgia,'Times New Roman',serif;
  font-weight:400;
  letter-spacing:-.018em;
  text-wrap:balance;
  margin:0;
}

a{color:inherit}
img{display:block;max-width:100%;height:auto}
button{font:inherit;color:inherit;background:none;border:0;cursor:pointer}

:focus-visible{
  outline:2px solid var(--peach-text);
  outline-offset:4px;
  border-radius:2px;
}

.shell{ padding-inline:var(--gutter); }

.sr{
  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 ---------- */
.skip{
  position:absolute;left:var(--gutter);top:-100px;z-index:60;
  background:var(--ink);color:var(--paper);
  padding:10px 18px;font-weight:600;font-size:14px;
  transition:top 180ms var(--ease);
}
.skip:focus{top:14px}

/* ---------- document-level backdrop for the MG monogram ---------- */
/* a sibling of the header/main/footer, not an ancestor or descendant of any
   of them, so its z-index:-1 sits in the ROOT stacking context: behind
   body's own background paint, but behind every normal in-flow section too
   (see .hero-mark's own comment, further down, for the full rationale).
   No declared height: the mark inside is positioned in absolute DOCUMENT
   pixels (see measureMarkGeometry() in the script), so this wrapper only
   needs to BE a positioned ancestor anchored at the page's own origin,
   not actually span the page's rendered height itself. */
.mg-backdrop{
  position:absolute;
  top:0;left:0;width:100%;
  z-index:-1;
  pointer-events:none;
  /* round 6, item 1: overflow-x ONLY, never the overflow:clip shorthand.
     This box deliberately has no declared height (see the comment above)
     so the mark can render, and be transform-shifted by the parallax rig,
     across the WHOLE document height without a fixed clip boundary
     truncating it partway down the page; its own content-box height is
     therefore 0. Shorthand overflow:clip (both axes) clips a 0-height box
     to nothing, which was tried and immediately regressed to a fully
     invisible mark, caught by screenshot before shipping. Splitting the
     axes keeps overflow-y:visible (so the mark still escapes this 0-height
     box vertically exactly as designed) while overflow-x:clip alone
     contains the horizontal bleed that was widening the document. */
  overflow-x:clip;
  overflow-y:visible;
}

/* ============================================================
   HEADER
   ============================================================ */
.masthead{
  position:sticky;top:0;z-index:50;
  background:var(--paper);
  border-bottom:var(--rule);
  transition:background-color 320ms var(--ease), border-color 320ms var(--ease);
}
/* ---- the masthead while the takeover menu is open ----
   The takeover is position:fixed at z-index 40, i.e. one layer BELOW this
   sticky header, on purpose: the header is not covered, it is re-skinned.
   That is what makes "the X sits in exactly the same position as the
   trigger" literally true rather than a second button drawn at matching
   coordinates -- there is only ever one menu button on the page, and it
   turns into the close control in place (its own aria-expanded rule below
   already rotates the two rules into an X).
   The inline links and Book fade out and go visibility:hidden, which is
   what takes them out of the tab order too, so the focus trap only ever
   has to manage the takeover's own contents plus this one button. */
.nav-open .masthead{ background-color:transparent;border-bottom-color:transparent; }
/* Phase 7 delays the ink-to-paper flip. The panel used to wipe DOWN from
   this header line, so the ground under the wordmark went dark on the
   first frame and the mark could flip with it. The wave flood rises from
   the BOTTOM instead and reaches the masthead last, so flipping on frame
   one left a paper-white wordmark sitting on the still-paper page for a
   fifth of a second. 240ms is where the ink waterline clears the header
   (see the timing table at .tk-flood), and 170ms of fade lands it just
   after. Only the OPENING is delayed: .nav-open comes off the moment the
   close starts, so the mark returns to ink immediately, ahead of the
   draining panel rather than behind it. */
.nav-open .mark{ background:var(--paper);transition-duration:170ms;transition-delay:240ms; }
.nav-open .wordmark:hover .mark,.nav-open .wordmark:focus-visible .mark{ background:var(--peach); }
.nav-open .menu-btn,.nav-open .menu-btn[aria-expanded="true"]{
  color:var(--paper);transition-delay:240ms;
}
/* round 7, item 6: was a 3-column grid (auto 1fr auto) built to hold the
   header's centre status line in the middle 1fr column. That line is gone
   for good now (item 2: opening times removed sitewide), so the middle
   column had no content at any width and the measured bug this replaces
   was the grid auto-flowing the nav INTO that empty middle track, which
   ate the gap meant for the right edge (nav landed ~38px short of the
   shell's own right padding while the wordmark sat correctly at its 22px
   left padding, measured at 390 wide before this fix). Two real children
   now (wordmark, nav), so a two-item flex row with space-between is the
   honest structure: no empty middle column to auto-flow into, and gap
   still guarantees a minimum if the row ever ran too narrow to reach its
   full space-between spread. */
.masthead-in{
  display:flex;align-items:center;justify-content:space-between;
  gap:clamp(16px,3vw,48px);
  min-height:74px;padding-block:14px;
}
.wordmark{ display:flex;align-items:center;flex:0 0 auto;text-decoration:none; }
.mark{
  display:block;
  width:clamp(146px,15vw,196px);
  aspect-ratio:2092/236;
  background:var(--ink);
  -webkit-mask:url("img/mg-logo.svg") no-repeat left center;
  mask:url("img/mg-logo.svg") no-repeat left center;
  -webkit-mask-size:contain;
  mask-size:contain;
  transition:background 260ms var(--ease);
}
.wordmark:hover .mark,.wordmark:focus-visible .mark{ background:var(--peach-text) }

.nav{
  display:flex;align-items:center;gap:clamp(14px,2vw,30px);
  font-size:13.5px;letter-spacing:.01em;
}
.nav a{
  text-decoration:none;color:var(--ink-soft);
  padding-block:6px;position:relative;white-space:nowrap;
  transition:color 200ms var(--ease);
}
.nav a::after{
  content:"";position:absolute;left:0;right:0;bottom:0;height:1px;
  background:var(--peach);transform:scaleX(0);transform-origin:left;
  transition:transform 300ms var(--ease);
}
.nav a:hover,.nav a:focus-visible{color:var(--ink)}
.nav a:hover::after{transform:scaleX(1)}
.nav-book{
  border:1px solid var(--ink);color:var(--ink) !important;
  background:transparent;
  padding:9px 17px !important;font-weight:600;
  transition:background 220ms var(--ease),color 220ms var(--ease);
}
.nav-book::after{display:none}
.nav-book:hover{background:var(--ink);color:var(--paper) !important}

/* The inline links and Book button fade away under the open takeover.
   visibility (not just opacity) is what actually removes them from the tab
   order; it is delayed by the fade's own length on the way out so the fade
   is still seen, and applied instantly on the way back in. */
.nav-links,.nav-book{
  transition:opacity 240ms var(--ease), visibility 0s,
             background 220ms var(--ease), color 220ms var(--ease);
}
.nav-open .nav-links,.nav-open .nav-book{
  opacity:0;visibility:hidden;pointer-events:none;
  transition:opacity 240ms var(--ease), visibility 0s 240ms;
}

/* menu toggle: phase 4 makes this the site's menu at EVERY width (the
   takeover replaced the old mobile-only drawer), so it is no longer inside
   a max-width query. It is gated on .js instead, because it can do nothing
   without script: with JS off the inline .nav-links stay visible at all
   widths (see the max-width:900px block below) and wrap in the header
   rather than hiding behind a button that would never open. */
.menu-btn{
  display:none;align-items:center;gap:9px;
  font-size:13.5px;color:var(--ink-soft);padding:8px 0;
  /* phase 7: the ink-to-paper flip under .nav-open needs something to
     interpolate, or the label and the X snap to paper on frame one and sit
     invisible on the still-paper page until the flood arrives. See the
     delay on .nav-open .menu-btn above. */
  transition:color 170ms var(--ease);
}
.js .menu-btn{ display:inline-flex; }
.menu-btn svg{display:block}
.menu-btn svg line{
  stroke:currentColor;stroke-width:1.3;
  transition:transform 320ms var(--ease), opacity 200ms var(--ease);
  transform-origin:center;
}
.menu-btn[aria-expanded="true"]{color:var(--ink)}
.menu-btn[aria-expanded="true"] .l1{transform:translateY(3.5px) rotate(45deg)}
.menu-btn[aria-expanded="true"] .l2{transform:translateY(-3.5px) rotate(-45deg)}

@media(max-width:900px){
  .js .nav-links{display:none !important}
  .nav{gap:18px}
  /* With script off there is no menu button and no takeover, so these five
     links are the ONLY navigation on the page and they have to fit. The
     header row does not wrap by default (it is a two-item space-between
     flex row), so at 390px they ran straight off the edge and FAQ, Contact
     and Book a table were unreachable -- screenshotted before this rule
     existed. Letting both rows wrap makes the header taller and the links
     all present, which is the right trade with no JS. Scoped to
     :not(.js), which only ever matches when script has not run, so the
     normal path is untouched. */
  html:not(.js) .masthead-in{ flex-wrap:wrap;row-gap:10px; }
  html:not(.js) .nav{ flex-wrap:wrap;justify-content:flex-start;row-gap:6px; }
}

/* narrow phones: the wordmark, status-free nav and book button were
   crowding past the viewport edge below ~400px */
@media(max-width:420px){
  .mark{ width:clamp(118px,34vw,146px); }
  .menu-btn .menu-label{ display:none; }
  .nav-book{ padding:8px 12px !important; }
}

/* ============================================================
   HERO
   generous white space, statement left, full-bleed strip below
   ============================================================ */
/* the hero text AND the photo strip below it. The monogram used to live
   IN here and get clipped by this box's own overflow:hidden; it now lives
   in the document-level .mg-backdrop (see that rule, and the markup just
   inside <body>), painted behind everything via z-index and occluded for
   free by whichever in-flow section happens to sit on top of it as the
   page scrolls. overflow:hidden stays because .hero-strip's own reveal
   fade still benefits from a clean edge, but nothing here clips the mark
   any more: it was moved out specifically so it could survive past the
   bottom of this zone. */
.hero-zone{
  position:relative;
  isolation:isolate;
  overflow:hidden;
}
.hero{
  position:relative;
  padding-top:clamp(56px,9vw,120px);
  padding-bottom:clamp(36px,5vw,56px);
}
/* mobile/tablet, <1100px: round 6 moved the mark out of this gap entirely
   (it now rests near the masthead, see .hero-mark's mobile branch in
   measureMarkGeometry()), so .hero no longer needs to reserve a large
   bottom gap to hold it clear of the lede. Ordinary breathing room only. */
@media(max-width:1099.98px){
  .hero{ padding-bottom:clamp(28px,6vw,56px); }
}

/* ---- the monogram, now a document-level backdrop, not a child of .hero ----
   Round 5 rebuild: two problems with the old in-hero version. (1) The lag
   bug: the shared .ls entrance rule left a transform transition on the
   element that also received the per-frame parallax write, so every
   scroll tick eased toward its target over ~560ms instead of tracking the
   wheel. Fixed structurally by SPLITTING the element: .hero-mark (outer)
   holds ONLY position and the parallax transform, transition:none,
   always; .hero-mark-inner (below) holds the mask/background and the
   opacity-only load entrance, so the two transitions can never collide.
   (2) The mark was clipped out of existence the moment .hero-zone scrolled
   off screen, so it could never survive to the bottom of the page. Moved
   to .mg-backdrop, a position:absolute, z-index:-1 wrapper directly under
   <body> (see markup): at that stacking position it paints above body's
   own canvas background but below every in-flow section's own background
   and image, so the hero strip, the plate photos, the reviews band and
   the panels all occlude it for free, and it shows through wherever a
   section is left transparent (.hero-zone, .hero and #venues all are).
   top/height are set in JS (measureMarkGeometry(), near the bottom of the
   script) in DOCUMENT pixels, not CSS, because they depend on .hero's own
   rendered height and document position, neither of which this element
   has any CSS relationship to any more. The values below are only the
   no-JS fallback. */
.hero-mark{
  position:absolute;
  left:0;
  pointer-events:none;
  --par:0px;
  transform:translate3d(0,var(--par),0);   /* round 6, item 4: 3d form keeps
                                               the per-frame write on the
                                               compositor instead of forcing
                                               a 2d-to-3d layer promotion
                                               decision every tick */
  will-change:transform;   /* round 6, item 4: this is the ONLY element that
                               gets will-change on the page. It receives a
                               genuine per-frame transform write for as long
                               as the mark is on screen (see updateParallax),
                               so it earns a standing compositor layer;
                               nothing else here does. */
  transition:none;      /* load-bearing: this element receives a per-frame
                            transform write, so it must NEVER ease toward
                            it, or the mark visibly "catches up" a beat
                            after the wheel, which is the exact bug this
                            rebuild exists to fix. */
}
.hero-mark{
  right:-6px;               /* round 6, item 3: was -14px. -6px plus the
                               mask's own right-alignment (see
                               .hero-mark-inner) keeps the G essentially
                               whole on screen; the M crops off the LEFT
                               edge instead, the editorially safe crop. */
  top:600px;                 /* no-JS fallback only; JS overrides precisely */
  height:clamp(140px,43.7vw,240px); /* three quarters of the viewport wide
                                        (43.7vw tall at the glyph's 1.716
                                        ratio), right-aligned, whole glyph
                                        on screen; JS mirrors this formula
                                        in measureMarkGeometry. */
}
@media(min-width:1100px){
  .hero-mark{
    right:-8px;               /* mostly visible: a small poke into the gutter */
    top:280px;                /* no-JS fallback only; JS overrides precisely */
    height:340px;             /* no-JS fallback only; JS overrides precisely */
    max-height:720px;
  }
}
/* the visual layer: mask, background, opacity, and the load-entrance fade.
   Exactly fills the outer box (inset:0), so the mask math (auto 100%
   height, right-aligned, deliberate left-side bleed off a wide box) is
   identical to the old single-element version. */
.hero-mark-inner{
  position:absolute;
  inset:0;
  background:var(--peach);
  -webkit-mask:url("img/mg-mark.svg") no-repeat right center;
  mask:url("img/mg-mark.svg") no-repeat right center;
  -webkit-mask-size:auto 100%;
  mask-size:auto 100%;
  --mark-op:.25;
  opacity:var(--mark-op);
}
@media(min-width:1100px){
  .hero-mark-inner{ --mark-op:.32; }
}
.hero-support{
  margin:0 0 clamp(16px,2vw,22px);
  font-family:'Zodiak','Zodiak-fallback',Georgia,serif;
  font-style:italic;
  font-size:16.5px;color:var(--ink-soft);
}
.hero h1{
  font-size:clamp(44px,7vw,96px);
  line-height:.98;
  letter-spacing:-.02em;
  max-width:24ch;
  margin:0;
}
.hero-head{
  position:relative;
  margin:0 0 clamp(24px,3vw,36px);
}
/* the desktop void beside the h1: the client's own "6 venues, 5 brands"
   line, hidden below 1100px where there is no room for it to sit comfortably */
.hero-fact{ display:none; }
@media(min-width:1100px){
  /* the fact block is lifted out of flow so it can be placed over the
     monogram's lower-left quarter. top:100% (of .hero-head, whose only
     content is the h1) anchors it to h1's OWN rendered bottom edge rather
     than a guessed pixel value, so it can never collide with h1's text
     regardless of whether the headline wraps to one line or two at a given
     viewport width. */
  .hero-fact{
    display:block;text-align:right;
    position:absolute;z-index:1;
    /* item 10: right:0 puts this flush with .hero-head's own right edge,
       which is the shell's gutter edge, i.e. the SAME right edge as the
       masthead's "Book a table" button. Previously this floated at an
       arbitrary clamp() inset with no relationship to anything else on
       the page; now it actually aligns to the page's one other
       right-set element. */
    right:0;
    top:100%;
    margin-top:14px;
    /* no panel behind it: the text sits directly on the peach glyph,
       which at .32 opacity leaves contrast well clear of AA */
  }
}
.hero-fact-num{
  font-family:'Zodiak','Zodiak-fallback',Georgia,serif;
  font-weight:400;font-size:22px;line-height:1.15;
  margin:0 0 4px;color:var(--ink);white-space:nowrap;
}
.hero-fact-sub{
  font-size:14px;color:var(--ink-soft);margin:0;white-space:nowrap;
}
.hero-lede{
  max-width:46ch;color:var(--ink-soft);
  font-size:clamp(16px,.6vw + 14.4px,18.5px);line-height:1.62;
  margin:0;
}

.hero-strip{
  margin:0;
  height:56vh;    /* fallback for browsers without svh support */
  height:56svh;   /* round 6, item 2b: svh doesn't resize as the mobile URL
                     bar collapses/expands, so this stops reflowing the page
                     mid-scroll the way vh does */
  min-height:320px;max-height:680px;
  overflow:hidden;
  background:var(--paper-deep);
}
.hero-strip img{
  width:100%;height:100%;object-fit:cover;
  /* item 15: no filter. The re-exported photo (see img/hero.webp) already
     reads correctly untreated; saturate/contrast here were compensating
     for the old export, not doing anything the client actually wants. */
}

@media(max-width:660px){
  .hero-strip{height:46vh;height:46svh;min-height:260px}
}

/* ============================================================
   OCCASION FILTER
   ============================================================ */
.occasions{
  /* nested directly under .plates-head now, which already carries the
     section's top rule, so this reads as the venues' own filter row
     rather than a stranded, separately-bordered bar */
  padding-top:clamp(6px,1vw,10px);
  padding-bottom:clamp(18px,2.2vw,26px);
  display:flex;flex-wrap:wrap;align-items:baseline;
  gap:10px clamp(14px,1.8vw,26px);
}
.occasions-label{
  font-size:13px;color:var(--ink-faint);
  margin-right:4px;flex:0 0 auto;
}
.occ{
  display:inline-flex;align-items:center;min-height:44px;
  font-size:14.5px;color:var(--ink-soft);
  padding:5px 0;position:relative;white-space:nowrap;
  transition:color 200ms var(--ease);
}
.occ::after{
  content:"";position:absolute;left:0;right:0;bottom:0;height:1px;
  background:var(--peach);transform:scaleX(0);transform-origin:left;
  transition:transform 320ms var(--ease);
}
.occ:hover{color:var(--ink)}
.occ[aria-pressed="true"]{color:var(--peach-text)}
.occ[aria-pressed="true"]::after{transform:scaleX(1);background:var(--peach-text)}
.occ-count{ display:inline-block }

/* round 6, item 6: no room for a filter row on mobile, so it is hidden
   below 660px. Desktop untouched.
   Phase 4 correction to this comment: it used to claim the mobile drawer
   carried a "Rooms at Ennios" link as the route to the one occasion that
   has nowhere else to live. That link had already gone before phase 4 (the
   drawer it describes shipped with five page links and no venue links),
   and phase 4 removed the drawer itself, so the sentence was describing
   markup that no longer existed. The `a[href="#rooms"]` handler still in
   shared.js is now the last trace of that route and matches nothing on any
   page; it is left in place rather than removed here because whether the
   Rooms shortcut comes back is a content decision, not a motion one. */
@media(max-width:660px){
  .occasions{ display:none; }
}

/* ============================================================
   THE PLATES
   staggered editorial arrangement, captions beneath the image
   ============================================================ */
.plates-head{
  padding-top:clamp(34px,4.4vw,62px);
  padding-bottom:clamp(10px,1.4vw,16px);
  display:flex;flex-wrap:wrap;gap:14px 40px;align-items:start;
  border-top:var(--rule);
}
.plates-head h2{
  font-size:clamp(28px,3.4vw,46px);line-height:1.02;max-width:18ch;
}
.plates-head p{
  margin:0;color:var(--ink-soft);font-size:15px;max-width:42ch;
  margin-left:auto;
}
@media(max-width:660px){
  .plates-head p{ margin-left:0; }
}
.plates-status{
  min-height:1.4em;margin:10px 0 0;color:var(--ink-faint);font-size:14px;
}
/* item 11: the plain-text fallback for JS-off visitors, so it reads as a
   deliberate list rather than broken markup */
.noscript-venues{
  list-style:none;margin:22px 0 0;padding:0;max-width:60ch;
}
.noscript-venues li{ border-top:var(--rule); }
.noscript-venues li:first-child{ border-top:0; }
.noscript-venues a{
  display:block;padding:14px 0;text-decoration:none;color:var(--ink-soft);
  font-size:15px;line-height:1.5;
}
.noscript-venues a:hover{ color:var(--peach-text); }

.plates{
  display:grid;
  grid-template-columns:repeat(12,1fr);
  gap:clamp(30px,3.8vw,62px) clamp(20px,2.6vw,44px);
  padding-top:clamp(22px,2.8vw,34px);
  padding-bottom:clamp(56px,7vw,104px);
  list-style:none;margin:0;padding-inline:0;
  align-items:start;
}
/* with JS off #plates is permanently empty (item 11's noscript list is the
   real content then), so its own top/bottom padding otherwise leaves an
   odd, unexplained gap between the occasions filter and that list */
.plates:empty{
  padding-top:0;padding-bottom:0;
}

.plate{
  display:flex;flex-direction:column;
  position:relative;   /* so the hovered tile can lift above its neighbours
                          in paint order while its frame scales up into the
                          grid gap (see the hover block below) */
  transition:opacity 420ms var(--ease), filter 420ms var(--ease);
}

/* A mirrored composition: 5-4-3 across the top, 3-4-5 back across the bottom,
   so the two rows pinwheel around the centre. Every frame across a row shares
   one fixed, viewport-relative height (not a per-span aspect-ratio, which drifted
   off the baseline once gaps and rounding entered the maths) so the six
   plate-name captions always start at the same line. The rhythm comes from
   the changing crop width, not from ragged holes. */
/* Round 9: the branded duotone tiles are a matched set at one size, so the
   grid is uniform: three equal tiles per row, every frame the tile's own
   1920x1314 ratio. The pinwheel belonged to mixed photography; a brand wall
   wants equality. */
.plate:nth-child(n){grid-column:span 4}

/* !important on opacity: the reveal-entrance rule (.js .reveal.seen{opacity:1})
   outranks this on plain specificity since .plate also carries .reveal.seen
   once shown, which would otherwise silently cancel the dim. This state must
   always win once the visitor has picked an occasion. */
.plate[data-dim="true"]{opacity:.28 !important;filter:grayscale(1)}

.plate-frame{
  position:relative;overflow:hidden;background:var(--paper-deep);
  height:auto;aspect-ratio:1920/1314;
  transform:scale(1);
  transition:transform 640ms var(--ease-grow);
}
/* The duotone tiles carry their venue's lockup in-composition, so no scroll
   travel inside these frames: the full tile is always visible and static
   (the depth rule lives on the monogram and the two panel images). */
#plates .plate-shift{position:static;height:100%;transform:none}

/* the parallax carrier: oversized (126% of the frame) and centred, so the
   frame always has 13% of the image's own height spare above and below to
   travel into (raised from 116%/8% for real travel headroom, item 6a).
   126%, not the ~124% first tried: the per-column rate multiplier below
   (item 6b) scales the fastest column up to 1.18x, and 124% only left
   headroom for an UN-multiplied travel, so the fast column overshot its
   spare by a few px at the scroll extremes, a real sliver. 126% plus a
   16% (not 18%) base travel coefficient keeps EVEN the 1.18x column
   inside its spare with margin to px-rounding; see the JS travel/rate
   comment for the arithmetic. JS only ever touches --plate-par on THIS
   element, never the img's own transform, so the scroll drift and the
   hover zoom below can never fight over one transform value. No
   transition here: the drift must track the scroll position 1:1, not
   ease toward a stale target. Reused verbatim for the two panel photos
   below (item 8): same carrier, same travel formula, same magnitude,
   just a different parent frame class. */
.plate-shift{
  position:absolute;top:50%;left:0;
  width:100%;height:126%;
  --plate-par:0px;
  transform:translate3d(0,calc(-50% + var(--plate-par)),0);
}

.plate-frame img{
  width:100%;height:100%;object-fit:cover;
  filter:saturate(.82) contrast(1.05) brightness(1.02);
  transform:scale(1.001);
  transition:transform 700ms var(--ease-grow), filter 200ms var(--ease);
}
.plate a.plate-hit{
  position:absolute;inset:0;z-index:2;
  text-decoration:none;
}

/* ---- HOVER-GROW, phase 4. Kirsty's ask, verbatim: the tiles should grow
   on hover "like the Banana Wharf menu". That menu was opened, measured and
   watched before this was written; the findings and how they map onto a
   fixed editorial grid are worth writing down, because the numbers alone
   would mislead whoever reads this next.

   What Banana Wharf actually does (components/MenuHighlights.tsx, and
   confirmed live against the built site): on desktop the hovered card's
   FLEX goes 1 -> 2.5 while every sibling goes 1 -> 0.7, over 600ms on
   cubic-bezier(.25,1,.5,1). Measured widths on a 1600px viewport: four
   cards at 312px each at rest; hovered 678px, siblings 190px. So the
   hovered card ends up 2.17x its own resting width and 3.57x its
   neighbours'. Sampled frame by frame, that move is 56% done at 106ms and
   83% at 206ms, then spends 300ms settling the last 15%. Their image
   itself only scales 1.04 (700ms, same curve); the drama is entirely in
   the card growing and the row yielding around it.

   What we do with it. Our plates are a fixed three-column editorial grid
   whose six captions are deliberately locked to one baseline, so a real
   width change is off the table twice over: it would break that alignment
   and it is a layout animation. The translation keeps the three things
   that make BW's version feel the way it does, and drops only the
   mechanism:
     1. the CURVE and the tempo -- BW's own ease-out-quart at 640/700ms
        (--ease-grow), replacing the old 900ms cubic-out, which read as
        sludge rather than as a decision;
     2. COMPOUND growth, so the tile grows as a tile and not just as a
        photo -- the frame scales 1.03 into the grid gap (at the tightest
        gap that is 4.6px a side against 28px of gap, so nothing ever
        touches) and the image scales 1.05 inside it, ~1.08 in total,
        against the 1.045 image-only move this replaces;
     3. the ROW YIELDING, which is the half people actually remember:
        siblings counter-scale to .985 on the same curve and drop
        saturation to .42 on the 200ms colour tempo BW uses for its own
        colour changes. Their siblings shrink to 61% of resting; a
        three-up grid of branded duotone tiles cannot go anywhere near
        that, so the yield is carried by colour with a whisker of scale
        underneath it.
   Pointer-gated: :hover only under (hover:hover) and (pointer:fine), so a
   tap on a phone goes to the venue instead of leaving a tile stuck in its
   hovered state. Keyboard gets the identical move via :focus-within,
   outside that query, so it works on any device. ---- */
@media (hover:hover) and (pointer:fine){
  .plate:hover{ z-index:2; }
  .plate:hover .plate-frame{ transform:scale(1.03); }
  .plate:hover .plate-frame img{ transform:scale(1.05); }
  .plate:hover .plate-cap::before{ transform:scaleX(2.5); }
  .plate:hover .plate-name{
    color:var(--peach-text);
    /* the venue's own accent, mixed toward ink so every one of the six
       clears 4.5:1 on paper. Neat as they are, --grand (#AD8A40) and
       --hamble (#699883) sit around 3:1 raw, which is not enough for a
       23px caption; at 62% over ink they land between 6:1 and 8:1 while
       still reading as that venue's colour. Older engines that cannot
       parse color-mix simply keep the --peach-text line above. */
    color:color-mix(in srgb, var(--accent,var(--peach)) 62%, var(--ink));
  }

  /* the row yields around whichever tile has your attention */
  .plates:hover .plate:not(:hover) .plate-frame{ transform:scale(.985); }
  .plates:hover .plate:not(:hover) .plate-frame img{
    filter:saturate(.42) contrast(1.05) brightness(1.02);
  }
  .plates:hover .plate:not(:hover) .plate-cap{ opacity:.68; }
}
/* keyboard path, every device */
.plate:focus-within{ z-index:2; }
.plate:focus-within .plate-frame{ transform:scale(1.03); }
.plate:focus-within .plate-frame img{ transform:scale(1.05); }

/* accent hairline in the gap BETWEEN frame and caption, not overlaid on the
   photo: always visible at 40% width, hover/focus grows it to the full width. */
.plate-cap{ padding-top:14px;transition:opacity 200ms var(--ease) }
.plate-cap::before{
  content:"";display:block;
  width:40%;height:3px;margin-bottom:14px;
  background:var(--accent,var(--peach));
  transform:scaleX(1);transform-origin:left;
  /* on the growth curve now, and given a longer beat than the old 320ms so
     the hairline, the frame and the photo all read as one gesture rather
     than three events; shorter than the frame's 640ms because it has less
     distance to cover */
  transition:transform 520ms var(--ease-grow);
}
.plate:focus-within .plate-cap::before{ transform:scaleX(2.5); }

/* The venue's own lockup, redrawn in a single ink so six unrelated brand
   marks (a script, two engraved serifs, a boxed monogram and a cartoon
   monkey) read as one set rather than a sticker sheet. Sizes are matched by
   OPTICAL weight, not pixel height: the wide wordmarks sit at 28-30px, the
   two roughly square marks at 42-44px, which lands them all on about the
   same ink area.

   The mark is absolutely positioned inside a zero-height slot rather than
   being a plain flex sibling, on purpose. The plates share one fixed frame
   height specifically so all six captions start on the same line (see
   .plate-frame); a mark in the flow would let Grumpy Monkey's 44px one push
   its own kind/where/hours block ~10px below its two row-mates' and quietly
   break that alignment. The slot reserves the mark's WIDTH but no height, so
   the row is only ever as tall as the name, whatever mark sits beside it,
   and the name's own padding-right keeps its text off the mark rather than
   running underneath.

   alt="" and aria-hidden are deliberate: the <h3> right next to it already
   carries the venue name for assistive tech and for search, so the mark is
   purely decorative and announcing it again would only stutter. */
/* min-height reserves TWO lines of the name at every width, and the flex
   centring parks a one-line name inside them. Measured, not defensive:
   "Banana Wharf Ocean Village" is 315px of text at the name's 23px floor,
   and its plate is only ~340px wide at 1200, so once a mark sits beside it
   that name takes two lines on every viewport below about 1730px. Left
   alone it would drop its own kind/where/hours block a line under its two
   row-mates', which is the alignment .plate-frame's fixed height exists to
   protect. Reserving the second line everywhere keeps all six captions
   locked at every width, and keeps the caption rhythm identical on a 1440
   laptop and a 1920 desktop instead of only tidy on the wide one. */
.plate-namerow{
  display:flex;align-items:center;
  font-size:clamp(23px,1.7vw,31px);   /* the name's own curve, so the em below tracks it */
  min-height:calc(2 * 1.08em);        /* 1.08 is .plate-name's line-height */
  margin:0 0 5px;
}
/* a zero-height slot pushed to the column's right edge by margin-left:auto.
   Zero height so the mark can never drive the row's height (see the block
   comment above); it only reserves the horizontal width, and the mark is
   centred on the slot's own centre line, which align-self:center has already
   parked on the row's centre line.

   Right edge rather than tucked against the name: set beside it, "Rubys
   RUBYS AT THE GRAND" reads as a stutter, and the same goes for Ennios and
   both Banana Wharfs. Pushed to the column edge the mark reads as a masthead
   instead, and the photo edge and the accent rule directly above it make the
   column it belongs to unambiguous even where the neighbouring plate's name
   happens to start closer. */
/* round 7, item 3: the icons + logo now travel together as one right-hand
   cluster, pushed to the column edge by margin-left:auto on THIS wrapper
   (moved here from .plate-logo-slot, which no longer needs its own: it is
   the last thing in the row either way, and .plate-namerow's flex row
   already keeps both icons and logo vertically centred on the row's own
   centre line, precisely the "top-right, intact" positioning item 3 asks
   for once a long name wraps to its reserved second line: the cluster's
   position is a function of row height, never of how many lines the name
   actually takes, exactly like the logo already worked pre-round-7. */
.plate-namerow-right{
  display:flex;align-items:center;gap:14px;
  flex:none;margin-left:auto;
}
.plate-icons{ display:flex;align-items:center;gap:10px; }
.plate-logo-slot{
  flex:none;position:relative;
  width:calc(var(--logo-w,68px) * var(--logo-scale,1));height:1px;
  align-self:center;
}
.plate-logo{
  position:absolute;right:0;top:50%;
  transform:translateY(-50%);
  height:calc(var(--logo-h,30px) * var(--logo-scale,1));width:auto;
  opacity:.86;
  transition:opacity 200ms var(--ease);
}
.plate:hover .plate-logo,
.plate:focus-within .plate-logo{ opacity:1; }

.plate-name{
  font-family:'Zodiak','Zodiak-fallback',Georgia,serif;
  font-size:inherit;
  line-height:1.08;letter-spacing:-.02em;
  margin:0;min-width:0;padding-right:18px;
  transition:color 200ms var(--ease);
}
.plate:focus-within .plate-name{
  color:var(--peach-text);
  color:color-mix(in srgb, var(--accent,var(--peach)) 62%, var(--ink));
}
.plate-kind{
  font-size:13px;color:var(--ink-soft);
  margin:0 0 3px;
}
.plate-where{
  font-size:12.5px;color:var(--ink-faint);
  margin:0 0 14px;
}
/* round 6, item 7: mobile-only merged line ("Cocktail lounge · At The
   Grand, Southampton"), aria-hidden because .plate-kind/.plate-where still
   carry the same two facts to assistive tech (visually hidden, not
   removed, below), so nothing is announced twice. Hidden by default;
   the max-width:660px block below swaps which of the two is visible. */
.plate-meta{
  display:none;
  font-size:13px;color:var(--ink-soft);
  margin:0 0 8px;
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis;
  /* genuinely ONE line, whatever the venue: Ennios' kind+note+where combo
     runs long enough to wrap to two lines otherwise, which blew the
     mobile plate-height budget (item 7) for exactly that plate. The full,
     untruncated text is never lost: .plate-kind/.plate-where carry it to
     assistive tech regardless (see the max-width:660px block). */
}
/* round 7, item 4: the two-sentence venue description that replaces the
   old hours line. Same slot in the caption stack (between the meta line
   and the actions row below), ink-soft to sit quietly under the darker
   kind/where line above it. */
.plate-desc{
  font-size:14px;line-height:1.5;color:var(--ink-soft);
  max-width:44ch;margin:6px 0 16px;
}

/* round 7, item 3: Menus/Book a table, now drawn icon buttons instead of
   text links. Lives in .plate-namerow (see .plate-namerow-right below),
   not down here with the old text-link row, which item 3's placement
   spec moved them out of entirely. */
.plate-icon{
  display:inline-flex;align-items:center;justify-content:center;
  width:44px;height:44px;flex:none;
  color:var(--ink-soft);
  position:relative;z-index:3;
  transition:color 200ms var(--ease);
}
.plate-icon:hover,.plate-icon:focus-visible{color:var(--peach-text)}
.plate-icon svg{display:block;width:22px;height:22px}

@media(max-width:1080px){
  .plate:nth-child(n){grid-column:span 6}
  /* .plate-name has bottomed out on its own clamp by here (23px, down from
     31px), so the marks come down with it, or the two roughly square ones
     start overhanging a 23px line by half their own height. Round 6, item 8:
     .82 -> .888. The venue data's logoW/logoH now hold the DESKTOP target
     display size directly (wordmarks 36px tall, square marks 52px tall,
     both raised from the old accidental values, which were just whatever
     each source asset's own native pixel height happened to be); this
     scale is tuned so the mobile result lands on the spec's mobile targets
     (~32px wordmark, ~46px square) instead of shrinking further from an
     already-too-small base. */
  .plate{ --logo-scale:.888 }
}
@media(max-width:660px){
  .plate:nth-child(n){grid-column:span 12}
  .plates{gap:30px 0}   /* round 6, item 7: was 44px; plates run to ~524px
                            each on mobile before this pass, so gap alone
                            wasn't the main cost, but it still tightens once
                            the plate itself is shorter. */
  /* round 6, item 7: the fixed clamp() height below reads as a near-5:4
     crop at mobile widths (frame stays near its 300px floor while the
     column narrows); a real 3:2 crop instead, and shorter overall. */
  .plate-frame{ height:auto; aspect-ratio:1920/1314; }

  /* round 7, item 3/4: the icons are new real estate the namerow didn't
     have to share before (item 3), and item 4's description adds height
     of its own below it, so mobile needs some of that space back to stay
     inside the <=460px budget. Tightening these three, measured against
     the longest name (Banana Wharf Ocean Village): logo-scale .888->.72
     plus the two gap cuts recovers ~40px of width for the name text,
     which is what keeps it to two wrapped lines instead of three or four
     at 390 wide; the small margin trims below claw back the rest. */
  .plate{ --logo-scale:.72 }
  .plate-namerow-right{ gap:9px }
  .plate-icons{ gap:6px }
  .plate-cap::before{ margin-bottom:10px }
  .plate-desc{ margin:4px 0 12px }

  /* round 6, item 7: swap which line is VISUALLY shown, without removing
     either from the accessibility tree and without announcing the same
     two facts twice. .plate-meta is aria-hidden (see its own rule) so it
     is never read out; .plate-kind/.plate-where are visually hidden with
     the .sr clip technique (not display:none), so they stay exactly as
     available to a screen reader as they are on desktop, just off-screen. */
  .plate-meta{ display:block; }
  .plate-kind,.plate-where{
    position:absolute;width:1px;height:1px;padding:0;margin:-1px;
    overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap;border:0;
  }
}

/* ============================================================
   REVIEWS
   three real quotes, side by side, no stars or carousel
   ============================================================ */
.reviews{
  background:var(--paper-deep);
  border-top:var(--rule);
  border-bottom:var(--rule);
}
.reviews-in{
  display:grid;grid-template-columns:repeat(12,1fr);
  padding-block:clamp(52px,6.5vw,88px);
}
/* asymmetric: Hannah's quote leads at 6 of 12 columns, the other two sit
   side by side in the remaining 6 */
.review:nth-child(1){grid-column:span 6}
.review:nth-child(2){grid-column:span 3}
.review:nth-child(3){grid-column:span 3}
.review{
  margin:0;padding:0 clamp(20px,3vw,40px);
  border-left:var(--rule);
}
.review:first-child{border-left:0;padding-left:0}
.review-quote{
  font-family:'Zodiak','Zodiak-fallback',Georgia,serif;
  font-style:italic;color:var(--ink);
  font-size:20px;line-height:1.22;
  margin:0 0 14px;
}
.review:first-child .review-quote{ font-size:34px; }
.review-line{
  font-size:15px;color:var(--ink-soft);line-height:1.55;
  margin:0 0 20px;
}
.review-by{
  display:block;font-style:normal;
  font-size:13px;color:var(--ink-faint);
}
@media(max-width:860px){
  .reviews-in{grid-template-columns:1fr;gap:32px}
  /* :nth-child(n) matches the specificity of the desktop span rules above,
     so this reset actually wins at mobile width instead of losing to them */
  .review:nth-child(n){grid-column:auto}
  .review{border-left:0;padding:0 0 0;padding-top:32px;border-top:var(--rule)}
  .review:first-child{border-top:0;padding-top:0}
}

/* ============================================================
   TWO PANELS, unequal: private hire and gift vouchers
   ============================================================ */
.pair{
  display:grid;grid-template-columns:1.62fr 1fr;
  align-items:start;
  gap:clamp(20px,2.6vw,40px);
  padding-block:clamp(58px,8vw,104px);
}
.panel{
  display:flex;flex-direction:column;
  background:var(--paper);
  text-decoration:none;
}
.panel-img{
  display:block;position:relative;aspect-ratio:3/2;overflow:hidden;background:var(--paper-deep);
}
.panel-img img{
  width:100%;height:100%;object-fit:cover;
  filter:saturate(.9) contrast(1.03);
  transition:transform 700ms var(--ease-grow);
}
.panel:hover .panel-img img{transform:scale(1.045)}
.panel-in{padding:clamp(22px,2.8vw,36px)}
.panel h3{
  font-size:clamp(23px,2.1vw,33px);line-height:1.08;color:var(--ink);margin:0 0 12px;
}
.panel p{
  margin:0 0 18px;color:var(--ink-soft);font-size:14.5px;line-height:1.55;max-width:42ch;
}
.panel-go{
  font-size:13px;color:var(--peach-text);
  display:inline-flex;align-items:center;gap:9px;min-height:44px;
}
.panel-go svg{transition:transform 300ms var(--ease)}
.panel:hover .panel-go svg{transform:translateX(5px)}
@media(max-width:760px){ .pair{grid-template-columns:1fr} }

/* ============================================================
   NEWS
   round 8: placeholder editorial section, the client's own real old
   posts standing in until their blog is live. Deliberately NOT a
   uniform three-card grid: one lead (7/12) plus two stacked, text-first
   secondary rows (5/12), same asymmetric-editorial instinct as .reviews
   above it. Static HTML throughout, no JS involvement, so it renders
   identically with JS off. */
.news{
  padding-block:clamp(48px,6vw,84px);
  border-bottom:var(--rule);
}
.news-head{
  padding-bottom:clamp(22px,2.8vw,34px);
  display:flex;flex-wrap:wrap;gap:14px 40px;align-items:baseline;
}
.news-head h2{
  font-size:clamp(28px,3.4vw,46px);line-height:1.02;margin:0;
}
/* the quiet text link: .nav a's own underline pattern, verbatim */
.news-all{
  margin-left:auto;
  text-decoration:none;color:var(--ink-soft);
  font-size:13.5px;position:relative;padding-block:6px;
  transition:color 200ms var(--ease);
}
.news-all::after{
  content:"";position:absolute;left:0;right:0;bottom:0;height:1px;
  background:var(--peach);transform:scaleX(0);transform-origin:left;
  transition:transform 300ms var(--ease);
}
.news-all:hover,.news-all:focus-visible{color:var(--ink)}
.news-all:hover::after,.news-all:focus-visible::after{transform:scaleX(1)}

.news-grid{
  display:grid;
  grid-template-columns:7fr 5fr;
  gap:clamp(28px,3.4vw,56px);
  align-items:start;
}

/* ---- lead: Ennios' rosette ---- */
.news-lead{ display:block;text-decoration:none; }
.news-lead-img{
  display:block;position:relative;aspect-ratio:3/2;overflow:hidden;
  background:var(--paper-deep);
}
.news-lead-img img{
  width:100%;height:100%;object-fit:cover;
  filter:saturate(.9) contrast(1.03);
  transform:scale(1.001);
  transition:transform 700ms var(--ease-grow);
}
.news-lead:hover .news-lead-img img,
.news-lead:focus-visible .news-lead-img img{ transform:scale(1.045); }
.news-lead-body{ display:block;padding-top:18px; }
.news-date{
  display:block;font-size:13px;color:var(--ink-faint);margin:0 0 8px;
}
.news-lead-title{
  font-size:clamp(24px,2.1vw,30px);line-height:1.1;color:var(--ink);
  margin:0 0 10px;transition:color 200ms var(--ease);
}
.news-lead:hover .news-lead-title,
.news-lead:focus-visible .news-lead-title{ color:var(--peach-text); }
.news-standfirst{
  /* one line at the widths the spec describes: no measure cap, so a short
     standfirst never force-wraps inside a lead column this wide. white-space
     protects it at the narrow end of the desktop range (900-1200px); the
     mobile rule below reverts to normal wrapping, where the lead is
     full-width and one line would run off comfortable reading size. */
  font-size:15px;color:var(--ink-soft);line-height:1.55;margin:0;
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:100%;
}

/* ---- secondaries: text-first rows, thumb to the right, hairline between ---- */
.news-secondaries{
  display:flex;flex-direction:column;
  /* nudges the first row's text baseline toward the lead headline rather
     than the lead image's top edge, without touching the grid's own
     align-items:start */
  padding-top:clamp(2px,.6vw,10px);
}
.news-story{
  display:flex;align-items:center;gap:clamp(16px,2vw,22px);
  padding-block:clamp(18px,2.2vw,26px);
  text-decoration:none;
  border-top:var(--rule);
}
.news-secondaries .news-story:first-child{ border-top:0;padding-top:2px; }
.news-story-body{ flex:1;min-width:0;order:1; }
.news-thumb{
  order:2;flex:none;
  display:block;position:relative;overflow:hidden;
  width:120px;aspect-ratio:1/1;background:var(--paper-deep);
}
.news-thumb img{
  width:100%;height:100%;object-fit:cover;
  filter:saturate(.9) contrast(1.03);
  transform:scale(1.001);
  transition:transform 700ms var(--ease-grow);
}
.news-story:hover .news-thumb img,
.news-story:focus-visible .news-thumb img{ transform:scale(1.045); }
.news-story-title{
  font-size:19px;line-height:1.22;color:var(--ink);
  margin:0 0 8px;transition:color 200ms var(--ease);
}
.news-story:hover .news-story-title,
.news-story:focus-visible .news-story-title{ color:var(--peach-text); }
.news-story .news-date{ margin:0; }

@media(max-width:899.98px){
  .news-grid{ grid-template-columns:1fr; }
  .news-secondaries{ padding-top:clamp(22px,4vw,32px); }
  /* below the editorial split the lead is full-width and stacked, so the
     one-line-with-ellipsis treatment (a narrow-column safeguard) is no
     longer needed; a short summary wrapping to two lines reads naturally
     here, same as any other mobile teaser. */
  .news-standfirst{ white-space:normal;overflow:visible;text-overflow:clip; }
}
@media(max-width:659.98px){
  .news{ padding-block:clamp(36px,7vw,48px); }
  .news-lead-title{ font-size:clamp(21px,5.6vw,25px); }
  .news-thumb{ width:88px; }
  .news-story{ gap:14px;padding-block:16px; }
}

/* ============================================================
   FOOTER
   one lean band
   ============================================================ */
.foot{
  position:relative;
  overflow:clip;     /* clips the big background mark below at this box's
                        own edge: no horizontal scroll, whatever it bleeds.
                        clip rather than hidden (round 6, item 1/2a): it
                        does not create a scroll container, so it can never
                        interact with a sticky descendant the way hidden can. */
  border-top:var(--rule);
  padding-top:clamp(40px,5vw,64px);
}
/* item 9: the same monogram, echoed large and faint behind the footer.
   Fixed viewport-relative sizing (not a percentage of .foot's own height,
   which is auto/content-driven and would make a percentage height invalid)
   so it never affects .foot's own layout height: it is position:absolute
   and clipped, so however large it renders, the footer band itself stays
   exactly as tall as its real content. */
.foot-mark-bg{
  position:absolute;
  z-index:-1;
  pointer-events:none;
  left:clamp(-120px,-9vw,-30px);
  bottom:-14%;
  height:clamp(220px,32vw,440px);
  aspect-ratio:404.749/235.874;
  background:var(--peach-text);
  opacity:.12;   /* raised from .08 (item 1e): the client asked for a large
                    logo here and at .08 it whispered */
  -webkit-mask:url("img/mg-mark.svg") no-repeat left center/contain;
  mask:url("img/mg-mark.svg") no-repeat left center/contain;
}
.foot-row{
  position:relative;   /* stays above the background mark, z-index:auto beats -1 */
  display:flex;flex-wrap:wrap;justify-content:space-between;align-items:flex-start;
  gap:28px 40px;
  padding-bottom:clamp(32px,4vw,52px);
}
.foot-mark .mark{width:170px;margin-bottom:16px}
.foot-tagline{margin:0;color:var(--ink-soft);font-size:14.5px}
.foot-contact{display:flex;flex-wrap:wrap;gap:28px clamp(32px,4vw,56px);align-items:flex-start}
.foot-contact address{
  font-style:normal;color:var(--ink-soft);font-size:14.5px;line-height:1.9;
  display:flex;flex-direction:column;
}
.foot-contact address a{color:var(--ink-soft);text-decoration:none}
.foot-contact address a:hover{color:var(--peach-text)}
/* Privacy and Work with us live on the copyright line, small, right-aligned.
   The tap targets keep their 44px height through padding, invisibly. */
.foot-links{display:inline-flex;gap:22px;margin-left:auto}
.foot-links a{display:inline-flex;align-items:center;min-height:44px;color:var(--ink-faint);text-decoration:none;font-size:12.5px;transition:color 200ms var(--ease)}
.foot-links a:hover{color:var(--peach-text)}
.foot-base{
  border-top:var(--rule);padding-block:8px;
  font-size:12.5px;color:var(--ink-faint);
  display:flex;align-items:center;flex-wrap:wrap;gap:0 22px;
}
@media(max-width:760px){ .foot-row{flex-direction:column} }
/* round 6, item 9: below 660px .foot-contact was still the desktop
   flex-wrap row (address + links), which half-wrapped awkwardly once
   there wasn't room for both side by side. Stack clean instead: mark+
   tagline (.foot-row already puts this first), then address, then the
   two links side by side (not stacked, which is .foot-links' desktop
   default), then .foot-base (copyright) below, unchanged, since it was
   never part of this flex row. */
@media(max-width:660px){
  .foot-contact{ flex-direction:column;align-items:flex-start;gap:22px }
}

/* ============================================================
   BOOKING CHOOSER
   ============================================================ */
.chooser{
  /* no position here: the UA stylesheet gives dialog:modal position:fixed
     plus the centring inset/margin, and author styles win over UA styles
     on ANY matching property regardless of specificity, so a stray
     position:relative here used to silently strip that fixed behaviour
     and drop the dialog into normal document flow (it would then scroll
     away with the page while still open). .chooser-x below still gets a
     valid containing block, since fixed is a positioned value too. */
  border:0;padding:0;background:transparent;
  max-width:min(560px,calc(100vw - 32px));width:100%;
  color:var(--ink);
}
.chooser::backdrop{
  background:color-mix(in srgb,var(--ink) 45%,transparent);
  backdrop-filter:blur(6px);
}
.chooser-x{
  position:absolute;top:8px;right:8px;z-index:2;
  width:44px;height:44px;
  display:inline-flex;align-items:center;justify-content:center;
  color:var(--ink-soft);
  transition:color 200ms var(--ease);
}
.chooser-x:hover,.chooser-x:focus-visible{color:var(--ink)}
.chooser-in{
  background:var(--paper);border:var(--rule);
  padding:clamp(24px,3.4vw,38px);
}
.chooser h2{font-size:clamp(24px,2.4vw,32px);margin:0 0 6px;padding-right:36px}
.chooser > .chooser-in > p{color:var(--ink-soft);font-size:14.5px;margin:0 0 22px}
.chooser ul{list-style:none;margin:0;padding:0;display:flex;flex-direction:column}
.chooser li + li{border-top:var(--rule)}
/* round 7, item 2/5: rows are name + location only now, so this is a
   single flex child (.chooser-main), not two baseline-aligned columns any
   more; display:flex + align-items:center is enough on its own, no wrap
   or gap left to manage. */
.chooser li a{
  display:flex;align-items:center;
  padding:14px 0;min-height:44px;text-decoration:none;
  transition:transform 260ms var(--ease);
}
.chooser li a:hover,.chooser li a:focus-visible{transform:translateX(9px)}
/* plain inline flow, not flex: flex would wrap at the span boundary and
   could strand the leading comma alone at the start of a line ("Grumpy
   Monkey" is long enough to trigger this at 390px). Normal text wrapping
   only ever breaks at a space, which keeps "Monkey," together. */
.chooser-main{display:inline}
.chooser-name{
  font-family:'Zodiak','Zodiak-fallback',Georgia,serif;font-size:19px;
}
.chooser-where{
  font-size:12.5px;color:var(--ink-faint);
}
.chooser-shut{
  margin-top:24px;font-size:13px;color:var(--ink-faint);
  border-bottom:1px solid var(--line);padding-bottom:2px;
}
.chooser-shut:hover{color:var(--ink)}

/* ============================================================
   THE TAKEOVER MENU (phase 4, the site's signature moment)
   Replaces the old mobile drawer and becomes the menu at every width.
   Built entirely by script (see buildTakeover() in shared.js) rather
   than duplicated into nine page files, for the same reason the venue
   plates and the booking chooser are: one source of truth, no drift.
   It is a JS-only affordance by definition, and nothing depends on it:
   with script off the inline header links stay visible at all widths
   instead (see .menu-btn's own comment above), so no navigation is
   ever behind a button that cannot open.
   ============================================================ */
.takeover{
  /* two local tokens rather than raw values in the rules below: paper at
     a fraction over ink, for muted text and hairlines on this one dark
     surface. 58% of paper over ink measures ~7.5:1, so the meta text
     here clears AA comfortably. */
  --tk-dim:  color-mix(in srgb, var(--paper) 58%, transparent);
  --tk-line: color-mix(in srgb, var(--paper) 15%, transparent);

  position:fixed;inset:0;
  z-index:40;               /* one below .masthead's 50, deliberately: the
                               header is re-skinned, not covered, so the
                               trigger button turns into the close X in
                               place. See .nav-open .masthead above. */
  display:block;
  color:var(--paper);
  overflow:hidden;
  /* The right-hand edge is dealt with on html.nav-open, not here -- see the
     note beside it below. Short version: nothing this element can be given
     reaches the reserved scrollbar gutter, so the gutter gets painted from
     the canvas instead. */
  /* deliberately NO background of its own. Phase 4 painted the panel ink
     here and revealed it with a clip-path curtain; phase 7 replaced that
     with the wave flood below, and the flood's own ink layer IS the
     panel's surface. A background here would sit under the rising water
     and there would be nothing to rise into. Reduced motion puts the ink
     back (see the bottom of this file), because there is no flood then. */
}
.takeover[hidden]{ display:none; }

/* ---- the takeover's right-hand edge, an old bug fixed in phase 7 ----
   html carries scrollbar-gutter:stable, so a strip of the window is
   permanently reserved for the scrollbar and is NOT part of the initial
   containing block. A position:fixed panel with inset:0 is therefore
   1425px wide inside a 1440px window, and a full-screen takeover shipped
   with a 15px stripe of paper down its right-hand edge. Screenshotted at
   1440, and present in the phase-4 build as well, so this predates the
   flood rather than arriving with it.

   Neither width:100vw nor a negative right offset fixes it, and both were
   tried and measured rather than reasoned about: 100vw resolves against
   the same gutter-less block (1425px), and right:-20px does widen the box
   to 1445px but html's own overflow-x:clip then clips the paint back to
   1425 anyway. The one thing that does reach the gutter is the canvas
   background, which is what this is. Giving html a background stops body's
   own paper from propagating to the canvas, so body keeps painting paper
   across its own box and only the gutter turns ink.

   Delayed and faded so it arrives with the ink waterline instead of
   snapping dark on the first frame. The transition is declared on the
   .nav-open rule ONLY, so it exists on the way in and not on the way out,
   and that asymmetry is deliberate rather than sloppy. Fading BACK is what
   causes trouble: a partly-transparent root background still suspends
   body's propagation to the canvas, so for the length of the fade the
   canvas is ink-over-white rather than paper. On a page whose body is
   shorter than the viewport -- the FAQ is 1559px tall against a half-4K
   2160px window -- that showed up as a grey band across the bottom for a
   moment after the panel had drained past it. Measured at 1920x2160,
   which is why it was caught. Removing the class with no transition on it
   returns the canvas to paper in one frame and the band never exists. */
html.nav-open{ background:var(--ink);transition:background-color 320ms var(--ease) 200ms; }
/* the panel is the focus target on open (see openNav) purely so the dialog
   is announced; it is not an interactive control, so it shows no ring */
.takeover:focus{ outline:none; }

/* ---- THE WAVE FLOOD (phase 7) ----
   The menu no longer wipes down from the header, it floods UP from the
   bottom of the viewport with a wave on its leading edge, the way water
   rises up a page. Three layers, built in shared.js (buildFlood): a peach
   crest running out in front, a deeper peach behind it, and the ink body
   that stays as the settled panel once the two peach bands have been
   squeezed off the top.

   HOW IT IS BUILT, and why it is built this way. Every frame of this is
   one composited transform per layer -- no clip-path animated per frame,
   no canvas redrawn per frame, nothing scheduled on the rAF loop at all.
   Each .tk-wave is a viewport-sized carrier that is translated; inside it
   sit a .tk-fill hanging three viewports DOWNWARD from the carrier's top
   edge, and a .tk-crest pinned at bottom:100% of that edge. So the crest
   always rides the waterline, in front on the way up and in front again on
   the way down, and the fill behind it is effectively bottomless.

   That bottomless fill is what makes the whole thing safe rather than
   fiddly. Three layers with three different distances and three different
   durations will not stay in order by arithmetic; sooner or later a fast
   layer overtakes a slow one mid-flight. Because each fill covers
   EVERYTHING below its own waterline, overtaking is harmless: the screen
   is always a band of peach over a band of deeper peach over ink, and a
   stripe of the page can never appear between two of them.

   THE NUMBERS. Rest is calc(100% + 130px), i.e. one viewport down plus
   enough clearance that no crest peeks above the bottom edge in the single
   frame between the panel being unhidden and .is-in landing. From there:

     layer  colour        travel          duration  delay   crest on screen
     1      --peach       116% + 130px    520ms     0ms     ~20 to ~150ms
     2      --peach-deep  112% + 130px    660ms     45ms    ~60 to ~255ms
     3      --ink         100% + 132px    660ms     110ms   ~120ms to cover

   Layer 1 covers the most ground in the least time, which is what puts it
   in front; the two stagger delays only sharpen an ordering the distances
   already create. Checked at 20ms intervals across the whole run: no layer
   ever overtakes the one in front of it, so the bands stay in colour order
   from first frame to last.

   Two different numbers both describe "how long this takes", and they are
   both true. The ink layer's transition is 660ms behind a 110ms delay, so
   it is NOMINALLY done at 770ms. But --ease-exp is heavily front-loaded,
   and the ink waterline actually reaches the top of the screen at ~520ms;
   everything after that is the last one percent settling. So the flood
   reads as roughly half a second and finishes inside 780ms, which is what
   the brief asked for. The first schedule tried here had layer 1 running
   138% in 620ms, which threw the leading crest off the top of the screen
   by 108ms -- the swell was over before it was legible.

   OUT is 470ms on the ink alone, and the two peach layers snap home with
   no transition. They are underneath the ink at the moment the close
   starts, so animating them would only risk a peach band being revealed
   from under a receding panel; snapping them leaves a clean drain, one ink
   sheet dropping away with its own waterline still wavy. ---- */
.tk-flood{
  position:absolute;inset:0;z-index:0;
  overflow:hidden;            /* crests that overshoot the top are clipped here */
  pointer-events:none;
}
.tk-wave{
  position:absolute;left:0;right:0;top:0;height:100%;
  transform:translateY(calc(100% + 130px));
  will-change:transform;
}
.tk-fill{
  position:absolute;left:0;right:0;top:0;height:300%;
  background:currentColor;
}
/* one pixel of overlap onto the fill, so no rounding of the layer's own
   transform can open a hairline between the crest and the water under it */
.tk-crest{
  position:absolute;bottom:calc(100% - 1px);
  display:block;fill:currentColor;
}

/* colour, distance and crest geometry per layer. `color` drives both the
   fill's background and the crest's fill, so a layer's colour is stated
   once. The crests are given different widths and different negative
   left-offsets on purpose: all three share one 1200-unit path grid, and
   stretching that grid across 148%, 126% and 132% of the viewport is what
   stops the three from reading as one shape at three speeds. */
.tk-wave--1{ color:var(--peach);      --tk-in:-16%; }
.tk-wave--2{ color:var(--peach-deep); --tk-in:-12%; }
.tk-wave--3{ color:var(--ink);        --tk-in:-2px; }

/* the heights are close together on purpose. An earlier pass had the ink
   crest at roughly half the leading crest's height, and because the ink is
   the edge you watch all the way to full cover, that is the one that
   cannot afford to be the shallow one. */
.tk-wave--1 .tk-crest{ left:-24%;width:148%;height:clamp(34px,7.6vw,118px); }
.tk-wave--2 .tk-crest{ left:-9%; width:126%;height:clamp(30px,6.4vw,100px); }
.tk-wave--3 .tk-crest{ left:-16%;width:132%;height:clamp(28px,6vw,94px); }

.takeover-in{
  position:relative;z-index:1;
  height:100%;
  display:flex;flex-direction:column;
  overflow-y:auto;overscroll-behavior:contain;
  padding-inline:var(--gutter);
  padding-top:clamp(94px,11vh,140px);
  padding-bottom:clamp(28px,5vh,56px);
}
/* margin-block:auto rather than justify-content:center, which strands the
   top of an over-tall panel above the scroll origin in every browser */
.takeover-grid{
  width:100%;margin-block:auto;
  display:grid;grid-template-columns:1fr;
  gap:clamp(30px,4vh,44px);
}
@media(min-width:900px){
  /* the venue column is capped at a real reading width rather than taking a
     share of a 1920 viewport, where its rows stretched into a name at one
     edge and a location at the other. The pages column takes the slack,
     which is what gives the panel its asymmetry. */
  .takeover-grid{
    grid-template-columns:minmax(0,1fr) minmax(280px,430px);
    gap:clamp(40px,5vw,110px);
    align-items:end;
  }
}

/* ---- group one: the pages, at display size ---- */
.tk-pages{ list-style:none;margin:0;padding:0; }
.tk-pages a{
  display:inline-block;position:relative;
  text-decoration:none;color:var(--paper);
  font-family:'Zodiak','Zodiak-fallback',Georgia,'Times New Roman',serif;
  font-weight:400;letter-spacing:-.024em;
  font-size:clamp(34px,9.2vw,74px);
  line-height:1.08;
  padding-block:clamp(3px,.45vw,7px);
}
@media(min-width:900px){
  .tk-pages a{ font-size:clamp(44px,5.2vw,74px); }
}
/* the same underline device as .nav a and .news-all, at display weight.
   [aria-current] leaves it permanently drawn, which is how the visitor's
   current page is marked (the Nils reference's "one highlighted item",
   done with the house's own mechanism rather than a new one). */
.tk-pages a::after{
  content:"";position:absolute;left:0;right:0;bottom:.18em;height:2px;
  background:var(--peach);transform:scaleX(0);transform-origin:left;
  transition:transform 460ms var(--ease-grow);
}
.tk-pages a:hover::after,
.tk-pages a:focus-visible::after,
.tk-pages a[aria-current="page"]::after{ transform:scaleX(1); }

/* ---- group two: the six venues, compact, with their accent hairlines ---- */
.tk-label{
  font-size:13px;color:var(--tk-dim);
  margin:0 0 clamp(8px,1vw,14px);
}
.tk-venues{ list-style:none;margin:0;padding:0; }
.tk-venues li + li .tk-venue{ border-top:1px solid var(--tk-line); }
.tk-venue{
  display:flex;align-items:center;gap:13px;
  min-height:44px;padding-block:clamp(7px,.9vw,11px);
  text-decoration:none;color:var(--paper);
}
.tk-venue::before{
  content:"";flex:none;width:26px;height:3px;
  background:var(--accent,var(--peach));
  transform:scaleX(.6);transform-origin:left;
  transition:transform 460ms var(--ease-grow);
}
.tk-venue:hover::before,.tk-venue:focus-visible::before{ transform:scaleX(1); }
.tk-venue-name{
  font-family:'Zodiak','Zodiak-fallback',Georgia,serif;
  font-size:clamp(16px,1.15vw,19px);line-height:1.2;
  transition:color 200ms var(--ease);
}
.tk-venue:hover .tk-venue-name,.tk-venue:focus-visible .tk-venue-name{ color:var(--peach); }
.tk-venue-where{
  margin-left:auto;padding-left:14px;
  font-size:12px;color:var(--tk-dim);text-align:right;
}
@media(max-width:520px){ .tk-venue-where{ display:none; } }

.tk-actions{
  margin-top:clamp(18px,2.4vw,28px);
  display:flex;flex-wrap:wrap;gap:12px 18px;align-items:center;
}
.tk-book{
  display:inline-flex;align-items:center;min-height:44px;
  padding:10px 20px;font-size:13.5px;font-weight:600;
  border:1px solid color-mix(in srgb,var(--paper) 42%,transparent);
  color:var(--paper);text-decoration:none;
  transition:background-color 260ms var(--ease),color 260ms var(--ease),
             border-color 260ms var(--ease);
}
.tk-book:hover,.tk-book:focus-visible{
  background-color:var(--paper);color:var(--ink);border-color:var(--paper);
}

/* ============================================================
   THE FLOATING PILL (phase 4)
   The nilsamsee steal: a persistent action bar at the bottom of the
   viewport. Its Book action opens the venue CHOOSER, never a booking
   -- "you don't book Mustang Group" -- exactly as the header's own
   Book button already does. Script-built for the same reason as the
   takeover; everything it points at is reachable without it.
   ============================================================ */
/* Phase 7 splits this into three: a round icon cap, the text group, and a
   second round cap. The surface (paper, radius, shadow) moved OFF .pill
   and onto the group and the caps individually, because the whole point of
   the client's ask is that the two icons read as their own controls with
   daylight around them, not as two more segments of one long bar. .pill
   itself is now just the positioner and the row. */
.pill{
  position:fixed;z-index:45;
  left:50%;bottom:calc(18px + env(safe-area-inset-bottom,0px));
  transform:translate(-50%,8px);
  opacity:0;pointer-events:none;
  display:flex;align-items:stretch;gap:8px;
  max-width:calc(100vw - 24px);
}
.pill.is-up{ transform:translate(-50%,0);opacity:1;pointer-events:auto; }
/* never floats over the takeover */
.nav-open .pill{ transform:translate(-50%,8px);opacity:0;pointer-events:none; }

/* the shared surface of all three pieces, stated once */
.pill-group,.pill-cap{
  background:var(--paper);
  box-shadow:0 8px 26px -14px color-mix(in srgb,var(--ink) 42%,transparent),
             0 1px 2px color-mix(in srgb,var(--ink) 8%,transparent);
}
.pill-group{
  display:flex;align-items:stretch;min-width:0;
  border-radius:999px;
  /* the primary action's fill is a rounded left cap of its own; clipping
     here is what keeps it flush inside the group's radius rather than a
     peach rectangle poking through it */
  overflow:hidden;
}
.pill-cap{
  flex:none;
  display:inline-flex;align-items:center;justify-content:center;
  width:46px;height:46px;border-radius:50%;
  color:var(--ink-soft);
  transition:color 200ms var(--ease), background-color 200ms var(--ease);
}
/* 46px square, so the cap clears the 44px minimum target on its own without
   needing padding to make up the difference */
.pill-cap svg{
  display:block;width:19px;height:19px;
}
.pill-cap:hover,.pill-cap:focus-visible{
  color:var(--ink);background:var(--paper-deep);
}

.pill-btn{
  display:inline-flex;align-items:center;justify-content:center;
  min-height:44px;padding:0 clamp(13px,1.5vw,20px);
  font-size:13.5px;color:var(--ink-soft);text-decoration:none;
  white-space:nowrap;
  transition:color 200ms var(--ease);
}
.pill-btn + .pill-btn{ border-left:var(--rule); }
.pill-btn:first-child{
  padding-left:clamp(17px,1.9vw,24px);color:var(--ink);font-weight:600;
}
.pill-btn:last-child{ padding-right:clamp(17px,1.9vw,24px); }
.pill-btn:hover,.pill-btn:focus-visible{ color:var(--peach-text); }
@media(max-width:420px){
  .pill{ bottom:calc(16px + env(safe-area-inset-bottom,0px));gap:6px; }
  /* the caps hold their 44px target; only the text group gives ground,
     because a shrunken icon button is a missed tap and a shrunken label is
     just a shorter word */
  .pill-cap{ width:44px;height:44px; }
  .pill-btn{ font-size:12.5px;padding:0 9px; }
  .pill-btn:first-child{ padding-left:14px; }
  .pill-btn:last-child{ padding-right:14px; }
}

/* ============================================================
   ONE authored entrance. Nothing else moves on scroll.
   ============================================================ */
/* Progressive enhancement, deliberately.
   The hidden start state is scoped to .js, which is only set by script, and a
   failsafe timer reveals everything regardless. Nothing on this page is ever
   invisible because an animation did not run: no JS, a throttled background
   tab, a dead IntersectionObserver and reduced-motion all end up with readable
   content. */
@media(prefers-reduced-motion:no-preference){
  /* no html{scroll-behavior:smooth} here: Lenis owns smooth in-page
     scrolling when it's running, and the two would fight if both were
     live at once. Anchor clicks are routed through Lenis in the script
     below; with JS off entirely they still work, just as an instant
     native jump rather than an eased one. */

  .js .reveal{
    opacity:0;
    transform:translateY(8px);
    transition:opacity var(--slow) var(--ease),
               transform var(--slow) var(--ease);
    transition-delay:var(--d,0ms);
  }
  .js .reveal.seen{opacity:1;transform:none}

  /* ---- C1: the hero's own one-off load sequence, separate from the
     scroll .reveal system above. One rAF-chained class add on <html>
     (ls-go) fires every staggered entrance at once; --d does the staggering. ---- */
  .js .ls{
    opacity:0;
    transform:translateY(10px);
    transition:opacity 560ms var(--ease), transform 560ms var(--ease);
    transition-delay:var(--d,0ms);
  }
  .js.ls-go .ls{ opacity:1; transform:none }
  /* the monogram's load entrance is opacity-only and lives on the INNER
     element, deliberately never on .hero-mark itself (see that rule's own
     comment): .hero-mark carries the per-frame scroll transform and must
     never own a transform transition, so it never gets the shared .ls
     class at all. This uses --mark-op rather than a literal opacity value
     so the two breakpoints (.25 / .32) stay defined in exactly one place. */
  .js .hero-mark-inner{
    opacity:0;
    transition:opacity 560ms var(--ease);
    transition-delay:var(--d,0ms);
  }
  .js.ls-go .hero-mark-inner{ opacity:var(--mark-op); }

  /* the hero strip is the sequence's finale. Item 14: the client does not
     like the swipe-in effect, so this is now a plain fade, opacity only,
     no clip-path wipe and no scale on the figure or the image. The strip
     is never invisible with JS off: the opacity:0 start state is scoped
     to .js, which script alone sets, and the .ls-go/3s failsafes below
     still apply the same way as every other .ls element on the page. */
  .js .hero-strip{
    opacity:0;
    transition:opacity 700ms var(--ease) var(--d,0ms);
  }
  .js.ls-go .hero-strip{ opacity:1; }

  /* ---- C4: booking dialog entrance ---- */
  .chooser{
    transition:opacity 280ms var(--ease), transform 280ms var(--ease),
               overlay 280ms allow-discrete, display 280ms allow-discrete;
  }
  .chooser::backdrop{
    transition:opacity 240ms var(--ease),
               overlay 240ms allow-discrete, display 240ms allow-discrete;
  }
  @starting-style{
    .chooser[open]{ opacity:0; transform:translateY(12px); }
    .chooser[open]::backdrop{ opacity:0; }
  }

  /* ---- C7: the occasion count fades/slides in as its own span ---- */
  .occ-count{
    opacity:1;transform:translateX(0);
    transition:opacity 180ms var(--ease), transform 180ms var(--ease);
  }
  @starting-style{
    .occ-count{ opacity:0; transform:translateX(4px); }
  }

  /* ---- P7a: the takeover's wave flood ----
     Everything animated about the takeover lives in here, so under
     reduced-motion the panel simply IS open the moment the class lands:
     no flood, no crests, no stagger (and no flood ELEMENT at all -- script
     never constructs one, see buildTakeover() in shared.js).
     Full cover in 780ms; the drain out is 470ms. Exit faster than
     entrance, which is what reads as draining rather than reversing.
     The distances and the reasoning behind them are up at .tk-flood. */
  .tk-wave{
    transition:transform 520ms var(--ease-exp);
  }
  .tk-wave--2{ transition-duration:660ms;transition-delay:45ms; }
  .tk-wave--3{ transition-duration:660ms;transition-delay:110ms; }
  .takeover.is-in .tk-wave{ transform:translateY(var(--tk-in)); }

  /* the drain: the ink sheet drops away on its own, with its crest still
     riding the waterline. The two peach layers are behind it and already
     off the top, so they go straight home rather than sweeping back down
     through the panel that is covering them.

     --ease-grow rather than --ease-exp, and it is the one place in this
     system the two differ. Expo-out is right for the way in, where the
     water should surge and then settle. Run in reverse it puts 86% of the
     drop in the first 130ms, which does not read as water leaving, it
     reads as the panel being switched off; screenshotted mid-drain, which
     is how this was caught. Quart-out is the house's other ease-out and it
     spreads the same 470ms enough for the wavy edge to be legible on the
     way down. Same family, no new curve. */
  .takeover.is-closing .tk-wave--3{
    transition:transform 470ms var(--ease-grow);transition-delay:0ms;
  }
  .takeover.is-closing .tk-wave--1,
  .takeover.is-closing .tk-wave--2{ transition:none; }

  /* the content stagger, riding in behind the curtain. --d is written per
     item by script (40ms steps, capped inside buildTakeover). Closing
     collapses every delay to zero so the panel does not have to unwind
     item by item on the way out. */
  .takeover .tk-item{
    opacity:0;transform:translateY(14px);
    transition:opacity 520ms var(--ease-exp), transform 520ms var(--ease-exp);
    transition-delay:var(--d,0ms);
  }
  .takeover.is-in .tk-item{ opacity:1;transform:none; }
  .takeover.is-closing .tk-item{
    transition-duration:150ms;transition-delay:0ms;
  }

  /* ---- P4b: the floating pill's 8px rise ---- */
  .pill{
    transition:opacity 420ms var(--ease-exp), transform 420ms var(--ease-exp);
  }
}
@media(prefers-reduced-motion:reduce){
  *{animation-duration:.001ms !important;transition-duration:.001ms !important}
  /* the takeover has no surface of its own now -- the flood's ink layer is
     its surface (see .takeover above). With no flood there has to be one,
     so it gets painted here instead. Belt and braces: script already skips
     buildFlood() under reduced motion, so .tk-flood normally does not
     exist at all, and this hides it if the preference flips mid-session
     after the panel was built. The panel simply appears, fully covered,
     with no crest ever drawn. */
  .takeover{ background:var(--ink); }
  .tk-flood{ display:none; }
}

/* ============================================================
   PAGE COMPONENTS (phase 2)
   Added for the four content pages built in this phase: private hire,
   christmas, faq, contact. Everything below is used by two or more of
   them; anything genuinely single-page lives in that page's own <style>
   block instead. Same tokens, same hairline language as everything
   above, no new colours or type introduced.
   ============================================================ */

/* ---- content band: a hairline, a plain-label header, then the band's
   own body. This is how "sections separated by hairlines" is built on
   every page below, the same instinct as .plates-head above but generic
   rather than venue-specific. ---- */
.band{
  border-top:var(--rule);
  padding-block:clamp(40px,5.6vw,72px);
}
.band-head{
  display:flex;flex-wrap:wrap;gap:14px 40px;align-items:start;
  padding-bottom:clamp(20px,2.6vw,30px);
}
.band-head h2{
  font-size:clamp(28px,3.4vw,46px);line-height:1.02;max-width:20ch;
}
.band-head p{
  margin:0;color:var(--ink-soft);font-size:15px;max-width:46ch;
  margin-left:auto;
}
@media(max-width:660px){
  .band-head p{ margin-left:0; }
}
.band-lede{
  max-width:var(--measure);color:var(--ink-soft);
  font-size:clamp(15.5px,.4vw + 14px,16.5px);line-height:1.62;
  margin:0 0 clamp(20px,2.6vw,30px);
}
.band-lede + .band-lede{ margin-top:-10px; }

/* ---- capability / venue rows: private hire's celebrations+business
   lists and christmas's per-venue list share this exact shape, so it
   lives here rather than twice. A left name column (with the venue's own
   accent tick, reusing the --grand/--ocean/etc custom properties already
   declared in :root) and a right body column; stacks on mobile. ---- */
.rows{ list-style:none;margin:0;padding:0; }
.row{
  border-top:var(--rule);
  padding-block:clamp(26px,3.4vw,40px);
  display:grid;
  grid-template-columns:minmax(180px,260px) 1fr;
  gap:clamp(6px,2vw,40px) clamp(20px,3vw,48px);
}
.rows > .row:first-child{ border-top:0; }
.row-name{ display:flex;flex-direction:column;gap:8px; }
.row-name::before{
  content:"";display:block;width:32px;height:3px;
  background:var(--accent,var(--peach));
}
.row-name h3{
  font-family:'Zodiak','Zodiak-fallback',Georgia,serif;
  font-size:clamp(19px,1.6vw,24px);line-height:1.1;margin:0;
}
.row-name a{
  font-size:12.5px;color:var(--ink-faint);text-decoration:none;
  transition:color 200ms var(--ease);
}
.row-name a:hover,.row-name a:focus-visible{ color:var(--peach-text); }
.row-kind{ font-size:13px;color:var(--ink-faint);margin:0; }
.row-body p{
  margin:0;color:var(--ink-soft);font-size:15px;line-height:1.6;max-width:56ch;
}
.row-body p + p{ margin-top:10px; }
@media(max-width:660px){
  .row{ grid-template-columns:1fr; gap:10px; }
}

/* ---- accordion: FAQ's whole page, native <details>/<summary>, no JS
   involved anywhere (it works with script disabled exactly as it does
   with it on). A drawn plus/minus, not a unicode glyph or icon font. ---- */
.faq-list{ margin:0;padding:0; }
.faq-item{ border-top:var(--rule); }
.faq-list > .faq-item:first-child{ border-top:0; }
.faq-item summary{
  list-style:none;
  display:flex;align-items:center;justify-content:space-between;gap:24px;
  padding-block:clamp(20px,2.6vw,26px);
  cursor:pointer;
  font-family:'Zodiak','Zodiak-fallback',Georgia,serif;
  font-size:clamp(17px,1.4vw,20px);line-height:1.3;
  color:var(--ink);
  transition:color 200ms var(--ease);
}
.faq-item summary::-webkit-details-marker{ display:none; }
.faq-item summary::marker{ content:""; }
.faq-item summary:hover,.faq-item summary:focus-visible{ color:var(--peach-text); }
.faq-icon{ flex:none;position:relative;width:18px;height:18px; }
.faq-icon::before,.faq-icon::after{
  content:"";position:absolute;background:currentColor;
  top:50%;left:50%;transform:translate(-50%,-50%);
}
.faq-icon::before{ width:18px;height:1.4px; }
.faq-icon::after{
  width:1.4px;height:18px;
  /* the marker rotates rather than scaling away: the upright rule turns
     through 90 degrees and lands exactly on top of the horizontal one
     (both are 18 x 1.4), so a plus becomes a minus by rotation alone and
     the rotation itself is the thing you see. Timed and eased with the
     panel below so the two are one movement. */
  transition:transform 340ms var(--ease-exp);
}
.faq-item[open] .faq-icon::after{ transform:translate(-50%,-50%) rotate(90deg); }

/* ---- the panel's own open/close, phase 4 ----
   ::details-content as a one-row grid going 0fr -> 1fr. Height is the
   thing genuinely being animated here, and there is no transform that can
   fake it honestly, so this uses the grid-rows technique rather than
   animating height/max-height directly: it is the one sanctioned way to do
   this, and it keeps the file clean under the detector.
   Everything is inside @supports, so a browser without ::details-content
   gets the plain instant native open it has always had, and everything is
   inside the reduced-motion gate, so a visitor who asked for no motion
   gets that same instant open. Content is never hidden by this: the
   closed state is the browser's own closed <details>. ---- */
@supports selector(::details-content){
  @media(prefers-reduced-motion:no-preference){
    .faq-item::details-content{
      display:grid;
      grid-template-rows:0fr;
      overflow:hidden;
      transition:grid-template-rows 340ms var(--ease-exp),
                 content-visibility 340ms allow-discrete;
    }
    /* the row can only collapse to zero if its own child is allowed to */
    .faq-item > :not(summary){ min-height:0; }
    .faq-item[open]::details-content{ grid-template-rows:1fr; }
  }
}
.faq-a{
  margin:0 0 clamp(22px,2.8vw,30px);
  color:var(--ink-soft);font-size:15px;line-height:1.6;max-width:60ch;
  padding-right:42px;
}

/* ---- pull-quote: the one real, non-self-praising review a band is
   allowed to carry, set apart with its own top/bottom hairline rather
   than a card. Same italic Zodiak treatment as .review-quote elsewhere,
   kept separate because it stands alone here rather than inside the
   three-column grid .reviews-in provides on the homepage. ---- */
.pull-quote{
  border-top:var(--rule);border-bottom:var(--rule);
  margin:clamp(8px,1.4vw,16px) 0;
  padding:clamp(28px,4vw,40px) 0;
}
.pull-quote p{
  font-family:'Zodiak','Zodiak-fallback',Georgia,serif;
  font-style:italic;color:var(--ink);
  font-size:clamp(20px,2.2vw,27px);line-height:1.28;
  margin:0 0 14px;max-width:34ch;
}
.pull-quote cite{
  display:block;font-style:normal;font-size:13px;color:var(--ink-faint);
}

/* ---- standalone arrow CTA: the same visual move as .panel-go, but not
   dependent on a .panel ancestor for its hover state, for the plain-text
   enquiry links on private hire, christmas and contact. ---- */
.cta-arrow{
  display:inline-flex;align-items:center;gap:9px;min-height:44px;
  color:var(--peach-text);text-decoration:none;font-size:14px;font-weight:600;
}
.cta-arrow svg{ transition:transform 300ms var(--ease); }
.cta-arrow:hover svg,.cta-arrow:focus-visible svg{ transform:translateX(5px); }

/* ---- visible build note: a fact that isn't confirmed yet, marked
   plainly rather than invented. Deliberately not hidden, so it stays
   findable when Kirsty reviews the demo. ---- */
.tbc{ color:var(--peach-text);font-style:italic; }

/* ============================================================
   NEWS INDEX + ARTICLE (phase 3)
   /news/ itself reuses the homepage's own .news-grid/.news-lead/
   .news-story/.news-date/.news-all rules VERBATIM (see the NEWS
   block far above) -- same lead-plus-two-secondaries editorial
   split, same hairlines, no new grid of its own. This block adds
   only what a single ARTICLE page needs that the listing doesn't
   already have: the back link's own top spacing, the headline/
   byline block, the hero photo frame, and the running body copy.
   ============================================================ */
.article-back{
  padding-top:clamp(20px,3vw,30px);
}
.article-head{
  padding-top:clamp(28px,3.6vw,44px);
  padding-bottom:clamp(24px,3vw,34px);
}
.article-title{
  font-size:clamp(30px,4.2vw,50px);
  line-height:1.08;
  max-width:20ch;
  margin:0 0 14px;
}
.article-byline{
  font-size:14px;color:var(--ink-soft);margin:0;
}
.article-hero{
  margin:0 0 clamp(32px,4.4vw,56px);
  max-width:min(920px,100%);
}
/* the AA Rosette post's only available photo is the original 640px press
   shot (see the build report): capping ITS hero at its own native width,
   rather than the 920px default above, keeps it honestly sized instead of
   upscaled soft. */
.article-hero.is-compact{ max-width:min(640px,100%); }
.article-hero-frame{
  display:block;position:relative;aspect-ratio:3/2;overflow:hidden;
  background:var(--paper-deep);
}
.article-hero-frame img{
  width:100%;height:100%;object-fit:cover;
  filter:saturate(.9) contrast(1.03);
}
.article-body{ max-width:68ch; }
.article-body p{
  font-size:clamp(16px,.6vw + 14.4px,18.5px);
  line-height:1.72;
  color:var(--ink);
  margin:0 0 22px;
}
.article-body p:last-child{ margin-bottom:0; }
.article-foot{
  padding-top:clamp(8px,1.2vw,14px);
  padding-bottom:clamp(56px,7vw,96px);
}

/* the index page's own listing wrapper: same .news-grid as the homepage,
   but standalone -- no .news border-bottom, which exists there only to
   separate the section from the hire/vouchers panels that follow it on
   THAT page. Reusing it here too would sit the rule flush against the
   footer's own border-top and read as a doubled line. */
.news-listing{
  padding-top:clamp(8px,1.4vw,16px);
  padding-bottom:clamp(56px,7vw,96px);
}

/* ============================================================
   QC FIXES (post phase 6)
   ============================================================ */

/* Interior pages set the h1 and its lede as siblings with no gap, so a 96px
   headline's descenders collided with the intro's cap height. Spaced off the
   h1's own size so it scales with the clamp. */
.page-hero h1, main > .hero h1, .hero > h1 { margin-block-end:.38em }

/* The venue meta line was nowrap+ellipsis on phones, which truncated Ennios
   and Grumpy Monkey mid-address at 390 and 412 (his own handset width).
   Two tidy lines beat a clipped one. */
@media(max-width:720px){
  .plate-meta{ white-space:normal; text-overflow:clip; overflow:visible }
}

/* The two panel headings sat one gutter step deeper than every other heading
   on the page (L44 vs L22 at 390). Inherit the standard gutter. */
.panel .panel-in{ padding-inline:0 }

/* The FAQ's last question sat under the floating pill at 390. Reserve the
   pill's height plus its offset below the list so nothing is ever covered. */
@media(max-width:719.98px){
  .faq-list, #faq-list{ padding-block-end:96px }
}

/* The pill carried a 1px hairline under a 26px shadow, the thin-border-wide-
   shadow pattern the detector flags. Keep the elevation, drop the outline. */
.pill{ border:0 }

/* The christmas confirm-with-Kirsty note ran the full 1737px content width at
   1920 (~204 characters a line). */
.tbc{ max-width:62ch }

/* Display italic pull quotes sat at 1.28 leading, under the 1.3 floor. */
.pull-quote, .pull-quote p, blockquote{ line-height:1.34 }

/* The accordion stretched a 615px question across 1737px, leaving a huge void
   before the plus marker and a much narrower answer beneath it. */
.faq-list, #faq-list{ max-width:900px }

/* Book at a venue is the pill's primary action, so it carries the brand peach
   rather than sitting as one of several equal text links. Ink text on peach
   measures 9.97:1. The rounded left cap matches the pill's own radius so the
   fill reads as part of the shape, not a sticker on top of it.
   (Phase 7 moved the surface onto .pill-group and added the two round icon
   caps either side of it; this rule is unchanged and still applies, since
   Book is still the group's first child. The "three equal text links" this
   originally distinguished itself from are now two -- Gift vouchers left the
   middle group when the gift cap took over that job.) */
.pill-btn--primary{
  background:var(--peach);
  color:var(--ink);
  font-weight:600;
  border-radius:999px 0 0 999px;
  margin-inline-end:2px;
}
.pill-btn--primary:hover,.pill-btn--primary:focus-visible{
  background:var(--peach-deep,#C8825F);
  color:var(--ink);
}
