/* =====================================================================
   AMBIORA HOTELS — v2 stylesheet (new direction)

   Self-contained on purpose. This file shares nothing with the live
   site's tokens.css, and the live site never loads it — the two designs
   can drift apart without either breaking the other.

   The direction in one line: sans-led, built from solid blocks of the
   brand red, espresso and bone, with 8px corners. The opposite of the
   current site's serif-only hairlines on ivory.
   ===================================================================== */

:root {

  /* ---- Colour -------------------------------------------------------
     Grounds are used as FILLS behind whole panels, not as tints. The
     red in particular is meant to be impossible to miss; it is not a
     metallic hairline accent.
  ------------------------------------------------------------------- */
  --bone:       #F5F1E8;
  --clay:       #E3D5C3;
  /* Darkened from #7E8C6B, where bone text sat at 3.18:1 against it on
     the ways-to-stay steps and the culture tile — an AA failure that
     predates the red. This reads 5.03:1. */
  --sage:       #5F6B4F;
  --espresso:   #2A211C;

  /* The brand red, sampled from the Ambiora Group logo rather than
     picked to sit near it: #CD2F24 is the median of every strongly-red
     pixel in the mark, and #B52825 is where its gradient bottoms out.

     It replaced a burnt terracotta (#C0562B) that was never in the
     identity and, at 4.04:1 on bone, quietly failed AA for body text.
     The red is 4.63:1 — the brand colour is the more accessible one. */
  --brand: #CD2F24;

  /* Small text needs to clear AA on clay as well as bone, and the logo
     red is only 3.62:1 on clay. This is deep enough for both: 6.43:1 on
     bone, 5.03:1 on clay. Fills and large type use --brand itself. */
  --brand-ink: #A82119;

  /* Pressed state for solid fills. */
  --brand-deep: #9E211D;

  /* Red on espresso is 3.02:1 — under AA for the tracked 11px labels it
     gets used for. No single red clears 4.5:1 on both bone and espresso
     (one needs luminance below 0.15, the other above 0.25), so the
     accent is a token that dark sections re-point at this lighter tint:
     4.66:1 on espresso. The old terracotta was 3.46:1 there and failed
     the same way — this fixes a bug the palette already had. */
  --brand-on-dark: #E8604F;

  /* What accent text actually resolves to. The default is the deep ink
     rather than the brand red itself, because accent text lands on bone
     AND on clay, and the red is only 3.62:1 on clay. The ink clears
     both (6.43 and 5.03). Dark sections re-point it below. Inherited,
     so anything nested inside one is correct without knowing it. */
  --brand-accent: var(--brand-ink);

  --ink:        #2A211C;
  --ink-70:     rgba(42, 33, 28, 0.72);
  --ink-45:     rgba(42, 33, 28, 0.55);
  --rule:       rgba(42, 33, 28, 0.14);
  --rule-strong:rgba(42, 33, 28, 0.24);

  --on-dark:    #F5F1E8;
  --on-dark-70: rgba(245, 241, 232, 0.72);
  --on-dark-45: rgba(245, 241, 232, 0.52);
  --rule-dark:  rgba(245, 241, 232, 0.18);

  /* ---- Type ---------------------------------------------------------
     Two families, hard contrast. Epilogue carries every display size and
     every numeral; Work Sans carries body and labels. No serif anywhere
     — that absence is the point of this direction.
  ------------------------------------------------------------------- */
  --font-display: "Epilogue", "Segoe UI", system-ui, sans-serif;
  --font-body:    "Work Sans", "Segoe UI", system-ui, sans-serif;

  --fs-hero:  clamp(2.75rem, 1rem + 5.2vw, 6rem);
  --fs-h2:    clamp(1.875rem, 1rem + 2.4vw, 3.25rem);
  --fs-h3:    clamp(1.5rem, 1.1rem + 1.4vw, 2.25rem);
  --fs-lede:  clamp(1rem, 0.95rem + 0.25vw, 1.125rem);
  --fs-body:  1rem;
  --fs-label: 0.6875rem;

  /* ---- Layout ------------------------------------------------------- */
  --gutter: clamp(1.25rem, 0.5rem + 3.6vw, 4.5rem);
  --max:    1600px;
  --hdr-h:  64px;
  --section-y: clamp(3.5rem, 2rem + 5vw, 7.5rem);

  /* ---- Shape -------------------------------------------------------- */
  --r: 8px;

  --ease: cubic-bezier(0.22, 0.61, 0.36, 1);
}


/* =====================================================================
   BASE
   ===================================================================== */

*, *::before, *::after { box-sizing: border-box; }

html { -webkit-text-size-adjust: 100%; }

body {
  margin: 0;
  background: var(--bone);
  color: var(--ink);
  font-family: var(--font-body);
  font-size: var(--fs-body);
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
  overflow-x: hidden;
}

h1, h2, h3 {
  font-family: var(--font-display);
  font-weight: 600;
  letter-spacing: -0.025em;
  line-height: 1.02;
  margin: 0;
  text-wrap: balance;
}

p { margin: 0; }
ul, ol { margin: 0; padding: 0; list-style: none; }
/* height: auto is not optional here. max-width alone clamps the WIDTH
   of an <img> that carries width/height attributes and leaves the
   height at its attribute value, so the picture is drawn to a shape
   it never had. The footer lockup was rendering 348x242 from a
   760x242 file — a 54% error — on every page. */
img { max-width: 100%; height: auto; }

a { color: inherit; text-decoration: none; }

:focus-visible {
  outline: 2px solid var(--brand);
  outline-offset: 3px;
}

.visually-hidden {
  position: absolute; width: 1px; height: 1px;
  margin: -1px; padding: 0; overflow: hidden;
  clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap;
}

/* Honeypot — present for bots, never for people. */
.trap { position: absolute; left: -9999px; opacity: 0; pointer-events: none; }

.skip {
  position: absolute; top: -100%; left: var(--gutter); z-index: 100;
  background: var(--espresso); color: var(--bone);
  padding: 0.75rem 1.25rem; border-radius: var(--r);
}
.skip:focus { top: 0.5rem; }


/* =====================================================================
   PRIMITIVES
   ===================================================================== */

/* --ink-70, not --ink-45. Eyebrows are 11px with 0.18em tracking, which
   is small text by WCAG's definition and needs 4.5:1 — the lighter token
   measured 3.52:1 on bone and 3.25:1 on clay, so every eyebrow on the
   site was failing AA. Changed here rather than on --ink-45 itself,
   because that token is correct at the larger sizes the other 38 rules
   using it are set at. */
.eyebrow {
  font-size: var(--fs-label);
  text-transform: uppercase;
  letter-spacing: 0.18em;
  font-weight: 600;
  color: var(--ink-70);
  margin-bottom: 1rem;
}
.eyebrow--accent { color: var(--brand-accent); }

.h2 { font-size: var(--fs-h2); }
.h2--onDark { color: var(--on-dark); }

.lede { font-size: var(--fs-lede); color: var(--ink-70); }

.rule {
  display: block;
  width: 60px; height: 3px;
  background: var(--brand);
  border-radius: 2px;
}

.btn {
  display: inline-flex; align-items: center; justify-content: center;
  font-family: var(--font-body);
  font-size: var(--fs-label);
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.16em;
  padding: 0.95rem 1.6rem;
  border: 0; border-radius: var(--r);
  cursor: pointer;
  transition: background-color 200ms var(--ease), color 200ms var(--ease);
}
.btn--solid { background: var(--brand); color: var(--bone); }
.btn--solid:hover { background: var(--brand-deep); }
.btn--bone { background: var(--bone); color: var(--espresso); }
.btn--bone:hover { background: #fff; }

.link {
  font-weight: 500;
  border-bottom: 1px solid currentColor;
  padding-bottom: 2px;
  transition: color 200ms var(--ease);
}
.link:hover { color: var(--brand-ink); }
.link--onDark { color: var(--on-dark); }
.link--onDark:hover { color: var(--brand-accent); }
.link--arrow::after { content: " \2192"; }

.chip {
  display: inline-block;
  background: var(--bone);
  color: var(--espresso);
  font-size: var(--fs-label);
  text-transform: uppercase;
  letter-spacing: 0.14em;
  font-weight: 600;
  padding: 0.5rem 0.85rem;
  border-radius: var(--r);
}

/* Figures — a big light numeral over a small tracked label. */
.fig__value {
  display: block;
  font-family: var(--font-display);
  font-size: clamp(1.75rem, 1.2rem + 1.6vw, 2.5rem);
  font-weight: 600;
  line-height: 1;
  color: var(--brand-accent);
}
.fig__label {
  display: block;
  margin-top: 0.4rem;
  font-size: 0.625rem;
  text-transform: uppercase;
  letter-spacing: 0.16em;
  font-weight: 600;
  color: var(--on-dark-45);
}
.fig--lg .fig__value { font-size: clamp(2.5rem, 1.5rem + 2.6vw, 4rem); }
.fig--lg .fig__label { color: var(--ink-45); }


/* =====================================================================
   FIGURE — the markup Img::render emits
   ===================================================================== */

.figure {
  position: relative;
  display: block;
  margin: 0;
  overflow: hidden;
  aspect-ratio: var(--ratio, 3 / 2);
  background: var(--clay);
  border-radius: var(--r);
}
.figure img {
  display: block;
  width: 100%; height: 100%;
  object-fit: cover;
}

/* Focal point. object-fit: cover crops from the centre, which is wrong
   for a building shot from the ground: the subject sits in the lower
   two thirds and the top third is empty sky, so a centred crop throws
   away the entrance and keeps the weather.

   Per-image rather than per-component, because the other houses in the
   same component are framed differently and centre is right for them. */
.figure--low img    { object-position: 50% 72%; }
.figure--lower img  { object-position: 50% 82%; }
.figure--bottom img { object-position: 50% 100%; }

/* The typographic placeholder, for a property with no photography yet.
   Deliberately not a stock photo. */
.figure--empty {
  display: flex; flex-direction: column;
  align-items: center; justify-content: center;
  gap: 0.35rem;
  background: var(--clay);
  color: var(--ink-45);
  text-align: center;
  padding: 1rem;
}
.figure__empty-label {
  font-family: var(--font-display);
  font-size: 1.1rem; font-weight: 600;
  color: var(--ink-70);
}
.figure__empty-note {
  font-size: 0.625rem;
  text-transform: uppercase;
  letter-spacing: 0.16em;
  /* Inherited --ink-45 from the parent, which is 3.25:1 at 10px on clay
     — under AA for small text. The placeholder is the one thing in the
     frame, so it should at least be legible. */
  color: var(--ink-70);
}
.figure--empty-quiet .figure__empty-label,
.figure--empty-quiet .figure__empty-note { display: none; }


/* =====================================================================
   HEADER
   ===================================================================== */

.hdr {
  position: sticky; top: 0; z-index: 50;
  background: var(--bone);
  border-bottom: 1px solid var(--rule);
}
.hdr__in {
  display: flex; align-items: center; gap: 2rem;
  max-width: var(--max); margin: 0 auto;
  min-height: var(--hdr-h);
  padding: 0 var(--gutter);
}

/* Nothing typographic left here. These were the settings for the word
   "Ambiora" that used to sit beside the mark; the lockup is one image
   now, so a font-family and a letter-spacing on its wrapper describe
   text that does not exist. .wordmark--onDark went with them — it was
   only ever a colour for that word, and it was already unused. */
.wordmark { white-space: nowrap; }

.hdr__nav { margin-inline: auto; }
.hdr__nav ul { display: flex; gap: clamp(1rem, 2vw, 2rem); }
.hdr__nav a {
  font-size: var(--fs-label);
  text-transform: uppercase;
  letter-spacing: 0.16em;
  font-weight: 600;
  color: var(--ink-70);
  padding-bottom: 3px;
  transition: color 200ms var(--ease);
}
.hdr__nav a:hover { color: var(--ink); }
.hdr__nav a[aria-current="page"] {
  color: var(--brand-ink);
  border-bottom: 1px solid var(--brand);
}

.hdr__cta { padding: 0.7rem 1.25rem; }


/* =====================================================================
   01 — CINEMATIC HERO
   The photograph carries it. The rail along the floor is what stops it
   being the usual full-bleed hero: three real buildings, one of which
   is behind the type at any moment.
   ===================================================================== */

.cine {
  position: relative;
  min-height: min(90svh, 840px);
  display: flex; flex-direction: column; justify-content: flex-end;
  overflow: hidden;
  background: var(--espresso);
  padding-top: clamp(3rem, 10vh, 7rem);
}

/* z-index: 0 is load-bearing, not decoration. The frames below stack
   against each other with z-index, and without a stacking context here
   those values escape into the hero's own context and paint the active
   photograph straight over the scrim and the headline. Everything that
   sits on top of the stage is given an explicit layer for the same
   reason — so adding a z-index inside the stage can never reach them. */
.cine__stage { position: absolute; inset: 0; z-index: 0; }

/* The dissolve is deliberately longer than the gap between changes is
   short: at 4.2s a hold and 1.6s a crossfade, roughly a third of the
   time two photographs are on screen at once. That overlap is the whole
   difference between a slideshow and something that reads as footage —
   a fast, clean cut would look like a bug. */
/* A TRUE cross-dissolve, which is not what fading both frames gives you.
   If the outgoing frame fades 1->0 while the incoming fades 0->1 over
   the espresso ground, the compositing works out to
   p·in + (1-p)²·out + p(1-p)·ground — so a quarter of the background
   shows through at the midpoint and every change dips to black.

   Instead the outgoing frame HOLDS at full opacity underneath and is
   cut only once the dissolve is over: a 0s transition with a 1600ms
   delay does exactly that. The incoming frame fades in above it, over
   a photograph rather than over the ground, so coverage is 1 the whole
   way across. The final snap is invisible because the frame above it
   is opaque by then. */
.cine__frame {
  position: absolute; inset: 0;
  opacity: 0;
  z-index: 1;
  transition: opacity 0s linear 1600ms;
}
.cine__frame.is-active {
  opacity: 1;
  z-index: 2;
  transition: opacity 1600ms cubic-bezier(0.4, 0, 0.2, 1) 0s;
}

/* The floor. Holding the outgoing frame opaque covers the timed cycle,
   but not an INTERRUPTED one: hovering along the rail can start a
   second dissolve while the first is still mid-fade, and a frame that
   froze at half opacity has nothing solid beneath it.

   This is a separate layer rather than the first frame doing double
   duty — pinning frame one opaque would mean one change in three
   arriving as a hard cut instead of a dissolve, which is far more
   visible than the artifact it fixes. It carries the same photograph
   as the resting frame, so it is a cache hit and never moves. */
.cine__floor {
  position: absolute; inset: 0;
  z-index: 0;
}
.cine__floor .figure {
  height: 100%;
  aspect-ratio: auto;
  border-radius: 0;
  background: var(--espresso);
}
.cine__floor .figure img { transform: scale(1.06); }
.cine__frame .figure {
  height: 100%;
  aspect-ratio: auto;
  border-radius: 0;
  background: var(--espresso);
}

/* The drift. A transition cannot do this: it would run once on arrival
   and then sit still for the rest of the hold, which is exactly what
   made the old stage read as a flat backdrop. An animation restarts
   every time the frame becomes active, so the photograph is never
   stationary while it is being looked at.

   Each frame pushes a different way — in, out, across — so three
   houses in a row do not all lean the same direction. The animation
   runs 14s against a ~5.8s visible life, so no frame ever reaches the
   end of its travel and the motion never visibly stops or snaps back. */
@keyframes cine-drift-a { from { transform: scale(1.00) translate3d(0, 0, 0); }        to { transform: scale(1.14) translate3d(-1.5%, -1%, 0); } }
@keyframes cine-drift-b { from { transform: scale(1.14) translate3d(1.5%, 1%, 0); }    to { transform: scale(1.00) translate3d(0, 0, 0); } }
@keyframes cine-drift-c { from { transform: scale(1.06) translate3d(2%, 0, 0); }       to { transform: scale(1.12) translate3d(-2%, -1.5%, 0); } }

.cine__frame .figure img { transform: scale(1.06); }

/* will-change is scoped to the frame in play. Declaring it on all nine
   would ask the compositor to hold nine full-bleed layers at once for a
   reel that only ever animates two of them — the outgoing frame holds
   at a fixed opacity and does not need a layer of its own. */
.cine__frame.is-active,
.cine__frame.is-active .figure img { will-change: transform, opacity; }
.cine__frame.is-active .figure img {
  animation: cine-drift-a 14s linear forwards;
}
.cine__frame:nth-child(3n+2).is-active .figure img { animation-name: cine-drift-b; }
.cine__frame:nth-child(3n+3).is-active .figure img { animation-name: cine-drift-c; }

/* Someone who has asked for less motion gets the photographs and the
   dissolve, but nothing that moves on its own. The auto-advance is
   stopped in JS for the same preference. */
@media (prefers-reduced-motion: reduce) {
  .cine__frame { transition: opacity 0s linear 300ms; }
  .cine__frame.is-active { transition: opacity 300ms linear 0s; }
  .cine__frame .figure img,
  .cine__frame.is-active .figure img { animation: none; transform: none; }
}

/* Two gradients, because the three frames behind this are not equally
   dark: Club House is a night shot, Northwind is bright daylight. A
   vertical scrim alone left the headline sitting on a pale terrace at
   roughly 2:1. The horizontal pass guarantees the left column — where
   all the type lives — is dark whichever photograph is showing, while
   the right side of the frame stays open. */
.cine__scrim {
  position: absolute; inset: 0; z-index: 1;
  background:
    linear-gradient(to right,
      rgba(22, 17, 14, 0.88) 0%,
      rgba(22, 17, 14, 0.62) 38%,
      rgba(22, 17, 14, 0.16) 72%,
      rgba(22, 17, 14, 0.04) 100%),
    linear-gradient(to top,
      rgba(22, 17, 14, 0.88) 0%,
      rgba(22, 17, 14, 0.46) 38%,
      rgba(22, 17, 14, 0.12) 70%,
      rgba(22, 17, 14, 0.42) 100%);
}

.cine__in {
  position: relative; z-index: 2;
  width: 100%; max-width: var(--max);
  margin-inline: auto;
  padding: 0 var(--gutter) clamp(1.75rem, 3.5vw, 3rem);
  color: var(--on-dark);
}
.cine__title {
  font-size: clamp(2.75rem, 0.4rem + 6.6vw, 7rem);
  font-weight: 600;
  letter-spacing: -0.042em;
  line-height: 0.9;
}
.cine__lede {
  margin-top: 1.5rem;
  max-width: 44ch;
  font-size: var(--fs-lede);
  color: var(--on-dark-70);
}
.cine__actions {
  display: flex; align-items: center; gap: 1.75rem;
  flex-wrap: wrap;
  margin-top: 2rem;
}

/* The hero rail and its house cards lived here — a grid of one card
   per property across the floor of the hero, with a progress line that
   filled across the active one over exactly one hold.

   Removed because it did not scale. The grid was repeat(3, 1fr) with a
   fixed card, so a fourth property already wrapped to a second row and
   each further one grew the hero taller. A first screen whose height
   depends on how many hotels the group has signed is not a first
   screen. The houses band below does the same job and scales by adding
   rows where there is room for them.

   The reel itself is untouched and still runs. */


/* =====================================================================
   02 — FIGURES BAND
   ===================================================================== */

/* The ground was the full brand red, straight across the viewport under
   the hero. Three small facts do not earn the loudest surface on the
   page, and the red pulled the eye off the houses immediately above it —
   which are the thing the section exists to lead you into.

   Clay rather than bone, because bone would run straight into the houses
   band below with nothing between them. Espresso hero, clay figures,
   bone houses reads as a tonal descent out of the photograph instead of
   an interruption. The red survives where it belongs: on the numerals. */
.figband {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  background: var(--clay);
}
.figband__item {
  padding: clamp(1.25rem, 2.5vw, 2.25rem) var(--gutter);
  border-left: 1px solid var(--rule-strong);
  color: var(--ink);
}
.figband__item:first-child { border-left: 0; }
.figband__v {
  display: block;
  font-family: var(--font-display);
  font-size: clamp(1.75rem, 1rem + 2.2vw, 3rem);
  font-weight: 600; line-height: 1;
  /* --brand-ink, not --brand: the deep one is the token that clears AA
     on clay (5.03:1 against the red's 3.62:1). */
  color: var(--brand-ink);
}
.figband__l {
  display: block; margin-top: 0.5rem;
  font-size: 0.625rem;
  text-transform: uppercase; letter-spacing: 0.16em; font-weight: 600;
  color: var(--ink-70);
}

/* =====================================================================
   03 — THE HOUSES, expanding panels
   ---------------------------------------------------------------------
   One band, three panels, the hovered one taking the room from the
   other two. No gap anywhere in it — the panels always sum to the full
   width, so there is no leftover space that can read as empty. That was
   the fault in all three earlier attempts: a staggered ladder left the
   text floating in 280px of nothing, full-bleed halves closed the gap
   by flattening the composition into two slabs, and overlapping plates
   still left a margin down the outside.

   Structurally unlike anything else on this page, which is the point —
   the mosaic, the split and the table are already spoken for.

   Pure CSS. flex-grow is the only thing animating, and :focus-within
   does for the keyboard what :hover does for the pointer, so the panel
   opens when you tab into it without a line of script. With CSS off it
   is three equal panels with everything visible.
   ===================================================================== */

/* Bone, not espresso. The panels carry their own dark scrims, so the
   section does not need a dark ground — and taking one put .houses
   between the dark hero and the dark featured house, which is ~2000px
   of unbroken espresso with no relief in it. */
.houses {
  padding: var(--section-y) 0 0;
}

.ladder {
  display: flex;
  gap: 4px;
  height: clamp(430px, 56vh, 640px);
  padding-inline: var(--gutter);
  max-width: var(--max);
  margin-inline: auto;
}

.rung {
  flex: 1 1 0;
  min-width: 0;
  border-radius: var(--r);
  overflow: hidden;

  /* Scopes the per-frame layout work to the panel. flex-grow cannot be
     composited, so this animation does run on the main thread — the
     job is to make each of its frames cheap, not to pretend otherwise. */
  contain: layout paint;

  transition: flex-grow 620ms cubic-bezier(0.32, 0.72, 0, 1);
}

/* The panel carries data-reveal, and `.js [data-reveal]` sets its own
   transition for the scroll-in. That is a shorthand, so it REPLACED the
   rule above wholesale and took flex-grow with it — the expansion was
   not easing at all, it was snapping, which is what "not smooth" was.

   Higher specificity (0,3,0 against the reveal.s 0,2,0) and all three
   properties declared together, so the panel eases open AND still
   fades in on scroll. */
.js .ladder .rung {
  transition: flex-grow 620ms cubic-bezier(0.32, 0.72, 0, 1),
              opacity 700ms var(--ease),
              transform 700ms var(--ease);
}

/* The other two give way. Both rules carry the same specificity, so the
   order below is what makes the hovered panel win. */
.ladder:hover .rung,
.ladder:focus-within .rung { flex-grow: 0.82; }
.ladder .rung:hover,
.ladder .rung:focus-within { flex-grow: 2.2; }

.rung__link {
  position: relative;
  display: block;
  height: 100%;
  color: var(--on-dark);

  /* The panel is a photograph, so accent text needs the tint mixed for
     dark grounds rather than the ink the light sections use. */
  --brand-accent: var(--brand-on-dark);
}

/* THE PHOTOGRAPH DOES NOT RESIZE WITH THE PANEL.

   With inset:0 the media box was tied to the panel width, so every
   frame of the expansion re-laid-out and re-rasterised a full-bleed
   photograph — the single most expensive thing in the animation and
   the reason it stuttered.

   A fixed width in viewport units is constant for the whole
   transition, so the image is rasterised once and the panel just moves
   its crop across it. min() caps the paint area on very wide screens,
   where 54vw would be far larger than any panel can ever open to.
   Centred, so a collapsed panel shows the middle of the frame rather
   than one edge of it. */
.rung__media {
  position: absolute;
  top: 0; bottom: 0; left: 50%;
  width: min(54vw, 900px);
  transform: translateX(-50%);
  display: block;
}
.rung__media .figure {
  height: 100%;
  aspect-ratio: auto;
  border-radius: 0;
  background: var(--espresso);
}
.rung__media .figure img { transition: transform 900ms var(--ease); }
.rung__link:hover .rung__media .figure img { transform: scale(1.05); }

/* The scrim lives on the body so it travels with the text and does not
   need an extra element in the markup. */
.rung__body {
  position: absolute;
  inset: auto 0 0 0;
  display: block;
  padding: clamp(1.25rem, 2vw, 2rem);
  background: linear-gradient(to top,
    rgba(22, 17, 14, 0.94) 0%,
    rgba(22, 17, 14, 0.78) 42%,
    rgba(22, 17, 14, 0.30) 76%,
    rgba(22, 17, 14, 0) 100%);
}

.rung__num {
  display: block;
  font-family: var(--font-display);
  font-size: 0.8125rem;
  font-weight: 700;
  letter-spacing: 0.12em;
  color: var(--brand-on-dark);
  margin-bottom: 0.5rem;
}

.rung__name {
  display: block;
  font-family: var(--font-display);
  font-size: clamp(1.4rem, 0.9rem + 1.3vw, 2.25rem);
  font-weight: 600;
  letter-spacing: -0.02em;
  line-height: 1.04;
  color: var(--on-dark);
}

.rung__line {
  display: block;
  margin-top: 0.45rem;
  font-size: 0.9375rem;
  line-height: 1.5;
  color: var(--on-dark-70);
  max-width: 32ch;
}

/* Held back until the panel opens. grid-template-rows rather than
   height so it sizes to its own content, and a delay so it arrives
   after the panel has finished widening instead of reflowing during. */
.rung__points {
  display: grid;
  grid-template-rows: 0fr;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 0 clamp(0.75rem, 1.5vw, 1.5rem);
  max-width: none;
  opacity: 0;

  /* Synced to the panel, not racing it. These used to run at 420ms
     against the panel.s 620ms with margin-top animating as a third
     layout property, so two different layout animations fought over
     the same frames. Same curve, same duration, one less property. */
  transition: grid-template-rows 620ms cubic-bezier(0.32, 0.72, 0, 1),
              opacity 300ms linear 140ms;
}
.rung:hover .rung__points,
.rung:focus-within .rung__points {
  grid-template-rows: 1fr;
  opacity: 1;
}

/* The top offset lives INSIDE the clipped row, so it collapses with
   the row rather than animating as its own layout property. */
.rung__pt {
  min-height: 0;
  overflow: hidden;
  position: relative;
  padding: 0.85rem 0 0 1.1rem;
  font-size: 0.875rem;
  line-height: 1.7;
  color: var(--on-dark-70);
}
.rung__pt::before {
  content: "";
  position: absolute; left: 0; top: 0.75em;
  width: 8px; height: 2px;
  background: var(--brand-on-dark);
}

.rung__foot {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  margin-top: clamp(0.9rem, 1.6vw, 1.25rem);
  padding-top: clamp(0.75rem, 1.4vw, 1rem);
  border-top: 1px solid var(--rule-dark);
}

.rung__where {
  font-size: var(--fs-label);
  text-transform: uppercase;
  letter-spacing: 0.16em;
  font-weight: 600;
  color: var(--brand-on-dark);
}

.rung__go {
  flex: none;
  width: 2.25rem; height: 2.25rem;
  display: grid; place-items: center;
  background: var(--brand); color: var(--bone);
  border-radius: var(--r);
  font-size: 1rem;
  transition: transform 260ms var(--ease);
}
.rung__link:hover .rung__go { transform: translateX(4px); }

.houses__more {
  max-width: var(--max);
  margin-inline: auto;
  padding: clamp(1.5rem, 3vw, 2.5rem) var(--gutter) var(--section-y);
  text-align: right;
}

@media (max-width: 61.99em) {
  /* An accordion needs a pointer to hover with. On touch it becomes
     three stacked cards with everything already open — no hidden
     content behind an interaction the device cannot perform. */
  .ladder {
    display: grid;
    grid-template-columns: minmax(0, 1fr);
    gap: clamp(1rem, 2.5vw, 1.5rem);
    height: auto;
  }
  .rung,
  .ladder:hover .rung,
  .ladder .rung:hover,
  .ladder .rung:focus-within { flex: none; }
  .rung__link { min-height: clamp(360px, 62vw, 460px); }

  /* Stacked, the panel no longer changes width, so the photograph can
     go back to filling it — the fixed viewport width above exists only
     to keep the image from re-rasterising mid-expansion. */
  .rung__media {
    inset: 0;
    left: 0;
    width: 100%;
    transform: none;
  }

  .rung__points,
  .rung:hover .rung__points {
    grid-template-rows: 1fr;
    opacity: 1;
    transition: none;
  }
}

@media (prefers-reduced-motion: reduce) {
  .rung, .rung__points, .rung__media .figure img { transition: none; }
}
/* =====================================================================
   04 — MANIFESTO
   The one centred moment, knocked off balance by a single plate that
   the left edge crops.
   ===================================================================== */

/* Three tracks: a plate, the centred statement, a second plate. The
   plates were absolutely positioned and cropped by the viewport edge
   before, which read as a rendering fault rather than as composition —
   now they sit in the grid, fully visible, offset vertically against
   each other so the arrangement is still deliberately off-balance. */
.manifesto {
  background: var(--clay);
  padding: clamp(3.5rem, 2rem + 5vw, 7rem) var(--gutter);
  display: grid;
  grid-template-columns: 1fr minmax(0, 720px) 1fr;
  gap: clamp(1rem, 3vw, 3rem);
  align-items: center;
}

.manifesto__plate { width: min(100%, 250px); margin: 0; }
.manifesto__plate figcaption {
  margin-top: 0.6rem;
  font-size: 0.6875rem;
  text-transform: uppercase;
  letter-spacing: 0.16em;
  font-weight: 600;
  color: var(--ink-45);
}
/* The margin on the second plate is what creates the offset. With
   align-self alone the two plates were the tallest things in the row, so
   "start" and "end" resolved to the same line and the collage read as a
   pair of matched columns. */
.manifesto__plate--a { justify-self: start; align-self: start; }
.manifesto__plate--b { justify-self: end;   align-self: end; margin-top: clamp(2rem, 5vw, 4.5rem); }

.manifesto__in { text-align: center; }
.manifesto__in .rule { margin: 0 auto 2rem; }

.manifesto__title {
  font-size: clamp(1.75rem, 1rem + 2.4vw, 3.5rem);
  font-weight: 600;
  line-height: 1.05;
}

.manifesto__cols {
  display: grid; grid-template-columns: 1fr 1fr;
  gap: clamp(1.5rem, 3vw, 2.75rem);
  margin-top: 2.5rem;
  text-align: left;
  color: var(--ink-70);
}


/* =====================================================================
   05 — EXPERIENCES, a mosaic
   ===================================================================== */

.exp {
  max-width: var(--max); margin: 0 auto;
  padding: var(--section-y) var(--gutter);
}

/* Shared section header: label + heading on the left, a link or a figure
   pushed to the right. Used by most sections across the whole site. */
.sec-head {
  display: flex; align-items: flex-end; justify-content: space-between;
  gap: 2rem; flex-wrap: wrap;
  margin-bottom: clamp(1.5rem, 3vw, 2.5rem);
}
.sec-head .eyebrow { margin-bottom: 0.5rem; }
.sec-head .fig { text-align: right; }
.sec-head--wrap {
  max-width: var(--max);
  margin-inline: auto;
  padding-inline: var(--gutter);
}

/* Explicit equal rows. With auto rows the two photo tiles — one of them
   spanning two rows — dragged the first row to their natural height and
   left the colour tiles squashed underneath. */
.mosaic {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  grid-template-rows: repeat(3, clamp(150px, 14.5vw, 215px));
  gap: 0.5rem;
}

.tile {
  position: relative;
  display: flex; flex-direction: column; justify-content: flex-end;
  gap: 0.35rem;
  padding: 1.25rem;
  border-radius: var(--r);
  overflow: hidden;
  transition: transform 300ms var(--ease);
}
.tile:hover { transform: translateY(-3px); }

.tile__name {
  font-family: var(--font-display);
  font-size: clamp(1.125rem, 0.9rem + 0.7vw, 1.5rem);
  font-weight: 600;
  letter-spacing: -0.02em;
}
.tile__line { font-size: 0.8125rem; line-height: 1.45; opacity: 0.78; }

.tile--brand { background: var(--brand); color: var(--bone); }
.tile--sage       { background: var(--sage);       color: var(--bone); }
.tile--espresso   { background: var(--espresso);   color: var(--bone); }

.tile--photo { padding: 0; }
.tile--photo .figure { height: 100%; aspect-ratio: auto; border-radius: 0; }
.tile__chip {
  position: absolute; left: 0.85rem; bottom: 0.85rem;
  background: var(--bone); color: var(--espresso);
  font-size: 0.625rem; text-transform: uppercase;
  letter-spacing: 0.14em; font-weight: 600;
  padding: 0.4rem 0.7rem; border-radius: 6px;
}

.tile--cta {
  background: var(--brand); color: var(--bone);
  justify-content: space-between;
}
.tile__arrow { font-size: 1.5rem; line-height: 1; }

/* Placements — photographs and colour tiles alternate rather than the
   photographs bunching into the first two rows and the colour tiles
   filling the last. Read across or down and the two kinds take turns,
   which is what stops the flat tiles looking like the ones that were
   left over. Twelve cells, ten tiles, nothing spare. */
.tile--dining       { grid-column: 1;           grid-row: 1; }  /* photo  */
.tile--romance      { grid-column: 2;           grid-row: 1; }  /* colour */
.tile--wellness     { grid-column: 3 / span 2;  grid-row: 1; }  /* photo  */

.tile--family       { grid-column: 1;           grid-row: 2; }  /* colour */
.tile--celebrations { grid-column: 2;           grid-row: 2; }  /* photo  */
.tile--culture      { grid-column: 3;           grid-row: 2; }  /* colour */
.tile--business     { grid-column: 4;           grid-row: 2; }  /* photo  */

.tile--nature       { grid-column: 1;           grid-row: 3; }  /* photo  */
.tile--adventure    { grid-column: 2;           grid-row: 3; }  /* colour */
.tile--cta          { grid-column: 3 / span 2;  grid-row: 3; }  /* colour */


/* =====================================================================
   06 — DINING
   Three tracks: the espresso panel, a red spine carrying the word
   on its side, and the photograph taking the rest of the frame.

   Laid out as a grid rather than absolute positioning. The old version
   pinned the colour block at a fixed 36% while the panel was sized
   min(600px, 56%) — two rules that scale differently, so between roughly
   1000px and 1300px the panel grew into the block. As a grid the tracks
   cannot overlap except by the amount the panel is explicitly given.
   ===================================================================== */

.dining-band {
  display: grid;
  grid-template-columns: minmax(0, 0.92fr) auto minmax(0, 1.08fr);
  background: var(--espresso);
  min-height: clamp(420px, 50vw, 620px);
  overflow: hidden;
}

.dining-band__panel {
  grid-area: 1 / 1;
  align-self: center;
  position: relative; z-index: 2;
  background: var(--espresso);
  padding: clamp(2rem, 3vw, 3.25rem);
  padding-left: var(--gutter);
  /* The break into the spine. The spine reserves exactly this much on
     its left, so the word is never covered. */
  margin-right: -3rem;
  border-radius: 0 var(--r) var(--r) 0;
}
.dining-band__body {
  margin: 1.5rem 0 2rem;
  color: var(--on-dark-70);
  max-width: 46ch;
}

.dining-band__mark {
  grid-area: 1 / 2;
  background: var(--brand);
  display: grid; place-items: center;
  padding: clamp(1.75rem, 3.5vw, 3rem) clamp(0.85rem, 1.5vw, 1.4rem);
  padding-left: calc(3rem + clamp(0.85rem, 1.5vw, 1.4rem));
}

/* Set on its side, reading upward. Vertical type loses the tracking it
   gets from horizontal kerning, so it takes a little back. */
.dining-band__word {
  writing-mode: vertical-rl;
  transform: rotate(180deg);
  font-family: var(--font-display);
  font-size: clamp(2rem, 4.2vw, 3.75rem);
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  line-height: 1;
  color: var(--bone);
}

.dining-band__media { grid-area: 1 / 3; margin: 0; }
.dining-band__media .figure {
  height: 100%;
  aspect-ratio: auto;
  border-radius: 0;
}

/* =====================================================================
   07 — WAYS TO STAY, a step band
   ===================================================================== */

.ways {
  display: grid;
  grid-template-columns: repeat(5, 1fr);
  background: var(--sage);
}

.step {
  padding: clamp(1.5rem, 1rem + 1.6vw, 2.75rem) clamp(1rem, 1.6vw, 1.75rem);
  border-left: 1px solid rgba(42, 33, 28, 0.18);
  color: var(--bone);
  transition: background-color 250ms var(--ease);
}
.step:first-child { border-left: 0; }
.step:hover { background: rgba(42, 33, 28, 0.1); }

.step__num {
  display: block;
  font-family: var(--font-display);
  font-size: clamp(1.5rem, 1.1rem + 1vw, 2rem);
  font-weight: 500;
  line-height: 1;
  opacity: 0.85;
}
.step__title {
  display: block;
  margin-top: 0.85rem;
  font-family: var(--font-display);
  font-size: clamp(1.1rem, 0.95rem + 0.5vw, 1.4rem);
  font-weight: 600;
  letter-spacing: -0.02em;
}
.step__line {
  display: block;
  margin-top: 0.5rem;
  font-size: 0.8125rem;
  line-height: 1.45;
  color: rgba(245, 241, 232, 0.82);
}


/* =====================================================================
   08 — THE DIRECTORY
   ===================================================================== */

.directory {
  max-width: var(--max); margin: 0 auto;
  padding: var(--section-y) var(--gutter);
}

.dtable { width: 100%; border-collapse: collapse; }

.dtable thead th {
  text-align: left;
  font-size: 0.625rem;
  text-transform: uppercase;
  letter-spacing: 0.18em;
  font-weight: 600;
  color: var(--ink-45);
  padding: 0 1rem 0.9rem;
  border-bottom: 1px solid var(--rule-strong);
}

.dtable tbody th,
.dtable tbody td {
  padding: 1.15rem 1rem;
  border-bottom: 1px solid var(--rule);
  vertical-align: middle;
  text-align: left;
  font-weight: 400;
  color: var(--ink-70);
}

.dtable__house {
  display: flex; align-items: center; gap: 1rem;
  font-family: var(--font-display);
  font-size: clamp(1.125rem, 0.95rem + 0.6vw, 1.5rem);
  font-weight: 600;
  letter-spacing: -0.02em;
  color: var(--ink);
}
.dtable__thumb { width: 56px; flex: none; }
.dtable__thumb .figure { border-radius: 6px; }

.dtable__go { width: 3rem; text-align: right !important; }
.dtable__go a {
  color: var(--brand-accent);
  font-size: 1.25rem;
  transition: transform 200ms var(--ease);
  display: inline-block;
}
.dtable tbody tr:hover .dtable__go a { transform: translateX(4px); }


/* =====================================================================
   09 — THE JOURNAL
   ===================================================================== */

.journal {
  max-width: var(--max); margin: 0 auto;
  padding: 0 var(--gutter) var(--section-y);
  display: grid;
  grid-template-columns: 1fr 2fr;
  gap: clamp(2rem, 5vw, 5rem);
  align-items: start;
}
.journal__note {
  margin-top: 1.25rem;
  color: var(--ink-70);
  max-width: 38ch;
}

.subjects { border-top: 1px solid var(--rule); }
.subjects li { border-bottom: 1px solid var(--rule); }
.subjects a {
  display: flex; align-items: center; justify-content: space-between;
  gap: 1rem;
  padding: clamp(0.75rem, 1.4vw, 1.1rem) 0;
  font-family: var(--font-display);
  font-size: clamp(1.5rem, 1rem + 1.6vw, 2.5rem);
  font-weight: 500;
  letter-spacing: -0.025em;
  transition: padding-left 250ms var(--ease), color 250ms var(--ease);
}
.subjects a:hover { padding-left: 0.75rem; color: var(--brand-ink); }
.subjects__arrow { color: var(--brand-accent); font-size: 1.125rem; }


/* =====================================================================
   10 — NEWSLETTER
   ===================================================================== */

.news {
  background: var(--espresso);
  padding: clamp(3.5rem, 2rem + 5vw, 7rem) var(--gutter);
}
.news__in { max-width: 680px; margin: 0 auto; text-align: center; }
.news__body {
  margin-top: 1.25rem;
  color: var(--on-dark-70);
}

/* The answer to a submission — the one line the person is looking for,
   so it sits brighter than the body above it. Never red: a mistyped
   address does not warrant an alarm in a footer band. */
.news__result {
  margin-top: 1.5rem;
  color: var(--on-dark);
}
.news__result--ok { color: var(--brand); }

.news__form {
  display: flex; gap: 0.75rem;
  margin-top: 2.25rem;
  flex-wrap: wrap;
  justify-content: center;
}
.news__input {
  flex: 1 1 320px;
  background: var(--bone);
  border: 1px solid transparent;
  border-radius: var(--r);
  padding: 0.95rem 1.1rem;
  font-family: var(--font-body);
  font-size: var(--fs-body);
  color: var(--espresso);
}
.news__input::placeholder { color: var(--ink-45); }
.news__input:focus {
  outline: none;
  border-color: var(--brand-accent);
  box-shadow: 0 0 0 3px rgba(192, 86, 43, 0.3);
}


/* =====================================================================
   FOOTER
   ===================================================================== */

.ftr {
  background: var(--espresso);
  color: var(--on-dark-70);
  border-top: 1px solid var(--rule-dark);
  padding: clamp(3rem, 2rem + 3vw, 5rem) var(--gutter) 2rem;
}

.ftr__top {
  max-width: var(--max); margin: 0 auto;
  display: grid;
  grid-template-columns: 1.4fr 3fr;
  gap: clamp(2rem, 5vw, 5rem);
}

/* The footer lockup. It had no rule of any kind, which is why it fell
   back to its own attributes and came out stretched — and why it was
   drawn at 242px tall, five times the header's, dominating the column it
   sits in.

   Sized by height like the header so the two agree, and larger than it
   because this is where the full company name and the tagline are meant
   to be read rather than glanced at: 56 to 76px against the header's 48. */
.ftr__lockup {
  display: inline-block;
  margin-bottom: 1.5rem;
}
.ftr__logo {
  display: block;
  height: clamp(56px, 5vw, 76px);
  width: auto;
}
.ftr__brand p {
  margin-top: 1rem;
  max-width: 32ch;
  font-size: 0.875rem;
  color: var(--on-dark-45);
}

.ftr__cols {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: clamp(1.5rem, 3vw, 2.5rem);
}
.ftr__title {
  font-size: 0.625rem;
  text-transform: uppercase;
  letter-spacing: 0.18em;
  font-weight: 600;
  color: var(--on-dark-45);
  margin-bottom: 1rem;
}
.ftr__col li { margin-bottom: 0.6rem; }
.ftr__col a {
  font-size: 0.875rem;
  color: var(--on-dark-70);
  transition: color 200ms var(--ease);
}
.ftr__col a:hover { color: var(--bone); }

.ftr__bottom {
  max-width: var(--max); margin: clamp(2.5rem, 5vw, 4rem) auto 0;
  padding-top: 1.5rem;
  border-top: 1px solid var(--rule-dark);
  display: flex; align-items: center; justify-content: space-between;
  gap: 1rem; flex-wrap: wrap;
  font-size: 0.75rem;
  color: var(--on-dark-45);
}
.ftr__legal { display: flex; gap: 1.25rem; flex-wrap: wrap; }
.ftr__legal a:hover { color: var(--bone); }


/* =====================================================================
   RESPONSIVE
   Breakpoints are placed where each layout actually breaks, not on a
   device list.
   ===================================================================== */

@media (max-width: 62em) {

  /* The nav drops to its own row and scrolls sideways rather than being
     hidden behind a burger. Six links do not justify a menu, and hiding
     them outright would leave the page with no navigation at all. */
  /* Tight, because this bar is sticky: every pixel here is a pixel of
     phone screen the visitor never gets back. The 44px touch targets
     inside set the floor, so the padding around them is what gives. */
  .hdr__in { flex-wrap: wrap; gap: 0.25rem; padding-block: 0.3rem; }
  .wordmark { margin-right: auto; }
  .hdr__nav {
    order: 3;
    width: 100%;
    margin-inline: 0;
    padding-top: 0.25rem;
    border-top: 1px solid var(--rule);
    overflow-x: auto;
    scrollbar-width: none;
    -webkit-overflow-scrolling: touch;
  }
  .hdr__nav::-webkit-scrollbar { display: none; }
  .hdr__nav ul { gap: 1.25rem; }
  .hdr__nav a { white-space: nowrap; }

  .cine { min-height: min(88svh, 720px); }
  .cine__lede { max-width: none; }

  .figband { grid-template-columns: 1fr; }
  .figband__item {
    border-left: 0;
    border-top: 1px solid var(--rule-strong);
  }
  .figband__item:first-child { border-top: 0; }

  .manifesto { grid-template-columns: 1fr; }
  .manifesto__plate { display: none; }

  /* Four columns is too dense to read below this width; two keeps the
     unequal tiles legible without turning the mosaic into a list. Six
     rows now, so they need restating. */
  .mosaic {
    grid-template-columns: repeat(2, 1fr);
    grid-template-rows: repeat(6, clamp(150px, 22vw, 200px));
  }
  /* A true checkerboard at two columns: every photo sits beside and
     above a colour tile. */
  .tile--dining       { grid-column: 1 / span 2; grid-row: 1; }  /* photo  */
  .tile--romance      { grid-column: 1;          grid-row: 2; }  /* colour */
  .tile--wellness     { grid-column: 2;          grid-row: 2; }  /* photo  */
  .tile--celebrations { grid-column: 1;          grid-row: 3; }  /* photo  */
  .tile--family       { grid-column: 2;          grid-row: 3; }  /* colour */
  .tile--culture      { grid-column: 1;          grid-row: 4; }  /* colour */
  .tile--business     { grid-column: 2;          grid-row: 4; }  /* photo  */
  .tile--nature       { grid-column: 1;          grid-row: 5; }  /* photo  */
  .tile--adventure    { grid-column: 2;          grid-row: 5; }  /* colour */
  .tile--cta          { grid-column: 1 / span 2; grid-row: 6; }  /* colour */

  /* Stacked: the panel above, the colour block below it as a banner.
     The plate goes — at this width the word alone fills the block. */
  /* The panel takes a full row of its own, then the spine and the
     photograph share the row beneath it. */
  .dining-band {
    grid-template-columns: auto minmax(0, 1fr);
    min-height: 0;
  }
  .dining-band__panel {
    grid-area: 1 / 1 / 2 / 3;
    margin-right: 0;
    border-radius: 0;
    padding: clamp(2rem, 6vw, 3rem) var(--gutter);
  }
  .dining-band__mark {
    grid-area: 2 / 1;
    padding: clamp(1.5rem, 4vw, 2.25rem) clamp(0.75rem, 2vw, 1.1rem);
  }
  .dining-band__media {
    grid-area: 2 / 2;
    min-height: 56vw;
  }

  .ways { grid-template-columns: repeat(2, 1fr); }
  .step { border-left: 1px solid rgba(42, 33, 28, 0.18); }
  .step:nth-child(odd) { border-left: 0; }
  .step { border-top: 1px solid rgba(42, 33, 28, 0.18); }
  .step:nth-child(-n+2) { border-top: 0; }

  .journal { grid-template-columns: 1fr; }

  .ftr__top { grid-template-columns: 1fr; }
  .ftr__cols { grid-template-columns: repeat(2, 1fr); }
}

@media (max-width: 48em) {


  .manifesto__plate { display: none; }
  .manifesto__cols { grid-template-columns: 1fr; }

  /* A three-column table cannot hold its shape at this width, so each
     row becomes a block and the headers are dropped — the values are
     self-describing. */
  .dtable thead { display: none; }
  .dtable tbody tr {
    display: block;
    padding: 1.25rem 0;
    border-bottom: 1px solid var(--rule);
  }
  .dtable tbody th,
  .dtable tbody td {
    display: block;
    padding: 0;
    border: 0;
  }
  .dtable tbody td { margin-top: 0.4rem; font-size: 0.875rem; }
  .dtable__house { margin-bottom: 0.5rem; }
  .dtable__go { display: none !important; }

  .sec-head .fig { text-align: left; }
}

@media (max-width: 30em) {
  .ways { grid-template-columns: 1fr; }
  .step { border-left: 0; border-top: 1px solid rgba(42, 33, 28, 0.18); }
  .step:first-child { border-top: 0; }
  .ftr__cols { grid-template-columns: 1fr; }
}


/* =====================================================================
   MOTION
   ===================================================================== */

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
  }
  /* The hero still swaps frames — the script keeps auto-advance off
     under reduced motion — but the slow settle on the image goes. */
  .cine__frame .figure img { transform: none; }
}


/* =====================================================================
   ADDITIONS — texture, reveal, and the small hover affordances
   ===================================================================== */

/* A single film grain over dark grounds, so large flat areas of espresso
   and red read as printed rather than as a filled rectangle. */
.grain { position: relative; isolation: isolate; }
.grain::after {
  content: "";
  position: absolute; inset: 0;
  pointer-events: none;
  z-index: 1;
  opacity: 0.16;
  mix-blend-mode: overlay;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)' opacity='0.5'/%3E%3C/svg%3E");
}
.grain > * { position: relative; z-index: 2; }

/* Reveal on scroll is defined in assets/css/motion.css, with the rest
   of the motion primitives. The will-change that used to sit on every
   [data-reveal] at once went with it — the script now sets it on the
   one element that is about to move. */

/* Header gains a hairline and a shadow once the page has moved. */
.hdr.is-stuck {
  border-bottom-color: var(--rule-strong);
  box-shadow: 0 10px 30px -24px rgba(42, 33, 28, 0.6);
}

/* Band rows — the arrow sits on the footer rule beside the location,
   always visible rather than appearing on hover. It used to float over
   the photograph and only show on pointer-over, which meant a touch
   visitor never saw the one affordance saying the band was a link. */
.rung__go {
  flex: none;
  width: 2.5rem; height: 2.5rem;
  display: grid; place-items: center;
  background: var(--brand); color: var(--bone);
  border-radius: var(--r);
  font-size: 1.05rem;
  transition: transform 260ms var(--ease), background-color 200ms var(--ease);
}
.rung__link:hover .rung__go,
.rung__link:focus-visible .rung__go {
  transform: translateX(4px);
  background: var(--brand-deep);
}

/* Photo tiles in the mosaic need a scrim so the label chip stays legible
   over whatever the photograph happens to be doing in that corner. */
.tile__scrim {
  position: absolute; inset: 0;
  background: linear-gradient(to top, rgba(42, 33, 28, 0.55), transparent 55%);
  pointer-events: none;
}
.tile--photo .tile__chip { z-index: 2; }


/* =====================================================================
   HOME — sections carried over from the original site
   Destinations, a featured house, wellness, sustainability and a closing
   frame. Each takes a structure not already used on this page.
   ===================================================================== */

/* ---- Featured house — a bone card on a full-bleed photograph.
   The hero sets its type straight onto the picture, so this one uses a
   solid card instead; two photographic sections that handle type the
   same way would read as the same section twice. ------------------- */
.spot {
  position: relative;
  min-height: clamp(460px, 56vw, 700px);
  display: flex; align-items: center;
  overflow: hidden;
  background: var(--espresso);
}
.spot__media { position: absolute; inset: 0; }
.spot__media .figure { height: 100%; aspect-ratio: auto; border-radius: 0; }
/* Runs to the LEFT, following the card. The gradient's job is to sink
   the half of the frame the card sits over so the bone plate has
   something to sit against rather than a lit facade; flipping the card
   without flipping this would have put it on the brightest part of the
   picture and left the darkened half empty. */
.spot__scrim {
  position: absolute; inset: 0;
  background: linear-gradient(to left,
    rgba(22, 17, 14, 0.55) 0%, rgba(22, 17, 14, 0.2) 48%, rgba(22, 17, 14, 0) 80%);
}
.spot__card {
  position: relative; z-index: 2;
  width: min(460px, 100%);
  /* auto on the left is what pushes it over — .spot is a flex row, so
     the free space collects on whichever side is auto. */
  margin-left: auto;
  margin-right: clamp(1.25rem, 6vw, 8rem);
  background: var(--bone);
  color: var(--ink);
  padding: clamp(1.75rem, 3vw, 2.75rem);
  border-radius: var(--r);
}

/* No object-position override here, deliberately. The instinct on moving
   the card was to slide the crop left to keep the facade clear — but the
   master is 3:2 into a band that is never narrower than about 2:1, so
   cover always scales to the width and there is no horizontal slack to
   shift. A rule setting it would read as doing something and do nothing.
   The facade sits centre-left of frame on its own; the card lands over
   the night sky beside it. */
.spot__name {
  font-size: clamp(1.75rem, 1.2rem + 2vw, 3rem);
  font-weight: 600; letter-spacing: -0.03em;
}
.spot__line { margin-top: 0.75rem; color: var(--ink-70); }
.spot__points { margin-top: 1.25rem; display: grid; gap: 0.4rem; }
.spot__points li {
  position: relative; padding-left: 1.25rem;
  font-size: 0.9375rem; color: var(--ink-70);
}
.spot__points li::before {
  content: ""; position: absolute; left: 0; top: 0.62em;
  width: 8px; height: 2px; background: var(--brand);
}
.spot__card .btn { margin-top: 1.75rem; }

/* ---- Destinations ------------------------------------------------- */
.atlas-home { padding-top: var(--section-y); }
.atlas-home__intro {
  max-width: 62ch;
  margin-bottom: clamp(1.5rem, 3vw, 2.5rem);
  font-size: var(--fs-lede);
  color: var(--ink-70);
}

/* ---- Wellness — a plain split, nothing overlapping, deliberately
   unlike the dining band directly above it. ------------------------- */
.wsplit {
  max-width: var(--max); margin-inline: auto;
  padding: var(--section-y) var(--gutter);
  display: grid; grid-template-columns: 44% minmax(0, 1fr);
  gap: clamp(2rem, 5vw, 5rem);
  align-items: center;
}
.wsplit > * { min-width: 0; }
.wsplit__media { margin: 0; }
.wsplit__body .h2 { margin-bottom: 1.25rem; }
.wsplit__cta { margin-top: 1.75rem; }

/* ---- Sustainability — type over a dark band ----------------------- */
.eco-band {
  position: relative;
  min-height: clamp(420px, 50vw, 600px);
  display: flex; align-items: center;
  overflow: hidden;
  background: var(--espresso);
}
.eco-band__media { position: absolute; inset: 0; }
.eco-band__media .figure { height: 100%; aspect-ratio: auto; border-radius: 0; }
.eco-band__scrim {
  position: absolute; inset: 0;
  background: linear-gradient(to right,
    rgba(22, 17, 14, 0.92) 0%, rgba(22, 17, 14, 0.66) 44%, rgba(22, 17, 14, 0.22) 80%);
}
.eco-band__in {
  position: relative; z-index: 2;
  max-width: var(--max); width: 100%; margin-inline: auto;
  padding: clamp(2.5rem, 5vw, 4rem) var(--gutter);
  color: var(--on-dark);
}
.eco-band__body {
  margin-top: 1.5rem; max-width: 52ch;
  font-size: var(--fs-lede); color: var(--on-dark-70);
}
.eco-band__note {
  margin-top: 1rem; max-width: 52ch;
  font-size: 0.9375rem; color: var(--on-dark-45);
}
.eco-band__in .link { display: inline-block; margin-top: 1.75rem; }

/* ---- Closing frame ------------------------------------------------ */
.closing {
  position: relative;
  min-height: clamp(440px, 56vw, 660px);
  display: flex; align-items: center; justify-content: center;
  overflow: hidden;
  background: var(--espresso);
  text-align: center;
}
.closing__media { position: absolute; inset: 0; }
.closing__media .figure { height: 100%; aspect-ratio: auto; border-radius: 0; }
.closing__scrim {
  position: absolute; inset: 0;
  background: linear-gradient(to top, rgba(22, 17, 14, 0.9), rgba(22, 17, 14, 0.55));
}
.closing__in {
  position: relative; z-index: 2;
  max-width: 780px;
  padding-inline: var(--gutter);
  color: var(--on-dark);
}
.closing__title {
  font-size: clamp(1.875rem, 1.1rem + 2.8vw, 3.5rem);
  font-weight: 600; letter-spacing: -0.035em; line-height: 1;
}
.closing__lede {
  margin-top: 1.25rem;
  font-size: var(--fs-lede); color: var(--on-dark-70);
}
.closing__actions {
  display: flex; align-items: center; justify-content: center;
  gap: 1.75rem; flex-wrap: wrap; margin-top: 2rem;
}

@media (max-width: 62em) {
  .spot { min-height: 0; display: block; }
  .spot__media { position: relative; height: 62vw; }
  .spot__scrim { display: none; }
  .spot__card {
    width: auto;
    margin: -3rem var(--gutter) var(--section-y);
  }
  .wsplit { grid-template-columns: 1fr; }
}

/* Header tail — search sits beside the enquire button, outside the nav
   list, so six destinations do not read as seven. */
.hdr__end { display: flex; align-items: center; gap: clamp(0.65rem, 1.2vw, 1rem); }

.hdr__search {
  display: grid; place-items: center;
  width: 2.35rem; aspect-ratio: 1;
  border-radius: 999px;
  color: var(--ink-70);
  transition: color 200ms var(--ease), background 200ms var(--ease);
}
.hdr__search:hover { color: var(--ink); background: rgba(42, 33, 28, 0.06); }
.hdr__search[aria-current="page"] { color: var(--brand-ink); background: rgba(42, 33, 28, 0.06); }


/* =====================================================================
   TOUCH — coarse pointers only
   ---------------------------------------------------------------------
   Keyed to (pointer: coarse) rather than to a width, because the thing
   that needs a bigger target is a finger, not a narrow window. A phone
   held in landscape is still a phone; a 380px-wide desktop window is
   still a mouse and does not want the nav getting 20px taller.

   Only standalone controls are grown. Inline links inside a sentence
   are deliberately left alone — WCAG exempts them, and padding them out
   would break the line boxes of the prose they sit in.
   ===================================================================== */
@media (pointer: coarse) {

  /* Primary navigation. 16px of text was the whole target before. */
  .hdr__nav a {
    display: inline-flex;
    align-items: center;
    min-height: 44px;
    padding-bottom: 0;
  }
  .hdr__nav a[aria-current="page"] {
    border-bottom: 0;
    box-shadow: inset 0 -1px 0 0 var(--brand);
  }

  .wordmark { display: inline-flex; align-items: center; min-height: 44px; }

  /* Both of these were set to 44 wide and left at their own height —
     the search came out 44x38 and Enquire 99x42, so neither actually
     met the 44 square this block exists to enforce. Measured, not
     assumed. There is room for it now the phone bar carries the mark
     rather than the full lockup: at 320px the row totals 268 of the
     280 available even with every target square. */
  .hdr__search { width: 44px; height: 44px; aspect-ratio: auto; }
  .hdr__cta { min-height: 44px; display: inline-flex; align-items: center; }


  /* Footer columns — a list of links is navigation, not prose. */
  .ftr__col a,
  .ftr__legal a {
    display: inline-flex;
    align-items: center;
    min-height: 40px;
  }
  .ftr__col li { margin-bottom: 0; }

  /* Gallery filters and anything else chip-shaped. */
  .filters__btn,
  .chip--link { min-height: 44px; display: inline-flex; align-items: center; }

  /* Standalone calls to action that sit on their own line. */
  .link--arrow,
  .btn { min-height: 44px; }
  .btn { display: inline-flex; align-items: center; justify-content: center; }

}


/* =====================================================================
   SMALL SCREENS — legibility
   ---------------------------------------------------------------------
   The tracked uppercase labels are set at 10–11px, which is fine at
   arm's length on a monitor and too small on a phone held in one hand.
   Raised, not restyled: the tracking and weight are what make them read
   as labels, and those are untouched.
   ===================================================================== */
@media (max-width: 47.99em) {
  :root { --fs-label: 0.75rem; }

  .fig__label,
  .figband__l,
  .tile__chip,
  .ftr__title,
  .mast__meta-label { font-size: 0.6875rem; }

  /* Long uppercase labels with heavy tracking run out of room first. */
  .fig__label,
  .mast__meta-label { letter-spacing: 0.12em; }
}


/* =====================================================================
   IN-PAGE ANCHORS
   ---------------------------------------------------------------------
   The header is sticky, so an untreated jump to #rooms or #venues puts
   the heading underneath it and the visitor lands mid-paragraph with no
   idea what section they are in. The offset is its own token rather
   than --hdr-h, which is a min-height the mobile header already exceeds
   once the nav wraps onto its second row.

   :where() keeps specificity at zero so any component can still set its
   own offset without fighting this.
   ===================================================================== */
:root { --hdr-offset: 5rem; }

@media (max-width: 61.99em) {
  :root { --hdr-offset: 7.5rem; }
}

:where([id]) { scroll-margin-top: var(--hdr-offset); }

@media (prefers-reduced-motion: no-preference) {
  html { scroll-behavior: smooth; }
}


/* =====================================================================
   BRAND MARK
   ===================================================================== */

/* One image, no type beside it — the lockup contains the name already.
   48px is a floor, not a preference: the tagline is the fourth line in a
   3.14:1 lockup, and below 48 it stops resolving into words. It scales up
   a little on a wide viewport and holds at 40 on a phone, where the bar
   is shorter and the tagline is decorative rather than read. */
.wordmark { display: inline-flex; align-items: center; flex: none; }
.wordmark__logo {
  display: block;
  width: auto;
  height: clamp(48px, 3.4vw, 56px);
}

/* The icon is the search affordance everywhere it fits, so the menu entry
   is not wanted there. Declared BEFORE the phone block: same specificity,
   so whichever comes last wins, and this one coming last hid the menu
   entry at every width — search vanished from the phone header entirely. */
.hdr__nav-search { display: none; }

/* THE BAR ON A PHONE — THE BUDGET.
   From 320 to 414px the header stood at 106px rather than 64, because
   .hdr__end wrapped under the logo. The row has to hold, at 12px gaps:

     lockup 151 + search 44 + Enquire 99 + burger 44 + 3 gaps  = 374px

   and a phone offers 280 (at 320px) to 368 (at 414px) after its gutters.
   Short at every phone width, by 94px at the bottom of the range.

   The lockup cannot give: its note in header.php says 48px is where the
   tagline stops being readable, and the whole logo was asked for. So the
   search icon leaves the bar and becomes a menu entry, which takes the
   requirement to 318 and fits at full size from 360px up — every current
   phone. Only 320 (an iPhone SE of 2016) still needs the logo smaller,
   and it gets 38px rather than losing anything else.

   Enquire loses a little horizontal padding here so the row is
   comfortable rather than exactly full. */
@media (max-width: 30em) {
  .hdr__in { gap: 0.75rem; }

  /* Out of the bar, into the menu. Both are rendered; exactly one shows. */
  .hdr__search { display: none; }
  .hdr__nav-search { display: block; }

  .hdr__cta { padding-inline: 0.7rem; }

  /* The lockup takes whatever the row leaves, rather than stepping at a
     breakpoint. Measured against the budget above: Enquire (89) plus the
     burger (44) plus the gaps claim 155, so the logo gets everything
     else. 10vw lands it at 100px on a 320 phone, 118 on a 375, and back
     to its full 151 at exactly 480 — where the search icon returns to
     the bar and the phone budget stops applying.

     11vw was tried first and left the row 1px short at 320. Measured on
     a 335px frame, which is what a 320 phone's content box actually
     comes to once the test scrollbar a phone does not have is taken off.

     Below roughly 44px the tagline under the name is decoration rather
     than something anyone reads. That is the cost of carrying the whole
     lockup on a 320px screen, and it was the trade asked for. */
  .wordmark__logo { height: clamp(32px, 10vw, 48px); }
}

/* =====================================================================
   THE MENU TOGGLE
   ---------------------------------------------------------------------
   Hidden above 62em, where all six links fit in the bar. Below it the
   links move into a panel under the header and this button opens it.

   Everything here is gated on .js, which the script sets on <html>
   before first paint. Without it the nav stays exactly where the markup
   puts it — a toggle that cannot toggle would be worse than a long row.
   ===================================================================== */
.hdr__burger {
  display: none;
  width: 44px; height: 44px;
  align-items: center; justify-content: center;
  background: transparent;
  border: 0; border-radius: var(--r);
  color: var(--ink);
  cursor: pointer;
}

/* Three bars from one element: the middle is the box, the outer two are
   its pseudo-elements, so the cross on open is two transforms and an
   opacity rather than three moving parts. */
.hdr__bars,
.hdr__bars::before,
.hdr__bars::after {
  display: block;
  width: 20px; height: 2px;
  background: currentColor;
  border-radius: 2px;
  transition: transform 240ms var(--ease), opacity 160ms linear;
}
.hdr__bars { position: relative; }
.hdr__bars::before,
.hdr__bars::after { content: ""; position: absolute; left: 0; }
.hdr__bars::before { top: -6px; }
.hdr__bars::after  { top: 6px; }

[aria-expanded="true"] .hdr__bars { background: transparent; }
[aria-expanded="true"] .hdr__bars::before { transform: translateY(6px) rotate(45deg); }
[aria-expanded="true"] .hdr__bars::after  { transform: translateY(-6px) rotate(-45deg); }

/* The bar collapses EARLIER than the rest of the layout: 67.49em
   (1080px), not 61.99em. Seven links plus the wordmark and the Enquire
   button need about 1080px, and below that the nav wrapped onto a
   second line inside the bar — the header jumped from 65px to 85px at
   992px. Everything else on the page is happy at 62em, so only the
   header moves its breakpoint.

   The panel is absolutely positioned, so between 62em and 67.49em it
   simply lifts out of the flex row and the bar becomes wordmark plus
   button. No other rule has to know. */
@media (max-width: 67.49em) {
  .js .hdr__burger { display: inline-flex; }

  /* The panel drops BELOW the bar rather than inside it. Expanding it
     in flow made the sticky header itself grow, so the bar changed
     height as you opened it and the page jumped underneath.

     An earlier attempt animated grid-template-rows 0fr -> 1fr. That
     collapses correctly but will not expand: min-height:0 on the child
     is what lets the row shrink, and it also strips the automatic
     minimum that 1fr needs to find the content height in an
     auto-height grid. It resolved to the padding and nothing else. */
  .js .hdr__nav {
    position: absolute;
    top: 100%; left: 0; right: 0;
    margin: 0;
    background: var(--bone);
    border-top: 1px solid var(--rule);
    border-bottom: 1px solid var(--rule);
    box-shadow: 0 14px 28px rgba(42, 33, 28, 0.10);
    padding: 0.25rem var(--gutter) 0.5rem;
    max-height: calc(100svh - 100%);
    overflow-y: auto;
    opacity: 0;
    visibility: hidden;
    transform: translateY(-8px);
    transition: opacity 200ms var(--ease), transform 200ms var(--ease), visibility 0s linear 200ms;
  }
  .js .hdr.is-open .hdr__nav {
    opacity: 1;
    visibility: visible;
    transform: none;
    transition: opacity 200ms var(--ease), transform 200ms var(--ease), visibility 0s;
  }

  .js .hdr__nav ul { display: block; }
  .js .hdr__nav li { border-bottom: 1px solid var(--rule); }
  .js .hdr__nav li:last-child { border-bottom: 0; }

  .js .hdr__nav a {
    display: flex;
    align-items: center;
    min-height: 52px;
    font-size: 0.8125rem;
    letter-spacing: 0.14em;
  }
  .js .hdr__nav a[aria-current="page"] {
    border-bottom: 0;
    box-shadow: none;
    color: var(--brand-ink);
  }

  /* The CTA is the one thing that should never move into a menu. */
  .hdr__cta { padding-inline: 1rem; }
}

@media (prefers-reduced-motion: reduce) {
  .js .hdr__nav { transition: none; }
  .hdr__bars, .hdr__bars::before, .hdr__bars::after { transition: none; }
}


/* Dark grounds re-point the accent.
 *
 * Extracted mechanically from every selector in this stylesheet that
 * fills with --espresso, --sage or --brand, rather than listed by hand.
 * Getting it wrong in the other direction is the expensive mistake: the
 * default accent is the deep ink, which on espresso is 2.11:1 — worse
 * than the red it replaced. A section missing from this list is a
 * section whose accent text goes nearly invisible.
 *
 * The slab panel alternates espresso and clay, so it needs both halves. */
/* .figband is NOT in this list any more. It was a brand-red ground and is
   now clay, so on-dark accent (#E8604F) would sit at 2.4:1 on it. Light
   grounds take the default --brand-accent, which is the deep ink. */
.apanel__foot, .cine, .closing, .dining-band, .dining-band__panel, .dtags,
.eco-band, .eveband, .factstrip, .ftr, .gone, .hiring,
.mast--brand, .mast--espresso, .mast--sage, .news, .note-band, .pass, .pmap,
.reported, .rmetric, .slab__panel, .spot, .stmt--espresso, .stmt--sage,
.tile--brand, .tile--cta, .tile--espresso, .tile--sage, .ways, .dhx,
.wgap, .wpend, .xothers { --brand-accent: var(--brand-on-dark); }

.slab:nth-child(even) .slab__panel { --brand-accent: var(--brand-ink); }

/* Light panels nested inside a dark section have to hand the token
   back, or they inherit a tint mixed for espresso and sit at 3:1 on
   bone. The featured-house card is a bone plate on an espresso band. */
.spot__card { --brand-accent: var(--brand-ink); }

/* The slab numeral is large enough for the 3:1 threshold, but it sits
   on espresso at 3.02:1 and reads muddy rather than accented. */
.slab__num { color: var(--brand-accent); }



/* =====================================================================
   ...AND BACK TO BONE INSIDE IT
   ---------------------------------------------------------------------
   .rperson cards are bone surfaces sitting inside the clay .roster band,
   so they inherit the clay-tuned values above and would run too dark:
   --ink-70 composites 8.96:1 on bone rather than the 5.89 it is drawn
   for. Same reason .fwcard__detail is hardcoded in v2-pages.css.

   --ink-45 does NOT go back to its root value. At 0.55 it measures
   3.52:1 on bone, and .rperson__role is 13px — that is fourteen job
   titles under AA. 0.65 reads 4.73 and is the smallest step that clears
   it with margin. The same 0.55-on-bone weakness is still live in about
   seventy other places on the site (.amen__title, .fig__label,
   .step__line, .callcard__where); this fixes the roster only, not the
   root cause. The wellness page no longer appears on that list: its
   .clist__label went when the comparison became a table, and the
   replacement sets 0.65 directly rather than reaching for --ink-45.
   ===================================================================== */
.rperson {
  --ink-70: rgba(42, 33, 28, 0.72);   /* 8.96 -> 5.89 on bone, as drawn */
  --ink-45: rgba(42, 33, 28, 0.65);   /* 3.52 -> 4.73 on bone, clears AA */
}

/* =====================================================================
   MUTED INK ON CLAY
   ---------------------------------------------------------------------
   --ink-70 and --ink-45 are alpha mixes tuned against bone. Clay is a
   mid-tone, so the same alpha composites much lighter on it: --ink-70
   measures 5.89:1 on bone and only 5.16:1 on clay, and --ink-45 falls to
   3.25:1, under AA for small text outright.

   The visible symptom is a secondary line sitting at less than half the
   contrast of the label directly above it — 5.16 against 10.93 — which
   reads as washed out even where it technically passes.

   Re-pointed here rather than patched rule by rule: custom properties
   cascade, so every clay band inherits values that actually read, and a
   new clay section gets them for free. Only these two are changed; --ink
   itself is already full strength.
   ===================================================================== */
.callcard,
.dreach,
.dsubject,
.dtile,
.factband,
.figband,
.forwhat,
.jrn__about,
/* .manifesto is mostly photography, which is why it was nearly left out
   — but its plate captions sit directly on the clay and were the last
   two failures on the site at 3.25:1. */
.manifesto,
.mast--clay,
.ocard a,
.pending,
.position,
.rcard,
/* Added when the about-page roster arrived: it is a clay band, and the
   list above is a list, not a rule — the block comment claimed a new
   clay section would inherit this for free, which is only true of
   sections already named here. */
.roster,
/* The wellness facility cards. Their body is a clay plate on the bone
   page, so the muted label under each numeral was composited against
   clay while its alpha was tuned for bone: 4.25:1, measured, under AA.
   The colour-filled cards in the same grid are NOT clay and take
   on-dark values of their own. */
.fcard--photo,
.sfield,
.sheet__aside,
.slab:nth-child(even) .slab__panel,
.sourcing,
.stmt--clay {
  --ink-70: rgba(42, 33, 28, 0.85);   /* 5.16 -> 7.45 on clay */
  --ink-45: rgba(42, 33, 28, 0.70);   /* 3.25 -> 4.88 on clay, measured */
}
