/* =============================================================
   Modern Coastal Dunedin — v3 · light editorial re-skin
   A warm light ground, ink contrast blocks, one coral accent.
   Structure, layout, copy and behaviour are unchanged from v2:
   this file only re-points the tokens and the two type stacks.
   ============================================================= */

/* Metric-tuned fallback so the Italiana swap doesn't shift the hero.
   Georgia renders wider and taller than Italiana; size-adjust pulls the
   fallback rendering onto Italiana's metrics so the swap is near-zero CLS. */
/* Italiana is SIL OFL — self-hosted so the display face is present at first
   paint (the async Google css2 arrival was the page's one real CLS: the H1
   re-wrapped when the family landed seconds after paint under throttling). */
@font-face {
  font-family: "Italiana";
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url("../fonts/italiana.woff2") format("woff2");
}
@font-face {
  font-family: "Silk Serif Trial";
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url("../fonts/silkserif-light.woff2") format("woff2");
}
@font-face {
  font-family: "Silk Serif Trial";
  font-style: italic;
  font-weight: 400;
  font-display: swap;
  src: url("../fonts/silkserif-lightitalic.woff2") format("woff2");
}
@font-face {
  font-family: "Patrician";
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url("../fonts/patrician.woff2") format("woff2");
}
@font-face {
  font-family: "Apparel";
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url("../fonts/apparel.woff2") format("woff2");
}
@font-face {
  font-family: "Instrument Sans Fallback";
  src: local("Arial");
  size-adjust: 102.6%;
  line-gap-override: 0%;
}
@font-face {
  font-family: "Italiana Fallback";
  src: local("Georgia");
  size-adjust: 93.5%;
  ascent-override: 90%;
  descent-override: 24%;
  line-gap-override: 0%;
}

/* ── tokens ─────────────────────────────────────────────────── */
:root {
  /* the page ground and its two raised steps */
  --lanai:   #FBF9F7;  /* page ground — near-white with a warm hint */
  --lanai-2: #E9E2DC;  /* raised light surfaces: .frame placeholder, review cards */
  --lanai-3: #D9D1C9;  /* hairlines on the light ground */

  /* The one accent. Coral is a fill colour and a mark colour: it never
     carries small text on the light ground, where it measures 2.25:1.
     Coral text is legal over ink and over a photo scrim only. */
  --pool:    #8A504A;  /* clay — fills on the light ground; takes light text */
  --pool-tint: #8A504A; /* unified on the logo clay per Cal (2026-08-07) */
  --sun:     var(--pool-tint);  /* star mark — only ever over a photo scrim */
  /* clay takes light text, never ink: #F3EEEB on #8A504A is 5.4:1; ink is 2.7:1 */
  --accent-deep: #8A504A;

  /* The ink block. One colour does all of it: the .section--shell panels,
     the marquee band, the footer, the mobile book bar, the overlay menu
     and every photo scrim. */
  --shell:   #141C23;

  /* The ink blocks are not flat any more. Three layers, in paint order:
     a grain tile that dithers away the banding a dark gradient would
     otherwise show in ~200px steps, a cool highlight falling in from the
     top-left, a coral breath in the far corner (the accent, at a level
     you read as depth rather than as colour), and the vertical ramp.
     Deltas are deliberately small — this should register as a lit
     surface, not as a gradient. */
  --ink-surface:
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='140' height='140'%3E%3Cfilter id='g'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3C/filter%3E%3Crect width='140' height='140' filter='url(%23g)' opacity='0.05'/%3E%3C/svg%3E"),
    radial-gradient(90% 62% at 8% 0%, rgba(126, 160, 190, 0.14) 0%, rgba(126, 160, 190, 0) 58%),
    radial-gradient(80% 58% at 100% 100%, rgba(217, 162, 154, 0.07) 0%, rgba(217, 162, 154, 0) 62%),
    linear-gradient(to bottom, #18212C 0%, #141C23 46%, #121A21 100%);

  /* The putting green the putt intro rolls on — the ink surface's exact
     recipe in green: the same grain, a cool highlight falling in from the
     top-left, the same coral breath in the far corner, and a shallow ramp.
     Deep and muted so it reads as the ink blocks' sibling, not as sport. */
  --turf-surface:
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='140' height='140'%3E%3Cfilter id='g'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3C/filter%3E%3Crect width='140' height='140' filter='url(%23g)' opacity='0.05'/%3E%3C/svg%3E"),
    radial-gradient(90% 62% at 8% 0%, rgba(150, 200, 170, 0.16) 0%, rgba(150, 200, 170, 0) 58%),
    radial-gradient(80% 58% at 100% 100%, rgba(217, 162, 154, 0.07) 0%, rgba(217, 162, 154, 0) 62%),
    linear-gradient(to bottom, #204532 0%, #1B3B2B 46%, #163226 100%);

  --mist:    #6B655F;  /* muted text on the light ground — 5.0:1 */

  /* contrast substitutes for use on --shell (mist and coral fail there) */
  --teal-on-shell:  #F3EEEB;  /* 13.9:1 on ink */
  --muted-on-shell: #A8ADB3;  /* 7.6:1 on ink */

  /* The lightbox sheet stays light while the backdrop behind it is ink,
     so it needs its own name rather than borrowing --shell. */
  --sheet:   #F3EEEB;

  --r-sm:   14px;
  --r-md:   26px;
  --r-lg:   40px;
  --r-xl:   64px;
  --r-pill: 999px;

  /* HBA pairing. "Beausite Slick" (display) and "Beausite Classic" (text and
     the label voice) are commercial Fatype families — they are named first so
     the licensed webfonts take over the moment an @font-face block is dropped
     in, and nothing else has to change. Until then the free stand-ins carry
     the page: Italiana for display, Instrument Sans for everything else. */
  --display: "Silk Serif Trial", "Apparel", "Italiana", "Italiana Fallback", Georgia, serif;
  --body:    "Beausite Classic", "Instrument Sans", "Instrument Sans Fallback", ui-sans-serif, system-ui, sans-serif;
  /* The label/caption voice is tracked uppercase sans, not a monospace — the
     var name is kept, the face is not. Sizes and tracking are unchanged. */
  --mono:    "Beausite Classic", "Instrument Sans", "Instrument Sans Fallback", ui-sans-serif, sans-serif;

  --inset: 16px;
  --gutter: clamp(20px, 5vw, 72px);
  --gap: clamp(14px, 2vw, 28px);
  --maxw: 1400px;

  /* v3 · the chrome bar is in normal flow on the page ground above the hero
     frame, so the hero's height has to subtract it. Kept as a token because
     both the bar and the hero measure off it. */
  --chrome-h: 80px;

  /* v3 · one rhythm for every section boundary (HBA pass) */
  --section-pad: clamp(88px, 12vh, 160px);

  /* Contextual. Flipped on the ink blocks (.section--shell, .marquee, .foot,
     .bookbar, .overlay) and over photography (.hero__inner, .closer__inner,
     the breather frame). --shell is the ink now, so the page default is ink text
     on the light ground: 14.9:1. */
  --fg: var(--shell);
  --fg-muted: var(--mist);
  --accent: var(--shell);
  --rule: rgba(20, 28, 35, 0.16);

  --ease: cubic-bezier(0.22, 1, 0.36, 1);
}

/* ── reset-ish ──────────────────────────────────────────────── */
*, *::before, *::after { box-sizing: border-box; }
/* nothing on this page scrolls sideways: block the horizontal pan gesture
   and Safari's horizontal rubber-band, which dragged the whole sheet and
   showed the html canvas beside it during a putt */
html, body { overscroll-behavior-x: none; }
body { touch-action: pan-y pinch-zoom; }

html {
  -webkit-text-size-adjust: 100%;
  /* no scroll-behavior:smooth — Lenis owns smoothing, and native smooth
     fights both Lenis and in-page fragment jumps */
  /* Dual-tone canvas: iOS overscroll shows the html background — light past
     the top (the chrome/hero ground) and ink past the bottom (the footer).
     The 50% seam sits mid-document, permanently covered by content. */
  background: linear-gradient(to bottom, var(--lanai) 0%, var(--lanai) 50%, #141C23 50%, #141C23 100%);
  /* Reserve the scrollbar track permanently. Without this, locking scroll for
     the overlay menu collapses a classic scrollbar and reflows the whole page
     ~15px wider, which also invalidates every cached ScrollTrigger position.
     Desktop-only: recent iOS can reserve a real gutter for it, which showed
     as a light strip down the right edge of the ink sheets. */
}
@media (hover: hover) and (pointer: fine) {
  html { -webkit-text-size-adjust: 100%; text-size-adjust: 100%; }
  body { overflow-x: clip; }
  @media (pointer: fine) { html { scrollbar-gutter: stable; } }
}
/* The page scrollbar is styled to the site: a slim ink thumb on a fully
   transparent track, so the gutter never reads as a white column and the
   bar's return after a dialog is a sliver, not a slab. Firefox gets the
   two-value shorthand; WebKit engines the pseudo styles. */
@media (pointer: fine) {
html { scrollbar-width: thin; scrollbar-color: rgba(20, 28, 35, 0.35) transparent; }
::-webkit-scrollbar { width: 10px; height: 10px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb {
  background: rgba(20, 28, 35, 0.32);
  border-radius: 999px;
  border: 3px solid transparent;
  background-clip: padding-box;
}
::-webkit-scrollbar-thumb:hover { background: rgba(20, 28, 35, 0.5); background-clip: padding-box; border: 3px solid transparent; }
}
.lb__stack::-webkit-scrollbar { display: none; }
/* (The light right-edge sliver on ink sheets is Safari's own scroll
   indicator — OS-drawn. The client chose to keep it.) */

body {
  margin: 0;
  background: var(--lanai);
  color: var(--fg);
  font-family: var(--body);
  font-weight: 300;
  font-size: 17px;
  line-height: 1.6;
  overflow-x: hidden;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

img { max-width: 100%; }
/* <picture> must not introduce a box — the <img> inside it is positioned and
   sized against the .frame / .slide, not against the picture element. */
picture { display: contents; }
button { font: inherit; color: inherit; }
a { color: inherit; }

/* ── Lenis (required by the vendor build) ───────────────────── */
html.lenis, html.lenis body { height: auto; }
.lenis.lenis-smooth { scroll-behavior: auto !important; }
.lenis.lenis-smooth [data-lenis-prevent] { overscroll-behavior: contain; }
.lenis.lenis-stopped { overflow: hidden; }
.lenis.lenis-smooth iframe { pointer-events: none; }
/* Scroll lock for the overlay menu. overflow:hidden alone is not honoured by
   iOS Safari, so js/main.js also pins the body with position:fixed and
   restores the scroll offset on close. */
html.is-locked, html.is-locked body { overflow: hidden; overscroll-behavior: none; }
body.is-locked-body { position: fixed; left: 0; right: 0; width: 100%; }

/* ── typography ─────────────────────────────────────────────── */
/* Italiana ships a single 400 weight and no italic, exactly as the previous
   display face did, so the display law is unchanged: never synthesise a bold
   or an oblique. It draws lighter and smaller on the body, so the three
   heading clamps are up ~7% and the tracking is positive — its high contrast
   strokes want a little air rather than the old negative squeeze. */
.h1, .h2 {
  font-family: var(--display);
  font-weight: 400;              /* never above 400 — Italiana has one weight */
  font-style: normal;
  letter-spacing: 0.005em;
  margin: 0;
  text-wrap: balance;
}
.h1 { font-size: clamp(2.7rem, 6.3vw, 6rem); line-height: 0.98; }
/* v3 HBA pass — section heads run larger and stay on the single 400 weight */
.h2 { font-size: clamp(36px, 4.9vw, 68px); line-height: 1.04; }
.h2--big { font-size: clamp(2.05rem, 6vw, 5.15rem); }
.h2--big .nw { white-space: nowrap; }

/* Italiana ships one hairline weight; a proportional text-stroke gives it
   real optical weight without synthetic bold (which smears high-contrast faces).
   Applied to every element on the display face. */
.h1, .h2, .h2--big, .area-list__name,
.foot__mark, .lb__heading, .overlay__list a {
  -webkit-text-stroke: 0.016em currentColor;
}

.h1 em, .h2 em {
  font-style: italic;            /* the Rossàr flourish: true italics, no color shift */
  color: inherit;
}

/* One utility voice, everywhere: the label face at ~11px, uppercase, .16em.
   In rem, not px: these labels carry information (the facts strip, every tile
   caption, the rooms terms, the review bylines, the lightbox counter), so they
   have to grow with the root when someone raises their browser font size, the
   way .lede / .sec-lede / .h1 / .h2 already do. 0.7rem = 11.2px at the 16px
   default. */
.mono {
  font-family: var(--mono);
  font-weight: 400;
  font-size: 0.7rem;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  line-height: 1.5;
}

/* v3 · eyebrows are muted, not accent — the head does the talking */
.eyebrow { color: var(--fg-muted); margin: 0 0 clamp(18px, 2.2vw, 28px); }

.lede {
  font-size: clamp(1.04rem, 1.35vw, 1.28rem);
  line-height: 1.55;
  font-weight: 300;
  color: var(--fg);
  margin: 0;
  max-width: 34em;
}

.sec-lede {
  font-size: clamp(1rem, 1.2vw, 1.16rem);
  line-height: 1.65;
  color: var(--fg-muted);
  max-width: 34em;
  margin: clamp(20px, 2.6vw, 32px) 0 0;
}

.cap { color: var(--fg-muted); display: block; font-size: 0.7rem; }

/* Placeholder tokens the owner must fill in. Coral is not available to them:
   on the light ground it measures 2.25:1. They read as muted text with the
   dotted underline doing the marking. */
.ph {
  font-family: var(--mono);
  font-size: 0.76em;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--mist);
  border-bottom: 1px dotted currentColor;
  padding-bottom: 1px;
}
.pending {
  font-family: var(--mono);
  font-size: 0.72rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  line-height: 1.7;
  color: var(--mist);
  margin: 0;
}
/* --mist has no contrast on ink; use the on-ink substitute. .foot is in this
   list because the footer is an ink block and carries both .ph placeholders. */
.section--shell .ph,
.section--shell .pending,
.foot .ph,
.foot .pending { color: var(--teal-on-shell); }

/* ── layout helpers ─────────────────────────────────────────── */
.wrap {
  width: 100%;
  max-width: var(--maxw);
  margin-inline: auto;
  padding-inline: var(--gutter);
}

.inset {
  margin-inline: var(--inset);
  border-radius: var(--r-xl);
  overflow: hidden;
}

.section { padding-block: var(--section-pad); }
/* no hairlines between sections: the ink sheets, the green capsule and the
   breather photo already cut the page — a rule on top would be a second
   divider (client, 2026-08-06) */
.section--dark { background: var(--lanai); }
/* the marquee capsule is the boundary above the backyard now; a hairline
   under it would read as a second rule */
/* the gap above is the facts strip's own padding plus the capsule's top
   margin; this matches it, so the capsule sits centred in its own band */
#backyard.section--dark { padding-top: clamp(72px, 9.4vw, 120px); }
/* The ink block. Same mechanism as v2, flipped: the section re-points the
   four contextual tokens and every eyebrow, caption, rule and rooms row
   inside it follows without a single per-element colour. */
.section--shell {
  background: var(--ink-surface);
  background-color: var(--shell);
  --fg: var(--teal-on-shell);
  --fg-muted: var(--muted-on-shell);
  --accent: var(--pool-tint);
  --rule: rgba(243, 238, 235, 0.16);
  color: var(--fg);
}
/* the one green headline on the page: the rating takes the turf's ink */
#reviews .h2 { color: #273442; }
#reviews .trust--inline { color: #273442; }
#reviews .trust--inline .trust__star { color: #8A504A; }

.sec-head { max-width: min(1100px, 86vw); margin-bottom: clamp(52px, 7vw, 104px); }
.sec-head--split {
  display: grid;
  grid-template-columns: minmax(0, 1.05fr) minmax(0, 0.95fr);
  gap: clamp(24px, 5vw, 80px);
  align-items: end;
  max-width: none;
}
.sec-head--split .sec-lede { margin-top: 0; }

/* ── image frames ───────────────────────────────────────────── */
.frame {
  position: relative;
  overflow: hidden;
  border-radius: var(--r-md);
  background: var(--lanai-2);
}
/* picture is display:contents, so it carries the radius down to the image */
.frame picture { border-radius: inherit; }
.frame img {
  position: absolute;
  inset: 0;
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  /* The image rounds itself rather than relying on the frame to clip it:
     the parallax puts it on its own compositor layer, and a transformed
     child can slip a hairline past a parent's rounded clip — leaking the
     frame's light placeholder at the corners. Invisible on the light
     ground, obvious against the ink sections. */
  border-radius: inherit;
  /* --z + transform-origin pick out a region of the source photograph.
     GSAP's parallax animates yPercent only and preserves this scale. */
  transform: scale(var(--z, 1));
}
.frame--wide { border-radius: var(--r-lg); }

.ar-16-10 { aspect-ratio: 16 / 10; }
.ar-4-3   { aspect-ratio: 4 / 3; }
.ar-3-4   { aspect-ratio: 3 / 4; }

/* extra height only when parallax is live, so there is slack to move into */
.anim .frame img.px { height: 112%; top: -6%; }

/* Text for assistive tech only — still in the accessibility tree, no box. */
.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  border: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
}

/* ── skip link + focus ──────────────────────────────────────── */
.skip {
  position: fixed;
  top: 10px;
  left: 10px;
  z-index: 200;
  transform: translateY(-160%);
  background: var(--accent-deep);
  color: #F3EEEB;               /* light on clay — 5.4:1. Never ink: 2.7:1. */
  font-family: var(--mono);
  font-size: 0.72rem;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  padding: 12px 20px;
  border-radius: var(--r-pill);
  text-decoration: none;
}
.skip:focus { transform: none; }

/* Two-tone ring, inverted for the light page: the outline is contextual
   (--accent = ink on the light ground and inside the lightbox sheet, coral
   inside every ink block and over photography) and the halo is a constant
   light ring. One ring covers all three grounds:
     · light ground — ink outline on the ground, 14.9:1
     · ink block    — coral outline 6.6:1 and light halo 13.9:1 on the ink
     · over a photo — the halo lands on the scrim floor, which is the same
       band that already holds light text at ≥4.5:1 (see .hero__scrim). */
:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 3px;
  box-shadow: 0 0 0 5px rgba(243, 238, 235, 0.95);
}

/* ── buttons ────────────────────────────────────────────────── */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
  border: 1px solid transparent;
  border-radius: var(--r-pill);
  padding: 11px 26px;
  font-family: var(--body);
  font-weight: 500;
  font-size: 0.94rem;
  letter-spacing: 0.005em;
  line-height: 1;
  text-decoration: none;
  cursor: pointer;
  transition: background-color 0.35s var(--ease), color 0.35s var(--ease), border-color 0.35s var(--ease);
}
.btn--lg { padding: 12px 32px; font-size: 1rem; }
.btn--sm { padding: 9px 20px; font-size: 0.82rem; }

/* Hover tints are derived from the tokens, never hand-mixed, so a palette
   change propagates to them (BRIEF §3: introduce no new hues). */
/* ── HBA fill hover ──────────────────────────────────────────
   On hover a fill rises from the bottom of the pill; on mouse-out it
   collapses toward the top, so the resting color appears to fill back
   up (transform-origin flips between states). Label colors cross-fade.
   Each variant sets --fill (the hover color) and --ink-to (label on fill). */
.btn {
  position: relative;
  /* the fill layer is z:-1: without an own stacking context it would paint
     BEHIND an opaque button background instead of above it */
  isolation: isolate;
  overflow: hidden;
  transition: color 0.3s cubic-bezier(0.65, 0, 0.35, 1),
              border-color 0.3s cubic-bezier(0.65, 0, 0.35, 1);
}
.btn::before {
  content: '';
  position: absolute;
  inset: -1px;
  z-index: -1;
  background: var(--fill, transparent);
  transform: scaleY(0);
  transform-origin: top;
  transition: transform 0.34s cubic-bezier(0.65, 0, 0.35, 1);
  pointer-events: none;
}
.btn:hover::before, .btn:focus-visible::before {
  transform: scaleY(1);
  transform-origin: bottom;
}
.btn:hover, .btn:focus-visible { color: var(--ink-to, currentColor); }

/* ── ring-wipe hover: the utility controls ─────────────────────
   Menu and both close buttons keep the lightbox ✕'s
   original treatment: resting ring fades as a second ring wipes in
   diagonally. CTAs above use the fill instead. */
.menu-btn, .overlay__close, .lb__close { position: relative; }
.menu-btn::before, .overlay__close::before, .lb__close::before,
.menu-btn::after, .overlay__close::after, .lb__close::after {
  content: '';
  position: absolute;
  inset: 0;
  border-radius: var(--r-pill);
  border: 1px solid currentColor;
  pointer-events: none;
}
.menu-btn::before, .overlay__close::before, .lb__close::before {
  transition: opacity 0.5s cubic-bezier(0.165, 0.84, 0.44, 1);
}
.menu-btn::before, .overlay__close::before { opacity: 0.3; }
.lb__close::before { opacity: 0.22; }
.menu-btn::after, .overlay__close::after, .lb__close::after {
  opacity: 0;
  clip-path: polygon(100% 0, 100% 0, 0% 100%, 0% 100%);
  transition:
    clip-path 0.5s cubic-bezier(0.165, 0.84, 0.44, 1),
    opacity 0.5s cubic-bezier(0.25, 0.46, 0.45, 0.94);
}
.menu-btn:hover::before, .menu-btn:focus-visible::before,
.overlay__close:hover::before, .overlay__close:focus-visible::before,
.lb__close:hover::before, .lb__close:focus-visible::before { opacity: 0; }
.menu-btn:hover::after, .menu-btn:focus-visible::after,
.overlay__close:hover::after, .overlay__close:focus-visible::after,
.lb__close:hover::after, .lb__close:focus-visible::after {
  opacity: 1;
  clip-path: polygon(0 0, 100% 0, 100% 100%, 0 100%);
}

/* The booking CTA: clay fill, light label. Ink on clay is 2.7:1 and is
   never used; light on clay is 5.4:1. */
.btn--pool {
  background: transparent;
  border-color: #fff;
  color: #fff;
  --fill: var(--pool);
  --ink-to: #F3EEEB;
}

/* The CTA inside an ink block — the inverse of the page default. The v2 hover
   mixed --teal-on-shell with --lanai, which are now the same light value and
   would have made the hover a no-op; it mixes toward the ink instead. */
.btn--teal {
  background: transparent;
  border-color: #fff;
  color: #fff;
  --fill: var(--pool);
  --ink-to: #F3EEEB;
}

/* Photo-only control: it exists once, in the hero, over the scrim floor.
   Its light hairline is unchanged and its label follows the hero's --fg. */
.btn--ghost {
  background: transparent;
  border-color: rgba(245, 241, 233, 0.34);
  color: var(--fg);
  --fill: var(--mist);
  --ink-to: #F3EEEB;
}

/* The chrome's Check dates pill. v2 drew it as a hairline over the page
   ground; on the light ground the booking action is the one thing that
   should be coral, so it takes the same fill and ink label as .btn--pool. */
.btn--line {
  background: transparent;
  border-color: rgba(20, 28, 35, 0.35);
  color: var(--shell);
  --fill: var(--pool);
  --ink-to: #F3EEEB;
}
/* on hover the border joins the fill — no white (or ink) ring left behind */
.btn--pool:hover, .btn--pool:focus-visible,
.btn--teal:hover, .btn--teal:focus-visible,
.btn--line:hover, .btn--line:focus-visible { border-color: var(--pool); }

/* The tour buttons are not booking actions, so they do not flood salmon:
   each fills its section's opposite. The backyard tour on the light ground
   floods ink; the interior tour on the ink ground floods light. */
.gallery-cta .btn--line { --fill: var(--shell); --ink-to: var(--teal-on-shell); }
.gallery-cta .btn--line:hover,
.gallery-cta .btn--line:focus-visible { border-color: var(--shell); }
.gallery-cta .btn--teal { --fill: var(--teal-on-shell); --ink-to: var(--shell); }
.gallery-cta .btn--teal:hover,
.gallery-cta .btn--teal:focus-visible { border-color: var(--teal-on-shell); }

/* ── chrome ─────────────────────────────────────────────────── */
/* v3 (Capsules pass): in normal flow on --lanai, above the hero frame.
   Fixed height so the hero can subtract it and still finish inside the
   first viewport. */
.chrome {
  position: relative;
  z-index: 60;
  min-height: var(--chrome-h);
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 20px;
  padding-inline: clamp(20px, 2.6vw, 38px);
  background: var(--lanai);
}

/* three-column grid: equal side tracks keep the lockup on the true center
   line, and the bar grows with the logo instead of clipping it */
.chrome {
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  justify-content: initial;
}
.chrome > .btn { justify-self: start; }
.chrome__mark { display: block; line-height: 0; justify-self: center; padding-block: 4px; grid-column: 2; }
.chrome__right { justify-self: end; grid-column: 3; }
.chrome__lockup {
  display: block;
  /* sized against Rossàr empirically: equal windows, their lockup ~212px wide.
     150-unit canvas: 54px tall renders ~212px of name width */
  height: 63px;
  width: auto;
}
@media (max-width: 560px) {
  .chrome__lockup { height: 57px; }
  /* phones: the lockup is absolutely pinned to the bar's exact center line —
     no grid math involved — with the menu pill hard right */
  .chrome { position: relative; grid-template-columns: 1fr auto; }
  .chrome__mark {
    /* grid-column: auto — a definite grid placement would make that narrow
       column the containing block for these absolute offsets */
    grid-column: auto;
    position: absolute;
    left: 50%;
    top: 50%;
    transform: translate(-50%, -50%);
    padding-block: 0;
  }
  .chrome__right { grid-column: 2; justify-self: end; }
}
/* the wordmark hangs over the slideshow, centered under the badge */
.hero__wordmark {
  position: absolute;
  top: clamp(26px, 3.4vw, 52px);
  left: 50%;
  transform: translateX(-50%);
  width: clamp(250px, 27vw, 330px);
  height: auto;
  z-index: 3;
  pointer-events: none;
}

.chrome__right { display: flex; align-items: center; gap: 12px; }

.menu-btn {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  background: transparent;
  border: 1px solid var(--rule);
  border-radius: var(--r-pill);
  padding: 11px 16px;
  border-color: transparent;
  color: var(--shell);
  cursor: pointer;
}

/* icon-only hamburger on every viewport */
.menu-btn__label { display: none; }
.menu-btn__bars { display: grid; gap: 5px; width: 20px; }
.menu-btn__bars i { display: block; height: 1px; background: currentColor; }

@media (max-width: 860px) { :root { --chrome-h: 60px; } }
@media (max-width: 560px) {
  .chrome .btn { display: none; }
  .chrome { padding-inline: 16px; }
  .overlay__close { right: 16px; }
}
@media (max-width: 400px) and (max-height: 640px) { :root { --chrome-h: 52px; } }

/* ── overlay menu ───────────────────────────────────────────── */
/* The menu is a raised ink surface — one step off #141C23 so it reads as a
   sheet lifted over the page rather than as the same block. Same contextual
   flip as .section--shell. */
.overlay {
  position: fixed;
  inset: 0;
  z-index: 90;
  background: var(--shell);  /* same ink as marquee/footer — no blue cast */
  --fg: var(--teal-on-shell);
  --fg-muted: var(--muted-on-shell);
  --accent: var(--pool-tint);
  --rule: rgba(243, 238, 235, 0.16);
  color: var(--fg);
  clip-path: inset(0 0 100% 0);
  visibility: hidden;
  overflow-y: auto;
  overscroll-behavior: contain;
  -webkit-overflow-scrolling: touch;
  transition: clip-path 0.85s var(--ease), visibility 0s linear 0.85s;
}
.overlay.is-open {
  clip-path: inset(0 0 0 0);
  visibility: visible;
  transition: clip-path 0.85s var(--ease), visibility 0s linear 0s;
}

.overlay__inner {
  height: 100%;
  max-width: var(--maxw);
  margin-inline: auto;
  padding: clamp(90px, 12vh, 150px) var(--gutter) clamp(40px, 6vh, 70px);
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 0.62fr);
  gap: clamp(30px, 6vw, 90px);
  align-items: center;
}

.overlay__eyebrow { color: var(--fg-muted); margin: 0 0 26px; }

.overlay__list { list-style: none; margin: 0 0 clamp(30px, 4vh, 50px); padding: 0; }
.overlay__list li { border-top: 1px solid var(--rule); }
.overlay__list li:last-child { border-bottom: 1px solid var(--rule); }
.overlay__list a {
  display: block;
  padding: clamp(12px, 1.7vh, 22px) 0;
  font-family: var(--display);
  font-weight: 400;
  font-size: clamp(1.6rem, 4.2vw, 3rem);
  line-height: 1.1;
  text-decoration: none;
  color: var(--fg);
  transition: color 0.35s var(--ease);
}
.overlay__list a:hover { color: var(--accent); }

.overlay__list li,
.overlay__cta,
.overlay__media {
  opacity: 0;
  transform: translateY(20px);
  transition: opacity 0.6s var(--ease), transform 0.6s var(--ease);
  transition-delay: 0s;
}
.overlay.is-open .overlay__list li,
.overlay.is-open .overlay__cta,
.overlay.is-open .overlay__media {
  opacity: 1;
  transform: none;
  transition-delay: calc(0.34s + var(--i, 0) * 0.055s);
}

.overlay__media .cap { margin: 14px 0 0; }


/* the thin white mark holds the menu's top-left, wordless */
.overlay__mark {
  position: absolute;
  top: calc(var(--chrome-h) / 2);
  left: clamp(20px, 2.6vw, 38px);
  transform: translateY(-50%);
  line-height: 0;
  pointer-events: none;
}
/* capped to the close pill's box (54x31) — the mark never outgrows the X */
.overlay__mark img { display: block; height: 31px; width: auto; }

/* Sits exactly where the chrome's Menu pill sits (same right padding, centered
   in the same --chrome-h band) so open/close reads as one control swapping. */
.overlay__close {
  position: absolute;
  top: calc(var(--chrome-h) / 2);
  transform: translateY(-50%);
  right: clamp(20px, 2.6vw, 38px);
  background: transparent;
  border: 1px solid transparent;
  border-radius: var(--r-pill);
  padding: 11px 16px;   /* matches .menu-btn exactly: same pill, same box */
  color: var(--fg);
  cursor: pointer;
}
/* the X: two hairlines crossing inside the same 20×7 box as the hamburger bars */
.overlay__close-x { position: relative; display: block; width: 20px; height: 7px; }
.overlay__close-x i {
  position: absolute;
  left: 4px;
  top: 50%;
  width: 12px;
  height: 1px;
  background: currentColor;
}
.overlay__close-x i:first-child { transform: rotate(35deg); }
.overlay__close-x i:last-child { transform: rotate(-35deg); }


@media (max-width: 900px) {
  .overlay__inner { grid-template-columns: 1fr; align-content: center; }
  .overlay__media { display: none; }
}

/* ── hero ───────────────────────────────────────────────────── */
.hero { position: relative; }

/* min-height, not height: at 320px the copy stack is taller than the viewport,
   and a fixed height + overflow:hidden sliced the eyebrow and the top of the
   H1 clean off (WCAG 1.4.10 reflow). The frame now grows with its content.
   v3: the chrome bar is above the frame, in flow, so subtract its height and
   the one remaining bottom inset — the frame then ends 16px short of the
   fold with its full top corners and a band of page ground above. */
.hero__frame {
  isolation: isolate;
  transform: translateZ(0);
  position: relative;
  display: grid;
  min-height: max(520px, calc(100svh - var(--chrome-h) - var(--inset)));
  overflow: hidden;
  border-radius: var(--r-xl);
  background: var(--lanai-2);
  isolation: isolate;
}

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

.slide {
  position: absolute;
  inset: 0;
  opacity: 0;
  visibility: hidden;
  transition: opacity 1.4s ease, visibility 0s linear 1.4s;
}
/* will-change only on the two slides actually crossfading — promoting all five
   full-viewport images for the life of the page costs ~95MB on a DPR-3 phone. */
.slide.is-active, .slide.is-leaving { will-change: opacity; }
.slide.is-active {
  opacity: 1;
  visibility: visible;
  transition: opacity 1.4s ease, visibility 0s linear 0s;
}
/* The outgoing slide is still visible for the 1.4s fade. Keep it visible and
   keep its Ken Burns running, or the transform snaps back mid-crossfade. */
.slide.is-leaving { visibility: visible; transition: opacity 1.4s ease, visibility 0s linear 1.4s; }
.slide img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  transform: scale(calc(var(--z, 1) * 1.02));
  transform-origin: 50% 55%;
}

@keyframes kb {
  from { transform: scale(calc(var(--z, 1) * 1.02)); }
  to   { transform: scale(calc(var(--z, 1) * 1.07)); }
}
/* Slide 1 pulls BACK (1.07 → 1.02) instead of pushing in. A growing LCP image
   re-registers as a larger candidate on every frame of the zoom, which read as
   a 7.4s render delay in lab traces; starting at its largest pins the LCP to
   the first paint. A pull-back also reads naturally on an establishing shot. */
@keyframes kb-first {
  from { transform: scale(calc(var(--z, 1) * 1.07)); }
  to   { transform: scale(calc(var(--z, 1) * 1.02)); }
}
.anim .slide[data-slide="0"].is-active img,
.anim .slide[data-slide="0"].is-leaving img { animation-name: kb-first; }
/* 7.4s = HOLD (6s, js/main.js) + the 1.4s crossfade, so the slide reaches 1.07
   exactly as it finishes fading out. The declaration is identical for
   .is-active and .is-leaving, so handing over between them does not restart or
   tear down the animation — the departing image keeps zooming while it fades. */
.anim .slide.is-active img,
.anim .slide.is-leaving img { animation: kb 7.4s linear forwards; }

/* v3: the top band is gone with the chrome — nothing sits over the top of the
   photograph any more. What is left is the floor under the copy stack
   (bottom-left) and nothing else.

   The floor is its own layer, bottom-anchored at 90% of the frame rather than
   folded into the frame-wide ramp. That matters because the copy stack is
   bottom-aligned and its top edge moves with the breakpoint — 27% of the frame
   at 390px, 39% at 1440px — so a ramp keyed to the frame cannot cover it at
   both. Anchoring to the bottom means the band the copy occupies is always the
   dark part, and the frame-wide ramp is then free to stay light (0.10 at the
   foot instead of 0.88) so the photograph keeps its top two-thirds.

   Measured, not eyeballed: worst 6×6 block inside each element's own text box
   (Range.getBoundingClientRect), composited against every one of the five
   slides at 390×844 and 1440×900. Before: eyebrow 1.31–5.41, H1 1.87–3.11,
   H1 em 1.11–2.75, lede 2.19–5.17 — a fail on 4 of 5 slides at both sizes.
   After: eyebrow ≥4.95, H1 ≥4.78, em ≥3.11, lede ≥5.90. 01-top (the overhead
   aerial, white pool deck under the copy) is the worst case; re-measure
   against it if these stops are ever touched.
   Worst case per element after: eyebrow 4.82, H1 5.27, em 3.55, lede 6.61
   (390×844); 9.16 / 6.77 / 4.51 / 9.55 at 1440×900.

   The scrim ink changed with the palette: it is rgba(20, 28, 35, x) now,
   where it used to be the old navy. Every stop keeps its opacity, and the
   new ink is darker at all of them (luminance 0.011 against 0.029), so
   each measurement above is a floor, not a ceiling: the composited text
   contrast moves up, never down. */
.hero__scrim {
  position: absolute;
  inset: 0;
  background:
    linear-gradient(180deg,
      rgba(20, 28, 35, 0) 0%,
      rgba(20, 28, 35, 0.64) 28%,
      rgba(20, 28, 35, 0.82) 66%,
      rgba(20, 28, 35, 0.88) 100%) bottom / 100% 90% no-repeat,
    linear-gradient(180deg, rgba(20, 28, 35, 0.28) 0%, rgba(20, 28, 35, 0.06) 26%, rgba(20, 28, 35, 0.10) 100%),
    linear-gradient(100deg, rgba(20, 28, 35, 0.72) 0%, rgba(20, 28, 35, 0.3) 42%, rgba(20, 28, 35, 0) 72%);
  pointer-events: none;
}

/* lanai-screen signature — CSS only, no image */

/* ── over photography ───────────────────────────────────────
   The page ground flipped to light, but these blocks sit on photographs
   behind a dark scrim, so their text has to stay light. Same contextual
   mechanism as the ink blocks, third variant: a slightly warm light for the
   muted line, and coral for the accent — which here only ever lands on the
   scrim floor (the star, a focus ring), never on bare photograph.
   ───────────────────────────────────────────────────────────── */
/* Light text over photography: the hero and the closer both sit on a scrimmed
   image, so they re-point the contextual tokens the way the ink blocks do. */
.hero__inner,
.closer__inner {
  --fg: #F3EEEB;
  --fg-muted: #C9C2BA;
  --accent: var(--sun);
  color: var(--fg);
}

.hero__crest { display: block; margin: 0 auto 22px; height: clamp(64px, 7vw, 92px); width: auto; }
.hero__copy .cta-row, .hero__copy .trust { justify-content: center; }
.hero__copy .cta-row { flex-direction: column; align-items: center; gap: 18px; }

/* In flow (not inset:0) so overflowing copy extends .hero__frame instead of
   being clipped by it. The slides, scrim and mesh stay absolute behind it. */
.hero__inner {
  position: relative;
  z-index: 2;
  grid-area: 1 / 1;
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  justify-items: center;
  text-align: center;
  align-items: center;
  align-content: center;
  gap: clamp(20px, 4vw, 64px);
  /* v3: no chrome overhead to clear, so the top padding is ordinary again */
  padding: clamp(26px, 3.4vw, 52px);
  padding-bottom: clamp(26px, 4vw, 60px);
}

.hero__copy { max-width: min(760px, 62vw); }
/* The muted line over photography tops out around 3:1 even under the floor
   above; the eyebrow is 11px, so it needs 4.5. --fg is the same value the H1
   and lede already use here — no new hue (BRIEF §2.8), and the eyebrow stays
   quieter than the head by size and tracking rather than by value. */
.hero__copy .eyebrow { color: var(--fg); }
/* The hero em keeps the light face, exactly as in v2 where the hero's --accent
   already resolved to it. Coral cannot go here: the worst 6×6 block inside the
   em's own text box measures 3.55:1 against 01-top with the light face (scrim
   note above), and coral's relative luminance is 0.357 against the light
   face's 0.877 — the same block lands at 0.436× that, 1.55:1, a fail at any
   size. The coral in the hero is the CTA immediately below it. */
.hero__copy .h1 em { color: var(--fg); }
.hero__copy .lede { margin-top: clamp(18px, 2.2vw, 28px); max-width: 34em; }

/* The hero keeps ONE pill; the backyard path is a quiet mono link beside it
   (two pills side by side competed with each other — client call, 2026-07-28). */
.hero__scroll-link {
  align-self: center;
  font-family: var(--body);
  font-size: 0.98rem;
  font-weight: 400;
  letter-spacing: 0.01em;
  color: var(--fg);
  text-decoration: underline;
  text-underline-offset: 5px;
  text-decoration-thickness: 1px;
  text-decoration-color: rgba(245, 241, 233, 0.45);
  opacity: 0.95;
  transition: text-decoration-color 0.35s var(--ease);
}
.hero__scroll-link:hover { text-decoration-color: currentColor; }

.cta-row {
  display: flex;
  flex-wrap: wrap;
  gap: 12px;
  margin-top: clamp(24px, 3vw, 38px);
}

.trust {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
  margin: clamp(20px, 2.4vw, 30px) 0 0;
  font-size: 0.9rem;
  color: var(--fg);
}
.trust strong { font-weight: 500; }
/* Coral, and only where it is legal: both --sun stars sit low in a photo
   frame, on the scrim floor. The third trust row is on the light ground,
   where coral is 2.25:1 — that one takes ink. */
.trust__star { color: var(--sun); }
.section--dark .trust__star { color: var(--fg); }
.trust__dot { color: var(--fg-muted); }
.trust--inline { margin-top: clamp(34px, 4vw, 54px); }


@media (max-width: 900px) {
  .hero__inner { grid-template-columns: 1fr; align-content: center; }
  .hero__copy { max-width: none; }
  .hero__copy .h1 { max-width: 12ch; }
}
@media (max-width: 700px) {
  .h1, .h2 { text-wrap: pretty; }
}

@media (max-width: 560px) {
  .cta-row .btn { flex: 1 1 auto; }
}

/* 320x568-class screens: step the hero type down so the eyebrow, H1, lede and
   the primary CTA all still land inside the first viewport (BRIEF §7.1/§7.2). */
@media (max-width: 400px) and (max-height: 640px) {
  .hero__inner { padding: 16px; }
  .hero__copy .h1 { font-size: 1.95rem; }
  .hero__copy .lede { font-size: 0.92rem; margin-top: 12px; }
  .hero__copy .eyebrow { margin-bottom: 10px; }
  .cta-row { margin-top: 14px; gap: 8px; }
  .cta-row .btn--lg { padding: 14px 22px; font-size: 0.92rem; }
  .trust { margin-top: 12px; font-size: 0.78rem; }
}

/* ── marquee ────────────────────────────────────────────────── */
/* A capsule, not a band. Full-bleed straight edges were the one square
   thing on a page built out of inset blocks with 64px corners, and it read
   as a rule cutting the page rather than as an object on it. Same inset and
   same ink surface as those blocks, taken to a pill: it now belongs to the
   family instead of interrupting it. The hairline borders go — the shape is
   the edge now. */
.marquee {
  overflow: hidden;
  --fg: var(--teal-on-shell);
  --fg-muted: var(--muted-on-shell);
  --accent: var(--pool-tint);
  --rule: rgba(243, 238, 235, 0.16);
  padding-block: clamp(15px, 1.9vw, 23px);
  border-radius: var(--r-pill);
  background: var(--ink-surface);
  background-color: var(--shell);
}
.marquee__track { display: flex; width: max-content; }
.marquee__set {
  display: inline-flex;
  align-items: center;
  gap: 14px;
  white-space: nowrap;
  padding-right: 14px;
  color: var(--fg);
  font-size: 0.72rem;
}
/* the separator is the one piece of coral small enough to be decorative — on
   ink it is 6.6:1, and it is aria-hidden either way */
.marquee__set i { color: var(--accent); font-style: normal; }

/* Each amenity takes the same fill hover as the buttons: coral rises from the
   bottom, the label crosses to ink, and on mouse-out the fill collapses upward
   so the ink appears to flood back. Rounded to a pill inside a pill.
   The strip stays aria-hidden and every item is tabindex="-1": this is a
   mouse-only shortcut into the tours, and everything it reaches is already
   reachable from the Tour buttons and the menu. */
.marquee__item {
  --mq-pad-y: 9px;
  --mq-pad-x: 13px;
  position: relative;
  overflow: hidden;
  border: 0;
  border-radius: var(--r-pill);
  padding: var(--mq-pad-y) var(--mq-pad-x);
  margin: calc(var(--mq-pad-y) * -1) 0;
  background: transparent;
  color: inherit;
  font: inherit;
  letter-spacing: inherit;
  text-transform: inherit;
  cursor: pointer;
  /* a plain highlight: salmon under the cursor, straight back to light off it */
  transition: color 0.15s ease;
}
.marquee__item:hover { color: var(--pool-tint); }
/* hovering the strip stops it: you cannot aim at a moving target */
.marquee:hover .marquee__track { animation-play-state: paused; }
/* a touch tap should not leave an item stuck in the hover state */
@media (hover: none) {
  .marquee__item { cursor: default; }
  .marquee__item:hover { color: inherit; }
  .putt--on .marquee__item.is-ringed:hover { color: inherit; }
  .putt--on .marquee__item::before { display: none; }
  /* the button fill is a hover preview; touch has no hover, so taps go
     straight to the action with the resting look */
  .btn::before { display: none; }
  .btn--pool:hover, .btn--teal:hover { color: #fff; border-color: #fff; }
  .btn--line:hover { color: var(--shell); border-color: rgba(20, 28, 35, 0.35); }
  .gallery-cta .btn--teal:hover { color: #fff; border-color: #fff; }
  .btn--ghost:hover { color: var(--fg); border-color: rgba(245, 241, 233, 0.34); }
}

/* ── putt intro ──────────────────────────────────────────────
   The stage owns the strip's old margins so the ball and the tag can sit
   outside the pill's overflow clip. Everything below is inert until
   js/main.js adds .putt--on — the classic strip is the no-JS, no-GSAP and
   reduced-motion experience, byte-for-byte what shipped before this. */
.putt {
  position: relative;
  -webkit-tap-highlight-color: transparent;
  -webkit-user-select: none;
  user-select: none;
  margin-block: clamp(20px, 2.4vw, 34px) 0;
  margin-inline: var(--inset);
  /* set from the measured pill height at init, so the ball always matches */
  --ball: 56px;
}
.putt--on .marquee {
  background: var(--turf-surface);
  background-color: #1B3B2B;
}
/* JS drives the track; the CSS loop would fight it */
.putt--on .marquee__track { animation: none !important; }
/* words start absent and only ever brighten via inline styles from the roll;
   hidden buttons must not be hit-targets */
.putt--on .marquee__item,
.putt--on .marquee__set i { opacity: 0; }
.putt--on .marquee__item { pointer-events: none; }
.putt--landed .marquee__item.is-landed { pointer-events: auto; }


/* The putter, seen from directly above at address: a blade square to the
   putt line. Revealed on the backswing, gone after the strike. */
.putt__putter {
  position: absolute;
  top: 50%;
  left: 0;
  width: calc(var(--ball) * 0.3);
  height: calc(var(--ball) * 1.12);
  opacity: 0;
  pointer-events: none;
  display: none;
}
.putt--on .putt__putter { display: block; }
.putt__blade {
  position: absolute;
  inset: 0;
  border-radius: 5px;
  background: linear-gradient(to right, #26303B 0%, #141C23 55%);
  /* the striking face catches the light */
  box-shadow: inset 2px 0 0 rgba(243, 238, 235, 0.28), 0 2px 7px rgba(10, 20, 14, 0.45);
}

.putt__ball {
  position: absolute;
  top: 50%;
  left: 0;
  width: var(--ball);
  height: var(--ball);
  border-radius: 50%;
  /* the paint is the baked SVG inside; the element keeps the ground
     shadow and the grip */
  background: transparent;
  box-shadow:
    0 1px 3px rgba(20, 28, 35, 0.30),
    0 4px 16px rgba(20, 28, 35, 0.42);
  cursor: grab;
  touch-action: none;
  user-select: none;
  -webkit-user-select: none;
  display: none;
}
.putt--on .putt__ball { display: block; }
.putt__ball:active { cursor: grabbing; }
/* the grip is a little bigger than the ball — enough that a near-miss
   drags the putter instead of the page, small enough that reaching for
   the buttons above never putts by accident */
.putt__ball::after {
  content: '';
  position: absolute;
  top: -5px;
  right: -6px;
  bottom: -24px;
  left: -23px;
}
/* The baked ball: a generated SVG sphere — 130 dimples projected onto
   the hemisphere (each ellipse radially foreshortened by its position),
   one shared light-axis gradient per dimple, a broad crown light and a
   cool lower shade, clipped round. The dimple GROUP rotates during the
   roll: radial foreshortening is rotation-invariant, so the churn reads
   as a rolling sphere — the thing no flat grid could do. */
.putt__svg {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  display: block;
  pointer-events: none;
}

.putt__title {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  color: rgba(243, 238, 235, 0.92);
  font-size: 0.72rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  white-space: nowrap;
  pointer-events: none;
  display: none;
}
.putt--on .putt__title { display: block; }

.putt__tag {
  position: absolute;
  top: calc(100% + 9px);
  left: 0;
  transform: translateX(-50%);
  font-size: 0.62rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--mist);
  white-space: nowrap;
  pointer-events: none;
  display: none;
  transition: opacity 0.3s var(--ease);
}
.putt--on .putt__tag { display: block; }

.putt--on .marquee__item::after {
  content: '';
  position: absolute;
  inset: 0;
  border-radius: var(--r-pill);
  border: 1px solid currentColor;
  pointer-events: none;
  opacity: 0;
  clip-path: polygon(100% 0, 100% 0, 0% 100%, 0% 100%);
  /* timed to the ball: the ring's entrance spans exactly the ball's
     0.34s fade — begins together, completes together */
  transition:
    clip-path 0.34s cubic-bezier(0.165, 0.84, 0.44, 1),
    opacity 0.34s cubic-bezier(0.25, 0.46, 0.45, 0.94);
}
.putt--on .marquee__item.is-ringed::after {
  opacity: 1;
  clip-path: polygon(0 0, 100% 0, 100% 100%, 0 100%);
}
/* Both layers exist on every item from init — Safari will not animate a
   transition on a pseudo-element created in the same frame as its class,
   so winning only ever flips states on layers that were already there. */
.putt--on .marquee__item {
  isolation: isolate;
  transition: color 0.34s cubic-bezier(0.65, 0, 0.35, 1);
}
.putt--on .marquee__item::before {
  content: '';
  position: absolute;
  inset: 0;
  z-index: -1;
  border-radius: var(--r-pill);
  background: #F3EEEB;
  transform: scaleY(0);
  transform-origin: top;
  transition: transform 0.34s cubic-bezier(0.65, 0, 0.35, 1);
}
.putt--on .marquee__item.is-ringed:hover::before {
  transform: scaleY(1);
  transform-origin: bottom;
}
.putt--on .marquee__item.is-ringed:hover { color: var(--shell); }

.putt__tag-arr {
  display: inline-block;
  animation: putt-nudge 1.7s var(--ease) infinite;
}
@keyframes putt-nudge {
  0%, 55%, 100% { transform: translateX(0); opacity: 0.65; }
  30%           { transform: translateX(5px); opacity: 1; }
}

@keyframes marquee { from { transform: translate3d(0, 0, 0); } to { transform: translate3d(-50%, 0, 0); } }
.anim .marquee__track { animation: marquee 46s linear infinite; }

/* ── facts strip ────────────────────────────────────────────── */
.facts { padding-block: clamp(48px, 7vw, 92px); }
.facts__grid {
  display: grid;
  grid-template-columns: repeat(6, minmax(0, 1fr));
  gap: var(--gap);
  margin: 0;
}
.facts__item {
  padding-top: 16px;
  margin: 0;
}
.facts__item dt { color: var(--fg-muted); margin: 0 0 10px; }
.facts__item dd {
  margin: 0;
  font-family: var(--body);
  font-weight: 500;
  font-size: clamp(1.05rem, 1.4vw, 1.3rem);
  line-height: 1.3;
}
.facts__sub { color: var(--fg-muted); font-size: 0.72em; }

@media (max-width: 900px) { .facts__grid { grid-template-columns: repeat(3, minmax(0, 1fr)); } }
@media (max-width: 480px) { .facts__grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } }

/* ── masonry (backyard + interior) ──────────────────────────── */
.masonry {
  display: grid;
  grid-template-columns: repeat(12, minmax(0, 1fr));
  gap: var(--gap);
  align-items: start;
}
.tile { margin: 0; }

.m-7  { grid-column: span 7; }
.m-5  { grid-column: span 5; }
.m-12 { grid-column: 1 / -1; }

/* the off-kilter offsets — must win over .tile's margin reset */
.masonry .push    { margin-top: 64px; }
.masonry .push-sm { margin-top: 44px; }

/* Every tile is a lightbox trigger. No hover chrome of any kind: no scale,
   no lift, no shadow, no label — the cursor is the whole affordance, and the
   affordance is the plain pointer (the "finger point"), same as the Fallon
   works plates. Not zoom-in: the tile opens a gallery, it does not magnify. */
.tile-btn {
  display: block;
  width: 100%;
  padding: 0;
  margin: 0;
  border: 0;
  background: none;
  text-align: inherit;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}
.tile-btn:focus-visible { border-radius: var(--r-md); }

.tile figcaption { padding-top: 12px; }
.tile figcaption .cap { color: var(--fg-muted); }
.tile figcaption p {
  margin: 7px 0 0;
  color: var(--fg-muted);
  font-size: 0.96rem;
  line-height: 1.6;
  max-width: 34em;
}
.tile figcaption .pending { margin-top: 8px; }

.masonry--tight { --gap: clamp(12px, 1.6vw, 22px); margin-top: clamp(52px, 7vw, 96px); }
.masonry--tight .tile figcaption { padding-top: 10px; }

@media (max-width: 900px) {
  .masonry { grid-template-columns: 1fr; gap: clamp(30px, 7vw, 52px); }
  .m-7, .m-5, .m-12 { grid-column: 1 / -1; }
  .masonry .push, .masonry .push-sm { margin-top: 0; }
  .masonry > *:nth-child(odd)  { margin-inline-end: 8vw; }
  .masonry > *:nth-child(even) { margin-inline-start: 8vw; }
  .masonry > .m-12 { margin-inline: 0; }
}

/* ── gallery tour cta ───────────────────────────────────────── */
/* Sits under each trimmed masonry, opens the same lightbox at the
   collection's first photo. Count is hardcoded for no-JS but is
   overwritten from DATA[key].items.length at lightbox init so it can
   never drift from the real collection size. */
.gallery-cta { margin: clamp(28px, 4vw, 44px) 0 0; text-align: center; }
/* .btn is inline-flex, which strips the span's leading space — the
   gap has to be a margin */
.btn__count { opacity: 0.72; margin-left: 0.35em; }

/* ── rooms list ─────────────────────────────────────────────── */
.rooms { margin: 0; display: grid; gap: 0; }
.rooms__row {
  display: grid;
  grid-template-columns: minmax(0, 0.26fr) minmax(0, 1fr);
  gap: clamp(14px, 3vw, 46px);
  padding-block: clamp(14px, 1.6vw, 20px);
  border-top: 1px solid var(--rule);
}
.rooms__row:last-child { border-bottom: 1px solid var(--rule); }
.rooms__row dt { color: var(--fg-muted); padding-top: 4px; }
.rooms__row dd { margin: 0; font-size: 1.02rem; }

.rooms__note { margin-top: clamp(28px, 3.4vw, 44px); }

@media (max-width: 640px) {
  .rooms__row { grid-template-columns: 1fr; gap: 6px; }
}

/* ── area list ──────────────────────────────────────────────── */
.area-list { list-style: none; margin: 0; padding: 0; }
.area-list__row {
  display: grid;
  grid-template-columns: minmax(0, 0.44fr) minmax(0, 1fr);
  align-items: baseline;
  gap: clamp(14px, 3vw, 46px);
  padding-block: clamp(18px, 2vw, 26px);
  border-top: 1px solid var(--rule);
}
.area-list__row:last-child { border-bottom: 1px solid var(--rule); }
.area-list__name {
  font-family: var(--display);
  font-weight: 400;
  font-size: clamp(1.15rem, 2vw, 1.75rem);
  line-height: 1.15;
}
/* The `estimated` flags in copy-deck.json → area.list are a build-time hint for
   the handover report, not a badge for visitors: there is no deck slot for an
   "unverified" marker, and --sun is the page's unfilled-placeholder colour. */
.area-list__note { color: var(--fg-muted); max-width: 34em; }

@media (max-width: 640px) {
  .area-list__row { grid-template-columns: 1fr; gap: 6px; }
}

/* ── breather ───────────────────────────────────────────────── */
/* the section no longer carries .inset — its overflow+radius mask was clipping
   the caption's first letters in the rounded corner. The frame clips itself. */
.breather { position: relative; margin-block: clamp(0px, 1vw, 8px); margin-inline: var(--inset); }
/* No min-height. A min-height fights aspect-ratio: once the ratio cannot reach
   it, the height wins and the WIDTH is derived from the ratio instead, so the
   frame sizes itself independently of its container (320 × 4/3 = 427px, wider
   than .breather at any viewport under ~459px). body{overflow-x:hidden} and
   .inset{overflow:hidden} then clip the excess, so scrollWidth never reports
   it — the image is silently over-scaled and cropped off one side. */
.breather__frame { border-radius: var(--r-xl); aspect-ratio: 16 / 7; }
.breather__fig { margin: 0; }
.breather__fig figcaption { padding-top: 12px; }
.breather__fig .cap { color: var(--fg-muted); }
@media (max-width: 700px) { .breather__frame { aspect-ratio: 4 / 3; } }

/* ── reviews ────────────────────────────────────────────────── */
.reviews {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: var(--gap);
}
.review {
  margin: 0;
  padding: clamp(24px, 3vw, 38px);
  /* one step off the page ground, kept neutral — the shared --lanai-2 read too red here */
  background: #EFECE8;
  border: 1px solid var(--lanai-3);
  border-radius: var(--r-lg);
  display: grid;
  gap: 20px;
  align-content: space-between;
  min-height: 210px;
}
.review__quote {
  margin: 0;
  font-family: var(--body);
  font-weight: 400;
  font-size: clamp(1rem, 1.3vw, 1.18rem);
  line-height: 1.6;
}
.review__quote.pending {
  font-family: var(--mono);
  font-size: 0.74rem;
  line-height: 1.8;
}
/* Measured: --mist is 4.99:1 on the page ground but only 4.48:1 on the raised
   card, and the byline is 11.2px, so it needs 4.5. Same warm grey, one step
   down, scoped to the card: 4.84:1. Nothing else on the page puts muted text
   on --lanai-2. */
.review { --fg-muted: #666059; }
.review__meta { color: var(--fg-muted); }

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

/* ── hosts ──────────────────────────────────────────────────── */
.hosts {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 0.86fr);
  gap: clamp(32px, 6vw, 96px);
  align-items: center;
}
.hosts__copy .btn { margin-top: 6px; }
.hosts__copy > p { margin-block: 0; }
.hosts__copy > p + p { margin-top: clamp(18px, 2vw, 26px); }
/* the zeroing rule above eats the eyebrow's spacing — restore the page rhythm
   (same clamp as the hero eyebrow) */
.hosts__copy > .eyebrow { margin-bottom: clamp(18px, 2.2vw, 28px); }
/* Peter and Lisa's profile photo, round, above the eyebrow */
.hosts__avatar {
  width: clamp(64px, 6.4vw, 88px);
  height: clamp(64px, 6.4vw, 88px);
  border-radius: 50%;
  object-fit: cover;
  display: block;
  margin-bottom: clamp(20px, 2.6vw, 30px);
  border: 1px solid var(--rule);
}
/* the hosts copy ran tight against the head — open it up to the page rhythm */
.hosts__copy .h2 { margin-bottom: clamp(10px, 1.4vw, 18px); }
.hosts__copy .sec-lede { margin-top: clamp(24px, 3vw, 40px); }
.hosts__copy .btn { margin-top: clamp(14px, 1.8vw, 24px); }

.hosts__media .cap { margin: 14px 0 0; }

@media (max-width: 900px) {
  .hosts { grid-template-columns: 1fr; }
  /* the cabana photo drops on mobile — the avatar carries the section */
  .hosts__media { display: none; }
}

/* ── closer ─────────────────────────────────────────────────── */
.closer { margin-top: var(--inset); }
.closer { position: relative; margin-bottom: var(--inset); }
.closer__frame {
  position: relative;
  overflow: hidden;
  border-radius: var(--r-xl);
  min-height: clamp(520px, 78svh, 800px);
  display: grid;
  isolation: isolate;
  /* ink, not the light placeholder: on the ink panel the light ground rimmed
     the rounded corners as a hairline "white border" (client, 2026-07-28) */
  background: var(--shell);
}
.closer__img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
}
/* slack for the parallax drift, only when motion is allowed */
.anim .closer__img { height: 110%; top: -5%; }
.closer__scrim {
  position: absolute;
  inset: 0;
  background: linear-gradient(180deg, rgba(20, 28, 35, 0.42) 0%, rgba(20, 28, 35, 0.62) 55%, rgba(20, 28, 35, 0.9) 100%);
}
.closer__inner {
  position: relative;
  align-self: center;
  justify-self: center;
  text-align: center;
  padding: clamp(40px, 7vw, 90px) clamp(24px, 5vw, 60px);
  max-width: min(780px, 86vw);
}
/* Measured against 47-patio the same way the hero scrim was, worst 6×6 block
   anywhere in the head band, at both 1440×900 and 390×844 and across the whole
   ±3% parallax drift: coral lands between 1.51 and 1.94:1 there. The closer
   scrim is 0.42→0.62 across the head, half the hero floor, and the frame is
   lit from the cabana — coral cannot hold a head on it. The light face runs
   3.32–6.82 over the same blocks, which is +0.25 to +0.96 better than v2's
   cream on the navy scrim, so the em stays light and the block gains margin. */
.closer__inner .h2 em { color: var(--fg); }
.closer__inner .lede { margin-inline: auto; margin-top: clamp(18px, 2vw, 26px); }
.closer__inner .trust { justify-content: center; }
.closer__inner > p:last-of-type { margin-bottom: 0; }
.closer__inner .btn { margin-top: clamp(26px, 3vw, 38px); }

/* Ground band under the closer, on top of what .closer/.foot's own margins
   already give it — carried over from the stacked-page build, where it stood
   in for a margin (margins do not hold between two panels riding as one
   sheet). Flow layout does not need it structurally, but it is the exact gap
   the design shipped with, so it stays. */
.panel--closer { padding-bottom: clamp(20px, 3vw, 40px); }

/* the chrome carries no left CTA — booking lives on the bottom bar */
.chrome > .btn { display: none; }
.btn__short { display: none; }
/* hover: the whole pill sharpens to black */
.chrome .menu-btn:hover, .chrome .menu-btn:focus-visible { color: #111111; }

/* the slim brand bar: appears when the hero has scrolled away */
.topbar {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  z-index: 70;
  height: 44px;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--lanai);
  transform: translateY(-110%);
  transition: transform 0.45s var(--ease);
}
.topbar.is-in { transform: none; }
/* the whole bar is the target, not just the wordmark's ink */
.topbar__link {
  display: flex;
  align-items: center;
  justify-content: center;
  align-self: stretch;
  flex: 1;
  line-height: 0;
}
.topbar img { display: block; height: 30px; width: auto; }



/* ── footer ─────────────────────────────────────────────────── */
.foot__lockup { display: block; width: clamp(180px, 20vw, 240px); height: auto; margin-bottom: 18px; }
/* The page closes on an ink block, full bleed under the inset closer frame. */
.foot {
  padding-block: clamp(56px, 8vw, 110px) clamp(28px, 4vw, 44px);
  background: var(--shell);
  --fg: var(--teal-on-shell);
  --fg-muted: var(--muted-on-shell);
  --accent: var(--pool-tint);
  --rule: rgba(243, 238, 235, 0.16);
  color: var(--fg);
  border-top: 1px solid var(--rule);
  margin-top: clamp(48px, 7vw, 96px);
}
.foot__grid {
  display: grid;
  grid-template-columns: minmax(0, 1.6fr) minmax(0, 1fr);
  gap: clamp(28px, 5vw, 72px);
}
.foot__mark {
  font-family: var(--display);
  font-weight: 400;
  font-size: clamp(1.3rem, 2.4vw, 1.9rem);
  margin: 0 0 12px;
}
.foot__note { margin: 0; color: var(--fg-muted); max-width: 34ch; font-size: 0.95rem; }
.foot__label { color: var(--fg-muted); margin: 0 0 14px; }
.foot__line { margin: 0 0 8px; color: var(--fg-muted); font-size: 0.95rem; }
/* coral measures 6.6:1 on the ink block, and keeps the underline that was
   already carrying the link affordance */
.foot__line a {
  color: var(--accent);
  text-decoration: underline;
  text-underline-offset: 3px;
  text-decoration-thickness: 1px;
}
.foot__line a:hover { color: var(--fg); }

.foot__base {
  display: flex;
  flex-wrap: wrap;
  /* left-cluster both lines: the persistent desktop dock lives bottom-right
     and was covering a right-aligned copyright */
  justify-content: flex-start;
  gap: 8px 40px;
  margin-top: clamp(46px, 7vw, 92px);
  padding-top: clamp(20px, 2.4vw, 30px);
  border-top: 1px solid var(--rule);
  color: var(--fg-muted);
}
.foot__base p { margin: 0; }
/* the studio credit holds the base row's right end; anoma stays lowercase
   inside the uppercased mono line, in the studio's own blue-gray (7.9:1 on
   the ink footer) */
.foot__credit { margin-left: auto; text-transform: lowercase; }
.foot__credit a {
  color: #91B2DB;
  text-transform: lowercase;
  letter-spacing: 0.1em;
  text-decoration: none;
}
.foot__credit a:hover, .foot__credit a:focus-visible { text-decoration: underline; text-underline-offset: 3px; }
/* the desktop book dock floats bottom-right: keep the credit clear of it */
@media (min-width: 861px) { .foot__credit { margin-right: 280px; } }

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

/* ── mobile sticky book bar ─────────────────────────────────── */
/* a floating rounded pod inset from the edges — Rossàr-style, not a full-width bar */
.bookbar {
  position: fixed;
  /* aligned to the section cards' inset — the pod never runs past them */
  left: calc(var(--inset) + env(safe-area-inset-left, 0px));
  right: calc(var(--inset) + env(safe-area-inset-right, 0px));
  bottom: calc(12px + env(safe-area-inset-bottom, 0px));
  z-index: 70;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 14px;
  padding: 10px 12px 10px 22px;
  border-radius: 999px;
  box-shadow: 0 12px 40px rgba(20, 28, 35, 0.22);
  /* light pod, one dark button — the CTA is the only ink in the bar */
  background: rgba(251, 249, 247, 0.96);
  backdrop-filter: blur(14px);
  -webkit-backdrop-filter: blur(14px);
  --fg: var(--shell);
  --fg-muted: #6B655F;
  --accent: var(--pool-tint);
  --rule: rgba(20, 28, 35, 0.14);
  color: var(--fg);
  /* phones: hidden until the hero has scrolled away (js toggles .is-in) */
  transform: translateY(140%);
  transition: transform 0.5s var(--ease);
}
.bookbar.is-in { transform: none; }
.bookbar__meta { display: grid; gap: 2px; min-width: 0; }
/* the pod's CTA: filled ink pill, light label — the inverse of the pod ground */
.bookbar .btn--pool {
  background: var(--shell);
  border-color: var(--shell);
  color: #F3EEEB;
}
.bookbar .btn--pool:hover, .bookbar .btn--pool:focus-visible { color: #F3EEEB; }
.bookbar__rating { color: var(--fg-muted); font-size: 0.66rem; }
.bookbar__place { font-size: 0.86rem; color: var(--fg); }

/* the bar is fixed and opaque — reserve room below the disclaimer so it can
   scroll clear. The reservation lives on the FOOTER (ink), not the body:
   body padding painted a light bar under the ink footer at page end. */
.foot { padding-bottom: calc(92px + env(safe-area-inset-bottom, 0px)); }
/* Desktop: same trigger (hero scrolled away), but a compact floating dock
   bottom-right instead of a full-width bar. Same [data-book] anchor, so
   conversions track identically. */
@media (min-width: 861px) {
  .bookbar {
    display: flex;
    left: auto;
    right: 24px;
    bottom: 24px;
    width: auto;
    gap: 18px;
    padding: 10px 12px 10px 22px;
    border-radius: var(--r-pill);
    box-shadow: 0 12px 40px rgba(20, 28, 35, 0.22);
    /* desktop dock is always docked */
    transform: none;
    transition: none;
  }
}

/* ── scrolling lightbox ─────────────────────────────────────
   Two collections (backyard / inside). The sheet is the light --sheet
   ground with the page's own ink text, floated over an ink backdrop —
   the one place the page inverts twice over. Nothing inside it exists
   in the DOM until the first open.
   ───────────────────────────────────────────────────────────── */
/* The container is a hard on/off switch — every frame of the open and the
   close is drawn by js/main.js on the backdrop and the sheet (the Fallon
   choreography: a clip-path sweep out of the clicked tile). It stays in the
   DOM through the whole close and only loses .is-open on completion, so the
   collapse is never cut short. Without GSAP this is still a working dialog:
   it simply appears and disappears. */
.lb {
  position: fixed;
  inset: 0;
  /* 100vw includes the scrollbar-gutter region no fixed inset can reach —
     the backdrop now covers the gutter and fades it with everything else */
  width: 100vw;
  z-index: 100;
  opacity: 0;
  visibility: hidden;
}
.lb.is-open {
  opacity: 1;
  visibility: visible;
}

.lb__backdrop {
  position: absolute;
  inset: 0;
  background: rgba(20, 28, 35, 0.78);
  backdrop-filter: blur(14px);
  -webkit-backdrop-filter: blur(14px);
}

.lb__sheet {
  position: absolute;
  inset: var(--inset);
  overflow: hidden;
  border-radius: var(--r-xl);
  background: var(--sheet);
  display: grid;
  grid-template-columns: 36% 64%;
  /* the sheet is a light ground inside a dialog, so it re-states the page
     defaults rather than inheriting anything from the block behind it */
  --fg: var(--shell);
  --fg-muted: var(--mist);
  --accent: var(--shell);
  --rule: rgba(20, 28, 35, 0.16);
  color: var(--fg);
  /* clip-path is written by JS (the sweep out of / back into the clicked
     tile). The radius travels with it — the tile's 26px opens out to the
     sheet's 64px — so nothing here fixes a starting shape. */
}

/* ── The ✕: two-ring redraw, lifted from the Fallon lightbox ───────────────
   The ring is two stacked pseudo-elements:
     ::before = resting ring (fades out on hover)
     ::after  = hover ring, revealed by a diagonal clip-path wipe from the
                top-left corner over 0.5s easeOutQuart
   The glyph is a separate child and never moves on hover — only the ring
   redraws. Keyboard :focus-visible gets the identical redraw on top of the
   page's standard focus outline, which stays put (the accessibility floor is
   not motion's to spend). The movement the button makes on *click* is not
   here: it belongs to the close timeline, which drops it 8px and fades it
   out with the rest of the chrome. */
.lb__close {
  position: absolute;
  top: clamp(18px, 2vw, 28px);
  left: clamp(18px, 2vw, 28px);
  z-index: 3;
  width: 42px;
  height: 42px;
  display: grid;
  place-items: center;
  padding: 0;
  border: 0;
  border-radius: var(--r-pill);
  background: var(--sheet);
  color: var(--fg);
  font-size: 13px;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}

.lb__close > * { pointer-events: none; }

/* Left column never scrolls: the sheet is overflow:hidden and only the
   stack has a scroller, so this column is fixed by construction. */
.lb__side {
  display: flex;
  flex-direction: column;
  justify-content: center;
  padding: clamp(70px, 7vw, 104px) clamp(24px, 3vw, 52px) clamp(26px, 3vw, 52px);
  min-width: 0;
}
.lb__eyebrow {
  display: flex;
  align-items: baseline;
  gap: 12px;
  margin: 0 0 clamp(14px, 1.6vw, 22px);
  color: var(--fg-muted);
}
.lb__count { color: var(--accent); white-space: nowrap; }
.lb__heading {
  font-family: var(--display);
  font-weight: 400;
  font-style: normal;
  font-size: clamp(26px, 2.6vw, 44px);
  line-height: 1.08;
  letter-spacing: 0.005em;
  margin: 0;
  text-wrap: balance;
}
.lb__intro {
  margin: clamp(14px, 1.6vw, 22px) 0 0;
  color: var(--fg-muted);
  font-size: clamp(0.95rem, 1.05vw, 1.05rem);
  line-height: 1.6;
  max-width: 34em;
}
.lb__hint { margin: clamp(22px, 2.6vw, 34px) 0 0; color: var(--fg-muted); }

.lb__stack {
  scrollbar-width: none;
  position: relative;
  overflow-y: auto;
  overscroll-behavior: contain;
  -webkit-overflow-scrolling: touch;
  padding: clamp(14px, 2vh, 26px);
  display: grid;
  /* MAD-tight: the stack reads as one column of photographs, not a list of
     cards — the caption stays with its own image, the images sit close */
  gap: clamp(10px, 1.4vh, 16px);
  align-content: start;
}
.lb__stack:focus-visible { outline-offset: -5px; }

.lb__item { margin: 0; min-width: 0; }
.lb__item img {
  display: block;
  width: 100%;
  height: auto;
  border-radius: var(--r-md);
  background: rgba(20, 28, 35, 0.07);
}
.lb__item figcaption { margin-top: 9px; color: var(--fg-muted); }
.lb__item figcaption .mono { display: block; }
.lb__body {
  display: block;
  margin-top: 6px;
  font-size: 0.94rem;
  line-height: 1.6;
  max-width: 34em;
}

@media (max-width: 860px) {
  .lb__sheet {
    inset: 10px;
    border-radius: var(--r-lg);
    grid-template-columns: 1fr;
    grid-template-rows: auto minmax(0, 1fr);
  }
  .lb__close { top: 9px; left: 10px; width: 38px; height: 38px; }
  /* The 56px pinned row keeps its shape; the deck's collection intro sits
     under it. copy-deck.json → lightbox.backyard_intro / inside_intro are deck
     slots, and 390px is the viewport this page buys traffic for — a slot that
     renders only above 860px is a slot that does not ship. */
  .lb__side {
    display: grid;
    grid-template-rows: 56px auto;
    align-content: start;
    padding: 0 14px 14px 14px;
    border-bottom: 1px solid var(--rule);
  }
  .lb__eyebrow {
    align-self: center;
    margin: 0;
    padding-left: 44px;
  }
  .lb__intro {
    margin: 0;
    font-size: 0.86rem;
    line-height: 1.5;
    max-width: none;
  }
  /* .lb__heading names the photo under the reading line — it is the same
     string the stack already prints under every image, so on one column it is
     a duplicate, not a lost slot. .lb__hint is authored chrome; the scroll
     affordance is carried by the stack's accessible name instead. */
  .lb__heading,
  .lb__hint { display: none; }
  .lb__stack { padding: 12px; gap: 10px; }
}

/* ── scroll reveals (only when motion is allowed) ───────────── */
/* No will-change here: GSAP sets it for the duration of each tween, and 57
   permanently promoted layers is exactly what tanks Lighthouse mobile. */
.anim .rv {
  opacity: 0;
  transform: translateY(26px);
}
/* Dead-man switch set in the <head> and cleared by js/main.js. If main.js never
   runs, these come back rather than leaving 8000px of empty page. */
.anim.rv-fallback .rv { opacity: 1 !important; transform: none !important; }

/* ── reduced motion ─────────────────────────────────────────── */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
    scroll-behavior: auto !important;
  }
  /* kill the Ken Burns, keep the composition — --z is a crop, not motion */
  .slide img { transform: scale(var(--z, 1)); animation: none !important; }
  .marquee__track { animation: none !important; transform: none; }
  .rv { opacity: 1 !important; transform: none !important; }
  .overlay__list li,
  .overlay__cta,
  .overlay__media { opacity: 1; transform: none; }
  .frame img.px { height: 100%; top: 0; }
  /* lightbox: no open/close movement and no blur, everything else works.
     The sweep, the rise and the ✕ ring redraw are all gated in js/main.js /
     by the transition kill above — the sheet is simply there, then not. */
  .lb__backdrop {
    backdrop-filter: none;
    -webkit-backdrop-filter: none;
    background: rgba(20, 28, 35, 0.93);
  }
}
