/* Evolve Escapes — design system v3 (round 3, 01/09/2026)
   Direction: "First Light" — sun-bleached, carved, strong.
   Round-3 changes:
   - Display face: Archivo (Expanded width, 600-800) replaces Syne. Conventional,
     legible letterforms with wide athletic energy. Body stays Hanken Grotesk.
   - Motion rebuilt on universal mechanics: CSS keyframe entrances + an
     IntersectionObserver reveal system in /assets/site.js. No chrome-only
     animation-timeline. A `js` class on <html> gates every pre-animation
     hidden state; prefers-reduced-motion disables everything.
   - Panel consistency rule: INK IS STRUCTURAL, NOT DECORATIVE. Ink surfaces are
     full-bleed bands only (header, hero, proof band, full ink sections, footer).
     Cards and panels inside light sections are ALWAYS white. Stone is a section
     background, never a card colour.
   - New components: .pillars (image-led), .tile-photo (photo-overlay destination
     tiles), .day-list (editorial itinerary), .ig-grid, .to-top.
   Palette unchanged from v2 (clients' verified brand system). Contrast (computed,
   WCAG AA): ink/cream 16.9:1 · text-2/cream 6.0:1 · on-ink/ink 15.0:1 ·
   gold/ink 7.6:1 · ink on clay 5.3:1 · clay-text/cream 5.75:1.
   Gold #D9A05C and clay #C67B5C are NEVER body text on light surfaces. */

/* ---------- Self-hosted fonts (21/09/2026, performance pass) ----------
   Was: a render-blocking <link> to fonts.googleapis.com in every page head,
   which cost a second DNS+TCP+TLS handshake to fonts.googleapis.com and a
   third to fonts.gstatic.com before a single glyph could be requested.
   Lighthouse measured that chain at 1,340ms of render-blocking on the home
   page. The files below are the exact woff2 subsets Google was serving,
   instanced down to the axis ranges this site actually uses (Archivo
   wght 600-800 / wdth 100-125, Hanken Grotesk wght 400-700), which is why
   they are 20-35% smaller than the originals. Untouched originals plus the
   source CSS are kept in site/assets-src/fonts-google-original/.

   To refresh: re-fetch the Google css2 URL with a modern Chrome UA, download
   the latin and latin-ext woff2 files, and re-run the instancer.

   Deliberately NOT preloaded. Measured on 21/09/2026: preloading these two
   files put 92KB at High priority alongside the hero image and pushed the home
   page's LCP from 2.26s to 2.91s and its Lighthouse score from 90 to 84. The
   fallback faces below cost almost nothing to swap, so the fonts are better off
   being discovered from this stylesheet after the hero has the bandwidth.

   The four "Fallback" faces below exist so the swap from system font to web
   font barely moves the layout. size-adjust is the measured ratio of average
   advance width (English letter frequency weighted) between the real face and
   the local fallback; ascent/descent are the real face's own hhea metrics
   re-expressed against that adjusted em. Archivo is measured at wght 700 /
   wdth 118%, which is what h1 and h2 use, so the biggest headings shift least. */

@font-face {
  font-family: "Archivo";
  font-style: normal;
  font-weight: 600 800;
  font-stretch: 100% 125%;
  font-display: swap;
  src: url(/assets/fonts/archivo-latin.woff2) format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: "Archivo";
  font-style: normal;
  font-weight: 600 800;
  font-stretch: 100% 125%;
  font-display: swap;
  src: url(/assets/fonts/archivo-latin-ext.woff2) format("woff2");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: "Hanken Grotesk";
  font-style: normal;
  font-weight: 400 700;
  font-display: swap;
  src: url(/assets/fonts/hanken-grotesk-latin.woff2) format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: "Hanken Grotesk";
  font-style: normal;
  font-weight: 400 700;
  font-display: swap;
  src: url(/assets/fonts/hanken-grotesk-latin-ext.woff2) format("woff2");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

/* Metric-matched fallbacks. Roboto first (Android), then Arial (Windows, macOS,
   iOS). If neither is installed the face is skipped and the stack falls through
   to Avenir Next and then the platform sans, exactly as before. */
@font-face { font-family: "Archivo Fallback"; src: local("Roboto"); size-adjust: 126.41%; ascent-override: 69.46%; descent-override: 16.61%; line-gap-override: 0%; }
@font-face { font-family: "Archivo Fallback 2"; src: local("Arial"); size-adjust: 127.30%; ascent-override: 68.97%; descent-override: 16.50%; line-gap-override: 0%; }
@font-face { font-family: "Hanken Fallback"; src: local("Roboto"); size-adjust: 101.12%; ascent-override: 98.89%; descent-override: 29.96%; line-gap-override: 0%; }
@font-face { font-family: "Hanken Fallback 2"; src: local("Arial"); size-adjust: 101.83%; ascent-override: 98.20%; descent-override: 29.75%; line-gap-override: 0%; }

:root {
  --cream: #FFFBF5;
  --cream-2: #F8F5F0;
  --stone: #E8DDD0;
  --stone-2: #F4EFE6;
  --line: #E0D3C1;
  --ink: #1A1A18;
  --ink-deep: #0E0E0E;
  --ink-soft: #2A2A26;
  --text: #1A1A18;
  --text-2: #6B5F4E;
  --on-ink: #F4EDE1;
  --on-ink-2: #CBC2B2;
  --gold: #D9A05C;
  --gold-deep: #8A6320;
  --clay: #C67B5C;
  --clay-soft: #D0906F;
  --clay-deep: #9C5136;
  --clay-text: #9C4E2F;
  --sage: #5B6144;
  /* legacy aliases kept for inline references */
  --sand: var(--cream);
  --sand-deep: var(--stone-2);
  --sand-line: var(--line);
  --radius: 14px;
  --radius-sm: 8px;
  --font-display: "Archivo", "Archivo Fallback", "Archivo Fallback 2", "Avenir Next", sans-serif;
  --font-body: "Hanken Grotesk", "Hanken Fallback", "Hanken Fallback 2", "Avenir Next", sans-serif;
  --ease-out: cubic-bezier(0.16, 1, 0.3, 1);
}

* { margin: 0; padding: 0; box-sizing: border-box; }
html { scroll-behavior: smooth; }
/* Anchored sections land clear of the sticky header (round 5: /retreats/#destinations) */
main [id] { scroll-margin-top: 78px; }

body {
  background: var(--cream);
  color: var(--text);
  font-family: var(--font-body);
  font-size: 1.0625rem;
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
}

img { display: block; max-width: 100%; }
/* Hero photos are now <picture> wrappers (AVIF/WebP with a JPEG fallback). A
   <picture> is inline by default and has no height of its own, so an inner
   `height: 100%` would have no definite parent to resolve against and the
   image would collapse or overflow. Giving the wrapper the same box as the
   image keeps every cover-crop identical to before. WebKit is stricter about
   this than Chromium, so it is a rule, not a nicety. */
.hero-photo picture,
.fl-hero-photo picture,
.lnk-top picture,
.pillar-photo picture,
.split-photo picture,
.band picture { display: block; width: 100%; height: 100%; }

h1, h2, h3 {
  font-family: var(--font-display);
  font-stretch: 112%;
  font-weight: 700;
  line-height: 1.06;
  letter-spacing: -0.01em;
  text-wrap: balance;
}
h1 { font-size: clamp(2.1rem, 5vw, 3.6rem); font-stretch: 118%; }
h2 { font-size: clamp(1.75rem, 4vw, 2.7rem); margin-bottom: 0.6em; font-stretch: 118%; }
h3 { font-size: clamp(1.15rem, 2.4vw, 1.4rem); font-weight: 700; margin-bottom: 0.4em; }
h4 { font-family: var(--font-body); font-weight: 700; font-size: 1.05rem; margin-bottom: 0.35em; }

/* Round 17 (Alex, 10/09/2026): NO CHARACTER-MEASURE CAPS ON BODY TEXT.
   This used to read `p, li { max-width: 68ch }`, which stopped every paragraph
   and list item short of its column — most visibly the itinerary closing lines
   at ~65ch inside a 1136px section. Body text now fills whatever column it sits
   in. The columns still do the constraining: the 7/5 `.split`, card padding,
   and the grid all keep measures sane where it matters. If a specific block
   ever needs a narrower measure again, cap that block, never `p` globally. */
p, li { max-width: none; }
p { text-wrap: pretty; }
/* Wrap-measure rule (round 5, supersedes round 4): a standalone line in a wide
   section runs the FULL container measure. Round 4 capped this at 95ch with
   text-wrap: balance, but balance splits any 100-250 char line into two equal
   half-width lines, which reads as the same narrow block the founder keeps
   flagging. Full width + pretty = first line fills the section, no orphans. */
.line-wide { max-width: 100%; text-wrap: pretty; }
a { color: inherit; }
strong { font-weight: 700; }

:focus-visible {
  outline: 3px solid var(--clay-deep);
  outline-offset: 3px;
  border-radius: 2px;
}
.panel-ink :focus-visible, .hero :focus-visible, footer :focus-visible,
.site-header :focus-visible, .to-top:focus-visible {
  outline-color: var(--gold);
}

.container { max-width: 1180px; margin-inline: auto; padding-inline: 22px; }
.section { padding-block: clamp(60px, 9.5vw, 116px); }
.section-tight { padding-block: clamp(44px, 6vw, 76px); }

/* The gold horizon — the site's signature rule. Draws on load, everywhere. */
.horizon {
  border: 0;
  height: 2px;
  background: var(--gold);
  width: 84px;
  margin: 0 0 26px;
  transform-origin: left;
  animation: horizon-draw 0.9s var(--ease-out) both;
}
.horizon-wide { width: 100%; opacity: 0.55; }

/* ---------- Header ---------- */
.site-header {
  position: sticky;
  top: 0;
  /* 70, not 50 (29/09): the header is the stacking context for the phone menu and the
     desktop dropdowns, so it must sit ABOVE the fixed page chrome at z-index 60 (.to-top on
     every page, .fl-sticky on /fitness-lab-marbella/). At 50 the open phone menu was painted
     under both. The fixed chrome lives at the bottom of the screen, so it never meets the
     closed header. The Travel Club <dialog> uses the top layer and is unaffected. */
  z-index: 70;
  background: var(--ink);
  color: var(--on-ink);
  border-bottom: 1px solid rgba(217, 160, 92, 0.35);
}
.nav-bar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  min-height: 64px;
}
.nav-logo img { height: 26px; width: auto; }
.nav-links {
  display: none;
  list-style: none;
  gap: 28px;
  align-items: center;
}
.nav-links a {
  text-decoration: none;
  font-weight: 600;
  font-size: 0.95rem;
  color: var(--on-ink);
  padding-block: 6px;
  border-bottom: 2px solid transparent;
  transition: border-color 0.2s ease;
}
.nav-links a:hover { border-bottom-color: var(--gold); }
.nav-links a[aria-current="page"], .nav-links a.current-section { border-bottom-color: var(--gold); }
.nav-cta { display: flex; align-items: center; gap: 12px; }

.nav-menu { position: relative; }
.nav-menu > summary {
  list-style: none;
  cursor: pointer;
  font-weight: 700;
  font-size: 0.9rem;
  padding: 8px 12px;
  border: 1px solid rgba(244, 237, 225, 0.4);
  border-radius: var(--radius-sm);
  color: var(--on-ink);
}
.nav-menu > summary::-webkit-details-marker { display: none; }
.nav-menu[open] > summary { background: var(--ink-soft); }
.nav-menu .menu-panel {
  position: absolute;
  right: 0;
  top: calc(100% + 10px);
  background: var(--ink);
  border-radius: var(--radius);
  padding: 14px;
  min-width: 220px;
  box-shadow: 0 18px 40px rgba(10, 10, 8, 0.45);
}
.nav-menu .menu-panel a {
  display: block;
  padding: 10px 12px;
  text-decoration: none;
  font-weight: 600;
  border-radius: var(--radius-sm);
  color: var(--on-ink);
}
.nav-menu .menu-panel a:hover { background: var(--ink-soft); color: #fff; }

/* Retreats dropdown (desktop). CSS-only open on :hover/:focus-within with a
   close-grace (delayed hide) so diagonal mouse paths survive; the parent link
   still navigates to /retreats. JS adds a caret toggle with aria-expanded for
   touch/keyboard; without JS the caret is hidden and hover/focus still work. */
.nav-drop { position: relative; display: flex; align-items: center; gap: 2px; }
.drop-panel {
  position: absolute;
  left: -14px;
  top: 100%;
  padding-top: 10px;
  opacity: 0;
  visibility: hidden;
  transform: translateY(6px);
  transition: opacity 0.18s ease 0.25s, transform 0.18s var(--ease-out) 0.25s, visibility 0s linear 0.43s;
  z-index: 60;
}
.drop-panel-inner {
  background: var(--ink);
  border-radius: var(--radius);
  padding: 10px;
  min-width: 250px;
  box-shadow: 0 18px 40px rgba(10, 10, 8, 0.45);
}
.nav-drop:hover .drop-panel,
.nav-drop:focus-within .drop-panel,
.nav-drop.open .drop-panel {
  opacity: 1;
  visibility: visible;
  transform: none;
  transition-delay: 0s;
}
/* Two dropdowns (round 5): the close-grace must die the instant a SIBLING drop
   engages, or both panels show at once during the grace window. When no drop is
   engaged the selector stops matching, so each panel keeps its own grace. */
.nav-links:has(.nav-drop:is(:hover, .open, :focus-within)) .nav-drop:not(:hover):not(.open):not(:focus-within) .drop-panel { transition: none; }
.drop-panel a {
  display: block;
  padding: 10px 12px;
  border-radius: var(--radius-sm);
  border-bottom: 0;
  font-size: 0.95rem;
  white-space: nowrap;
}
.drop-panel a:hover { background: var(--ink-soft); color: #fff; border-bottom: 0; }
.drop-panel a[aria-current="page"] { border-bottom: 0; color: var(--gold); }
.drop-panel .drop-all {
  border-top: 1px solid rgba(217, 160, 92, 0.3);
  margin-top: 6px;
  padding-top: 12px;
  color: var(--on-ink-2);
  font-size: 0.9rem;
}
/* Second page link in the bottom section ("Upcoming partner Escapes" under "All Escapes",
   Alex 29/09): same look, no second divider. */
.drop-panel .drop-all + .drop-all { border-top: 0; margin-top: 0; padding-top: 10px; }
.drop-toggle {
  display: none;
  background: none;
  border: 0;
  padding: 6px 4px;
  cursor: pointer;
  color: var(--on-ink);
}
.js .drop-toggle { display: inline-flex; align-items: center; }
.drop-toggle svg {
  width: 12px;
  height: 12px;
  stroke: var(--gold);
  stroke-width: 2.4;
  fill: none;
  transition: transform 0.25s var(--ease-out);
}
.nav-drop.open .drop-toggle svg, .nav-drop:hover .drop-toggle svg { transform: rotate(180deg); }

/* Mobile menu sub-items (retreats under Retreats) */
.nav-menu .menu-panel a.menu-sub {
  position: relative;
  padding-left: 32px;
  font-size: 0.92rem;
  color: var(--on-ink-2);
}
.menu-panel .menu-sub::before {
  content: "";
  position: absolute;
  left: 12px;
  top: calc(50% - 1px);
  width: 12px;
  height: 2px;
  background: var(--gold);
}

/* Round 5: five items + two carets need ~920px with the logo and CTA, so the
   desktop nav appears at 960px (was 880) and runs a tighter gap up to 1120px. */
@media (min-width: 960px) {
  .nav-links { display: flex; }
  .nav-menu { display: none; }
}
@media (min-width: 960px) and (max-width: 1120px) {
  .nav-links { gap: 18px; }
}

/* ---------- Buttons ---------- */
.btn {
  display: inline-block;
  font-family: var(--font-body);
  font-weight: 700;
  font-size: 1rem;
  text-decoration: none;
  border-radius: var(--radius-sm);
  padding: 14px 26px;
  border: 0;
  cursor: pointer;
  transition: background-color 0.2s ease, transform 0.2s var(--ease-out), box-shadow 0.25s var(--ease-out);
}
.btn-primary { background: var(--clay); color: var(--ink); }
.btn-primary:hover {
  background: var(--clay-soft);
  transform: translateY(-2px);
  box-shadow: 0 10px 24px rgba(156, 81, 54, 0.28);
}
.btn-ghost {
  background: transparent;
  color: var(--text);
  box-shadow: inset 0 0 0 2px var(--ink);
}
.btn-ghost:hover { background: var(--ink); color: var(--on-ink); }
.btn-ghost-light {
  background: transparent;
  color: var(--on-ink);
  box-shadow: inset 0 0 0 2px var(--on-ink);
}
.btn-ghost-light:hover { background: var(--on-ink); color: var(--ink); }
.btn-small { padding: 10px 18px; font-size: 0.92rem; }
@media (max-width: 480px) {
  .nav-cta .btn-small { white-space: nowrap; font-size: 0.85rem; padding: 9px 12px; }
}
.text-link {
  font-weight: 600;
  color: var(--text);
  text-underline-offset: 4px;
  text-decoration-thickness: 2px;
  text-decoration-color: var(--gold);
  transition: text-decoration-color 0.2s ease;
}
.text-link:hover { text-decoration-color: var(--clay-deep); }

/* ---------- Hero ---------- */
.hero {
  position: relative;
  background: var(--ink);
  min-height: 78vh;
  display: flex;
  align-items: flex-end;
  color: #fff;
  isolation: isolate;
  overflow: hidden;
}
.hero-full { min-height: calc(100vh - 64px); min-height: calc(100svh - 64px); }
.hero-photo {
  position: absolute;
  inset: 0;
  z-index: -2;
}
.hero-photo img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  animation: first-light 1.8s var(--ease-out) both;
}
.hero-scrim {
  position: absolute;
  inset: 0;
  z-index: -1;
  background: linear-gradient(
    to top,
    rgba(14, 14, 14, 0.88) 0%,
    rgba(14, 14, 14, 0.55) 34%,
    rgba(14, 14, 14, 0.12) 62%,
    rgba(14, 14, 14, 0.05) 100%
  );
}
.hero-inner { width: 100%; padding-block: clamp(48px, 7vw, 88px) clamp(24px, 4vw, 48px); }
@media (max-width: 640px) {
  .hero h1 { font-size: clamp(1.9rem, 8.2vw, 2.4rem); }
  .hero p { margin-top: 14px; }
  .hero-actions { margin-top: 20px; }
}
.hero .horizon { animation: horizon-draw 1.1s 0.5s var(--ease-out) both; }
.hero h1 { animation: rise-in 0.9s 0.15s var(--ease-out) both; }
.hero p { color: #F5EEE1; font-size: clamp(1.05rem, 2vw, 1.25rem); margin-top: 18px; animation: rise-in 0.9s 0.3s var(--ease-out) both; }
.hero-actions { display: flex; flex-wrap: wrap; gap: 16px; align-items: center; margin-top: 28px; animation: rise-in 0.9s 0.45s var(--ease-out) both; }
.hero-actions .text-link { color: #fff; }
.hero .ledger { animation: rise-in 0.9s 0.6s var(--ease-out) both; }
/* /retreats/ hero (01/10/2026 image-alignment pass). The athlete sits 10-45%
   across the frame; a centred crop on a phone or tablet showed only railings
   and the edge of a plate. Below 960px the crop now holds him, head to plate. */
/* Second pass (01/10/2026): with the photo full-bleed behind the copy the text
   still covered him at 390 and 820. Below 960px the photo is now a band above
   the text, fading into the ink, the same treatment as the lean Escape pages
   (.esc-hero-land). The band height is set so the athlete, head to plate, fits
   across the width: at 340px tall the 16:9 frame is 604px wide and he spans
   60-350px of it; at 615px tall (820 wide) it is 1093px wide and he spans
   110-635px. The text starts in the faded tail, under his waist, never on him.
   Desktop (960px up) is unchanged: the image is width-bound there, so a crop
   can only move it up or down, and centre is the only position that keeps his
   head and the bar clear of the headline without cutting the head off. */
@media (max-width: 959px) {
  .hero-escapes { --band: clamp(340px, 75vw, 620px); min-height: 0 !important; display: block; }
  .hero-escapes .hero-photo {
    bottom: auto;
    height: var(--band);
    -webkit-mask-image: linear-gradient(to bottom, #000 66%, transparent 100%);
    mask-image: linear-gradient(to bottom, #000 66%, transparent 100%);
  }
  .hero-escapes .hero-photo img { object-position: 14% 50%; }
  .hero-escapes .hero-scrim { background: linear-gradient(to bottom, rgba(14, 14, 14, 0.06) 0%, rgba(14, 14, 14, 0) 30%); }
  .hero-escapes .hero-inner { padding-top: calc(var(--band) - 32px); }
}

@keyframes first-light {
  from { filter: brightness(0.66) saturate(0.82); transform: scale(1.07); }
  to   { filter: brightness(1) saturate(1); transform: scale(1); }
}
@keyframes horizon-draw {
  from { transform: scaleX(0); }
  to   { transform: scaleX(1); }
}
@keyframes rise-in {
  from { opacity: 0; transform: translateY(26px); }
  to   { opacity: 1; transform: none; }
}

/* ---------- Scroll reveals (JS-gated: nothing hides without the js class) ----------
   site.js adds .reveal only to elements BELOW the first viewport, then flips
   .is-in via IntersectionObserver. Stagger comes from --rd set per sibling. */
.js .reveal {
  opacity: 0;
  transform: translateY(30px);
  transition: opacity 0.7s ease var(--rd, 0s), transform 0.8s var(--ease-out) var(--rd, 0s);
}
.js .reveal.is-in { opacity: 1; transform: none; }

/* The retreat ledger — one line of facts on the horizon.
   Wrap-safe (round 4): items are flex children spaced by gap, so each fact
   wraps as a whole. The gold dash lives in the gap via an absolute ::before;
   overflow:hidden clips the dash when an item starts a wrapped line, so no
   line ever opens with a dangling separator. */
.ledger {
  display: flex;
  flex-wrap: wrap;
  gap: 10px 30px;
  align-items: center;
  margin-top: 30px;
  padding-top: 16px;
  border-top: 2px solid var(--gold);
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  font-size: 0.98rem;
  color: #F5EEE1;
  overflow: hidden;
}
.ledger span { position: relative; }
.ledger span + span::before {
  content: "";
  position: absolute;
  left: -22px;
  top: calc(50% - 1px);
  width: 14px;
  height: 2px;
  background: var(--gold);
}
.ledger-dark { color: var(--text); border-top-color: var(--gold); }

/* ---------- Ink panels (FULL-BLEED SECTIONS ONLY — never cards) ---------- */
.panel-ink { background: var(--ink); color: var(--on-ink); }
.panel-ink h2, .panel-ink h3 { color: #fff; }
.panel-ink p { color: var(--on-ink); }
.panel-ink strong { color: #fff; }
.panel-ink .muted { color: var(--on-ink-2); }
.panel-sand-deep { background: var(--stone-2); }
.panel-stone { background: var(--stone); }

.muted { color: var(--text-2); }

/* A single pulled line in their voice — big, quiet band */
.pull-line {
  font-family: var(--font-display);
  font-stretch: 118%;
  font-weight: 700;
  font-size: clamp(1.4rem, 3.4vw, 2.3rem);
  line-height: 1.2;
  letter-spacing: -0.01em;
  text-wrap: balance;
}

/* ---------- Full-bleed image bands ---------- */
.band {
  position: relative;
  overflow: hidden;
  max-height: 520px;
}
.band img {
  width: 100%;
  height: clamp(240px, 42vw, 520px);
  object-fit: cover;
}
/* JS-driven settle-in: image starts zoomed, eases to rest when the band enters. */
.js .band img { transform: scale(1.1); transition: transform 1.6s var(--ease-out); transform-origin: center; }
.js .band.is-in img { transform: scale(1); }
.band-caption {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  max-width: none;
  padding: 60px 22px 22px;
  background: linear-gradient(to top, rgba(14, 14, 14, 0.85), rgba(14, 14, 14, 0.4) 60%, rgba(14, 14, 14, 0));
  color: #F5EEE1;
  font-weight: 600;
  font-size: 1rem;
  /* Round 5: bands carry a CTA. Caption left, button bottom-right on desktop;
     the button wraps below the caption on narrow screens. The clay button
     carries its own AA surface (ink on clay 5.3:1), independent of the photo. */
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  justify-content: space-between;
  gap: 12px 28px;
}
.band-caption > span {
  display: inline-block;
  border-top: 2px solid var(--gold);
  padding-top: 10px;
  max-width: 1136px;
  flex: 1 1 300px;
}
.band-cta { flex-shrink: 0; }
@media (min-width: 1180px) {
  .band-caption { padding-inline: calc((100vw - 1136px) / 2); }
}
/* Measured 16/09/2026, on the rendered page, sampling the painted pixels under
   the glyphs: at 1280 every band caption clears AA comfortably (5.3:1 to 6.9:1),
   but at 390 the caption wraps to two lines and the button drops below it, so
   the text rides up into the weak end of the gradient. Every band on the site
   with a CTA measured 2.3:1 to 3.1:1 there. The scrim is deepened and reaches
   higher on narrow screens only; desktop is untouched. */
@media (max-width: 759px) {
  .band-caption {
    padding-top: 80px;
    background: linear-gradient(
      to top,
      rgba(14, 14, 14, 0.94) 0%,
      rgba(14, 14, 14, 0.9) 46%,
      rgba(14, 14, 14, 0.78) 70%,
      rgba(14, 14, 14, 0.34) 90%,
      rgba(14, 14, 14, 0) 100%
    );
  }
}

/* ---------- Galleries ---------- */
.gallery {
  display: grid;
  gap: 14px;
  grid-template-columns: 1fr 1fr;
}
.gallery figure { position: relative; border-radius: var(--radius); overflow: hidden; margin: 0; }
.gallery img { width: 100%; height: 100%; object-fit: cover; aspect-ratio: 4 / 3; }
.gallery .gallery-wide { grid-column: 1 / -1; }
.gallery .gallery-wide img { aspect-ratio: 21 / 10; }
@media (min-width: 860px) {
  .gallery { grid-template-columns: repeat(3, 1fr); }
  .gallery .gallery-wide { grid-column: span 2; grid-row: span 1; }
  .gallery .gallery-wide img { aspect-ratio: auto; }
}
/* Round 7: gallery balancing utilities, so no empty grid cells at any width.
   .gallery-full spans the whole row at every width (hero image of a gallery);
   .gallery-wide keeps its round-1 behaviour (full row on phones, 2 of 3
   columns on desktop). Arrange counts so every row closes. */
.gallery .gallery-full { grid-column: 1 / -1; }
.gallery .gallery-full img { aspect-ratio: 16 / 10; }
@media (min-width: 860px) {
  .gallery .gallery-full img { aspect-ratio: 21 / 9; }
}
.gallery figcaption {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  padding: 36px 16px 12px;
  background: linear-gradient(to top, rgba(14, 14, 14, 0.82), rgba(14, 14, 14, 0.35) 65%, rgba(14, 14, 14, 0));
  color: #F5EEE1;
  font-weight: 600;
  font-size: 0.92rem;
}

/* ---------- Image-led pillars (salt "Why" pattern, our brand system) ----------
   Round 4: stagger removed — all pillar images sit at the same height, top-aligned. */
.pillars { display: grid; gap: 34px; margin-top: 40px; align-items: start; }
@media (min-width: 700px) {
  .pillars { grid-template-columns: 1fr 1fr; gap: 44px 48px; }
}
@media (min-width: 1024px) {
  .pillars-4 { grid-template-columns: repeat(4, 1fr); gap: 34px; }
}
.pillar-photo {
  border-radius: var(--radius);
  overflow: hidden;
  aspect-ratio: 4 / 5;
  margin-bottom: 18px;
}
.pillar-photo img { width: 100%; height: 100%; object-fit: cover; }
.pillar h3 { margin-bottom: 6px; }
.pillar p { font-size: 1rem; }

/* ---------- Photo-overlay destination tiles (salt destination-card pattern) ---------- */
a.tile-photo {
  position: relative;
  display: block;
  border-radius: var(--radius);
  overflow: hidden;
  aspect-ratio: 4 / 5;
  text-decoration: none;
  color: #fff;
  isolation: isolate;
  transition: transform 0.3s var(--ease-out), box-shadow 0.3s var(--ease-out);
}
.tile-photo img {
  position: absolute;
  inset: 0;
  z-index: -1;
  width: 100%;
  height: 100%;
  object-fit: cover;
}
.tile-photo .tile-overlay {
  position: absolute;
  inset: auto 0 0 0;
  padding: 90px 22px 20px;
  background: linear-gradient(to top, rgba(14, 14, 14, 0.94), rgba(14, 14, 14, 0.6) 55%, rgba(14, 14, 14, 0));
}
.tile-photo h3 { color: #fff; margin-bottom: 4px; }
.tile-photo .tile-overlay p { color: #F5EEE1; font-size: 0.98rem; margin: 0; }
.tile-photo .tile-go {
  display: inline-block;
  margin-top: 10px;
  font-weight: 700;
  font-size: 0.92rem;
  color: var(--gold);
}
.tile-photo::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: 3px;
  background: var(--gold);
  transform: scaleX(0);
  transform-origin: left;
  transition: transform 0.4s var(--ease-out);
}
.tile-photo:hover, .tile-photo:focus-visible {
  transform: translateY(-5px);
  box-shadow: 0 18px 38px rgba(26, 26, 24, 0.18);
}
.tile-photo:hover::after { transform: scaleX(1); }

/* ---------- Instagram grid ---------- */
.ig-grid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 10px;
  margin-top: 30px;
}
@media (min-width: 760px) { .ig-grid { grid-template-columns: repeat(4, 1fr); } }
.ig-grid a {
  position: relative;
  display: block;
  border-radius: 10px;
  overflow: hidden;
  aspect-ratio: 1;
  transition: transform 0.3s var(--ease-out), box-shadow 0.3s var(--ease-out);
}
.ig-grid img { width: 100%; height: 100%; object-fit: cover; }
.ig-grid a::after {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(to top, rgba(14, 14, 14, 0.3), transparent 45%);
  opacity: 0;
  transition: opacity 0.25s ease;
}
.ig-grid a:hover { transform: translateY(-4px); box-shadow: 0 14px 28px rgba(26, 26, 24, 0.16); }
.ig-grid a:hover::after { opacity: 1; }

/* ---------- Editorial day-by-day itinerary (no boxes) ---------- */
.day-list { margin-top: 34px; border-top: 2px solid var(--gold); }
.day {
  display: grid;
  gap: 8px 44px;
  padding-block: 30px;
  border-bottom: 1px solid var(--line);
}
@media (min-width: 760px) {
  .day { grid-template-columns: 200px 1fr; }
}
.day-name {
  font-family: var(--font-display);
  font-stretch: 118%;
  font-weight: 700;
  font-size: 1.25rem;
  line-height: 1.2;
}
.day-name small {
  display: block;
  font-family: var(--font-body);
  font-weight: 600;
  font-size: 0.88rem;
  color: var(--gold-deep);
  margin-bottom: 4px;
}
/* Round 16: one entry per line, each with its own gold tick. The previous
   wrapping row bunched two or three times together and broke the reading
   order of the day (Alex, 07/09). Body line-height is 1.6, so the tick sits
   at 0.8em to centre on the first line of a wrapping entry. */
.day-times {
  display: grid;
  gap: 9px;
  margin-top: 12px;
  font-variant-numeric: tabular-nums;
  font-weight: 600;
  font-size: 0.95rem;
  color: var(--text-2);
}
.day-times span {
  position: relative;
  padding-left: 24px;
}
.day-times span::before {
  content: "";
  position: absolute;
  left: 0;
  top: calc(0.8em - 1px);
  width: 12px;
  height: 2px;
  background: var(--gold);
}

/* ---------- Cards (always white — ink and stone are section colours) ---------- */
.card {
  background: #fff;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  overflow: hidden;
  display: flex;
  flex-direction: column;
  position: relative;
  transition: border-color 0.2s ease, transform 0.25s var(--ease-out), box-shadow 0.3s var(--ease-out);
}
a.card { text-decoration: none; color: inherit; }
.card::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: 3px;
  background: var(--gold);
  transform: scaleX(0);
  transform-origin: left;
  transition: transform 0.4s var(--ease-out);
}
.card:hover {
  border-color: var(--gold);
  transform: translateY(-5px);
  box-shadow: 0 18px 38px rgba(26, 26, 24, 0.14);
}
.card:hover::after { transform: scaleX(1); }
.card-photo { position: relative; aspect-ratio: 16 / 10; overflow: hidden; }
.card-photo img { width: 100%; height: 100%; object-fit: cover; }
.card-body { padding: 22px 24px 26px; display: flex; flex-direction: column; gap: 10px; flex: 1; }
.card-body h3 { margin-bottom: 0; }
.tile-label {
  font-family: var(--font-display);
  font-stretch: 118%;
  font-weight: 700;
  font-size: 1.3rem;
  letter-spacing: -0.01em;
}
.card-facts {
  font-variant-numeric: tabular-nums;
  font-weight: 600;
  font-size: 0.95rem;
  color: var(--text-2);
  border-top: 2px solid var(--gold);
  padding-top: 10px;
  margin-top: auto;
}

.badge {
  position: absolute;
  top: 14px;
  left: 14px;
  background: var(--ink);
  color: var(--on-ink);
  font-weight: 700;
  font-size: 0.74rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  padding: 7px 13px;
  border-radius: 999px;
}
.badge-live { background: var(--clay); color: var(--ink); }
.badge-inline {
  display: inline-block;
  position: static;
  margin-bottom: 4px;
  width: fit-content;
}

/* ---------- Split sections (image height locked to content height) ---------- */
.split {
  display: grid;
  gap: 28px;
  align-items: stretch;
}
.split-photo {
  border-radius: var(--radius);
  overflow: hidden;
  align-self: stretch;
  min-height: 300px;
  max-height: 600px;
}
.split-photo img { width: 100%; height: 100%; object-fit: cover; min-height: 300px; }
.split-photo-capped { max-height: 460px; }
.split-photo-capped img { object-position: center 30%; }
@media (min-width: 860px) {
  .split { grid-template-columns: 7fr 5fr; gap: 56px; }
  .split.flip .split-photo { order: 2; }
  .split-8-4 { grid-template-columns: 8fr 4fr; }
}
@media (max-width: 859px) {
  .split-photo, .split-photo img { min-height: 0; max-height: 380px; }
}

/* ---------- Steps on the horizon ---------- */
.steps {
  list-style: none;
  display: grid;
  gap: 30px;
  counter-reset: step;
}
.steps li {
  position: relative;
  padding-top: 18px;
  border-top: 2px solid var(--gold);
}
.steps li::before {
  counter-increment: step;
  content: counter(step);
  font-family: var(--font-display);
  font-stretch: 118%;
  font-weight: 800;
  font-size: 1.1rem;
  color: var(--gold-deep);
  display: block;
  margin-bottom: 6px;
}
.panel-ink .steps li::before { color: var(--gold); }
@media (min-width: 760px) {
  .steps { grid-template-columns: repeat(3, 1fr); gap: 40px; }
  .steps-4 { grid-template-columns: repeat(4, 1fr); }
}

/* ---------- Quotes ---------- */
.quote-row { display: grid; gap: 24px; }
@media (min-width: 860px) { .quote-row { grid-template-columns: repeat(3, 1fr); } }
.quote {
  background: #fff;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 26px 26px 22px;
  display: flex;
  flex-direction: column;
  gap: 14px;
}
.quote blockquote { font-size: 1.05rem; line-height: 1.55; }
.quote cite {
  font-style: normal;
  font-weight: 600;
  color: var(--text-2);
  font-size: 0.92rem;
  border-top: 2px solid var(--gold);
  padding-top: 10px;
  display: inline-block; /* one box, so the gold rule never repeats through a wrapped caption */
}

/* ---------- Host review feature (host-a-retreat, 21/09/2026) ----------
   Heading on top, review across the full container width, quote marks only on
   the bold first line and at the very end, credit under a gold rule. */
.panel-ink .host-review-first {
  font-family: var(--font-display);
  font-stretch: 112%;
  font-weight: 700;
  font-size: clamp(1.3rem, 2vw, 1.6rem);
  line-height: 1.25;
  color: #fff;
}
.host-review-q { color: var(--gold); font-family: var(--font-display); font-weight: 700; }
.host-review-first .host-review-q { margin-right: 0.06em; }
.host-review-full { margin-top: 8px; }
.host-review-full blockquote p { font-size: 1.05rem; line-height: 1.65; }
.host-review-full blockquote p + p { margin-top: 1em; }
.host-review-full blockquote p:last-child .host-review-q { margin-left: 0.04em; }
.host-review-credit {
  margin-top: 28px;
  padding-top: 18px;
  border-top: 2px solid var(--gold);
  display: grid;
  gap: 4px;
}
.host-review-stars { color: var(--gold); font-size: 1.25rem; letter-spacing: 0.18em; }
.host-review-name { color: #fff; font-family: var(--font-display); font-weight: 700; font-size: 1.35rem; }
.host-review-role { color: var(--gold); font-weight: 600; font-size: 1rem; }

/* ---------- "Enjoy the country you are in" (home, 21/09/2026) ----------
   Beth asked for the afternoon half of an Escape on the home page. It sits on
   ink between the villa band and Upcoming Escapes. Two rules keep it from
   reading as a fifth pillar row or a repeat of the destination tiles:
   1. The heading and the copy sit SIDE BY SIDE. Every other section on the home
      page stacks an h2 over a lede, so this row is the section's own shape.
   2. The two photos are deliberately unequal (4/7), top-aligned, and only the
      boat carries a caption. The caption block is what closes the height
      difference between the two columns, so no magic offset is needed.
   Photos are plain <img> at intrinsic size inside a block <picture>, so there is
   no percentage height resolving against an indefinite parent. That is the
   WebKit trap this site has hit before; this block cannot hit it. */
.afternoon-head { display: grid; gap: 10px 56px; }
.afternoon-copy > p + p { margin-top: 14px; }
.afternoon-more { margin-top: 22px; }
/* .text-link is ink on light surfaces, which would vanish here. Scoped to this
   section so no other ink panel on the site changes. on-ink/ink is 15:1. */
.afternoon .text-link { color: var(--on-ink); }
.afternoon .text-link:hover { color: #fff; text-decoration-color: #fff; }

.afternoon-photos {
  display: grid;
  gap: 30px;
  margin-top: clamp(32px, 4vw, 48px);
  align-items: start;
}
.afternoon-shot { margin: 0; }
.afternoon-shot picture { display: block; }
.afternoon-shot img {
  display: block;
  width: 100%;
  height: auto;
  border-radius: var(--radius);
}
.afternoon-boat figcaption {
  margin-top: 18px;
  padding-top: 14px;
  border-top: 2px solid var(--gold);
  display: grid;
  gap: 5px;
}
.afternoon-label {
  font-family: var(--font-display);
  font-stretch: 112%;
  font-weight: 700;
  font-size: clamp(1.15rem, 2vw, 1.35rem);
  line-height: 1.15;
  letter-spacing: -0.01em;
  color: #fff;
}
/* on-ink-2 on ink measures 9.9:1, so the caption line clears AA on its own. */
.afternoon-note { color: var(--on-ink-2); font-size: 0.98rem; line-height: 1.5; }

@media (min-width: 760px) {
  .afternoon-head { grid-template-columns: 5fr 7fr; }
  /* The gold rule sits above the heading in the left column, so without this the
     copy started 28px higher than the heading and read as not-quite-aligned.
     28px = the rule's 2px plus its 26px margin. */
  .afternoon-copy { padding-top: 28px; }
  .afternoon-photos { grid-template-columns: 4fr 7fr; gap: 40px; }
}

/* ---------- FAQ ---------- */
.faq { border-top: 1px solid var(--line); }
.faq details { border-bottom: 1px solid var(--line); }
.faq summary {
  cursor: pointer;
  list-style: none;
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 18px;
  padding: 20px 4px;
  font-weight: 700;
  font-size: 1.08rem;
}
.faq summary::-webkit-details-marker { display: none; }
.faq summary::after {
  content: "+";
  font-family: var(--font-display);
  font-size: 1.4rem;
  color: var(--gold-deep);
  flex-shrink: 0;
  transition: transform 0.25s var(--ease-out);
}
.faq details[open] summary::after { transform: rotate(45deg); }
.faq .faq-body { padding: 0 4px 22px; color: var(--text-2); }
.faq summary:hover { color: var(--clay-text); }

/* ---------- Tables ---------- */
.table-wrap { overflow-x: auto; }
table.data {
  border-collapse: collapse;
  width: 100%;
  font-size: 0.98rem;
}
table.data th, table.data td {
  text-align: left;
  padding: 12px 16px;
  border-bottom: 1px solid var(--line);
  vertical-align: top;
}
table.data th {
  font-weight: 700;
  border-bottom: 2px solid var(--gold);
  white-space: nowrap;
}

/* ---------- Forms ---------- */
.form-grid { display: grid; gap: 18px; }
@media (min-width: 700px) {
  .form-grid { grid-template-columns: 1fr 1fr; }
  .form-grid .form-full { grid-column: 1 / -1; }
}
.field label {
  display: block;
  font-weight: 700;
  font-size: 0.95rem;
  margin-bottom: 6px;
}
.field .hint { font-weight: 400; color: var(--text-2); font-size: 0.88rem; }
.field input, .field select, .field textarea {
  width: 100%;
  font: inherit;
  color: var(--text);
  background: #fff;
  border: 1px solid #B3A48C;
  border-radius: var(--radius-sm);
  padding: 12px 14px;
}
.field input:focus-visible, .field select:focus-visible, .field textarea:focus-visible {
  outline: 3px solid var(--clay-deep);
  outline-offset: 1px;
}
.field textarea { min-height: 130px; resize: vertical; }

.note-box {
  background: var(--stone-2);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 18px 22px;
  color: var(--text);
  font-size: 0.98rem;
}
.note-box p { max-width: none; }

/* ---------- Capture states ----------
   The capture worker answers a plain POST with a 303 back to the site:
   /enquiry/?sent=1 | ?error=1 and {page}?club=joined | ?club=error.
   These blocks are hidden until the page (inline script / site.js) reads
   the query param. Never show a success state the worker did not send. */
/* display:grid/flex on the form classes beats the [hidden] UA style */
.form-grid[hidden], .club-form[hidden] { display: none; }
.hp-field {
  position: absolute !important;
  left: -9999px !important;
  width: 1px;
  height: 1px;
  overflow: hidden;
}
.form-state {
  background: var(--stone-2);
  border-radius: var(--radius);
  padding: 22px 24px;
  color: var(--text);
  margin-top: 14px;
}
.form-state[hidden] { display: none; }
.form-state:focus { outline: none; }
.form-state p { max-width: none; }
.form-state h2 { font-size: 1.5rem; margin-bottom: 10px; }
.form-state .horizon { margin-bottom: 14px; }
.form-state-error { background: #F9E9E2; }
.form-state-error a { color: var(--clay-text); font-weight: 700; }
/* Panel rule: cards inside stone sections are white, never stone-on-stone */
.panel-sand-deep .form-state { background: #fff; }
.panel-sand-deep .form-state-error { background: #F9E9E2; }

/* Top-of-page strip for Travel Club popup joins returning to any page */
.club-return {
  background: var(--ink);
  color: var(--on-ink);
  text-align: center;
  padding: 14px 22px;
  font-size: 0.98rem;
}
.club-return a { color: var(--gold); font-weight: 700; }
.club-return-error { background: var(--clay-deep); color: #fff; }
.club-return-error a { color: #fff; font-weight: 700; }

/* ---------- Inline email capture ---------- */
.club-form { display: flex; flex-wrap: wrap; gap: 12px; margin-top: 18px; }
.club-form input {
  flex: 1 1 240px;
  font: inherit;
  padding: 13px 14px;
  border-radius: var(--radius-sm);
  border: 1px solid #B3A48C;
  background: #fff;
  color: var(--text);
}

/* ---------- Travel Club popup (JS-only enhancement; inline section is the no-JS path) ---------- */
.club-popup {
  margin: auto; /* the global reset strips the UA's dialog centring */
  border: 0;
  border-radius: var(--radius);
  padding: 0;
  max-width: min(92vw, 460px);
  overflow: hidden;
  background: var(--cream);
  color: var(--text);
  box-shadow: 0 30px 80px rgba(14, 14, 14, 0.4);
}
.club-popup::backdrop { background: rgba(14, 14, 14, 0.55); }
.club-popup[open] { animation: popup-in 0.45s var(--ease-out) both; }
@keyframes popup-in {
  from { opacity: 0; transform: translateY(14px); }
  to   { opacity: 1; transform: translateY(0); }
}
.club-popup-photo img { width: 100%; height: 150px; object-fit: cover; }
.club-popup-body { padding: 24px 26px 26px; }
/* Initial dialog focus lands on the panel (tabindex=-1 + autofocus), not the ×,
   so opening the popup never paints a focus ring on the close button. Keyboard
   users tabbing to controls still get full focus rings. */
.club-popup-body:focus { outline: none; }
.club-popup-body h2 { font-size: 1.4rem; margin-bottom: 8px; }
.club-popup-body .horizon { margin-bottom: 14px; }
.club-popup-body p { font-size: 0.98rem; }
.club-popup-offer {
  font-weight: 700;
  font-size: 1.12rem;
  color: var(--clay-text);
}
.club-popup .club-form { margin-top: 14px; }
.club-popup .club-form input { flex: 1 1 160px; }
/* Round 7: no ring; a clean white circle with a soft shadow reads on any photo. */
.club-popup-close {
  position: absolute;
  top: 10px;
  right: 10px;
  display: flex;
  align-items: center;
  justify-content: center;
  background: #fff;
  border: 0;
  border-radius: 999px;
  width: 44px;
  height: 44px;
  padding: 0;
  color: var(--text);
  cursor: pointer;
  box-shadow: 0 2px 12px rgba(14, 14, 14, 0.28);
}
.club-popup-close svg { display: block; width: 15px; height: 15px; stroke: currentColor; stroke-width: 2.2; }
.club-popup-close:hover { background: var(--stone); }
.club-popup-dismiss {
  background: none;
  border: 0;
  font: inherit;
  font-weight: 600;
  color: var(--text-2);
  text-decoration: underline;
  text-underline-offset: 3px;
  cursor: pointer;
  margin-top: 10px;
  padding: 4px 0;
}

/* ---------- Back to top ---------- */
.to-top {
  position: fixed;
  right: 18px;
  bottom: 18px;
  z-index: 60;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 48px;
  height: 48px;
  padding: 0;
  border: 0;
  border-radius: 999px;
  background: var(--ink);
  color: var(--on-ink);
  box-shadow: 0 10px 24px rgba(14, 14, 14, 0.3);
  cursor: pointer;
  opacity: 0;
  pointer-events: none;
  transform: translateY(10px);
  transition: opacity 0.3s ease, transform 0.3s var(--ease-out), background-color 0.2s ease;
}
/* 30/09/2026: the safe-area insets keep the button clear of the iPhone home bar
   and a landscape notch. When it shows is decided in site.js (section 3): on
   screens with no free margin beside the content it only appears while the
   visitor scrolls up, so it never parks over text they are reading. */
.to-top { right: calc(18px + env(safe-area-inset-right, 0px)); bottom: calc(18px + env(safe-area-inset-bottom, 0px)); }
.to-top.show, .to-top:focus-visible { opacity: 1; pointer-events: auto; transform: none; }
.to-top:hover { background: var(--ink-soft); }
.to-top svg { display: block; width: 18px; height: 18px; stroke: var(--gold); stroke-width: 2.4; fill: none; }

/* ---------- Footer ---------- */
.site-footer {
  background: var(--ink);
  color: var(--on-ink);
  padding-block: 56px 34px;
}
.footer-grid { display: grid; gap: 36px; }
@media (min-width: 860px) { .footer-grid { grid-template-columns: 2fr 1fr 1fr 1fr; } }
.site-footer .footer-title {
  font-weight: 700;
  font-size: 1.05rem;
  color: #fff;
  margin-bottom: 10px;
}
.site-footer ul { list-style: none; display: grid; gap: 8px; }
.site-footer .muted { color: var(--on-ink-2); }
.site-footer a { color: var(--on-ink); text-decoration: none; }
.site-footer a:hover { color: #fff; text-decoration: underline; text-underline-offset: 3px; }
.footer-logo img { height: 30px; width: auto; margin-bottom: 14px; }
.footer-base {
  margin-top: 44px;
  padding-top: 20px;
  border-top: 1px solid rgba(217, 160, 92, 0.4);
  display: flex;
  flex-wrap: wrap;
  gap: 12px 24px;
  justify-content: space-between;
  font-size: 0.9rem;
  color: var(--on-ink-2);
}
.footer-credit {
  color: var(--gold);
  font-weight: 600;
  text-decoration: none;
  border-bottom: 1px solid rgba(217, 160, 92, 0.5);
}
.footer-credit:hover { border-bottom-color: var(--gold); }
.social-links { display: flex; gap: 14px; }
.social-links a { display: inline-flex; padding: 6px; }
.social-links svg { width: 20px; height: 20px; fill: none; stroke: var(--on-ink); stroke-width: 1.6; }
.social-links a:hover svg { stroke: var(--gold); }

/* ---------- Utility ---------- */
.grid-3 { display: grid; gap: 24px; }
@media (min-width: 860px) { .grid-3 { grid-template-columns: repeat(3, 1fr); } }
.grid-2 { display: grid; gap: 24px; }
@media (min-width: 760px) { .grid-2 { grid-template-columns: 1fr 1fr; } }
.grid-4 { display: grid; gap: 18px; grid-template-columns: 1fr 1fr; }
@media (min-width: 860px) { .grid-4 { grid-template-columns: repeat(4, 1fr); } }
.center { text-align: center; }
.center p { margin-inline: auto; }
.stack > * + * { margin-top: 14px; }
.prose > * + * { margin-top: 16px; }
.prose h3 { margin-top: 34px; }
.prose > section + section { margin-top: 56px; }
.prose section h2 { margin-bottom: 16px; }
.prose section h3 { margin-top: 30px; margin-bottom: 8px; }
.prose ul, .prose ol { padding-left: 22px; }
.lede { font-size: clamp(1.1rem, 2vw, 1.3rem); color: var(--text-2); }
.wide-cta {
  border-radius: var(--radius);
  padding: clamp(30px, 5vw, 56px);
}
.toc {
  list-style: none;
  display: grid;
  gap: 6px;
  font-variant-numeric: tabular-nums;
}
.toc a { text-decoration: none; font-weight: 600; padding-block: 4px; display: inline-block; }
.toc a:hover { color: var(--clay-text); }
@media (min-width: 760px) { .toc { grid-template-columns: 1fr 1fr; } }

.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
}

/* Skip link */
.skip-link {
  position: absolute;
  left: -9999px;
  top: 0;
  background: var(--ink);
  color: #fff;
  padding: 10px 16px;
  z-index: 100;
}
.skip-link:focus { left: 12px; top: 12px; }

/* ---------- Reduced motion ---------- */
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    transition-delay: 0s !important;
  }
  .hero-photo img { animation: none; filter: none; transform: none; }
  .hero .horizon, .horizon { animation: none; transform: none; }
  .js .reveal { opacity: 1; transform: none; }
  .js .band img { transform: none; }
}

/* ---------- House of Faith lockup ----------
   The partner's own wordmark stands in for the H3 text in the hosts block. The
   alt carries the words, so heading order is intact and a failed image degrades
   to "House of Faith" rather than to nothing. Brown ink on cream: this belongs
   on a light section only, NEVER on .panel-ink where it would disappear.
   Beats `.stack > * + *` on source order, which is why the top margin is here. */
.hof-lockup { margin-top: 22px; margin-bottom: 18px; }
.hof-lockup img { display: block; width: 100%; max-width: 220px; height: auto; }
@media (min-width: 700px) {
  .hof-lockup img { max-width: 300px; }
}

/* The partner mark, twice more (Beth, 10/09/2026: she wants it shouted about).
   Both use the CREAM variant, because the brown original disappears on the hero
   photo and on the ink footer. The words carry the relationship, the mark
   carries the brand: "with House of Faith", "In partnership with House of
   Faith". Katie is deliberately not named anywhere. */
.hof-with,
.hof-partner {
  display: flex;
  align-items: center;
  gap: 14px;
  flex-wrap: wrap;
}
.hof-with img,
.hof-partner img { display: block; height: auto; }
/* Hero: a CO-BRAND, not a caption. Sized against the H1 rather than the body,
   so the partner mark holds its own next to the title (Alex, 10/09/2026). */
.hof-with img { width: 260px; }
/* Footer: a credit line, so it stays at label scale. */
.hof-partner img { width: 150px; }
.hof-with {
  margin-top: 16px;
  color: #F5EEE1;
  font-size: 1rem;
  /* The hero photo is bright where this lands (white villa wall, lawn, sky), so
     a cream fine-line wordmark all but vanished on it — the opposite of the
     "shout about the partner" brief. The H1 survives on weight alone; a hairline
     serif mark does not. A dark halo on the glyphs themselves (drop-shadow
     follows the alpha, unlike box-shadow) buys legibility on any part of the
     photo without putting a plate behind it. Hero only: the footer mark sits on
     solid ink and needs nothing. */
  text-shadow: 0 1px 10px rgba(14, 14, 14, 0.9), 0 0 26px rgba(14, 14, 14, 0.6);
}
.hero .hof-with img {
  filter:
    drop-shadow(0 1px 3px rgba(14, 14, 14, 0.95))
    drop-shadow(0 0 12px rgba(14, 14, 14, 0.75))
    drop-shadow(0 0 26px rgba(14, 14, 14, 0.5));
}
.hof-partner {
  margin-top: 18px;
  color: var(--on-ink-2);
  font-size: 0.92rem;
}
@media (max-width: 640px) {
  .hof-with img { width: 200px; }
}
@media (max-width: 480px) {
  .hof-partner img { width: 130px; }
}
/* The lean footer is a flex ROW with align-items: center. A top margin on a
   flex item shifts its box down inside the line, which is why the partner
   lockup sat ~10px below the wordmark and the links. The row-gap already
   handles the spacing once the footer wraps on a phone, so zeroing this costs
   nothing there. */
.footer-lean .hof-partner { margin-top: 0; }

/* ==========================================================================
   Main-site additions, 16/09/2026 (package C1)
   1. The reserve form + its return states, ported from the standalone Escape
      site so Emily's page can take a booking on the main domain instead of
      sending people to the enquiry form.
   2. Partner gyms ("Where you train") on the three Bali pages.
   3. The site-wide "Our partners" logo band.
   Depth is declared ONCE per element (a border OR a shadow), per the craft
   floor. Nothing here overrides a shared rule.
   ========================================================================== */

/* ---------- Reserve return states (?reserve=unavailable | error | full-only) --
   All three blocks ship in the HTML, hidden. The inline script only unhides the
   one the Worker named, so a state can never be invented client-side and
   nothing is built with innerHTML. Full-bleed, so it is impossible to miss when
   the Worker sends someone back to the top of the page. */
.notice {
  padding-block: 16px;
  border-bottom: 2px solid var(--gold);
}
.notice[hidden] { display: none; }
.notice:focus { outline: none; }
.notice p { font-size: 1rem; max-width: none; }
.notice a { color: inherit; text-decoration: underline; text-underline-offset: 3px; }
.notice-warn { background: var(--ink); color: var(--on-ink); }
.notice-error { background: var(--clay-deep); color: #fff; border-bottom-color: #fff; }

/* Inline version: the <noscript> fallback and the "plan closed" line. */
.notice-line {
  background: var(--stone-2);
  border: 1px solid var(--gold);
  border-radius: var(--radius-sm);
  padding: 12px 16px;
  margin-bottom: 22px;
  font-weight: 600;
  color: var(--text);
  max-width: none;
}
.notice-line a { color: var(--clay-text); }
.notice-line[hidden] { display: none; }

/* ---------- Reserve form ----------
   No width cap: the form IS the column, so its text fills the section measure.
   .form-grid still keeps the inputs from becoming one absurd 1100px line. */
.reserve-form { margin-top: 26px; }
.reserve-form .form-grid { margin-bottom: 26px; }

.choice-set {
  border: 0;
  border-top: 2px solid var(--gold);
  padding-top: 22px;
  margin-bottom: 26px;
  display: grid;
  gap: 12px;
}
/* display:grid on .choice-set beats the UA [hidden] rule, so the room-dependent
   fieldsets need this said out loud. Same trap as .form-grid[hidden] above. */
.choice-set[hidden] { display: none; }
.choice-set legend {
  font-family: var(--font-display);
  font-stretch: 112%;
  font-weight: 700;
  font-size: 1.15rem;
  letter-spacing: -0.01em;
  padding: 0;
  margin-bottom: 6px;
}
.choice-lede { color: var(--text-2); font-size: 0.95rem; margin-bottom: 4px; max-width: none; }
/* The "if you chose..." wording only makes sense when the conditional fieldsets
   cannot be shown and hidden, i.e. with JavaScript off. The <html class="js">
   flag is set by an inline script in the head of every page. */
.js .only-nojs { display: none; }

.choice {
  display: flex;
  gap: 14px;
  align-items: flex-start;
  background: #fff;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 16px 18px;
  cursor: pointer;
  transition: border-color 0.2s ease, background-color 0.2s ease;
}
/* display:flex on .choice beats the UA [hidden] rule. Without this the "hidden"
   deposit option would still render when no payment plan is possible. */
.choice[hidden] { display: none; }

.choice input {
  accent-color: var(--clay-deep);
  width: 20px;
  height: 20px;
  margin-top: 3px;
  flex: none;
}
.choice-body { display: grid; gap: 4px; }
.choice-title { font-weight: 700; font-size: 1.02rem; }
.choice-note { color: var(--text-2); font-size: 0.94rem; line-height: 1.5; }
.choice:hover { border-color: var(--gold); }
/* Selected. :has() styles the row; the sibling rule is the fallback that works
   without :has, so the choice is never ambiguous in an older browser. */
.choice input:checked ~ .choice-body .choice-title { color: var(--clay-text); }
.choice:has(input:checked) { border-color: var(--clay-deep); background: var(--stone-2); }
.choice:focus-within { outline: 3px solid var(--clay-deep); outline-offset: 2px; }
/* Stone-on-stone: inside a stone section the rows stay white and the selected
   row tints cream instead, so "selected" is still visible. */
.panel-sand-deep .choice:has(input:checked) { background: var(--cream); }

.reserve-submit { display: grid; gap: 12px; justify-items: start; }
.reserve-submit .muted { font-size: 0.95rem; }
/* justify-self: stretch because .reserve-submit is a grid with
   justify-items: start, which would otherwise shrink this line to its content
   and read as a measure cap. */
.reserve-submit .reserve-lede { color: var(--text); font-weight: 600; justify-self: stretch; max-width: none; }
.reserve-form noscript { display: block; margin-top: 20px; }
.reserve-form noscript .notice-line { margin-bottom: 0; }

@media (prefers-reduced-motion: reduce) {
  .choice { transition: none; }
}

/* A <legend> notches the fieldset's border-top and the rule resumes to its
   right. That reads well on desktop. Below 640px the heading wraps, the notch
   grows two lines tall and the rule collides with the second line. Fix: take the
   legend out of the notch and move the rule onto it, full width, above the
   heading. Desktop untouched. */
@media (max-width: 640px) {
  .choice-set { border-top: 0; padding-top: 0; }
  .choice-set legend {
    display: block;
    width: 100%;
    border-top: 2px solid var(--gold);
    padding-top: 18px;
    margin-bottom: 10px;
  }
}

/* ---------- Partner gyms: "Where you train" ---------- */
.gym-grid {
  display: grid;
  gap: 18px;
  margin-top: 28px;
}
@media (min-width: 760px) { .gym-grid { grid-template-columns: repeat(3, 1fr); } }

.gym {
  background: #fff;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  /* overflow:hidden lets the photo run to the card's rounded edge without a
     second radius on the image. Padding moves to .gym-body. */
  overflow: hidden;
  padding: 0;
  display: grid;
  align-content: start;
}
/* The room itself, first: a guest should see the floor and the kit before they
   read a word. 16:9 on every card so three very different photos read as one
   row. aspect-ratio reserves the box, so nothing shifts as the photo loads. */
.gym-photo { margin: 0; display: block; background: var(--stone-2); }
.gym-photo img {
  display: block;
  width: 100%;
  height: auto;
  aspect-ratio: 16 / 9;
  object-fit: cover;
}
.gym-body {
  padding: 22px 24px 22px;
  display: grid;
  gap: 12px;
  align-content: start;
}
/* The logo sits in a fixed-height plate so three marks of very different shapes
   read as one row. The mark is contained, never stretched. */
.gym-mark {
  height: 56px;
  display: flex;
  align-items: center;
}
.gym-mark img { max-height: 100%; max-width: 168px; width: auto; height: auto; display: block; }
/* No font-size override: the gym headings use the site's own h3 scale, which
   keeps the type steps on these pages where the rest of the site has them. */
.gym h3 { margin: 0; }
.gym p { color: var(--text-2); font-size: 0.96rem; line-height: 1.55; max-width: none; margin: 0; }
.gym .text-link { font-size: 0.94rem; }

/* ---------- "Our partners" logo band ----------
   TWO treatments share these classes.

   1. The BASE band (About): quiet by design. Stone or cream ground, ink marks,
      one small line of type, wrapping to as many rows as it needs on a phone.
   2. The STRIP (Home, .partner-band-strip): a floating banner. Real heading,
      marks at a readable height, and a slow marquee. Every size and motion rule
      for it is scoped to .partner-marquee, so the About band is untouched.

   How the strip degrades, in order:
   a. No JavaScript  -> ONE track, centred, wrapping. The extra tracks are cloned
      by script, so JavaScript off never prints the logos twice.
   b. Reduced motion -> the script does not clone and does not add .is-scrolling,
      so the reader gets the same static wrap. The media query at the end repeats
      the rule in case the preference changes after load.
   c. Hover or keyboard focus -> the animation pauses.
   Depth is declared once: a hairline, never a shadow. */
.partner-band { padding-block: clamp(34px, 5vw, 52px); }
.partner-band .partner-title {
  font-family: var(--font-display);
  font-stretch: 112%;
  font-weight: 700;
  font-size: 0.82rem;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--text-2);
  text-align: center;
  margin-bottom: 26px;
}
.partner-logos {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: 26px 44px;
  list-style: none;
  padding: 0;
  margin: 0;
}
.partner-logos li { display: flex; align-items: center; justify-content: center; }
.partner-logos img { width: auto; display: block; opacity: 0.72; }
/* Optical balance, not bounding-box balance: a 5:1 wordmark and a near-square
   3-line lockup cannot share one height and both stay readable. Set per mark. */
.partner-logos .pl-wide img { height: 26px; }
.partner-logos .pl-mid img  { height: 38px; }
.partner-logos .pl-tall img { height: 54px; }
@media (min-width: 760px) {
  .partner-logos .pl-wide img { height: 30px; }
  .partner-logos .pl-mid img  { height: 44px; }
  .partner-logos .pl-tall img { height: 62px; }
}
/* FITAID publishes no standalone logo on any of its own sites, so it is set as a
   wordmark in the site display face at the same optical weight as the marks.
   See site/_planning/2026-09-16-partners-sources.md. */
.partner-wordmark {
  font-family: var(--font-display);
  font-stretch: 118%;
  font-weight: 800;
  font-size: 1.32rem;
  letter-spacing: -0.01em;
  color: var(--ink);
  opacity: 0.72;
  line-height: 1;
  white-space: nowrap;
}
@media (min-width: 760px) { .partner-wordmark { font-size: 1.5rem; } }

/* ---------- The Home strip ---------- */
.partner-band-strip {
  padding-block: clamp(40px, 5.5vw, 66px);
  overflow: hidden;
  /* It sits under another stone section, so without this the two would read as
     one slab. A hairline, not a shadow: depth is declared once. */
  border-top: 1px solid var(--line);
  border-bottom: 1px solid var(--line);
}
.partner-band-strip .partner-head { text-align: center; }
.partner-band-strip .partner-head h2 {
  font-size: clamp(1.5rem, 3vw, 2.1rem);
  margin-bottom: 0.32em;
}
.partner-band-strip .partner-lede {
  color: var(--text-2);
  font-size: clamp(1rem, 1.6vw, 1.12rem);
  max-width: none;
  margin: 0;
}
.partner-marquee { margin-top: clamp(26px, 3.4vw, 38px); }
.partner-marquee .partner-logos { padding-inline: 22px; gap: 26px 42px; }
/* Marks at a readable height. The strip is the only place these apply. */
.partner-marquee .partner-logos img { opacity: 0.86; }
.partner-marquee .partner-logos .pl-wide img { height: 32px; }
.partner-marquee .partner-logos .pl-mid img  { height: 42px; }
.partner-marquee .partner-logos .pl-tall img { height: 64px; }
/* FITAID again, sized and tracked to the strip. font-weight drops to 700 so the
   text mark does not read heavier than the drawn logos beside it, and the
   opacity matches theirs exactly. */
.partner-marquee .partner-wordmark {
  font-weight: 700;
  font-size: 1.62rem;
  letter-spacing: 0.005em;
  opacity: 0.86;
}
@media (min-width: 760px) {
  .partner-marquee .partner-logos { gap: 30px 56px; }
  .partner-marquee .partner-logos .pl-wide img { height: 44px; }
  .partner-marquee .partner-logos .pl-mid img  { height: 56px; }
  .partner-marquee .partner-logos .pl-tall img { height: 86px; }
  .partner-marquee .partner-wordmark { font-size: 2.2rem; }
}

/* Scrolling state. Only ever added by script, and only when the reader has not
   asked for reduced motion. The mask softens both ends so marks slide in and
   out of the band instead of being chopped at the viewport edge. Decoration
   only: with no mask support the strip still reads, just with hard edges. */
.partner-marquee.is-scrolling { display: flex; flex-wrap: nowrap; width: 100%; }
.partner-marquee.is-scrolling {
  -webkit-mask-image: linear-gradient(90deg, transparent 0, #000 5%, #000 95%, transparent 100%);
          mask-image: linear-gradient(90deg, transparent 0, #000 5%, #000 95%, transparent 100%);
}
.partner-marquee.is-scrolling .partner-logos {
  flex: 0 0 auto;
  flex-wrap: nowrap;
  justify-content: flex-start;
  min-width: max-content;
  /* padding-right matches the column gap, so the seam between two tracks has the
     same spacing as every other pair of marks. */
  padding: 0 42px 0 0;
  animation: partner-scroll 54s linear infinite;
}
@media (min-width: 760px) {
  .partner-marquee.is-scrolling .partner-logos { padding-right: 56px; }
}
.partner-marquee.is-scrolling:hover .partner-logos,
.partner-marquee.is-scrolling:focus-within .partner-logos {
  animation-play-state: paused;
}
/* Every track is identical and every track animates, so as one leaves the frame
   the next has already taken its place. translateX(-100%) is of the track's own
   width, which is why the seam is exact. The script decides how many tracks a
   screen needs. */
@keyframes partner-scroll {
  from { transform: translateX(0); }
  to   { transform: translateX(-100%); }
}

@media (prefers-reduced-motion: reduce) {
  .partner-marquee.is-scrolling { display: block; }
  .partner-marquee.is-scrolling .partner-logos {
    flex-wrap: wrap;
    justify-content: center;
    padding: 0 22px;
    animation: none;
  }
  /* The clones would otherwise print every mark again. */
  .partner-marquee.is-scrolling .partner-logos[aria-hidden="true"] { display: none; }
}

/* ---------- Upcoming partner Escapes (/retreats/#upcoming, 29/09/2026) ----------
   Rendered by site/build.py from site/upcoming-escapes.json. Each card leads
   with a slim 3:1 photo (added 30/09/2026, see THUMB_POOLS in build.py), then
   the luggage-tag band: the place in the display face, the status pill beside
   it, the gold horizon underneath. The photo is kept deliberately smaller than
   the main Escape cards above, so the section still reads as "coming later".
   Status reads from the band: live = ink band + clay pill (the ones you can
   book), interest = stone band + ink pill, sold out = quiet stone + outline.
   The card is not a link (it holds two), so it has no hover lift; the button
   and the partner link carry their own feedback. */
.up-grid { list-style: none; display: grid; gap: 20px; margin-top: 26px; }
@media (min-width: 640px) { .up-grid { grid-template-columns: repeat(2, 1fr); } }
@media (min-width: 1000px) { .up-grid { grid-template-columns: repeat(3, 1fr); gap: 24px; } }
.up-card {
  background: #fff;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  overflow: hidden;
  display: flex;
  flex-direction: column;
}
/* Photo band. No percentage height anywhere (Safari mis-sizes img height:100%
   inside an indefinite grid row); the img sizes itself from its own
   aspect-ratio, and width/height attributes reserve the space before it loads. */
.up-photo { background: var(--stone-2); }
/* 16:10, the same ratio as the main Escape cards' .card-photo (Alex 01/10/2026:
   "make these upcoming pictures bigger ... similar to the other ones above").
   Every card file is cut at 16:10 on its focal point, so object-fit has nothing
   to crop: what make_card_images.py made is what shows, at every width. */
.up-photo img {
  display: block;
  width: 100%;
  height: auto;
  aspect-ratio: 16 / 10;
  object-fit: cover;
}
/* A partner logo: shown whole on white, never cropped. Padding is a share of
   the card width (percent padding resolves against width), so the logo keeps
   the same breathing room on a 340px phone card and a 363px desktop card. */
.up-photo-contain { background: #fff; border-bottom: 1px solid var(--line); }
.up-photo-contain img { object-fit: contain; padding: 13% 17%; box-sizing: border-box; }
/* Sold out: the photo goes quiet with the band, so the outline pill still leads. */
.up-sold-out .up-photo img { filter: grayscale(1); opacity: 0.7; }
.up-tag {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 16px 20px 14px;
  background: var(--stone-2);
  border-bottom: 2px solid var(--gold);
}
.up-place {
  font-family: var(--font-display);
  font-stretch: 118%;
  font-weight: 700;
  font-size: clamp(1.45rem, 3.2vw, 1.75rem);
  line-height: 1;
  letter-spacing: -0.015em;
  color: var(--text);
}
.up-status {
  flex: none;
  font-weight: 700;
  font-size: 0.74rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  padding: 7px 12px;
  border-radius: 999px;
  background: var(--ink);
  color: var(--on-ink);
  white-space: nowrap;
}
.up-live .up-tag { background: var(--ink); }
.up-live .up-place { color: var(--on-ink); }
.up-live .up-status { background: var(--clay); color: var(--ink); }
.up-sold-out .up-tag { background: var(--cream-2); border-bottom-color: var(--line); }
.up-sold-out .up-place { color: var(--text-2); }
.up-sold-out .up-status { background: transparent; color: var(--text); box-shadow: inset 0 0 0 1.5px var(--text-2); }
.up-body { padding: 18px 20px 22px; display: flex; flex-direction: column; gap: 6px; flex: 1; }
.up-body h3 { font-size: clamp(1.15rem, 2.2vw, 1.3rem); margin-bottom: 0; }
.up-partner { color: var(--text-2); font-weight: 600; font-size: 0.95rem; }
.up-facts {
  margin-top: auto;
  padding-top: 14px;
  font-variant-numeric: tabular-nums;
  font-size: 0.95rem;
  color: var(--text);
}
.up-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 12px 18px;
  margin-top: 12px;
}
.up-social { margin-top: -4px; }
.up-link {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  min-height: 36px;
  font-weight: 600;
  font-size: 0.93rem;
  color: var(--text-2);
  text-decoration: none;
  overflow-wrap: anywhere;
}
.up-link svg { width: 18px; height: 18px; flex: none; fill: none; stroke: currentColor; stroke-width: 1.7; }
.up-link span:not(.visually-hidden) {
  text-decoration: underline;
  text-decoration-color: transparent;
  text-underline-offset: 4px;
  text-decoration-thickness: 2px;
  transition: text-decoration-color 0.2s ease, color 0.2s ease;
}
.up-link:hover { color: var(--text); }
.up-link:hover span:not(.visually-hidden) { text-decoration-color: var(--gold); }
.up-actions .text-link { min-height: 44px; display: inline-flex; align-items: center; }
@media (prefers-reduced-motion: reduce) {
  .up-link span:not(.visually-hidden) { transition: none; }
}

/* Card name links to the Escape's own page (30/09/2026). */
.up-name { text-decoration: underline; text-decoration-color: transparent; text-underline-offset: 4px; text-decoration-thickness: 2px; transition: text-decoration-color 0.2s ease; }
.up-name:hover { text-decoration-color: var(--gold); }
@media (prefers-reduced-motion: reduce) { .up-name { transition: none; } }

/* ---------- Upcoming Escape pages (/retreats/<key>/, 30/09/2026) ----------
   Generated by site/build.py, one per shown entry in upcoming-escapes.json.
   The page carries the card's luggage tag forward: the place in the display
   face with the status pill, full-bleed under the hero, same colour logic as
   the card (live = ink + clay pill, interest = stone + ink pill, sold out =
   quiet cream + outline). Hero photos are destination landscapes, never villas. */
.esc-hero { min-height: 64vh; }
@media (max-width: 720px) { .esc-hero { min-height: 72svh; } }
/* Measured on the bright rice-terrace and surf frames: the shared scrim left the
   dateline too light, so the text column gets a left layer on desktop and a
   deeper band on phones. */
.esc-hero .hero-scrim {
  background:
    linear-gradient(to right, rgba(14, 14, 14, 0.55) 0%, rgba(14, 14, 14, 0.35) 45%, rgba(14, 14, 14, 0) 75%),
    linear-gradient(to top, rgba(14, 14, 14, 0.9) 0%, rgba(14, 14, 14, 0.62) 40%, rgba(14, 14, 14, 0.2) 70%, rgba(14, 14, 14, 0.05) 100%);
}
@media (max-width: 720px) {
  .esc-hero .hero-scrim {
    background: linear-gradient(to top, rgba(14, 14, 14, 0.92) 0%, rgba(14, 14, 14, 0.78) 55%, rgba(14, 14, 14, 0.3) 80%, rgba(14, 14, 14, 0.08) 100%);
  }
}
/* Image alignment (Alex 01/10/2026, a standing rule: focal point chosen per image,
   subject whole, no text over the busy part, checked at 390 / 820 / 1440).
   build.py sets --pos (below 960px) and --pos-wide (960px up) per image and adds
   esc-hero-land or esc-hero-portrait from the photo's shape. */
.esc-hero .hero-photo img { object-position: var(--pos-wide, center 60%); }
@media (max-width: 959px) {
  .esc-hero .hero-photo img { object-position: var(--pos, center 50%); }
  /* Landscape: a band above the text, fading into the ink, so the whole scene
     shows and the headline sits on ink, not on surf or a sunset line. */
  .esc-hero-land .hero-photo {
    bottom: auto;
    height: 56%;
    -webkit-mask-image: linear-gradient(to bottom, #000 62%, transparent 100%);
    mask-image: linear-gradient(to bottom, #000 62%, transparent 100%);
  }
  .esc-hero-land .hero-scrim { background: linear-gradient(to bottom, rgba(14, 14, 14, 0.05) 0%, rgba(14, 14, 14, 0) 30%); }
  /* Portrait: the faces sit in the upper half, so the dark band stops below them
     and starts where the text does (the horizon rule, about 42% up). */
  .esc-hero-portrait .hero-scrim {
    background: linear-gradient(to top, rgba(14, 14, 14, 0.92) 0%, rgba(14, 14, 14, 0.82) 42%, rgba(14, 14, 14, 0.3) 58%, rgba(14, 14, 14, 0.04) 78%);
  }
}
/* Landscape, desktop: full-bleed with the text column on the left. The left layer
   is deeper than the shared one so the headline reads on a calm, dark left and the
   subject (Ibiza's sunset line, Bali's mountain, Marbella's sail) stays bright on
   the right instead of running behind the type. */
@media (min-width: 960px) {
  .esc-hero-land .hero-scrim {
    background:
      linear-gradient(to right, rgba(14, 14, 14, 0.8) 0%, rgba(14, 14, 14, 0.62) 34%, rgba(14, 14, 14, 0.14) 60%, rgba(14, 14, 14, 0) 74%),
      linear-gradient(to top, rgba(14, 14, 14, 0.75) 0%, rgba(14, 14, 14, 0.3) 30%, rgba(14, 14, 14, 0) 55%);
  }
}
/* Portrait, desktop: the photo is a panel on the right, the text on ink at the
   left. The panel's left edge fades into the ink so it reads as one hero. */
@media (min-width: 960px) {
  .esc-hero-portrait .hero-photo {
    left: auto;
    width: 46%;
    -webkit-mask-image: linear-gradient(to right, transparent 0, rgba(0, 0, 0, 0.12) 6%, rgba(0, 0, 0, 0.45) 13%, rgba(0, 0, 0, 0.82) 20%, #000 27%);
    mask-image: linear-gradient(to right, transparent 0, rgba(0, 0, 0, 0.12) 6%, rgba(0, 0, 0, 0.45) 13%, rgba(0, 0, 0, 0.82) 20%, #000 27%);
  }
  .esc-hero-portrait .hero-scrim { background: linear-gradient(to top, rgba(14, 14, 14, 0.35) 0%, rgba(14, 14, 14, 0) 28%); }
  .esc-hero-portrait .hero-inner > * {
    max-width: calc(54vw - max(22px, (100vw - 1180px) / 2 + 22px) - 32px);
  }
  /* A landscape whose subject would sit behind the headline (Ibiza's sunset
     line): a wide right-hand panel with a long fade, text on the ink at left. */
  .esc-hero-panel .hero-photo {
    left: auto;
    width: 70%;
    -webkit-mask-image: linear-gradient(to right, transparent 0, rgba(0, 0, 0, 0.12) 8%, rgba(0, 0, 0, 0.45) 17%, rgba(0, 0, 0, 0.82) 26%, #000 36%);
    mask-image: linear-gradient(to right, transparent 0, rgba(0, 0, 0, 0.12) 8%, rgba(0, 0, 0, 0.45) 17%, rgba(0, 0, 0, 0.82) 26%, #000 36%);
  }
  .esc-hero-panel .hero-scrim { background: linear-gradient(to top, rgba(14, 14, 14, 0.55) 0%, rgba(14, 14, 14, 0) 40%); }
}
.hero .esc-cobrand {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 6px 12px;
  margin-top: 14px;
}
.esc-cobrand img {
  display: block;
  width: clamp(128px, 13vw, 176px);
  height: auto;
  filter: drop-shadow(0 1px 8px rgba(14, 14, 14, 0.6));
}
.esc-cobrand-x {
  align-self: flex-start;
  line-height: 1;
  margin-top: clamp(1px, 0.35vw, 5px);
  font-family: var(--font-display);
  font-stretch: 112%;
  font-weight: 700;
  font-size: 1.05rem;
  color: #fff;
  text-shadow: 0 1px 10px rgba(14, 14, 14, 0.55);
}
.hero .esc-dateline {
  margin-top: 14px;
  font-size: 1rem;
  font-weight: 700;
  letter-spacing: 0.02em;
  color: #fff;
}
.esc-hero h1, .esc-hero p { text-shadow: 0 1px 12px rgba(14, 14, 14, 0.5); }

.esc-tag { background: var(--stone-2); border-bottom: 2px solid var(--gold); }
.esc-tag-inner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 14px 20px;
  padding-block: clamp(22px, 3.4vw, 34px);
}
.esc-place {
  font-family: var(--font-display);
  font-stretch: 118%;
  font-weight: 800;
  font-size: clamp(2.6rem, 9vw, 5rem);
  line-height: 0.95;
  letter-spacing: -0.02em;
  color: var(--text);
}
.esc-status {
  flex: none;
  font-weight: 700;
  font-size: 0.8rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  padding: 9px 16px;
  border-radius: 999px;
  background: var(--ink);
  color: var(--on-ink);
  white-space: nowrap;
}
.esc-status a { color: inherit; text-decoration: none; }
.esc-status:has(a):hover { background: var(--ink-soft); }
.esc-tag-live .esc-status:has(a):hover { background: var(--clay-soft); }
.esc-tag-live { background: var(--ink); }
.esc-tag-live .esc-place { color: var(--on-ink); }
.esc-tag-live .esc-status { background: var(--clay); color: var(--ink); }
.esc-tag-sold-out { background: var(--cream-2); border-bottom-color: var(--line); }
.esc-tag-sold-out .esc-place { color: var(--text-2); }
.esc-tag-sold-out .esc-status { background: transparent; color: var(--text); box-shadow: inset 0 0 0 1.5px var(--text-2); }

.esc-about { display: grid; gap: 40px; align-items: start; }
@media (min-width: 900px) { .esc-about { grid-template-columns: 7fr 5fr; gap: 64px; } }
.esc-pitch .lede { color: var(--text); }
.esc-pitch p + p { margin-top: 14px; }
.esc-pitch .esc-follow {
  margin-top: 22px;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 4px 10px;
  color: var(--text-2);
  font-weight: 600;
}
.esc-facts {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 20px 28px;
  margin: 0;
  border-top: 2px solid var(--gold);
  padding-top: 20px;
}
.esc-facts .wide { grid-column: 1 / -1; }
.esc-facts dt { font-size: 0.85rem; font-weight: 700; color: var(--gold-deep); margin-bottom: 2px; }
.esc-facts dd { margin: 0; font-size: 1.08rem; font-weight: 600; line-height: 1.35; }
.esc-facts dd .muted { display: block; font-weight: 400; font-size: 0.95rem; }
.esc-list { padding-left: 20px; display: grid; gap: 8px; }
.esc-for { border-top: 2px solid var(--gold); padding-top: 18px; margin-bottom: 22px; font-size: 1.05rem; }
.esc-status:has(a) { padding: 0; }
.esc-status a { display: inline-flex; align-items: center; min-height: 44px; padding: 0 18px; border-radius: 999px; }
