/* ==========================================================================
   Sovana by SanLiving — ALT direction (v2)
   LAYOUT AUTHORITY: the reference sites.
     wyejunction.com · veloura-hotel-template.webflow.io · wilde.com
     (sanfelice.com unreachable — Cloudflare 403)
   FIGMA 80:9814 supplies CONTENT and SECTION ORDER only — not structure.

   TYPE RULE: no literal font sizes or families in this file. Every text node
   wears a .t-* class from tokens.css. Four token/inherit exceptions only:
     · body           font-family: var(--font-sans)  — inherited default
     · form controls  font: inherit                  — inputs don't inherit
     · narrow queries font-size: var(--size-*)       — steps DOWN the scale
     · em             font-style: italic             — display italic, the
                                                       client's new pairing
   ========================================================================== */

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

body {
  margin: 0;
  /* WHITE is the page (2026-09-25, the client): white about 65%, ivory 35%,
     no darker ivory anywhere. The funnel and account pages keep ivory under
     their white cards (.page-ivory). */
  background: var(--white);
  color: var(--ink);
  font-family: var(--font-sans);
  -webkit-font-smoothing: antialiased;
  overflow-x: hidden;
}

img { display: block; max-width: 100%; }
/* ol too, since the amenities list is ordered markup — the facilities have
   a running order — but must not show browser numbering. */
ul, ol { list-style: none; margin: 0; padding: 0; }
a   { color: inherit; text-decoration: none; }

/* Italic retired from BODY COPY 2026-09-21 by request: <em> runs roman.
   The italic face is live again as of 2026-09-22 — every eyebrow is set in
   Source Serif 4 italic (LT Remark until 2026-09-25) — so the file IS fetched now. This rule only keeps <em>
   out of it. */
em { font-style: normal; }

:root {
  /* ── SECTION FRAME · set 2026-09-22 ────────────────────────────────
     One frame for every section: 125px top and bottom, 80px left and
     right. Both were fluid clamps (72-128 / 24-110); they are now fixed
     so the figure is the figure.
     80px a side, and content capped at 1600 (--wrap). Written as ONE
     padding expression rather than padding + an inner max-width wrapper,
     because the two are the same arithmetic: whichever is larger, the 80px
     gutter or the slack needed to hold the content at 1600.
       viewport <= 1760  ->  80px a side, content = viewport - 160
       viewport  = 1920  -> 160px a side, content = 1600
     Above 1760 the two rules cannot both hold; the cap wins, which is what
     "padding 80, max-width 1600" means anywhere it is written.
     Two forms, NOT interchangeable: --frame-x uses 100% (exact, for padding
     on a section); --frame-vw uses 100vw (for children that bleed out of
     that padding and need the page edge).
     ────────────────────────────────────────────────────────────────── */
  --gutter:    80px;
  --section-y: 125px;
  /* a section's head (eyebrow · title · lede) to its content — ONE value
     for every section, 48 (2026-10-01, by request — 64 from 2026-09-25;
     each had its own clamp before that) */
  --head-gap: 48px;
  --wrap:      1600px;
  --frame-x:   max(var(--gutter), calc((100%   - var(--wrap)) / 2));
  --frame-vw:  max(var(--gutter), calc((100vw  - var(--wrap)) / 2));

  /* ── SHARP CORNERS ─────────────────────────────────────────────────
     Every radius token flattened to 0 for this direction. Overridden
     HERE, not in tokens.css, so the Figma-faithful build (index.html →
     styles.css) keeps §03's SHAPE LOCK untouched.
     This does override two hard rules from §03, deliberately and only
     for the alt direction:
       SHAPE LOCK  24 / 17 / 18 / 12 / 999  ->  all 0
       PILL RULE   "if it can be clicked, it is a pill"  ->  no pills
     Wilde's own booking bar and its SEARCH action are square, and half
     sharp / half pill would read as unresolved rather than firm.
     ────────────────────────────────────────────────────────────────── */
  --radius-shell: 0px;
  --radius-core:  0px;
  --radius-image: 0px;
  --radius-input: 0px;
  --radius-pill:  0px;

  /* ── FLOOR PLAN PAINT · 2026-09-23 ─────────────────────────────────
     The floor plans are line drawings (assets/plans/plan-*.svg, drawn by
     assets/plans/build.py), after the sabor-del-mar room pages but on the
     light end of the palette — no green. Gold line is the accent doing
     its job (rule, border); the only fill is a hovered room, which
     shades a touch darker with a 7% green wash (--sand, then white, before). The SVGs read --plan-ink and
     --plan-room once inlined, and fall back to the same values as <img>. */
  --plan-ground: var(--ivory);     /* the frame is the ivory step on a white section */
  --plan-ink:    var(--gold);
  --plan-room:   rgba(14, 43, 33, .07);  /* a hovered room shades a little darker (by request,
                                          2026-09-25) — the site's own hover wash, not a new ground */

  /* ── SHORT DURATIONS (2026-09-23, better-ui audit) ─────────────────
     --duration (450ms) is right for things that travel. It is too slow for
     a hover highlight or a panel you open twenty times a visit: the fill
     trails the cursor. Row and cell highlights take --dur-quick; a panel
     opens on --dur-enter and closes on --dur-quick, so exits stay softer
     than entrances. */
  --dur-quick: 150ms;
  --dur-enter: 250ms;
}
@media (prefers-reduced-motion: reduce) {
  :root { --dur-quick: 0ms; --dur-enter: 0ms; }
}

/* .wrap no longer sets a width or an inset — the section's own padding does
   both now. Kept as a markup hook and a block-level grouping element; the
   rules that target ".wrap" for layout (plans, programs head) still work. */
.wrap           { width: 100%; margin-inline: auto; }
.section        { padding: var(--section-y) var(--frame-x); }
/* The ivory 35% (2026-09-25, the client): Collection, FAQ + Contact on the
   home page, the fee breakdown elsewhere — 64 / 36 by height at 1440. */
.section--alt   { background: var(--ivory); }
/* pages whose white cards need a ground under them: checkout, payment,
   confirmation, login, account */
.page-ivory     { background: var(--ivory); }
.section--flush { padding: var(--section-y) var(--frame-x) 0; }   /* media runs to the edge */


/* EYEBROW → TITLE, CLOSER, EVERYWHERE (2026-09-29, by request). Every
   head stacks its eyebrow and title with an 18px flex gap, and the title's
   1.2 line height adds its own half-leading on top; the title is pulled up
   10px so the pair reads as one unit (~8px of gap). One rule, not a gap
   per section — the same containers also space the title from what follows
   it, which stays. The book band already sits at 10 and is left alone.
   Doubled class for specificity: page sheets set `h2 { margin: 0 }`. */
.eyebrow.eyebrow + :is(h1, h2, h3):not(.bookband__copy *) { margin-top: -10px; }

/* ── Section header — asymmetric, title left / note right ─────────────── */

/* HORIZONTAL again (2026-09-22): heading on the left, supporting line hung
   off the right. Vertical stacking was tried in between and pulled back.
   The note keeps its measure and is pinned to the RIGHT EDGE, so the slack
   falls between the two halves — text-align as well as justify-self, since
   placed-right-but-ragged-right just moves the hole down a line.
   ON THE HEADING'S LAST LINE (2026-09-28, by request) — centred from
   2026-09-22; now the note's last baseline sits on the title's last
   baseline, so the two end on one line. `end` is the fallback: box bottoms,
   a few px off the baselines.
   This is the shared section head — Collection, Floor plans, Amenities. */
.sec-head {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 0.8fr);
  gap: clamp(24px, 4vw, 72px);
  align-items: end;
  align-items: last baseline;
  margin-bottom: var(--head-gap);
}
.sec-head__lead { display: flex; flex-direction: column; gap: 18px; align-items: flex-start; }
.sec-head h1, .sec-head h2 { margin: 0; color: var(--green); }
/* Balanced, not `pretty`. These subheads carried balance while they were
   .t-lead; moving them to .t-body (16px) swapped it for pretty, which only
   protects the last line from being a stub and left one long line over one
   short one. Two-line blocks want even lines. */
.sec-head__note {
  margin: 0; color: var(--muted); max-width: 46ch;
  justify-self: end; text-align: right; text-wrap: balance;
}

.sec-head--center {
  grid-template-columns: 1fr; justify-items: center; text-align: center; gap: 18px;
}
.sec-head--center p { color: var(--muted); margin: 0; max-width: 52ch; }
/* LEFT-aligned head with its action at the far right, on the bottom line —
   the drone view's (2026-09-28, by request): eyebrow · title · lede left,
   "Our location" right. Stacks with every .sec-head at the narrow break. */
.sec-head--left { grid-template-columns: minmax(0, 1fr) auto; align-items: end; }
.sec-head--left p { color: var(--muted); margin: 0; max-width: 60ch; }
.sec-head--left .btn { align-self: end; }   /* .btn ships flex-start */

/* A sentence, not a title: it needs a measure or it runs the full 1600 and
   stops reading as one thought. */
.loc__lede { max-width: 26ch; }


/* ── Buttons ──────────────────────────────────────────────────────────── */

/* NO align-self HERE. It used to carry align-self: flex-start, which beat
   align-items on every parent that wanted its button centred — and so it was
   patched back to center in four separate places (.tier__foot,
   .bookband__act, .band__inner, .unit__act). The alignment of a button is
   the parent's business; the button states no opinion, and the four patches
   are gone with it.
   `gap` is gone too: it spaced a button's label from its arrow icon, and the
   arrows were removed from every button on 2026-09-22. */
.btn {
  display: inline-flex; align-items: center; justify-content: center;
  /* The right padding is SHORT BY ONE TRACKING STEP, on purpose. These
     labels are uppercase at 0.16em, and letter-spacing puts its space AFTER
     every character including the last — so the text box carries 1.92px of
     invisible air on its right that the left does not have. Equal padding
     therefore renders unequal: 26 left, 27.9 right to the eye.
     Taking 0.16em off the right makes the INK sit centred instead of the
     box. em, not px, so it follows the font size wherever .btn is used. */
  padding: 16px calc(26px - 0.16em) 16px 26px;
  border: 1px solid transparent; cursor: pointer;
}
/* EVERY button starts as an outline (2026-09-22) and fills on hover. The two
   solid variants, --primary (green fill) and --light (ivory fill), are gone:
   each of their uses moved to whichever outline matches its ground.
     light ground — .btn--ghost        fills --green
     dark ground  — .btn--ghost-media  fills --gold                        */
/* Transition list restated from tokens.css to add `scale` for the press. */
.btn {
  transition: background-color var(--duration) var(--ease-out),
              color var(--duration) var(--ease-out),
              border-color var(--duration) var(--ease-out),
              scale var(--dur-quick) var(--ease-out);
}
/* PRESS. 0.96, never below 0.95. The `scale` property, not transform, so it
   composes with the translateY(-50%) that centres the collection arrows. */
.btn:active, .unit__arrow:active, .gst__done:active,
.gst__btn:active:not([disabled]), .cal__nav:active:not([disabled]) { scale: .96; }

/* ICON SIZES were raised ~20% site-wide on 2026-09-24, by request (spec 17→20,
   booking bar 19→22, amenity 22→26, chevrons 14→17 / 20→24 …); the stroke stays.
   ONE STROKE for every line icon on the page (2026-09-23). The set shipped
   four — 1.25, 1.3, 1.5, 1.6 — in viewBoxes of 20 and 24 drawn at 13-20px,
   so the rendered lines ran 0.87-1.25px. non-scaling-stroke makes the width
   a screen measurement, independent of viewBox and render size. 1.25px is
   the Figma icons' own weight, and sits with the hairline language. */
:root { --icon-stroke: 1.25px; }
svg [stroke] { vector-effect: non-scaling-stroke; stroke-width: var(--icon-stroke); }

.btn--ghost   { background: transparent; color: var(--green); border-color: rgba(14,43,33,.28); }
/* HOVER FILLS THE OUTLINE, and the fill colour follows the ground the button
   is standing on (decided 2026-09-22):
     light ground — .btn--ghost      fills --green, label goes ivory
     dark ground  — .btn--ghost-media fills --gold,  label goes green
   The icon chip has to invert with it or it disappears into the new fill. */
.btn--ghost:hover {
  background: var(--green); color: var(--ivory); border-color: var(--green);
}
/* ghost on photography — the reference's bordered action. btn--ghost's green
   border and green label both vanish against a scrimmed image. */
.btn--ghost-media       { background: transparent; color: var(--ivory); border-color: rgba(252, 251, 248,.55); }
.btn--ghost-media:hover {
  background: var(--gold); color: var(--green); border-color: var(--gold);
}
/* PRIMARY, for the one action of a FORM (2026-09-24, by request) — solid in
   the footer's green, going a step darker on hover. It reverses the "every
   button is an outline" rule above for forms only: Confirm and pay, Pay, the
   contact form's send, and the actions inside the booking cards. Everything
   else stays an outline. */
:root { --green-900: #091C15; }   /* --green at 65% — the hover step, nothing else */
.btn--primary       { background: var(--green); color: var(--ivory); border-color: var(--green); }
.btn--primary:hover { background: var(--green-900); color: var(--ivory); border-color: var(--green-900); }


/* ══ NAV — layout after Veloura ═════════════════════════════════════════
   Three zones: brand left | links CENTRED | Menu + burger right.
   The 1fr/auto/1fr grid is what keeps the link group optically centred in
   the bar no matter how wide the brand or the Menu control gets — a flex
   space-between would push it off-centre.                                  */

/* What the scroll state is measured against. 12px tall, pinned to the top of
   the document and invisible — the bar fills once this leaves the viewport. */
.nav-sentinel {
  position: absolute; top: 0; left: 0;
  width: 1px; height: 12px; pointer-events: none;
}

.nav {
  position: fixed; inset: 0 0 auto; z-index: 20;
  display: grid; grid-template-columns: 1fr auto 1fr; align-items: center;
  /* 16 top and bottom since the wordmark went to 265 (2026-09-25): the bar
     stays 93px tall, which the residence hero and the sticky offsets assume. */
  gap: 24px; padding: 16px var(--frame-x);
  color: var(--ivory);
  border-bottom: 1px solid rgba(252, 251, 248, .20);
  transition: background-color var(--duration) var(--ease-out),
              color var(--duration) var(--ease-out),
              border-color var(--duration) var(--ease-out),
              box-shadow var(--duration) var(--ease-out);
}
/* ALWAYS SHOWN (2026-09-30, the client): the bar stays at the top on every
   page, scrolling down included. It slid away on the way down before. */

/* WHITE once filled (2026-09-23), not ivory — the bar reads as a layer over
   the page rather than a continuation of it.
   §03 says white must be LIFTED: a white surface with no elevation is a bug.
   So the hairline stays and a card shadow comes with it. Without the shadow
   this bar would be the one white plane on the site claiming no height. */
.nav--solid {
  background: var(--white); color: var(--green);
  border-bottom-color: var(--hairline-color);
  box-shadow: var(--shadow-card);
}

/* LEGIBILITY BAND (2026-09-23). The hero scrim is now Figma's own value and
   its top stop is gold at .08 — effectively clear — so the bar sits on the
   photograph's pale sky. Measured there: ivory nav text on rgba(180,193,190)
   = 1.6:1, against 4.5:1 for AA. Figma never had to solve this; its Hero
   frame (97:3021) contains no nav at all.
   The fix is LOCAL TO THE BAR, so the hero grading stays exactly as Figma
   draws it: a lightly tinted, lightly frosted band the exact size of the bar.
   It ENDS AT THE BAR rather than fading out below it — the fade tail tried
   first (168px + a mask) washed out the bar's own hairline, and the hairline
   is what terminates the band. A backdrop-filter clips hard at its own box,
   so the border has to be there to take that cut; the two are one detail.
   Border raised .18 -> .34 for the same reason: at .18 it read as an edge
   over a dark photograph, not over a frosted band.
   The tint is a GRADIENT, .42 -> .30, not a flat slab: a flat fill reads as
   an object sitting on the page, and the lower edge is where that was most
   obvious. Fading it toward the hairline lightens the bar perceptually and
   literally without giving up the text row, which sits around the 50% mark.

   RESOLVED 2026-09-23, and then the band's own tint was removed entirely.
   It was walked .55 -> .52/.42 -> .42/.30 on art direction and sat at
   ~3.6:1 — below AA, recorded at the time as a deliberate call. Deepening
   the HERO SCRIM to Surfside's weight fixed it from underneath, which meant
   the wash was no longer doing any work, so the band is now BLUR ONLY.
   Measured on the current render with zero tint in this bar:
   BOOK 6.00 · COLLECTIONS 5.81 · LOCATION 5.47 · FACILITIES 5.39 ·
   wordmark 10.13 · BY SANLIVING 9.66 · BOOK DIRECT 5.12. All past AA.
   EVERY ONE of those numbers is borrowed from the hero scrim. Lighten the
   scrim and this bar has nothing to fall back on — RE-MEASURE. */
.nav::before {
  content: "";
  position: absolute; inset: 0; z-index: -1;
  /* BLUR ONLY, no tint (2026-09-23). The band carried a green wash through
     three rounds of "too dark"; with the hero scrim now doing the darkening
     the wash has nothing left to do, so the bar is pure frost over the
     footage. Its contrast is borrowed entirely from the scrim below — see
     the note under the .nav--solid rule. */
  background: transparent;
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
  pointer-events: none;
  transition: opacity var(--duration) var(--ease-out);
}
/* Once the bar fills with ivory the band has nothing to do. */
.nav--solid::before { opacity: 0; }

/* left — the wordmark. Since 2026-10-12 the SOVANA mark from the client's
   brand guide (SOVANA-Brand-Guide.pdf, p.1 — their vector reconstruction):
   stroked lines with round ends, its gold = the V's tip and the two A dots,
   all three on --logo-accent. Same 149.4 × 34 box as the old Solana mark it
   replaced, so every size below holds. (Before: Figma 82:3035.)
   The SVG is inlined so the letterforms can ride `currentColor` and flip with
   the bar; its two accent dots ride --logo-accent for the same reason.

   Over the photograph the mark is ONE SOLID WHITE SILHOUETTE — letterforms,
   accent dots and descriptor all pure white, not ivory + gold. Decided
   2026-09-22: at 150px wide the gold dots and the gold-700 descriptor were
   reading as dirt on the image rather than as brand. Pure --white is a
   foreground here, so the "white must carry a hairline + shadow" rule in
   §03 (which governs white as a SURFACE) does not apply.
   Once the bar fills, the mark returns to green + gold. Only the logo is
   white — the links and the burger stay on ivory. */
/* The wordmark alone. The "by SanLiving" descriptor under it is gone
   (2026-09-23, by request) and so are its three rules — nothing is left to
   style. The mark took the space back and then some: 150x34 -> 186 -> 230,
   height derived from the viewBox (149.4/34 = 4.3941) rather than typed, so
   the letterforms cannot stretch at any size.
   .nav__brand no longer stacks anything, so the flex column went with it. */
.nav__brand { display: block; justify-self: start; line-height: 0; }
.nav__logo  {
  /* 265 = 230 + 15% (2026-09-25, by request); height from the viewBox
     ratio 4.3941. Held at 230 on 1081–1280 below, where 265 met the links. */
  width: 265px; height: 60.31px; display: block;
  color: var(--white);            /* letterforms, via currentColor */
  --logo-accent: var(--white);    /* the two dots in the wordmark */
  transition: color var(--duration) var(--ease-out);
}

.nav--solid .nav__logo, .nav.is-menu .nav__logo { color: var(--green); --logo-accent: var(--gold); }
/* an open menu is always the solid bar, on every page — only the home page's
   burger script set .nav--solid, so over a residence photo the bar stayed clear */
.nav.is-menu { background: var(--white); color: var(--green); border-bottom-color: var(--hairline-color); }
.nav.is-menu::before { opacity: 0; }

/* centre — the links */
.nav__links { display: flex; align-items: center; gap: clamp(16px, 2vw, 32px); justify-self: center; }
.nav__links a { position: relative; padding-block: 4px; opacity: .85; white-space: nowrap; }
/* Over the photograph the links run at FULL opacity and in PURE WHITE, not
   ivory. Both are contrast bought without darkening the band, which is the
   whole point — the band was called too dark twice.
     .85 ivory -> 3.98:1 · 1.0 ivory -> 4.81:1 · 1.0 white -> 4.54:1 on the
     .52/.42 band, ~3.6:1 on the lighter .42/.30 band shipping now.
   White also follows a decision already in this file: over the photograph
   the wordmark is one solid white silhouette. The links were the last thing
   still on ivory up there, which read as two different whites on one bar.
   The hover cue is the gold underline, not the opacity step, so nothing is
   lost. Once the bar fills, both revert — ivory is wrong on ivory. */
.nav:not(.nav--solid) .nav__links a,
.nav:not(.nav--solid) .btn--ghost-media { opacity: 1; color: var(--white); }
.nav--solid .nav__links a { color: var(--ink); }
.nav__links a:hover { opacity: 1; }
.nav__links a::after {
  content: ""; position: absolute; left: 0; right: 0; bottom: 0; height: 1px;
  background: var(--gold); transform: scaleX(0); transform-origin: left;
  transition: transform var(--duration) var(--ease-out);
}
.nav__links a:hover::after { transform: scaleX(1); }

/* THE CLIENT'S MAP (2026-09-30): Location (360° view) · The collection ·
   Ways to stay ▾ · Facilities · Other ▾ · Book now. */
/* Location carries "360° view" under it in gold, the pair centred on the
   bar's line (the tag hangs below, so the word lifts by half of it) */
.nav__loc { top: -7px; }
.nav__tag {
  /* tucked up into the link's padding and half-leading: ~5px under the
     word (2026-09-30, by request — it hung ~12px below) */
  position: absolute; left: 0; right: 0; top: calc(100% - 7px);
  text-align: center; line-height: 1; color: var(--gold);
}
.nav__links .nav__loc::after { bottom: -6px; }   /* the underline runs under the tag, not through it */
.nav--solid .nav__tag { color: var(--gold-700); }

/* the two dropdowns — a button that reads like the links, a chevron that
   turns, and a frosted white panel under the bar (the picker panels' dress) */
.nav__group { position: relative; }
.nav__drop {
  position: relative; display: inline-flex; align-items: center; gap: 6px;
  padding: 4px 0; background: none; border: 0; cursor: pointer;
  color: var(--ink); opacity: .85; white-space: nowrap;
}
.nav:not(.nav--solid) .nav__drop { color: var(--white); opacity: 1; }
.nav__drop:hover, .nav__group.is-open .nav__drop { opacity: 1; }
.nav__drop::after {
  content: ""; position: absolute; left: 0; right: 0; bottom: 0; height: 1px;
  background: var(--gold); transform: scaleX(0); transform-origin: left;
  transition: transform var(--duration) var(--ease-out);
}
.nav__drop:hover::after, .nav__group.is-open .nav__drop::after { transform: scaleX(1); }
/* 14px, was 10 (2026-10-01, by request); the stroke stays 1.25 (non-scaling) */
.nav__chev { width: 14px; height: 14px; flex: none; transition: rotate var(--duration) var(--ease-out); }
.nav__group.is-open .nav__chev { rotate: 180deg; }
.nav__drop:focus-visible { outline: 2px solid var(--gold); outline-offset: 4px; }

.nav__sub {
  /* close under its label, not at the bar's foot (2026-09-30, by request) */
  position: absolute; top: calc(100% + 12px); left: -24px; min-width: 210px;
  display: flex; flex-direction: column; align-items: flex-start; gap: 2px;
  padding: 14px 24px;
  background: rgba(255, 255, 255, .90);
  -webkit-backdrop-filter: blur(14px); backdrop-filter: blur(14px);
  box-shadow: 0 0 0 1px var(--hairline-color), var(--shadow-card);
  opacity: 0; visibility: hidden; translate: 0 -4px;
  transition: opacity var(--dur-quick) var(--ease-out), translate var(--dur-quick) var(--ease-out),
              visibility 0s linear var(--dur-quick);
}
/* a bridge over the gap, so the pointer can travel from the name to the panel */
.nav__sub::before { content: ""; position: absolute; left: 0; right: 0; bottom: 100%; height: 14px; }
.nav__group.is-open .nav__sub {
  opacity: 1; visibility: visible; translate: 0 0;
  transition: opacity var(--dur-enter) var(--ease-out), translate var(--dur-enter) var(--ease-out),
              visibility 0s;
}
.nav .nav__links .nav__sub a { color: var(--ink); opacity: .85; padding-block: 8px; }
.nav .nav__links .nav__sub a:hover, .nav .nav__links .nav__sub a[aria-current="page"] { opacity: 1; }
.nav__sub a::after { bottom: 6px; }
.nav__sub a[aria-current="page"]::after { transform: scaleX(1); }
/* Residence inside Ways to stay (2026-10-02, the client): a row like the
   links above it that opens Monthly and Yearly under itself, indented */
.nav__more { display: flex; flex-direction: column; align-items: flex-start; }
.nav__more-btn {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 8px 0; background: none; border: 0; cursor: pointer;
  color: var(--ink); opacity: .85;
}
.nav__more-btn:hover, .nav__more.is-open .nav__more-btn, .nav__more.is-here .nav__more-btn { opacity: 1; }
.nav__more-btn:focus-visible { outline: 2px solid var(--gold-700); outline-offset: 3px; }
.nav__group.is-open .nav__more .nav__chev { rotate: none; }   /* not the group's own turn */
.nav__group.is-open .nav__more.is-open .nav__chev { rotate: 180deg; }
.nav__more-list { display: none; flex-direction: column; align-items: flex-start; gap: 2px; padding-left: 16px; }
.nav__more.is-open .nav__more-list { display: flex; }

/* right — Figma's own navbar CTA sits in Veloura's right zone.
   Veloura parks a MENU label there; Sovana's navbar (38:43) parks
   "Book Direct". Kept Figma's, since it is a real conversion control. */
/* Symmetric padding, and no gap. Both were shaped around an arrow icon that
   sat on the right of the label — 22px of room on the left, 12px on the
   right, and a 10px gap between label and arrow. The arrows came off every
   button on 2026-09-22; the lopsided box outlived them.
   Tighter than the page's 16/26 because a nav sits at 12px of height
   pressure, but now tight on both sides equally. */
.nav__cta { justify-self: end; white-space: nowrap; }   /* the page's own 16 / 26 since the 2026-09-29 audit — one button height (54) everywhere; it was 12 / 22 (46 tall) */
/* the account link and Book now share the bar's end cell (js/nav.js) */
.nav__end { justify-self: end; display: flex; align-items: center; gap: clamp(16px, 2vw, 28px); }
.nav__acct { position: relative; display: inline-flex; align-items: center; gap: 8px; padding-block: 4px; color: inherit; white-space: nowrap; }
.nav__acct svg { width: 21px; height: 21px; flex: none; }
.nav__acct::after {
  content: ""; position: absolute; left: 26px; right: 0; bottom: 0; height: 1px;
  background: var(--gold); transform: scaleX(0); transform-origin: left;
  transition: transform var(--duration) var(--ease-out);
}
.nav__acct:hover::after, .nav__acct[aria-current="page"]::after { transform: scaleX(1); }
.nav__acct:focus-visible { outline: 2px solid var(--gold); outline-offset: 4px; }
.nav:not(.nav--solid) .nav__acct { color: var(--white); }
.nav__menu-acct { display: none; }   /* the burger menu's copy of the account link (≤1200) */
.nav__menu-cta { display: none; }    /* the burger menu's Check availability (≤1200) */
.nav--solid .nav__acct { color: var(--green); }

/* The CTA inverts with the bar. Figma draws it green because its navbar is a
   light glass pill; over our dark hero a green pill on a green scrim vanished,
   so it runs ivory there and only turns green once the bar goes solid. */
/* Over the hero the bar is transparent and the ground is a dark photograph,
   so the CTA takes the dark-ground outline; once the bar fills with ivory it
   takes the light-ground one. Both fill on hover, by the same rule. */
.nav .nav__cta {
  background: transparent; color: var(--ivory); border-color: rgba(252, 251, 248, .55);
}
.nav .nav__cta:hover {
  background: var(--gold); color: var(--green); border-color: var(--gold);
}

.nav--solid .nav__cta {
  background: transparent; color: var(--green); border-color: rgba(14, 43, 33, .28);
}
.nav--solid .nav__cta:hover {
  background: var(--green); color: var(--ivory); border-color: var(--green);
}   /* ships ivory already */

/* 44x44 target around 26px lines; the negative margin keeps the lines on
   the gutter edge rather than the button's. */
.nav__burger {
  display: none; flex-direction: column; justify-content: center; gap: 4px;
  width: 44px; height: 44px; padding: 0 9px; margin-right: -9px;
  background: none; border: 0; cursor: pointer;
  color: inherit; justify-self: end;
}
.nav__burger b { display: block; height: 1px; background: currentColor; }


/* ══ 1 · HERO — full viewport ═══════════════════════════════════════════ */

/* FULL VIEWPORT, CENTRED (2026-09-23, by request). The hero is exactly one
   screen tall and the lede sits dead centre of it. This reverses the earlier
   "height follows the footage" rule: a 16:9 video in a 100vh box is cropped
   by cover wherever the window is not 16:9, and that crop is the accepted
   cost. svh so mobile browser chrome does not push the fold. */
.hero {
  position: relative; overflow: hidden;
  height: 100vh; height: 100svh;
  display: grid;
  /* minmax(0,…), not auto: an auto track grows to the widest child's
     min-content, and the 460px sub made it 460 in a 390 viewport — the
     headline and sub were clipped by overflow: hidden. */
  grid-template-columns: minmax(0, 1fr);
  place-items: center;
  padding: var(--section-y) var(--frame-x);
}
.hero__media, .hero__scrim { position: absolute; inset: 0; }
/* THE SLIDER (2026-09-30, by request — the building reel before): three
   photographs stacked in one box, crossfading. The first is also the box's
   own ground, so the hero is never empty while the pictures load, with
   --green under it so even a failed request leaves the white type legible.
   contrast(.9) softens them together, once, as it did the reel. */
.hero__media {
  background: var(--green) url("../assets/site/hero-slide-1.jpg?v=2") center / cover no-repeat;   /* the living room opens (2026-09-30, by request) */
  filter: contrast(.9);
}
/* ZOOM OUT ON ARRIVAL, after belarosa-chalet.ch (2026-10-01, by request).
   Their numbers, sampled off the page: the picture's wrapper opens at scale
   1.3 and settles to 1 over ~4.5s, fast at first, a long slow finish —
   fitted to cubic-bezier(.3, .8, .6, 1). The whole box scales (the slides
   crossfade inside it, the parallax translates them), and only once the
   first picture has loaded — the script adds .is-zoom then. Reduced motion:
   none. */
.hero__media.is-zoom { animation: hero-zoom 3s cubic-bezier(.3, .8, .6, 1) both; }   /* 3s, not their 4.5 (by request) */
@keyframes hero-zoom { from { scale: 1.3; } to { scale: 1; } }
@media (prefers-reduced-motion: reduce) { .hero__media.is-zoom { animation: none; } }
.hero__slide {
  position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover;
  opacity: 0; pointer-events: none;
  transition: opacity calc(var(--duration) * 2.4) var(--ease-out);   /* ~1s: a slow dissolve, not a cut */
}
.hero__slide.is-on { opacity: 1; }
/* the picker: three short ivory bars on the right edge, the shown one at
   full strength and longer */
.hero__dots {
  position: absolute; z-index: 2; right: calc(var(--frame-x) - 14px); top: 50%; translate: 0 -50%;
  display: flex; flex-direction: column;
}
.hero__dot {
  /* 44 wide but 32 tall: the bars sit close, ~10px apart (2026-09-30, by
     request — 44 square left them far apart); the width keeps the target easy */
  width: 44px; height: 32px; padding: 0; border: 0; background: none; cursor: pointer;
  display: grid; place-items: center;
}
.hero__dot::before {
  content: ""; width: 2px; height: 16px; background: var(--ivory); opacity: .45;
  transition: height var(--duration) var(--ease-out), opacity var(--duration) var(--ease-out);
}
.hero__dot:hover::before { opacity: .8; }
.hero__dot.is-on::before { height: 28px; opacity: 1; }
.hero__dot:focus-visible { outline: 2px solid var(--gold); outline-offset: -6px; }
.hero__dot:active { scale: .96; }
/* EXACTLY Figma 97:3023 "hero3::after" (2026-09-23). Transcribed, not tuned:
     from  rgba(200,164,97,.08)  0%
     via   rgba(14,43,33,.44)   39.576%
     to    rgba(14,43,33,.55)   89.944%
   The previous value was mine, not Figma's, and ran .88 -> .55 -> .74 solid
   green — roughly twice the ink, which is what read as too dark. Note the
   TOP STOP IS GOLD: the champagne tint at the horizon is part of the design,
   and a flat green scrim removed the one place "Sol" shows up in the hero.
   Do not re-tune these numbers; they are the Figma values. */
.hero__scrim {
  /* TWO LAYERS, deliberately kept apart.
     TOP  — a flat deepening to surfsidebali.com's weight (2026-09-23).
            NEUTRAL BLACK, not green. A green deepening was tried first and
            read as a colour cast rather than as shade; black takes the
            luminance down without tinting the footage, which is the point.
            This is the ONE place black is allowed — §03's "no shadow is ever
            black" governs SHADOWS, and this is a scrim, not a shadow.
            Not a guessed value. Surfside's hero, captured headless at
            1440x900, measures mean luminance 0.1002; ours was 0.2196 —
            2.19x brighter. Calibrated by sweep against the real render:
            green .64 -> .1055 · black .46 -> .1137 · .50 -> .0998 · .52 ->
            .0969. Black .50 lands on the target almost exactly.
     UNDER— Figma 97:3023's stops and opacities, its green stops turned
            black (2026-09-24, by request: every overlay is black now). It stays its own layer so the
            transcription is still readable and the darkening can be removed
            by deleting one line rather than by re-deriving the gradient.
     The gold top stop survives underneath but reads much quieter now; that
     is the cost of matching Surfside and it was the explicit request. */
  background:
    /* the nav has no tint of its own: at .40 overall it measured 3.5:1, so
       the band behind it keeps a little more shade, and fades out by 180px */
    linear-gradient(to bottom, rgba(0,0,0,.28) 0, rgba(0,0,0,0) 180px),
    linear-gradient(rgba(0,0,0,.40), rgba(0,0,0,.40)),   /* .50 until 2026-09-24: lightened by request once the layer below went black */
    linear-gradient(to bottom,
      rgba(200,164,97,.08) 0%,
      rgba(0,0,0,.44) 39.576%,
      rgba(0,0,0,.55) 89.944%);
}
/* The lede sits dead centre. It shares the one grid cell with .hero__inner,
   whose booking bar is position: fixed and takes no room here. */
.hero__lede {
  position: relative; grid-area: 1 / 1;
  width: min(1000px, 100%); text-align: center;
  display: flex; flex-direction: column; align-items: center;
}
.hero__inner {
  position: relative; grid-area: 1 / 1; width: min(1180px, 100%);
  display: flex; flex-direction: column; align-items: center;
}
.hero__title { margin: 0; color: var(--ivory); }

/* .hero__rule removed 2026-09-22: the gold hairline Wilde sets under its
   headline is gone by request. Nothing replaces it — this is the one place
   the direction departs from the reference hero.
   Its 40px of space is folded into the sub's top margin below, so the
   headline keeps its breathing room instead of the sub jumping up into it. */
.hero__sub {
  /* A subhead, not prose — balanced like the headings. */
  text-wrap: balance;
  margin: clamp(24px, 3vh, 40px) 0 0;
  /* 640, not 460 (2026-09-25): the sub is now a three-item list and reads on
     one line from tablet up; on a phone it breaks only at a separator. */
  width: min(640px, 100%); color: rgba(252, 251, 248,.88);
}
.hero__sub span { white-space: nowrap; }
/* .hero__cue removed 2026-09-21 because the Programs panel broke the fold
   and already said "there is more below". That panel is gone as of
   2026-09-22 (Programs is now a full-bleed band that starts below the fold),
   so nothing signals the scroll any more. Left as-is rather than reinstated:
   the cue was not asked for back. Reopen if the fold reads as a dead end. */


/* ══ HERO BOOKING BAR — after Wilde ═════════════════════════════════════
   Flat segmented bar: tracked label over a value, hairline between fields,
   dark rectangular action on the end. Sharp throughout, per the reference.
   Three fields — check in, check out, guests — per the client's booking-bar
   reference of 2026-09-22; the shape stays square because SHAPE LOCK is 0 in
   this direction. The client's bar is rounded with a pill action, which is a
   separate decision from the field list and has NOT been taken here. */

/* The runway that used to sit here is gone: the bar now stands down when Get
   in touch reaches it, so nothing floats over the contact form or the
   footer and the page no longer needs dead space at its end. */

/* FLOATING FROM THE FIRST PAINT. The bar is fixed to the foot of the
   viewport for the whole page — it never sits in the hero's flow and never
   detaches. Wide here, after raffles.com, whose bar runs about two thirds of
   the viewport rather than the width of its own text. */
.book {
  position: fixed; z-index: 30;
  left: 50%; transform: translateX(-50%);
  /* Raised off the edge (2026-09-23, by request) — 16-28px read as
     sitting on the browser chrome. */
  /* 40px LOWER (2026-09-28, by request): was clamp(40px, 7vh, 80px), which
     sat it over the hero's subline. */
  bottom: clamp(0px, calc(7vh - 40px), 40px);
  display: flex; flex-wrap: wrap; align-items: stretch; justify-content: center;
  /* Translucent and frosted, the same idea as the nav band — the footage
     carries on through the bar instead of the bar cutting a solid slab out
     of it. Ivory is held back to .82 rather than lower: the bar is full of
     12px tracked labels, and every point of ground opacity comes straight
     off their contrast. Measured on the render, not guessed. */
  background: rgba(252, 251, 248, .82);
  /* The blur is on ::before below, not here: a backdrop-filter makes its
     element a backdrop root, and the picker panels inside would then blur
     only the bar, never the photograph behind them. */
  /* A hairline so the translucent edge still reads as an edge. Green, not
     ivory: the bar floats over the pale sections too once it narrows, and an
     ivory border would vanish there. */
  width: min(1400px, calc(100% - 2 * var(--gutter)));
  /* At rest the ring is the default hairline (.09, 2026-09-24 — .16 read
     heavy); while the bar is in use it steps back up to .16. */
  --book-ring: var(--hairline-color);
  box-shadow: 0 0 0 1px var(--book-ring),
              0 18px 50px -18px rgba(var(--shadow-rgb), .28),
              0 6px 18px -10px rgba(var(--shadow-rgb), .18);
  transition: width var(--duration) var(--ease-out),
              box-shadow var(--dur-quick) var(--ease-out),
              opacity var(--duration) var(--ease-out),
              transform var(--duration) var(--ease-out);
}

/* In use = a panel open (a click on a button does not focus it in Safari,
   so :focus-within alone would miss the mouse) or the keyboard inside it. */
.book:has(.pick.is-open), .book:focus-within { --book-ring: rgba(var(--shadow-rgb), .16); }
.book::before {
  content: ""; position: absolute; inset: 0; z-index: -1;
  backdrop-filter: blur(14px);
  -webkit-backdrop-filter: blur(14px);
  pointer-events: none;
}

/* NARROWED past the hero. The bar does not move, dock or detach — it is
   fixed from the first paint. Only the width changes, so there is nothing to
   jump and no placeholder to keep. */
.book.is-compact {
  width: min(980px, calc(100% - 2 * var(--gutter)));
}
/* ONLY the horizontal measurements change. The vertical padding stays at
   16px and the icon stays at 19px, so the bar is exactly as tall in both
   states — it was 80px wide / 72px compact, and because the bar is anchored
   by its BOTTOM edge that 8px showed up as the whole bar stepping down as
   you scrolled past the hero. Nothing about this transition should move
   anything but the width. */
.book.is-compact .book__field { padding: 16px 20px; min-width: 150px; flex: 1 1 150px; }
.book.is-compact .book__go    { --go-pad: 28px; }

/* STOOD DOWN over Get in touch and the footer. It drops out rather than
   fading in place — a bar that only fades still reads as hovering. The
   translateX(-50%) has to be repeated: it is what centres the bar, and a
   transform declaration replaces it whole. */
.book.is-gone {
  opacity: 0; visibility: hidden; pointer-events: none;
  transform: translate(-50%, 18px);
  transition: opacity var(--duration) var(--ease-out),
              transform var(--duration) var(--ease-out),
              visibility 0s linear var(--duration);
}

/* PHONES: a collapsed trigger, not the full bar (2026-09-23). Stacked, the
   three fields and the action measured 342x254 at 390 — 30% of the screen,
   floating over every section. Below 720 the bar is one 56px row until it
   is asked for. It is still fixed and still the same element; only its
   contents change. */
.book__open { display: none; }
@media (max-width: 720px) {
  .book__open {
    display: flex; align-items: center; justify-content: center;
    width: 100%; min-height: 56px; padding: 0 20px;
    background: none; border: 0; color: var(--green); cursor: pointer;
  }
  .book:not(.is-expanded) > :not(.book__open) { display: none; }
  .book.is-expanded .book__open { border-bottom: var(--hairline); }
  .book.is-expanded .book__go { flex: 1 1 100%; min-height: 48px; }
  /* Two months cannot sit side by side in 342px: they stack, and the panel
     scrolls within what the expanded bar leaves above it. */
  .book .pick__panel { max-height: calc(100svh - 360px); }
}

/* Panels must not run off the top on a short window. */
.book .pick__panel { max-height: calc(100vh - 150px); overflow-y: auto; }

@media (prefers-reduced-motion: reduce) {
  .book { transition: none; }
}
/* ICON + STACK, after raffles.com: a small outline mark on the left, the
   label and value stacked beside it. The field was a bare two-line stack;
   the mark is what makes it read as a control. */
.book__field {
  display: flex; align-items: center; gap: 14px;
  padding: 16px 24px; min-width: 190px; flex: 1 1 190px; cursor: pointer;
  transition: background-color var(--dur-quick) var(--ease-out),
              border-color var(--dur-enter) var(--ease-out);
}
.book__field:hover { background: rgba(14, 43, 33, .04); }
.book__ico { flex: none; width: 22px; height: 22px; color: var(--muted); }
.book__stack { display: flex; flex-direction: column; gap: 3px; min-width: 0; flex: 1; }
.book__field + .book__field { border-left: var(--hairline); }
.book__label { color: var(--muted); }
.book__value {
  /* fixed height: a native date input is intrinsically taller than a select,
     which pushed the CHECK IN / CHECK OUT labels 3px off the other two */
  width: 100%; height: 26px; padding: 0; border: 0; background: none;
  font: inherit; font-size: inherit; color: var(--green); cursor: pointer;
  appearance: none;
}
.book__value:focus-visible { outline: 2px solid var(--gold-700); outline-offset: 4px; }

/* The "Add date" hint layer and the showPicker trick that went with it are
   gone — the custom calendar renders that label on its own button, and the
   native date input behind it is hidden. */

/* Outline at rest like every other button. The bar it sits in is ivory, so
   it takes the light-ground pair. No left border: the bar already draws a
   hairline between its fields and two lines there read as a seam. */
/* The action is an UNDERLINE button now, not an outlined one — the same
   mechanic as the nav links and "Explore on map": a gold hairline that wipes
   in from the left. On a bar that is itself translucent, a boxed button was
   the one solid object left and it read as bolted on. */
/* AN OUTLINE BUTTON INSET IN THE BAR (2026-09-24, by request) — the
   results page's Update, now the one action style for every bar. It was an
   underline wipe here; a boxed action reads as the thing to press, where a
   fourth label in the row read as another field. .btn--ghost's mechanic:
   .28 green outline at rest, fills green with an ivory label on hover. */
.book__go {
  --go-pad: 30px;
  flex: 0 0 auto; margin: 12px; padding: 0 var(--go-pad); cursor: pointer;
  display: inline-flex; align-items: center; justify-content: center;
  /* PRIMARY green from the start (2026-09-24, by request) — the forms' own
     action: solid --green, ivory label, --green-900 on hover */
  background: var(--green); color: var(--ivory);
  border: 1px solid var(--green);
  transition: background-color var(--duration) var(--ease-out),
              color var(--duration) var(--ease-out),
              border-color var(--duration) var(--ease-out),
              scale var(--dur-quick) var(--ease-out);
}
.book__go:hover { background: var(--green-900); color: var(--ivory); border-color: var(--green-900); }
.book__go:active { scale: .96; }
.book__go:focus-visible { outline: 2px solid var(--gold-700); outline-offset: 3px; }

/* DARK OVER THE HERO, LIGHT ON HOVER (2026-09-28, by request, after
   raffles.com). While the bar sits over the hero (.is-hero, set by the page
   script — only while the bar lies wholly on the hero) it is a smoked-glass slab with ivory
   type and a white action; hovering it, focusing into it, opening a panel or
   expanding it on a phone brings back the light bar above. Past the hero it
   is light and stays light. Neutral black, like every overlay on the hero:
   a green slab read as a colour cast on the footage. */
.book { transition-property: width, box-shadow, opacity, transform, background-color; }
.book__ico, .book__label, .book__value, .book__empty, .book__open,
.book .pick__btn { transition: color var(--dur-enter) var(--ease-out); }
.book .pick__chev { transition: transform var(--duration) var(--ease-out),
                                color var(--dur-enter) var(--ease-out); }
.book.is-hero:not(:hover, :focus-within, :has(.pick.is-open), .is-expanded) {
  background: rgba(0, 0, 0, .15);
  --book-ring: rgba(252, 251, 248, .07);
}
.book.is-hero:not(:hover, :focus-within, :has(.pick.is-open), .is-expanded) :is(
  .book__value, .pick__btn, .book__open) { color: var(--ivory); }
.book.is-hero:not(:hover, :focus-within, :has(.pick.is-open), .is-expanded) :is(
  .book__ico, .book__label, .book__empty, .pick__chev) { color: rgba(252, 251, 248, .72); }
.book.is-hero:not(:hover, :focus-within, :has(.pick.is-open), .is-expanded) .book__field + .book__field {
  border-left-color: rgba(252, 251, 248, .16);
}
.book.is-hero:not(:hover, :focus-within, :has(.pick.is-open), .is-expanded) .book__go {
  background: var(--white); color: var(--green); border-color: var(--white);
}


/* ══ 2 · PROGRAMS — full-bleed dark band ════════════════════════════════
   REF San Felice "Wine Estates": a dark band, the title centred across the
   top, and the tiles FILLING the width on a small even inset — not a card
   on the page ground. Replaces Wye Junction's white lifted panel, which is
   why .panel / .panel__head are gone rather than left as dead rules.
   The band is LIGHT (decided 2026-09-22 — it was --green for one round).
   WHITE, the page ground (2026-09-25): Collection below is the ivory step,
   and the photographs carry the contrast on their own. Everything in the head is therefore on-light again.               */

.programs {
  background: var(--white);
  padding-block: var(--section-y);   /* horizontal comes from .section */
}
.programs__head {
  text-align: center;
  padding-bottom: var(--head-gap);
}
.programs__head .wrap {
  display: flex; flex-direction: column; align-items: center; gap: 18px;
}
.programs__title { margin: 0; color: var(--green); }

/* ── the three tiers · copy inside the frame (2026-09-29, the client) ────
   After the client's reference: each card IS its picture, portrait 3:4.
   At rest only the name and its kicker sit at the foot, left-aligned, over
   a black scrim. On hover or focus the words and the action open BENEATH
   them — .tier__more goes 0fr → 1fr — so the name rises by exactly the
   height of what appears; nothing is positioned by a guessed offset. The
   picture dims a step at the same time so the longer block stays legible.
   Scrims are neutral black, like every overlay. Screens that cannot hover
   (hover: none) show everything, always. (Before: surfsidebali.com's
   columns, every detail under the picture, 2026-09-25.) */
.tiers {
  display: grid; grid-template-columns: repeat(3, minmax(0, 1fr));
  column-gap: 20px;   /* 20 between cards, every page (2026-09-28) */
}
.tier {
  position: relative; overflow: hidden; isolation: isolate;
  aspect-ratio: 3 / 4; background: var(--ivory); cursor: pointer;
  scroll-margin-top: calc(var(--nav-space) + 24px);   /* the nav's Ways to stay links land here, clear of the bar */
}
.tier__media { position: absolute; inset: 0; margin: 0; z-index: -2; }
.tier__media img {
  width: 100%; height: 100%; object-fit: cover; display: block;
  /* softened 2026-09-24 (.75); raised back to .9 on 2026-09-25, by request */
  filter: contrast(.9);
}
/* ::before the resting scrim (foot only), ::after the hover step (whole) */
.tier::before, .tier::after { content: ""; position: absolute; inset: 0; z-index: -1; pointer-events: none; }
.tier::before { background: linear-gradient(to top, rgba(0, 0, 0, .78), rgba(0, 0, 0, .42) 40%, rgba(0, 0, 0, .12) 70%, rgba(0, 0, 0, .12)); }   /* darker 2026-09-29, by request (.62 / .28 / 0) */
.tier::after  { background: rgba(0, 0, 0, .45); opacity: 0;   /* .30 before, by request */
                transition: opacity var(--duration) var(--ease-out); }
.tier__body {
  position: absolute; left: 0; right: 0; bottom: 0;
  padding: clamp(24px, 2.4vw, 36px);
  display: flex; flex-direction: column; align-items: flex-start;
}
.tier__name { margin: 0; color: var(--ivory); }
.tier__kicker { --eyebrow-ink: rgba(252, 251, 248, .85); margin-top: 10px; }
.tier__more {
  display: grid; grid-template-rows: 0fr;
  transition: grid-template-rows var(--duration) var(--ease-out);
}
.tier__inner {
  min-height: 0; overflow: hidden; opacity: 0;
  transition: opacity var(--duration) var(--ease-out);
}
.tier__desc { margin: 20px 0 0; max-width: 60ch; color: rgba(252, 251, 248, .88); text-wrap: balance; }   /* two even lines (2026-10-02, by request): the measure makes a one-liner on a wide card break, balance evens it */
.tier__inner .btn { margin-top: 24px; max-width: 100%; }   /* may wrap: "Opening August · Pre-book" is wider than a 1100px card */
/* TWO ways to stay (2026-10-02, the client): Daily · Residence, two columns.
   LANDSCAPE 3:2 side by side (2026-10-14, by request — square before, then
   16:9 the same day, "lebihin lagi tingginya"; the square pictures are
   cropped to it). At 1081–1280 4:3, taller, so the opened detail fits (at
   16:9 it ran 24px past the top at 1100); stacked (≤1080) they stay square.
   Residence's two choices sit side by side. */
.tiers--2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.tiers--2 .tier { aspect-ratio: 3 / 2; }
@media (max-width: 1280px) { .tiers--2 .tier { aspect-ratio: 4 / 3; } }
@media (max-width: 1080px) { .tiers--2 .tier { aspect-ratio: 1 / 1; } }
.tier__acts { display: flex; flex-wrap: wrap; gap: 12px; margin-top: 24px; }
.tier__acts .btn { margin-top: 0; }
.tier:hover::after, .tier:focus-within::after { opacity: 1; }
.tier:hover .tier__more, .tier:focus-within .tier__more { grid-template-rows: 1fr; }
.tier:hover .tier__inner, .tier:focus-within .tier__inner { opacity: 1; }
@media (hover: none) {
  .tier::after { opacity: 1; }
  .tier__more { grid-template-rows: 1fr; }
  .tier__inner { opacity: 1; }
}


/* ══ 2b · DRONE VIEW — 360° panoramas (2026-09-25) ═══════════════════════
   After 360indonesia.id/thesanur, in this site's style: centred head, a row
   of underline tabs (the floor-plan filters' mechanic) choosing one of three
   views, then ONE FULL-WIDTH frame. Pannellum (js/vendor/pannellum, MIT)
   draws it once js/drone.js has loaded it; the chosen view's preview is the
   frame's background until then. Controls squared and lifted like every
   other white chip; the landmark pins are the site's own chips. */
/* no space under the map: it runs straight into the next section */
.drone { padding-bottom: 0; }
/* on its own page (location.html) the section starts under the solid nav */
.drone--page { padding-top: calc(93px + var(--section-y)); }
/* on the residence page it follows the story + card, whose own foot
   already leaves the room (2026-09-30, by request) */
.drone--res { padding-top: 0; }
/* location.html's Location Advantage: centred head, three points in a row
   (always open, a hairline above each), then the map across the full page
   width, running to the section's foot. */
.locpage .sec-head { margin-bottom: var(--head-gap); }
.locpage .sec-head h2 { max-width: 24ch; }
.locpage__pts {
  list-style: none; margin: 0 0 var(--head-gap); padding: 0;
  display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 0;
}
/* VERTICAL hairlines BETWEEN the points (2026-09-28, by request — a rule
   above each before): each point after the first carries one on its left */
.locpage__pts li { padding-inline: clamp(24px, 3vw, 48px); }
.locpage__pts li:first-child { padding-left: 0; }
.locpage__pts li:last-child { padding-right: 0; }
.locpage__pts li + li { border-left: var(--hairline); }
.locpage__pts h3 { margin: 0 0 12px; color: var(--green); }
.locpage__pts p { margin: 0; color: var(--muted); text-wrap: pretty; }
.locpage__map { margin: 0 calc(-1 * var(--frame-vw)); height: 70svh; min-height: 420px; }
.locpage__map iframe { display: block; width: 100%; height: 100%; border: 0; }
@media (max-width: 900px) {
  /* stacked: the rule turns horizontal again, between the points */
  .locpage__pts { grid-template-columns: minmax(0, 1fr); }
  .locpage__pts li { padding: 24px 0; }
  .locpage__pts li:first-child { padding-top: 0; }
  .locpage__pts li + li { border-left: 0; border-top: var(--hairline); }
}
/* FULL WIDTH: the stage runs out through the section's padding to the page
   edge; the frame fills it and the tabs float on top of the picture */
.drone__stage {
  position: relative;
  margin-inline: calc(-1 * var(--frame-vw));
}
/* the view tabs, INSIDE the map (2026-09-25, by request): one white,
   lifted, square panel — the same material as the zoom controls and the
   booking bar — the chosen view filled green like the primary action */
/* On the LEFT (2026-09-28, by request), one row, aligned with the head's
   left edge (--frame-vw) so the eyebrow, title and tabs share one line; the
   zoom / fullscreen controls moved to the right to make room. (A vertical
   column was tried the same day and turned back.) */
.drone__tabs {
  position: absolute; z-index: 3; top: 24px; left: var(--frame-vw);
  display: flex; padding: 6px; gap: 4px;
  /* SMOKED GLASS, the booking bar's own over-the-hero dress (2026-09-28,
     by request — white at .92 before): black .15 over blur(14px), an
     ivory .07 ring, ivory labels, the chosen view WHITE with a green label
     like the bar's action. */
  background: rgba(0, 0, 0, .15);
  -webkit-backdrop-filter: blur(14px); backdrop-filter: blur(14px);
  box-shadow: 0 0 0 1px rgba(252, 251, 248, .07), var(--shadow-card);
}
.drone__tab {
  padding: 12px 18px; border: 0; background: transparent; cursor: pointer; text-align: left;
  color: var(--ivory); white-space: nowrap;
  transition: background-color var(--dur-quick) var(--ease-out),
              color var(--dur-quick) var(--ease-out),
              scale var(--dur-quick) var(--ease-out);
}
.drone__tab:hover { background: rgba(252, 251, 248, .12); }
.drone__tab[aria-pressed="true"] { background: var(--white); color: var(--green); }
.drone__tab:active { scale: .96; }
.drone__tab:focus-visible { outline: 2px solid var(--gold-700); outline-offset: 2px; }
@media (max-width: 720px) {
  /* across the foot of the map, FOUR views as a 2 x 2 (2026-09-28) — four
     in one row squeezed "From Plaza Senayan" onto three lines */
  .drone__tabs { top: auto; bottom: 12px; left: 12px; right: 12px;
                 display: grid; grid-template-columns: 1fr 1fr; }
  .drone__tab  { text-align: center; }
  .drone__tab  { white-space: normal; padding: 10px 8px; line-height: 1.3; }
}
/* two classes: pannellum.css loads later and sets .pnlm-container to
   height: 100%, which is 0 inside an auto-height section. */
.drone .drone__frame {
  position: relative; width: 100%;
  height: 85vh; height: 85svh;   /* 85 of the screen, by request (2026-09-25) */
  background: var(--ivory) url("../assets/site/drone-above-preview.jpg?v=20261006d") center / cover no-repeat;
  display: grid; place-items: center;
}
/* landmark pins (2026-10-14, the client's From above mockup, every view):
   a ROUND gold dot ringed white on the point, a thin white line, and a
   white PILL facing the screen — the name in sentence case (.t-body 16,
   a step down from 18 the same day, by request),
   Senayan MRT's train after it, no icon for the malls. Sovana's pill is
   green, ringed gold, its name in capitals · a divider · a gold house.
   The dot and the pills are a deliberate exception to "no round shapes",
   by the client's mockup, like the payment radios.
   Everything is placed by js/drone.js paint() from the panorama's own
   projection; layout() picks each chip's distance and slide so none
   overlaps at any width. (Before: square chips in capitals with a walking
   icon for the malls, turned in 3D onto the panorama sphere after
   360indonesia.id/thesanur — 2026-09-30 → 10-14; Pannellum hot spots
   before that.) */
.drone .drone-pins {
  position: absolute; inset: 0; z-index: 1; overflow: hidden; pointer-events: none;
  transition: opacity var(--dur-enter) var(--ease-out);
}
.drone .drone-pins.is-wait { opacity: 0; transition-duration: 0ms; }
.drone .drone-pin { position: absolute; inset: 0; }
.drone .drone-pin__dot {
  position: absolute; left: -5px; top: -5px; width: 10px; height: 10px;
  border-radius: 50%; background: var(--gold);
  box-shadow: 0 0 0 2px var(--white), 0 1px 4px 2px rgba(0, 0, 0, .3);
}
.drone .drone-pin__line {
  position: absolute; left: 0; top: -.75px; height: 1.5px; transform-origin: 0 50%;
  /* WHITE with a thin dark halo (2026-09-30, by request — gold sank into
     the photograph's warm haze and beige walls): the halo holds it on the
     pale sky, the white on trees and road. The dot stays gold. */
  background: var(--white);
  box-shadow: 0 0 0 .5px rgba(0, 0, 0, .35), 0 0 3px rgba(0, 0, 0, .45);
}
.drone .drone-pin__chip { position: absolute; left: 0; top: 0; width: 0; height: 0; }
.drone .drone-pin__label {
  position: absolute; left: 0; bottom: 0; transform: translateX(-50%);
  display: flex; align-items: center; gap: 10px;
  white-space: nowrap; padding: 8px 15px;
  border-radius: 999px;   /* a pill — see above; --radius-pill is 0 in this build */
  background: var(--white); color: var(--green);
  box-shadow: 0 0 0 1px var(--hairline-color), 0 2px 10px rgba(0, 0, 0, .28);
}
/* the name in sentence case, as written; no rule under it */
.drone .drone-pin__name { line-height: 1; white-space: nowrap; }   /* ONE line: .t-body's text-wrap: pretty turns wrapping back on */
.drone .drone-pin__icon { width: 17px; height: 17px; flex: none; margin-right: -3px; }
/* chip hung BELOW its point */
.drone .drone-pin--below .drone-pin__label { bottom: auto; top: 0; }
/* Sovana: green, ringed gold, the name in capitals · a divider · a gold house */
.drone .drone-pin--own .drone-pin__label {
  padding: 11px 17px 11px 20px; gap: 12px;
  background: var(--green); color: var(--ivory); cursor: pointer; pointer-events: auto;
  box-shadow: 0 0 0 1.5px var(--gold), 0 2px 14px rgba(0, 0, 0, .4);
}
.drone .drone-pin--own .drone-pin__name { text-transform: uppercase; letter-spacing: var(--tracking-ui); }
.drone .drone-pin--own .drone-pin__icon {
  width: 19px; height: 19px; margin-right: 0;
  padding-left: 12px; box-sizing: content-box; border-left: 1px solid rgba(252, 251, 248, .25);
  color: var(--gold);
}
.drone .drone-pin--own .drone-pin__label:focus-visible { outline: 2px solid var(--gold); outline-offset: 3px; }
/* SMALLER on tablet and phone (2026-09-30, by request): the same landmarks
   sit closer together in pixels on a narrower frame */
@media (max-width: 1080px) {
  .drone .drone-pin__name { font-size: var(--size-11); }
  .drone .drone-pin__label { gap: 7px; padding: 6px 10px; }
  .drone .drone-pin__icon { width: 13px; height: 13px; margin-right: -2px; }
  .drone .drone-pin--own .drone-pin__label { gap: 9px; padding: 8px 11px 8px 14px; }
  .drone .drone-pin--own .drone-pin__icon { width: 14px; height: 14px; padding-left: 9px; }
  .drone .drone-pin__dot { left: -4.5px; top: -4.5px; width: 9px; height: 9px; }
}
@media (max-width: 720px) {
  .drone .drone-pin__name { font-size: var(--size-10); }
  .drone .drone-pin__label { gap: 5px; padding: 5px 9px; }
  .drone .drone-pin__icon { width: 11px; height: 11px; margin-right: -1px; }
  .drone .drone-pin--own .drone-pin__label { gap: 7px; padding: 6px 9px 6px 11px; }
  .drone .drone-pin--own .drone-pin__icon { width: 12px; height: 12px; padding-left: 7px; }
  .drone .drone-pin__dot { left: -3.5px; top: -3.5px; width: 7px; height: 7px;
                           box-shadow: 0 0 0 1.5px var(--white), 0 1px 3px 1px rgba(0, 0, 0, .3); }
  .drone .drone-pin__line { height: 1px; top: -.5px; }
}

/* From Senayan City's pins a step LARGER (2026-10-14, by request): the
   view is low and close, its two malls fill the frame */
.drone .drone-pins[data-scene="senayan-city"] .drone-pin__name { font-size: var(--size-20); }
.drone .drone-pins[data-scene="senayan-city"] .drone-pin__label { gap: 12px; padding: 10px 19px; }
.drone .drone-pins[data-scene="senayan-city"] .drone-pin__icon { width: 21px; height: 21px; }
.drone .drone-pins[data-scene="senayan-city"] .drone-pin--own .drone-pin__label { gap: 15px; padding: 14px 21px 14px 25px; }
.drone .drone-pins[data-scene="senayan-city"] .drone-pin--own .drone-pin__icon { width: 24px; height: 24px; padding-left: 15px; }
.drone .drone-pins[data-scene="senayan-city"] .drone-pin__dot { left: -6.5px; top: -6.5px; width: 13px; height: 13px; }
@media (max-width: 1080px) {
  .drone .drone-pins[data-scene="senayan-city"] .drone-pin__name { font-size: var(--size-14); }
  .drone .drone-pins[data-scene="senayan-city"] .drone-pin__label { gap: 8px; padding: 8px 13px; }
  .drone .drone-pins[data-scene="senayan-city"] .drone-pin__icon { width: 16px; height: 16px; }
  .drone .drone-pins[data-scene="senayan-city"] .drone-pin--own .drone-pin__label { gap: 11px; padding: 10px 14px 10px 17px; }
  .drone .drone-pins[data-scene="senayan-city"] .drone-pin--own .drone-pin__icon { width: 17px; height: 17px; padding-left: 11px; }
  .drone .drone-pins[data-scene="senayan-city"] .drone-pin__dot { left: -5.5px; top: -5.5px; width: 11px; height: 11px; }
}
@media (max-width: 720px) {
  .drone .drone-pins[data-scene="senayan-city"] .drone-pin__name { font-size: var(--size-12); }
  .drone .drone-pins[data-scene="senayan-city"] .drone-pin__label { gap: 6px; padding: 6px 11px; }
  .drone .drone-pins[data-scene="senayan-city"] .drone-pin__icon { width: 13px; height: 13px; }
  .drone .drone-pins[data-scene="senayan-city"] .drone-pin--own .drone-pin__label { gap: 8px; padding: 7px 11px 7px 13px; }
  .drone .drone-pins[data-scene="senayan-city"] .drone-pin--own .drone-pin__icon { width: 14px; height: 14px; padding-left: 8px; }
  .drone .drone-pins[data-scene="senayan-city"] .drone-pin__dot { left: -4.5px; top: -4.5px; width: 9px; height: 9px; }
}

/* the Sovana pin's video — a native dialog, black scrim like every
   ::backdrop; the film fills it at its own 16:9 */
.dvideo {
  width: min(1200px, calc(100% - 2 * var(--gutter)));
  max-height: calc(100svh - 48px);
  padding: 0; border: 0; background: var(--green); color: var(--ivory);
}
.dvideo::backdrop { background: rgba(0, 0, 0, .72); }
.dvideo__bar { display: flex; justify-content: space-between; align-items: center; gap: 16px; padding: 12px 16px 12px 20px; }
.dvideo__title { margin: 0; }
.dvideo__close {
  min-height: 44px; padding: 0 12px; border: 0; background: none; color: inherit; cursor: pointer;
}
.dvideo__close:hover { color: var(--gold); }
.dvideo__close:focus-visible { outline: 2px solid var(--gold); outline-offset: 2px; }
.dvideo__film { display: block; width: 100%; aspect-ratio: 16 / 9; max-height: calc(100svh - 120px); background: #000; }
/* the residences' 360° (js/pano.js, 2026-10-06): the film dialog's frame,
   holding each residence's 360° interiors; the rooms as the drone view's
   smoked-glass tabs, top-left inside the picture */
.dpano__stage { position: relative; }
.dpano__view { width: 100%; aspect-ratio: 16 / 9; max-height: calc(100svh - 160px); background: #000; }
.dpano .dpano__tabs { top: 16px; left: 16px; right: auto; }
.dpano__cap { margin: 0; padding: 12px 20px 16px; color: rgba(252, 251, 248, .78); }
.dpano .pnlm-container { font-family: var(--font-sans); }
.dpano .pnlm-controls-container { top: 16px; left: auto; right: 16px; }
.dpano .pnlm-controls { border-radius: 0; }
.dpano .pnlm-about-msg { display: none !important; }
@media (max-width: 720px) {
  .dpano__view { aspect-ratio: 4 / 5; }
  .dpano .dpano__tabs { top: auto; bottom: 10px; left: 10px; right: 10px; display: flex; flex-wrap: wrap; }
  .dpano .dpano__tabs .drone__tab { flex: 1 1 auto; }
}
@media (max-width: 720px) { .dvideo { width: calc(100% - 24px); } }
.drone__go { align-self: center; }   /* .btn's flex-start would win otherwise */
.drone__frame.is-loading .drone__go { opacity: .6; pointer-events: none; }
.drone .pnlm-container { background: var(--ivory); font-family: var(--font-sans); }
.drone .pnlm-controls-container { top: 24px; left: auto; right: var(--frame-vw); }
.drone .pnlm-controls {
  border-radius: 0; border: 0;
  background-color: var(--white);
  box-shadow: 0 0 0 1px var(--hairline-color), var(--shadow-card);
}
.drone .pnlm-zoom-controls, .drone .pnlm-fullscreen-toggle-button { border-radius: 0; }
.drone .pnlm-load-box {
  border-radius: 0; background: rgba(0, 0, 0, .55);
  font-family: var(--font-sans);
}
.drone .pnlm-about-msg { display: none !important; }   /* the library's right-click credit */
@media (max-width: 720px) {
}


/* ══ 3 · COLLECTION — 2 then 3, after Grand Miramare ════════════════════
   REF grandhotelmiramare.it/en/accommodation: image on top, the name large
   beneath it, then a two-column body — prose + an underlined action on the
   left, an icon'd spec list on the right.
   THE WILDE RAIL IS GONE and so is every rule that served it: .rail,
   .railbtn, .collection__grid, .collection__nav, .unit__act and the hover
   overlay. Nothing here scrolls sideways, so the drag, the snap points and
   the prev/next controls had nothing left to do.                        */

/* The head is the shared .sec-head since 2026-09-25 (left / right, by
   request) — it was centred, and a 35% column beside the rail before that. */

/* SIX COLUMNS so the row split can be 2 then 3 from one track definition:
   the first two cards take three columns each, the last three take two.
   Written as nth-child rather than modifier classes because the split is a
   property of the GRID, not of any particular residence — reorder the cards
   and the shape holds. */
/* Fixed gaps, not clamps: 20 across, 48 down (set 2026-09-23). The vertical
   is deliberately the larger of the two — cards in the same row read as one
   set, and the rows need to separate from each other more than the columns
   do. */
.units {
  display: grid; grid-template-columns: repeat(6, minmax(0, 1fr));
  column-gap: 20px;
  row-gap: 48px;
}
.units > :nth-child(-n + 2) { grid-column: span 3; }
.units > :nth-child(n + 3)  { grid-column: span 2; }

.unit { display: flex; flex-direction: column; }
/* One ratio for every card, so the two wide ones and the three narrow ones
   read as the same object at different sizes rather than as two designs.
   Was 2/3 portrait, which only worked because the rail fixed the width. */
.unit__media { position: relative; overflow: hidden; aspect-ratio: 4 / 3; }

/* Slides are stacked in the same box and cross-faded. The first one stays in
   flow so the box keeps its height even before the script runs; the rest are
   absolute over it. */
.unit__slide { margin: 0; position: absolute; inset: 0;
               opacity: 0; visibility: hidden;
               transition: opacity var(--duration) var(--ease-out),
                           visibility 0s linear var(--duration); }
.unit__slide.is-on { opacity: 1; visibility: visible; transition-delay: 0s; }
.unit__slide img {
  width: 100%; height: 100%; object-fit: cover; display: block;
  transition: transform var(--duration) var(--ease-out);
}
.unit:hover .unit__slide.is-on img { transform: scale(1.04); }

/* The plan-slide rules that were here are gone with the plan slides (pulled
   2026-09-23). PRD §5 still asks for a labelled plan slide on these cards;
   when it comes back it needs --ivory behind it, object-fit: contain and a
   corner label, because an unlabelled plan is one nobody finds. */

/* Arrows sit ON the image at its left and right edges, as the reference has
   them. Square, like everything in this direction. They are always present
   rather than hover-only: on touch there is no hover, and the plan slide is
   the thing most worth reaching. */
/* Same frosted chip as the floating booking bar — identical ivory at .82,
   identical blur(14px), identical .16 hairline. They are the two controls
   that float over photography, so they are one material. */
.unit__arrow {
  position: absolute; top: 50%; transform: translateY(-50%); z-index: 2;
  display: grid; place-items: center; width: 44px; height: 44px;
  background: rgba(252, 251, 248, .82); color: var(--green);
  border: 0; cursor: pointer;
  /* A ring, not a border: over a photograph a solid tinted border reads as
     dirt on the edge; a shadow ring blends with whatever is under it. */
  box-shadow: 0 0 0 1px rgba(var(--shadow-rgb), .16);
  backdrop-filter: blur(14px); -webkit-backdrop-filter: blur(14px);
  transition: background-color var(--dur-quick) var(--ease-out),
              color var(--dur-quick) var(--ease-out),
              scale var(--dur-quick) var(--ease-out);
}
/* A light chip on a light ground fills DARK on hover — the ground rule read
   the other way round, the same as every button on an ivory surface. */
.unit__arrow:hover { background: var(--green); color: var(--ivory); }

/* Revealed on hover (2026-09-23). Behind @media (hover: hover) so a touch
   screen — which has no hover and therefore no way to reveal them — keeps
   the arrows on permanently. :focus-within brings them back for keyboard. */
@media (hover: hover) {
  .unit__arrow { opacity: 0; pointer-events: none;
                 transition: opacity var(--duration) var(--ease-out),
                             background-color var(--dur-quick) var(--ease-out),
                             color var(--dur-quick) var(--ease-out),
                             scale var(--dur-quick) var(--ease-out); }
  .unit:hover .unit__arrow,
  .unit:focus-within .unit__arrow { opacity: 1; pointer-events: auto; }
}
.unit__arrow:focus-visible { outline: 2px solid var(--gold); outline-offset: -4px; }
/* Inset from the image edges, not flush against them — flush read as part of
   the crop rather than as a control sitting on top of it. */
.unit__arrow--prev { left: 18px; }
.unit__arrow--prev svg { transform: scaleX(-1); }
.unit__arrow--next { right: 18px; }
.unit__arrow svg { width: 18px; height: 18px; }

/* The "n / 5" counter that sat bottom-right is gone (2026-09-23, by
   request), and .unit__count with it. */

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

.unit__name { margin: 22px 0 0; color: var(--green); }

/* The two-column body. minmax(0,…) on both so a long word cannot push the
   spec column out of the card. */
.unit__body {
  display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, auto);
  gap: clamp(20px, 2vw, 44px); align-items: stretch; padding-top: 16px;
  /* fills the card, so every card in a row ends on one line and "Discover
     more" (pushed to the foot below) lines up across them — 2026-09-29
     audit; a longer description used to push it lower */
  flex: 1 1 auto;
}
.unit__specs { align-self: start; }
.unit__text { display: flex; flex-direction: column; align-items: flex-start; gap: 10px; }
/* Balanced, not `pretty`: these are short blocks read as a unit, and the
   standing rule on this site is that a two-to-four line block must not end
   on a stub. .t-body-sm ships `pretty` for prose, so it is overridden here
   rather than changed in the token — prose elsewhere still wants pretty. */
.unit__desc { margin: 0; color: var(--muted); text-wrap: balance; }
.unit__price { margin: 0; display: flex; flex-wrap: wrap; align-items: baseline; gap: 0 6px; color: var(--green); }
/* the figure never breaks inside itself ("Rp" / "1.100.000" in the narrow
   three-up cards, 2026-09-29 audit); "/ night" may drop under it instead */
.unit__price .t-price { white-space: nowrap; }
.unit__price .t-caption { color: var(--muted); }

/* "Discover more" is an underline button, like the nav and Explore on map —
   Miramare underlines theirs too, so the reference and the system agree. */
.unit__more {
  position: relative; margin-top: auto; padding-block: 5px; color: var(--green);   /* to the card's foot */
}
.unit__more::after {
  content: ""; position: absolute; left: 0; right: 0; bottom: 0; height: 1px;
  background: var(--gold); transform: scaleX(0); transform-origin: left;
  transition: transform var(--duration) var(--ease-out);
}
/* The whole card is the link (2026-09-28, by request): hovering anywhere on
   it draws this underline, so the action shows before the pointer reaches it. */
.unit { cursor: pointer; }
.unit__more:hover::after, .unit:hover .unit__more::after { transform: scaleX(1); }
.unit__more:focus-visible { outline: 2px solid var(--gold-700); outline-offset: 4px; }

/* Spec rows: icon then value, as the reference sets them. */
/* 40px on the RIGHT, so the spec column is held off the card's right edge
   rather than sitting flush against it. */
.unit__specs { list-style: none; margin: 0; padding: 0 40px 0 0;
               display: flex; flex-direction: column; gap: 8px; }
/* --lh-title (1.2), not prose's 1.7: a one-line spec needs no reading leading —
   rows sat ~39px apart, now ~28 (2026-10-01, by request). */
.unit__specs li { display: flex; align-items: center; gap: 10px; line-height: var(--lh-title);
                  color: var(--green); white-space: nowrap; }
.spec__ico { flex: none; width: 20px; height: 20px; color: var(--muted); }

/* One centred vertical stack, 2026-09-22 — eyebrow, heading, supporting line
   and action all on the same axis, the way the CTA band lower down is built.
   Was a two-column grid: heading left, action right. That grid already had
   align-items: center and both columns already measured centred, so the ask
   could not have been about the grid's vertical alignment.
   .btn carries align-self: flex-start of its own, which beats align-items on
   the parent, so it is reset explicitly — the same trap as the card CTA. */
.bookband {
  margin-top: clamp(40px, 5vw, 72px);
  background: var(--green); color: var(--ivory);
  border-radius: var(--radius-shell);
  padding: clamp(48px, 5vw, 80px) clamp(36px, 4vw, 56px);
  /* ONE gap value for the whole lede: eyebrow -> heading and heading ->
     supporting line are the same distance, by construction rather than by
     two numbers that happen to match. Was 16px inside the copy block and a
     clamp(22,2.6vw,36) between the blocks, so the second gap read as more
     than twice the first. Tightened 16 -> 10 on 2026-09-22; tightening the
     one token keeps the two gaps equal, which is the point of it. */
  --band-lede-gap: 10px;

  display: flex; flex-direction: column; align-items: center;
  gap: var(--band-lede-gap);
  text-align: center;
}
.bookband__copy { display: flex; flex-direction: column; align-items: center; gap: var(--band-lede-gap); }
/* No width cap on either the heading or the line below it — both run the
   full width of the band (2026-09-22). The 17ch cap tried here, which forced
   the heading onto two lines, is gone with it. text-wrap: balance still
   applies to whatever does wrap at narrower viewports. */
.bookband__copy h3 { margin: 0; }
/* The action is pushed well clear of the lede — it is a separate move, not
   part of that rhythm, and the contrast against the 10px lede gap is what
   makes the three lines above read as one block. */
.bookband__act  { display: flex; flex-direction: column; align-items: center; gap: 44px; }
/* .bookband__pair is gone with the second button — one action needs no
   pairing wrapper. If CHECK AVAILABILITY comes back, so does this. */
/* The Why Book Direct band runs a display line over a supporting one. */
/* .band__line is gone with the element — the supporting line is now part
   of the headline, not a paragraph under it. */
.bookband__act p { margin: 0; color: rgba(252, 251, 248,.82); text-wrap: balance; }


/* ══ 4 · FLOOR PLANS — plan bleeds off the right edge ═══════════════════
   REF Wye: media runs wider than the text column, block is asymmetric.  */

.plans > input { position: absolute; opacity: 0; pointer-events: none; }

/* Left — on the same axis as the heading above them. Centred was tried
   twice and pulled back both times. */
.plans__tabs {
  /* rows closer than columns once they wrap (2026-09-29, by request —
     32 both ways left two loose lines on a phone) */
  display: flex; flex-wrap: wrap; gap: 12px 32px;
  margin-bottom: clamp(32px, 4vw, 56px);
}
/* Underline buttons, not pills — same wipe as the bar's links. The ACTIVE
   one keeps its underline drawn; the inactive ones are held back by opacity.
   The box, fill and border are gone, so the gap has to carry the separation
   (8px between pills reads as a crowd between bare labels). */
.plans__tabs label {
  position: relative; padding: 6px 0;
  background: none; border: 0; border-radius: 0;
  color: var(--green); opacity: .65; cursor: pointer;
  transition: opacity var(--duration) var(--ease-out);
}
.plans__tabs label::after {
  content: ""; position: absolute; left: 0; right: 0; bottom: 0; height: 1px;
  background: var(--gold); transform: scaleX(0); transform-origin: left;
  transition: transform var(--duration) var(--ease-out);
}
.plans__tabs label:hover { opacity: 1; }
.plans__tabs label:hover::after { transform: scaleX(1); }

#p-urban:checked  ~ .wrap label[for="p-urban"],
#p-primo:checked  ~ .wrap label[for="p-primo"],
#p-one:checked    ~ .wrap label[for="p-one"],
#p-two:checked    ~ .wrap label[for="p-two"],
#p-grande:checked ~ .wrap label[for="p-grande"] { opacity: 1; }

#p-urban:checked  ~ .wrap label[for="p-urban"]::after,
#p-primo:checked  ~ .wrap label[for="p-primo"]::after,
#p-one:checked    ~ .wrap label[for="p-one"]::after,
#p-two:checked    ~ .wrap label[for="p-two"]::after,
#p-grande:checked ~ .wrap label[for="p-grande"]::after { transform: scaleX(1); }

/* The radios are invisible, so their focus is drawn on the label. */
#p-urban:focus-visible  ~ .wrap label[for="p-urban"],
#p-primo:focus-visible  ~ .wrap label[for="p-primo"],
#p-one:focus-visible    ~ .wrap label[for="p-one"],
#p-two:focus-visible    ~ .wrap label[for="p-two"],
#p-grande:focus-visible ~ .wrap label[for="p-grande"] {
  opacity: 1; outline: 2px solid var(--gold-700); outline-offset: 4px;
}

/* The ROW stays inside the section frame so its info column lines up exactly
   with every other section — only the FIGURE bleeds right, to the page edge.
   (Bleeding the whole row on 100vw put the info column 7px off, because
   100vw counts the scrollbar and the section's padding does not. On the
   bleeding edge that same 7px is invisible: it just overshoots and body's
   overflow-x: hidden clips it.)
   Restored 2026-09-22 — when the frame moved onto .section this row was left
   inside it with its own padding-left, which both stopped the figure short of
   the edge and double-inset the info column. */
.plans__body { display: none; }
#p-urban:checked  ~ .plans__bodies [data-plan="urban"],
#p-primo:checked  ~ .plans__bodies [data-plan="primo"],
#p-one:checked    ~ .plans__bodies [data-plan="one"],
#p-two:checked    ~ .plans__bodies [data-plan="two"],
#p-grande:checked ~ .plans__bodies [data-plan="grande"] {
  display: grid;
  /* 35 / rest. Gap fixed at 125 to match the section rhythm, and the
     description CENTRED against the plan again — top alignment was tried in
     between and pulled back. */
  grid-template-columns: 35% minmax(0, 1fr);
  gap: 125px;
  align-items: center;
  /* Both columns sit inside the section frame. The figure used to run past
     the right edge — "the asymmetry is the point" — and that is retired:
     nothing in this row crosses the padding now. */
}
.plans__info h3 { margin: 0 0 6px; color: var(--green); }
.plans__sub     { margin: 0 0 22px; color: var(--muted); text-transform: uppercase; }
.specrows > div {
  display: flex; align-items: baseline; justify-content: space-between; gap: 24px;
  padding: 12px 0; border-bottom: var(--hairline);
}
.specrows dt { color: var(--muted); text-transform: uppercase; }
.specrows dd { margin: 0; color: var(--green); }
.plans__info .btn { margin-top: 26px; }

.plans__figure {
  /* Inside the frame. It used to bleed right to the page edge
     (margin-right: -frame-vw) — that was the "asymmetry is the point" idea
     from the original section note, and it is dropped: overshooting the
     padding is exactly what read as broken.

     ONE HEIGHT FOR ALL FIVE TABS: a flat 650px, Urban through Grande.
     The five drawings are all different shapes (Urban 1600x1046, Grande
     1600x1449), so each one letterboxes inside that box via object-fit:
     contain below and finds its own width. Without a fixed height the whole
     row jumped every time a tab was clicked.
     A pixel height, not the aspect-ratio tried first — the figure is a
     viewport-sized slot here, not a shape that has to track its column. */
  height: 650px;
  display: grid; place-items: center;
  margin: 0;
  /* Panel inset, as Figma draws it: the image box sits 27px inside a
     720.9 x 533.6 panel there, which is ~3.7% — clamp(20,2.5vw,40) is that
     inset at our sizes. */
  padding: clamp(20px, 2.5vw, 40px);
  overflow: hidden;
  background: var(--plan-ground);
  border-radius: 0;
  position: relative;          /* anchors .plans__label */
}
/* The Figma box is 3:2 (666.9 x 444.6, nodes 22:207 / 23:95 / 23:299 /
   23:491 / 23:683), and every plan SVG is cut to 3:2 around its own
   drawing, so all five sit in the slot the same way. `contain`, not
   `cover`: the frame is not 3:2 at most widths, and cropping a line drawing
   cuts walls off mid-run. The SVG is transparent, so the letterbox is the
   frame's own ground and does not show. */
.plans__figure img,
.plans__figure svg { width: 100%; height: 100%; object-fit: contain; }

/* Room label. The script inlines each SVG, and hovering a room darkens its
   floor (inside the SVG) and puts its name here, centred on the room.
   Ivory chip, gold-700 eyebrow — a tag, square like everything else. */
.plans__label {
  position: absolute; left: 0; top: 0;
  padding: 6px 12px;
  background: var(--ivory);
  color: var(--gold-700);
  white-space: nowrap;
  pointer-events: none;
  opacity: 0;
  transform: translate(-50%, -50%);
  transition: opacity var(--duration) var(--ease-out);
}
.plans__label.is-on { opacity: 1; }
.fp-room { cursor: default; }
/* .plans__figure--missing removed 2026-09-22: all five plans now ship.
   The four placeholders it styled were there because only Urban was drawn on
   the Home frame — the other four came from the RESIDENCE frames instead
   (Figma 23:95 / 23:299 / 23:491 / 23:683). */


/* ══ 5 · AMENITIES — sticky list against a scrolling image column ══════
   REF Veloura "Relax / Treatments": the left column pins while the right
   column of photographs scrolls past it. The photograph in view decides
   which facility is marked and opened on the left; each photograph scales
   up as it arrives.
   Replaces the gutterless mosaic and its laid-over captions.             */

/* No padding-inline here or on .amen below: this section is .section--flush,
   which already frames itself with --frame-x. Adding it again inset the whole
   block twice — 320px a side at 1920 instead of 160. */
.amenities__head { margin-bottom: var(--head-gap); }

/* .section--flush ends at 0 so its media can run to the section edge. This
   one does not run to the edge any more, so it takes the section rhythm. */
.amenities { padding-bottom: var(--section-y); }

.amen {
  /* NO BOUNCE anywhere in this section. --ease-spring overshoots (the 1.56 in
     cubic-bezier(0.34, 1.56, 0.64, 1)) and that read as a wobble on both the
     frames and the marker. This is a plain decelerate — fast out of the gate,
     soft landing, never past the target. */
  --amen-ease: cubic-bezier(0.22, 1, 0.36, 1);

  display: grid;
  grid-template-columns: 35% minmax(0, 1fr);
  gap: 125px;
  align-items: start;
}

/* ── left: the pinned list ─────────────────────────────────────────────
   Sticks just under the fixed bar, and is centred in what is left of the
   viewport so the marker sits at eye level rather than at the top edge. */
/* CENTRED while pinned (2026-09-29, by request): the column is one screen
   tall and stuck at the top, and the list is centred inside it — between
   the nav bar (93px) and the floating booking bar (~120px with its gap),
   so it sits at eye level whatever its length. It hung 24vh from the top
   before, which pushed the ground floor's nine rows under the bar. */
.amen__col {
  position: sticky; top: 0;
  height: 100vh; height: 100svh; box-sizing: border-box;
  /* the nav (--nav-space) + the floating switcher above the list (24 + 56) + air */
  padding-block: calc(var(--nav-space) + 12px + 56px + clamp(14px, 2.4vh, 28px))
                 calc(86px + clamp(0px, calc(7vh - 40px), 40px));   /* the booking bar's own height + its lift */
  display: flex; flex-direction: column; justify-content: center;
}
/* THE BAR, after Veloura: one hairline TRACK running the full height of the
   list, and one MARKER that slides and resizes between points. It was a
   per-item border-left before, which only ever switched on and off — there
   was no travel between one point and the next, which is the whole effect.
   The marker's position and height are set in the script (the only two
   values CSS cannot derive), so the transition lives here. */
.amen__list { position: relative; display: flex; flex-direction: column; }
/* Track and marker match the Location progress bar (2026-09-23): the same
   --hairline-color track under the same --gold mark, so the two "where am I
   in this list" devices on the page read as one idea rather than two.
   Both are 1px now — the marker was 2px on a 1px track, and the .16 track
   was its own value rather than the hairline token. */
.amen__list::before {
  content: ""; position: absolute; left: 0; top: 0; bottom: 0;
  width: 1px; background: var(--hairline-color);
}
.amen__marker {
  /* 1px tall, stretched by scaleY in the script: height is a layout
     property and re-ran layout on every frame of the travel. */
  position: absolute; left: 0; top: 0; width: 1px; height: 1px;
  background: var(--gold); transform-origin: top;
  transform: scaleY(0);
  transition: transform .65s var(--amen-ease);
}

/* Veloura animates all of this through GSAP — every transition-duration in
   its stylesheet reads 0s — so there is no curve of theirs to copy verbatim.
   .65s on the page's own spring is the closest honest match. */
@media (prefers-reduced-motion: reduce) {
  .amen__marker { transition: none; }
}

/* a button, not a bare <li>: the name is a control that scrolls to its
   photograph, so it has to be reachable and pressable like one */
.amen__jump {
  display: block; width: 100%; text-align: left;
  padding: clamp(14px, 1.6vw, 22px) 0 clamp(14px, 1.6vw, 22px) clamp(20px, 2vw, 32px);
  background: none; border: 0; font: inherit; color: inherit; cursor: pointer;
}
.amen__jump:focus-visible { outline: 2px solid var(--gold-700); outline-offset: 2px; }

/* Inactive points are held back by OPACITY as well as colour — colour alone
   (muted vs green) was not enough separation once six of them are stacked.
   .45 and not lower: these are buttons you can tab to and click, so they have
   to stay readable, not become decoration. */
.amen__item { opacity: .3; transition: opacity var(--duration) var(--amen-ease); }   /* .65 until 2026-09-29; .3 by request — see the contrast note in CLAUDE.md */
.amen__item.is-active,
.amen__item:hover { opacity: 1; }

/* Green at .65 when inactive = 4.64:1. It was muted at .45 = 1.94:1, which
   left five of six tappable names below any contrast threshold. */
.amen__name { display: block; color: var(--green); }
/* Coming soon (2026-10-06, the client's C1 table: padel, pickleball and Noll
   Coffee open December 2026) — an outline tag under the name, gold edge:
   gold as a border, never a fill */
.amen__tag {
  display: inline-block; margin-top: 8px; padding: 4px 9px;
  border: 1px solid var(--gold); color: var(--green); letter-spacing: .16em; text-transform: uppercase;
}

/* 0fr -> 1fr so the line has a real height animation instead of snapping */
.amen__descwrap {
  display: grid; grid-template-rows: 0fr;
  transition: grid-template-rows var(--duration) var(--amen-ease),
              opacity var(--duration) var(--amen-ease);
  opacity: 0;
}
.amen__item.is-active .amen__descwrap { grid-template-rows: 1fr; opacity: 1; }
.amen__desc {
  display: block; min-height: 0; overflow: hidden;
  color: var(--muted); padding-top: 10px;
  /* Balanced: it wears .t-body, which is `pretty`, and the heading+p rule in
     tokens.css cannot reach it — the name above is a span, not a heading, and
     the line sits inside the 0fr/1fr wrapper. */
  text-wrap: balance;
}

/* ── right: the photographs ───────────────────────────────────────────── */
.amen__media {
  display: flex; flex-direction: column;
  gap: 20px;
}
/* THE FRAME scales, not the picture inside it. Scaling the <img> did nothing
   visible: the frame is a fixed height with overflow:hidden, so all that
   happened was the photograph zooming inside a box that never moved.
   Transform on the frame costs no layout — the row keeps its height while
   the block grows into it. */
.amen__shot {
  position: relative; margin: 0; overflow: hidden;
  height: clamp(420px, 78vh, 760px);
  border-radius: var(--radius-image);   /* 0 in this direction — Veloura rounds its own */

  /* SCALE ONLY — no fade. The opacity that rode along with it made the
     photograph arrive rather than grow, which is not what the reference does.
     Reversible on purpose: the class comes off when the frame leaves the
     viewport, so scrolling back up shrinks it again. */
  transform: scale(.5);
  transition: transform .9s var(--amen-ease);
}
.amen__shot.is-in { transform: none; }
.amen__shot img { width: 100%; height: 100%; object-fit: cover; }

/* A 1px neutral ring just inside every photograph, so the edge holds
   against ivory. Not on the plans: they are transparent line art on their
   own ground, and a ring would draw a box inside the frame. Pure black at .1, not green: a tinted ring picks up
   the photo's own colour and reads as dirt. It is an outline, not a
   shadow, so the green-shadow rule does not govern it. */
.unit__slide img, .amen__shot img {
  outline: 1px solid oklch(0 0 0 / .1); outline-offset: -1px;
}

.amen__ico {
  position: absolute; left: clamp(18px, 2vw, 28px); top: clamp(18px, 2vw, 28px);
  display: grid; place-items: center; width: 44px; height: 44px;
  border-radius: var(--radius-input); background: rgba(14, 43, 33, .38);
  backdrop-filter: blur(2px);
}
.amen__ico svg { width: 24px; height: 24px; color: var(--white); opacity: .92; }
/* A credit where a licence asks for one (the Commons mall interiors, CC BY /
   BY-SA — 2026-09-29): small, in the corner, on the icon's green chip so it
   reads over any picture; the link goes to the file's Commons page. */
.amen__credit {
  position: absolute; right: 0; bottom: 0; margin: 0; padding: 6px 10px;
  background: rgba(14, 43, 33, .55); color: var(--ivory);
}
.amen__credit a { color: inherit; text-decoration: underline; text-underline-offset: 2px; }

@media (prefers-reduced-motion: reduce) {
  .amen__shot { transform: none; }
}


/* ── floor by floor (2026-09-29) ──────────────────────────────────────────
   ONE continuous run: all sixteen photographs in one column (Level 5, then
   the ground floor, then the basement), beside ONE rolling list of all
   sixteen places (below). The photograph in view picks the place; the
   floating switcher marks its floor. (Three separate blocks, a section
   apart, then a list per floor, came first the same day.) */
/* --nav-space: the fixed nav's height — it is always shown (2026-09-30).
   The switcher and the pinned lists sit under it. */
:root { --nav-space: 93px; }
/* THE FLOATING FLOOR SWITCHER (2026-09-29, by request): pinned top-LEFT,
   over the pinned list's column (top-right over the photographs first) for the whole run of floors — sticky inside
   .amen-floors, so it arrives with Level 5 and leaves after the basement;
   a 0-high rail, so it takes no space and never pushes the list. (It sat
   at the head of each pinned list for a while the same day.) */
.amen-floors { position: relative; }
.amen-jump {
  position: sticky; top: calc(var(--nav-space) + 12px); z-index: 4; height: 0;
  display: flex; justify-content: flex-start; align-items: flex-start; pointer-events: none;
}
.amen-jump .amen-jump__panel { margin: 0; pointer-events: auto; }
.amen-jump__panel {
  display: flex; gap: 4px; padding: 6px;
  /* THE FLOATING BOOKING BAR'S OWN DRESS (by request): ivory .82 frosted
     over blur(14px) and the hairline ring (its shadow left off, by request);
     green labels, and the chosen floor filled solid green with an ivory
     label, like the bar's action. */
  background: rgba(252, 251, 248, .82);
  -webkit-backdrop-filter: blur(14px); backdrop-filter: blur(14px);
  box-shadow: 0 0 0 1px var(--hairline-color);   /* the ring only — no shadow (by request) */
}
.amen-jump__panel .amen-jump__tab { color: var(--green); }
.amen-jump__panel .amen-jump__tab:hover { background: rgba(14, 43, 33, .06); }
.amen-jump__panel .amen-jump__tab[aria-current="true"] { background: var(--green); color: var(--ivory); }
.amen-jump__tab {
  padding: 12px 18px; color: var(--ivory); white-space: nowrap; text-decoration: none;
  transition: background-color var(--dur-quick) var(--ease-out), color var(--dur-quick) var(--ease-out),
              scale var(--dur-quick) var(--ease-out);
}
.amen-jump__tab:hover { background: rgba(252, 251, 248, .12); }
.amen-jump__tab[aria-current="true"] { background: var(--white); color: var(--green); }
.amen-jump__tab:active { scale: .96; }
.amen-jump__tab:focus-visible { outline: 2px solid var(--gold-700); outline-offset: 2px; }
/* SIX tabs since 2026-10-06 (HGN's order: Basement · Ground floor · Level 5 ·
   MRT Station · Ratu Plaza Mall · Plaza Senayan): below 1440 the panel
   scrolls sideways inside its own width rather than running off the page */
@media (max-width: 1440px) {
  .amen-jump__panel { max-width: calc(100vw - 2 * var(--frame-vw)); overflow-x: auto; scrollbar-width: none; }
  .amen-jump__panel::-webkit-scrollbar { display: none; }
}
@media (max-width: 720px) {
  :root { --nav-space: 64px; }
  .amen-jump .amen-jump__panel { margin: 12px 0 0; }
  .amen-jump__tab { padding: 10px 12px; }
}
/* THE ROLLING LIST (2026-09-29): all sixteen places in ONE list, longer
   than the screen, inside a pinned VIEW that fades at both ends; the
   script rolls the list so the active place sits in the middle, with the
   same decelerate as everything else. Every row keeps Level 5's spacing —
   nothing has to fit any more. */
.amen__view {
  position: relative; flex: 1 1 auto; min-height: 0; overflow: hidden;
  -webkit-mask-image: linear-gradient(to bottom, transparent 0, #000 14%, #000 86%, transparent 100%);
          mask-image: linear-gradient(to bottom, transparent 0, #000 14%, #000 86%, transparent 100%);
}
.amen__view .amen__list { transition: transform var(--duration) var(--ease-out); will-change: transform; }
/* each place's own picture — for tablet and phone only (below) */
.amen__pic { display: none; }
.amen__picwrap { display: none; }
/* Level 5's padel, pickleball and Noll Coffee (2026-10-08, by request): the
   photograph dark with "Coming soon" centred on it — the sold-out frames'
   dress (.unit__sold) — in the photo column and in the phone picture */
.amen__shot.is-soon::after, .amen__picwrap.is-soon::after {
  content: ""; position: absolute; inset: 0; z-index: 1; pointer-events: none;
  background: rgba(0, 0, 0, .5);
}
.amen__piccredit { display: none; }
/* short screens (≤860 tall, desktop): tighter rows everywhere, so the
   pinned list still fits between the nav and the booking bar */
@media (min-width: 1081px) and (max-height: 860px) {
  .amen__jump { padding-block: 10px; }
}


/* SOLD OUT (2026-10-06 — Sovana Two and Grande, every page, by request;
   the flag is `sold: true` in booking.js, the static cards carry .is-sold).
   EVERY picture frame of theirs is DARK, always, with "Sold out" centred on
   it in ivory .t-h4 (2026-10-07, by request — it showed on hover only, and
   the generated lists faded the picture to .5): the home and daily cards,
   the long-stay rows, the results rows and plan cards, Other residences.
   The rate line still reads "Sold out" at the rate's own size. */
.is-sold :is(.unit__media, .row__media, .ocard__media, .pcard__plan) { position: relative; }
.is-sold :is(.unit__media, .row__media, .ocard__media, .pcard__plan)::after {
  content: ""; position: absolute; inset: 0; z-index: 1; pointer-events: none;
  background: rgba(0, 0, 0, .5);
}
.unit__sold {
  position: absolute; inset: 0; z-index: 2; display: grid; place-items: center;
  margin: 0; color: var(--ivory); pointer-events: none;
}
.unit.is-sold .unit__price .t-price-sm { color: var(--green); }


/* ══ 6 · CTA BAND ═══════════════════════════════════════════════════════ */

.band {
  position: relative; overflow: hidden; display: grid; place-items: center;
  /* 90vh, so the band is a held beat rather than a strip — the content stays
     centred in it by place-items, and the padding is the floor when a short
     viewport makes 90vh smaller than the content. */
  min-height: 90vh;
  padding: var(--section-y) var(--frame-x);
}
.band__media, .band__scrim { position: absolute; inset: 0; }
.band__media img { width: 100%; height: 100%; object-fit: cover; }
/* the one GREEN overlay (2026-09-24, by request) — every other scrim is black */
.band__scrim { background: rgba(14,43,33,.62); }
.band__inner {
  position: relative; width: min(920px, 100%);
  display: flex; flex-direction: column; align-items: center; gap: 30px; text-align: center;
}
/* Carries both sentences now, so it needs a measure — at 920px wide a
   40px display line would otherwise run the full band. */
.band__quote { margin: 0; color: var(--ivory); max-width: 34ch; text-wrap: balance; }
/* THREE LINES (2026-09-28, by request), broken at the sentence's own joints;
   at 22ch it balanced into four. Phones drop the breaks and balance. */
@media (max-width: 720px) { .band__br { display: none; } }


/* ══ 7 · LOCATION — points left, map right ═════════════════════════════
   Client revision 2026-09-23. The Grand Miramare zigzag is gone and so is
   every rule that served it (.loc__item, .loc__copy, .loc__media, .loc__cta
   and its odd/even order swaps).                                         */

/* How long each point holds before handing over. Declared rather than left
   to the var() fallback so there is one place to tune it. 6s -> 4.5s: with
   the description now 16px and only three points, six seconds read as a
   wait. Below about 4s the third point starts arriving before the first has
   been finished. */
.location { --loc-dwell: 4.5s; }
/* 26ch was a measure for the four-line sentence this replaced. The headline
   carries its own <br> now, so the cap only has to be wide enough not to
   fight it. */
/* No measure: the headline carries its own <br> and now sits in the left
   column of .sec-head, which is already half the frame. A ch cap on top of
   that was what pushed it to four lines. */
/* .sec-head is a two-column grid (lead left, note right). This section has
   no note, so the lead was living in half the frame and the headline broke
   four ways instead of two. One column gives it the width it was written
   for. */
.location .sec-head { grid-template-columns: 1fr; }
/* 620px is the measure that makes it THREE lines. Swept 520-720: below 600
   it falls to four, at 600 the split is [599, 597, 461], and from 620 up it
   settles on [494, 553, 611] — spread 117, the flattest the word boundaries
   allow at 64px. Wider caps change nothing, so 620 is the smallest number
   that reaches the best result. */
/* No cap: the column is the measure now. 620px was tuned for a heading that
   ran the full frame and would never bind inside a half-width column. */
.loc__lede { max-width: none; }

/* 35 / rest, as asked. minmax(0,…) on the second track or the Google iframe
   pushes the grid wider than the frame. */
/* The list is vertically CENTRED against the map, not hung from its top —
   the list is much the shorter of the two, so start-aligned it left a void
   under it the height of the difference. */
/* 50/50 with a fixed 200px gutter. minmax(0,…) on BOTH tracks, or the
   Google iframe — which has no intrinsic width of its own — pushes the grid
   wider than the frame. */
.loc__split {
  display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: 200px; align-items: center;
}
/* Heading and points are one block in the left column now. */
.loc__col { display: flex; flex-direction: column; }
.loc__col .sec-head { margin-bottom: var(--head-gap); }
/* Nothing sits under the list any more, so the column is just the list. */
.loc__list { display: flex; flex-direction: column; align-items: flex-start; }
.locpts { list-style: none; margin: 0; padding: 0; width: 100%; }

/* A BUTTON, not a link: clicking moves the active point rather than leaving
   the page. Reset to nothing so it inherits the page, not the UA. */
/* One column now — the 01/02/03 gutter is gone, and .locpt__n with it
   rather than being left as a rule nothing matches. */
.locpt {
  position: relative; display: block; width: 100%;
  padding: 24px 0 26px; text-align: left;
  background: none; border: 0; cursor: pointer; color: var(--green);
  font: inherit;
}
.locpt:focus-visible { outline: 2px solid var(--gold-700); outline-offset: 4px; }
.locpt__body { display: flex; flex-direction: column; min-width: 0; }
/* 32, via the token — not a raw number. .t-h4 still supplies the family,
   line-height and tracking; only the size steps up from its 28. */
.locpt__name { font-size: var(--size-32); color: var(--green);
               transition: opacity var(--duration) var(--ease-out); }

/* ONLY THE ACTIVE POINT SHOWS ITS DESCRIPTION. 0fr -> 1fr animates from
   nothing to the content's own height, with no measured max-height to keep
   in step with the copy. */
.locpt__detail {
  display: grid; grid-template-rows: 0fr;
  transition: grid-template-rows var(--duration) var(--ease-out),
              opacity var(--duration) var(--ease-out);
  opacity: 0;
}
.locpt__detail-in { overflow: hidden; min-height: 0; }
/* .t-body (16px). Was .t-body-sm at 14, then .t-body-lg at 18 — 16 is the
   settled figure. */
.locpt__sub { display: block; color: var(--muted); padding-top: 12px; text-wrap: balance; }
.locpt-row.is-active .locpt__detail { grid-template-rows: 1fr; opacity: 1; }
/* The same figure the Facilities list holds its inactive names at
   (.amen__item, .3). The two lists do the same job on one page — a set of
   names with one of them current — so an inactive name should be equally
   quiet in both (they were .65 and .3 until the 2026-09-29 audit). */
.locpt-row:not(.is-active) .locpt__name { opacity: .3; }   /* a LIST of places, like Facilities' (.3, 2026-09-29 audit); controls — the plan filters, the blog's contents — keep .65 */

/* THE RULE IS THE PROGRESS BAR. The track is the hairline the row already
   had; the fill sits on it. transform, not width, so it stays off layout. */
.locpt__bar {
  position: absolute; left: 0; right: 0; bottom: 0; height: 1px;
  background: var(--hairline-color); overflow: hidden;
}
.locpt__bar i {
  display: block; height: 100%; background: var(--gold);
  transform: scaleX(0); transform-origin: left;
}
.locpt-row.is-active .locpt__bar i {
  animation: loc-fill var(--loc-dwell, 6s) linear forwards;
}
/* The clock runs through hover. The .is-held pause that was here is gone
   (2026-09-23, by request), so the bar never stops once it starts. Hover
   still does not change which point is active — only a click does. */

@keyframes loc-fill { from { transform: scaleX(0); } to { transform: scaleX(1); } }

/* .loc__all — the "Explore the neighbourhood on Maps" underline link — is
   gone with the element it styled. */

/* The embed. aspect-ratio rather than a height, so it keeps its shape at
   every column width; the iframe has no intrinsic size of its own. */
/* Square, by request — it sits against a column of text rather than in a
   band of its own, and 1:1 keeps the two sides reading as a pair. */
.loc__map { margin: 0; aspect-ratio: 1; overflow: hidden; border: var(--hairline); }
.loc__map iframe { width: 100%; height: 100%; border: 0; display: block; }

@media (prefers-reduced-motion: reduce) {
  .locpt-row.is-active .locpt__bar i { animation: none; transform: scaleX(1); }
  .locpt__detail { transition: none; }
}

/* ══ 8 · FAQ — hairline rows, after Raffles ════════════════════════════
   REF raffles.com/singapore: no card, no shell, no shadow. Each question is
   a full-width row with a hairline under it, the question set large on the
   left and a chevron on the right; the answer opens beneath the question and
   inside the same row. Replaces the white lifted cards this section used.
   The list is narrower than the section frame — Raffles runs its rows at
   about 82% of the content width, centred.                               */

.faq {
  width: min(1310px, 100%); margin-inline: auto;
  display: flex; flex-direction: column;
  border-top: var(--hairline);
}
.faq__item {
  --faq-x: clamp(16px, 2vw, 32px);
  border-bottom: var(--hairline);
  transition: background-color var(--dur-quick) var(--ease-out);
}
/* A hovered row shades a touch darker (2026-10-01, by request) — the site's
   green wash, lighter than a plan room's .07 because it covers a whole row.
   The question and answer sit --faq-x in from the hairline's ends so the
   wash never runs flush against the text. Mouse only: a tap would leave it. */
@media (hover: hover) {
  .faq__item:hover { background-color: rgba(14, 43, 33, .04); }
}
.faq__item summary {
  display: flex; align-items: center; justify-content: space-between; gap: 40px;
  padding: clamp(18px, 1.8vw, 28px) var(--faq-x);   /* tighter 2026-09-29, by request (26 / 2.6vw / 40) */
  cursor: pointer; color: var(--green); list-style: none;
}
.faq__item summary::-webkit-details-marker { display: none; }
/* Open, the question sits close to its own answer. The row keeps its full
   padding at the top and bottom; it is only the space BETWEEN question and
   answer that closes, so the rows stay evenly spaced while the pair reads as
   one block. */
.faq__item[open] summary { padding-bottom: 12px; }
/* the closing row is measured with its question's CLOSED padding (index.html's FAQ script) */
.faq__item[open].is-closing summary { padding-bottom: clamp(18px, 1.8vw, 28px); }
.faq__item summary:focus-visible { outline: 2px solid var(--gold-700); outline-offset: 4px; }

/* Chevron, not the "+" this section used — Raffles rotates a chevron, and a
   rotating plus reads as a different control. Inline so it can ride
   currentColor; no such icon ships in assets/icons. */
.faq__chev {
  flex: none; width: 24px; height: 24px; color: var(--muted);
  transition: transform var(--duration) var(--ease-out),
              color var(--duration) var(--ease-out);
}
.faq__item[open] .faq__chev { transform: rotate(180deg); color: var(--green); }
/* closing: [open] stays until the row has folded (index.html's FAQ script),
   so the chevron is turned back as the close STARTS (2026-10-01, by request) */
.faq__item[open].is-shutting .faq__chev { transform: none; }
/* an open row closes from a click anywhere in it (index.html's FAQ script) */
.faq__item[open] { cursor: pointer; }
.faq__item summary:hover .faq__chev { color: var(--green); }

.faq__item p {
  margin: 0; padding: 0 var(--faq-x) clamp(18px, 1.8vw, 28px);
  color: var(--muted); max-width: 88ch;
}


/* ══ 9 · CONTACT — offset form panel ════════════════════════════════════ */

/* Equal halves. Written 1fr 1fr and not 50% 50%: with a gap between them,
   two literal 50% columns add up to more than the row and overflow. This
   splits what is left AFTER the gap, so the two are exactly equal. */
/* stretch, not start: the info column has to be as tall as the form before
   anything in it can be pushed to the foot */
.contact { display: grid; grid-template-columns: 1fr 1fr;
           gap: clamp(32px, 5vw, 80px); align-items: stretch; }
/* The supporting paragraph under the heading is gone (2026-09-23), so the
   heading is followed straight by the contact list. The 18px gap stays for
   eyebrow -> heading; .contact__list keeps its own margin-top: auto, which
   is what still drops it level with the submit button opposite. */
.contact__info { display: flex; flex-direction: column; align-items: flex-start; gap: 18px; height: 100%; }
.contact__info h2 { margin: 0; color: var(--green); }
/* Two lines, not three: the 42ch measure broke a 130-character sentence
   three ways. .t-lead balances what is left. */
.contact__info > p { margin: 0; color: var(--muted); max-width: 68ch; }
/* Dropped to the foot of the column so the three lines finish level with
   the send button opposite. margin-top: auto does the dropping; the padding
   is the gap between the button and the note under it, measured, so the list
   stops at the BUTTON's baseline rather than the note's. */
/* A MEASURED gap, not "whatever is left". margin-top: auto used to push the
   list to the foot of the column so it finished level with the send button —
   but with the supporting paragraph gone the column is short, and auto turned
   that into a 119px hole under the heading at 1440.
   A fixed distance keeps the heading and the list reading as one block, and
   the two columns still finish close together because their content heights
   are now similar rather than being forced. */
.contact__list {
  display: flex; flex-direction: column; gap: 18px;
  margin-top: clamp(36px, 4vw, 64px);
}
/* Real links now (2026-09-23): WhatsApp, mail and Maps. The hover used to
   dim three plain text rows, which promised a click nothing answered. The
   row goes muted -> green on hover instead, the same colour step the
   amenity and FAQ rows use. */
.contact__list a {
  display: flex; align-items: center; gap: 16px; color: var(--muted);
  transition: color var(--dur-quick) var(--ease-out);
}
.contact__list a:hover { color: var(--green); }
.contact__list a:focus-visible { outline: 2px solid var(--gold-700); outline-offset: 4px; }
.feat__ico { flex: none; display: grid; width: 24px; height: 24px; color: var(--gold-700); }
.feat__ico svg { width: 100%; height: 100%; }

/* ── The form, rebuilt 2026-09-22 ──────────────────────────────────────
   Underline fields: no box, no fill, no radius — a small label above a
   RULE, with the value sitting on it. Replaces the bordered, filled inputs
   this form used.
   The white lifted panel goes with them: a panel around borderless fields
   just puts the box back one level out. (.surface-lifted is off the markup,
   so §03's "white must carry a hairline + shadow" is not being broken —
   there is no white surface here any more.)                             */

/* TOPS ALIGNED. The 40px offset that used to sit here was deliberate once —
   it staggered the form against the heading — but with the contact list now
   dropped to the foot of the left column, the two columns have to start AND
   finish level or the stagger reads as a mistake at both ends. */
.contact__form {
  padding: 0;
  display: flex; flex-direction: column; gap: 40px;
}
/* .contact__form h3 removed 2026-09-22: "Send us a message" is gone from
   the markup, so nothing is left for this rule to style. */

.field     { display: flex; flex-direction: column; gap: 14px; }
.field-row { display: grid; grid-template-columns: 1fr 1fr; gap: 40px; }
.field label { color: var(--ink); text-transform: uppercase; letter-spacing: .16em; }   /* every form, 2026-09-24 — the button/nav step of the tracking ladder */

.field input, .field select, .field textarea {
  width: 100%; padding: 0 0 12px;
  font: inherit; font-size: inherit; color: var(--ink);
  background: none;
  border: 0; border-bottom: 1px solid rgba(14, 43, 33, .28);
  border-radius: 0;
  transition: border-color var(--duration) var(--ease-out);
}
/* FOCUS IS THE FIELD'S OWN RULE, not a box around it. The outline that used
   to sit on these (2px gold, 4px offset) drew a rectangle the moment anyone
   clicked into a text field — a text input matches :focus-visible on pointer
   focus too, not only on keyboard — and a box is the one thing this form is
   built to avoid.
   It is REPLACED, not deleted: the hairline stays 1px and goes from
   rgba(14,43,33,.28) to full --green. Doubling it to 2px was tried and
   pulled back — on a form whose whole language is hairlines, a 2px rule
   read as heavy. Colour alone carries it, and nothing shifts because the
   width never changes. */
.field input:focus, .field select:focus, .field textarea:focus {
  border-bottom-color: var(--green);
}
/* One line at rest, exactly like every other field — the 92px box left the
   placeholder floating 58px above its own rule while the inputs beside it
   sat 12px above theirs. It grows as you type (see the page script), so
   nothing is lost by starting small. */
.field textarea { resize: vertical; min-height: calc(1.7em + 12px); }
/* Only once the script has taken over the height — without it the box needs
   its scrollbar. */
.field textarea.is-autogrow { overflow: hidden; resize: none; }
/* --muted, not the #9a9a8e that used to be hardcoded here and in the page
   script — that grey measured 2.52:1 on the frosted booking bar.
   HELD BACK TO .65 here (2026-09-23, asked down twice). Measured on ivory:
   full --muted 5.72:1 · .8 -> 3.72 · .7 -> 3.05 · .65 -> 2.77 · .6 -> 2.52.
   .65 is BELOW the 3:1 non-text floor and well below AA — a deliberate art
   direction call, not an oversight. It is defensible only because every
   field is labelled above the rule, so the placeholder is a hint and never
   the only thing naming the field. Do not go under .6: that is the 2.52:1
   the booking bar was already called unreadable at. */
.field input::placeholder, .field textarea::placeholder {
  color: var(--muted); opacity: .65;
}
/* A select showing its placeholder is dimmed the same way a text field's is,
   so an unanswered field looks unanswered whichever kind it is. */
.field .pick__placeholder { color: var(--muted); opacity: .65; }

/* The booking bar's empty-date label keeps full --muted — it sits on a
   translucent, frosted ground where it had no contrast to spare. */
.book__empty { color: var(--muted); }

/* the native arrow goes; the chevron is drawn on the wrapper instead */
.field select { appearance: none; padding-right: 28px; cursor: pointer; }
/* Body text is 14 on phones, but a text field under 16px makes iOS Safari
   zoom the page in on focus — every field holds 16 (2026-10-01). */
@media (max-width: 720px) {
  :is(input, select, textarea):not([type="checkbox"], [type="radio"]) { font-size: var(--size-16); }
}
.field--select { position: relative; }
/* The chevron drawn here is for the NATIVE select — the no-JS fallback. Once
   the custom picker takes over it draws its own (.pick__chev) on the button,
   and both were rendering: two stacked arrows on every select in this form.
   :has() hides this one exactly when the picker exists. */
.field--select::after {
  content: ""; position: absolute; right: 4px; bottom: 18px;
  width: 9px; height: 9px; pointer-events: none;
  border-right: 1px solid var(--muted); border-bottom: 1px solid var(--muted);
  transform: rotate(45deg);
}
.field--select:has(.pick)::after { content: none; }

/* THE RULE HAS TO FOLLOW THE PICKER. The underline is declared on input /
   select / textarea, but the custom picker replaces the <select> with a
   <button> — so the two select fields in this form were the only ones with
   no line under them. Same border, same 12px of padding above it, so all
   four fields sit on one rhythm. Scoped to .field: the booking bar's
   pickers are a different control on a different ground. */
.field .pick__btn {
  padding: 0 0 12px;
  border-bottom: 1px solid rgba(14, 43, 33, .28);
  transition: border-color var(--duration) var(--ease-out);
}
.field .pick.is-open .pick__btn,
.field .pick__btn:focus-visible { border-bottom-color: var(--green); }
/* A hovered field draws its rule as if it were active (2026-10-01, by
   request) — the label counts, the whole .field is the target. Mouse only. */
@media (hover: hover) {
  .field:hover :is(input, select, textarea, .pick__btn):not([readonly], :disabled) { border-bottom-color: var(--green); }
}
/* the panel hangs below the rule, not on top of it */
.field .pick__panel { top: calc(100% + 8px); }

/* No outline box anywhere in this form — the rule above carries focus. */
.field :focus-visible { outline: none; }
/* Full width across the form column, by request — it reads as the form's
   own footer rather than as a button parked under it. */
.contact__form .btn { width: 100%; justify-content: center; margin-top: 4px; }
/* Back 2026-09-23 with different copy: the monthly-rate note under the
   submit. Tight to the button — it belongs to it, not to the form. */
.contact__note { margin: -24px 0 0; color: var(--muted); }


/* ══ FOOTER ═════════════════════════════════════════════════════════════ */

/* The footer used to take its inset from .wrap, which no longer gives one,
   so it pads itself. Top joins the frame; the bottom bar keeps its own 40. */
/* Figma rounds the footer's top corners; they stay SQUARE here by request,
   which also keeps SHAPE LOCK (all radii 0 in this direction) intact. */
.footer { background: var(--green); color: var(--white);
          border-radius: 0; padding: var(--section-y) var(--frame-x) 40px;
          /* 560 tall (2026-09-30, by request — it was 453; 715 read too
             tall): the extra room opens between the columns and the
             copyright bar, which the column layout pushes to the foot */
          min-height: 560px; box-sizing: border-box;
          display: flex; flex-direction: column; }
.footer__bar { margin-top: auto; }
.footer__top {
  display: grid; grid-template-columns: minmax(0,1.6fr) minmax(0,1fr) minmax(0,1fr);
  gap: 48px; padding-bottom: 40px;
}
/* The real wordmark, as Figma 80:10220 draws it — the footer used to set
   "solana" as tracked text, which is a different mark from the logo. Same
   inline SVG as the bar, so it rides currentColor; white here, and its two
   accent dots ride --logo-accent white with it. */
/* Same move as the nav (2026-09-23): the "by SanLiving" line is gone and the
   mark grew into the space. .footer__by went with it rather than being left
   as a dead rule. Height derived from the viewBox ratio 149.4/34 = 4.3941,
   so the letterforms hold at any width. The footer mark runs slightly larger
   than the nav's 230 — it is the sign-off, not a utility. */
.footer__brandmark { display: block; line-height: 0; margin-bottom: 26px; }
.footer__mark {
  width: 285px; height: 64.86px; display: block;   /* 248 + 15%, 2026-09-25 */
  color: var(--white); --logo-accent: var(--white);
}
/* Two lines, as Figma sets it (415px of a 499px brand column). 42ch broke
   it three ways. */
.footer__blurb { margin: 0; max-width: 52ch; opacity: .85; }
.footer__col   { display: flex; flex-direction: column; gap: 12px; }
.footer__col p { margin: 0; opacity: .85; }
.footer__h     { opacity: .6; margin-bottom: 18px !important; }
/* footer links, contact included (2026-09-29, by request): a WHITE 1px
   hairline wipes in from the left on hover — the site's underline-link
   mechanic, in white on the green (gold text on hover before) */
.footer__col a { position: relative; align-self: flex-start; }
/* the Explore links in TWO columns, every width (2026-09-29, by request) —
   seven stacked ran long; the heading spans both, the links fill down the
   left (4) then the right (3) */
nav.footer__col {
  display: grid; grid-template-columns: max-content max-content; grid-auto-flow: column;
  grid-template-rows: auto repeat(4, auto); column-gap: clamp(32px, 4vw, 64px); row-gap: 12px;
  align-content: start;
  justify-items: start;   /* each link its own width — stretched to the column, the hover underline ran past short words (2026-09-30) */
}
nav.footer__col .footer__h { grid-column: 1 / -1; }
.footer__col a::after {
  content: ""; position: absolute; left: 0; right: 0; bottom: -2px; height: 1px;
  background: var(--white); transform: scaleX(0); transform-origin: left;
  transition: transform var(--duration) var(--ease-out);
}
.footer__col a:hover::after, .footer__col a:focus-visible::after { transform: scaleX(1); }
/* the CONTACT column (the <div>, not the Explore <nav>): no underline —
   the text turns gold on hover instead (2026-09-29, by request) */
div.footer__col a::after { content: none; }
div.footer__col a { transition: color var(--dur-quick) var(--ease-out); }
div.footer__col a:hover, div.footer__col a:focus-visible { color: var(--gold); }
/* the copyright alone, CENTRED (2026-09-29, by request — "The Luxury of
   Time · Senayan-Sudirman" taken out; it sat left / right before) */
.footer__bar { display: flex; justify-content: center; text-align: center; gap: 24px; flex-wrap: wrap;
               padding-top: 28px; opacity: .7;
               /* the divider rides with the copyright to the foot, so the
                  footer's added height opens ABOVE it; .23 at the bar's .7
                  is the .16 it had on the columns */
               border-top: 1px solid rgba(255,255,255,.23); }
.footer__bar p { margin: 0; }


/* ── Narrow viewports — collapse only, no invented sizes ──────────────── */

@media (max-width: 1080px) {
  /* 80px a side is a third of a phone. Collapse only — the desktop figure
     stays 125 / 80. */
  :root { --gutter: 40px; --section-y: 96px; }
  /* Six columns of a 1080 viewport leaves the narrow cards too thin, so the
     2-then-3 split collapses to a plain 2-up and the body stops splitting. */
  .units > :nth-child(n) { grid-column: span 3; }
  .unit__body { grid-template-columns: 1fr; gap: 18px; }
  /* The alternation needs two halves; below this there is only one. */
  /* Points above the map rather than beside it — two narrow columns would
     leave neither the paragraphs nor the map's labels readable. */
  .loc__split { grid-template-columns: 1fr; }
  .location .sec-head { margin-bottom: var(--head-gap); }

  /* No room to pin a column beside the photographs: the list runs above them
     with every line open, and the marker stops meaning anything. */
  .amen { grid-template-columns: 1fr; gap: clamp(32px, 5vw, 56px); }
  .amen__col { position: static; height: auto; padding-block: 0; display: block; }
  .amen__view { overflow: visible; -webkit-mask-image: none; mask-image: none; flex: none; }   /* not pinned here: the whole list, still */
  /* ≤1080 (2026-09-29, by request): no photograph column — the open place
     shows its own picture, 16:9, under its line; a tap opens a place */
  .amen__media { display: none; }
  .amen__pic { display: block; width: 100%; aspect-ratio: 16 / 9; object-fit: cover; margin-top: 16px; }
  .amen__picwrap { display: block; position: relative; margin-top: 16px; }
  .amen__picwrap .amen__pic { margin-top: 0; }
  .amen__piccredit { display: block; margin-top: 6px; color: var(--muted); }
  .amen__piccredit a { color: inherit; text-decoration: underline; text-underline-offset: 2px; }
  /* the switcher takes its own room above the list (it floated, 0 high,
     over the first place) and still pins under the nav as you scroll */
  .amen-jump { height: auto; margin-bottom: 24px; }
  /* only the OPEN place shows its line and picture (the rest are closed,
     names at full strength — every one is a tap target) */
  .amen__item { opacity: 1; }
  /* the plan frame follows the drawing's 3:2 instead of a 650px slot */
  .plans__figure { height: auto; aspect-ratio: 3 / 2; }
  /* one column: say what it is (text, specs) before the way in (Discover more) */
  .unit__body { display: flex; flex-direction: column; align-items: flex-start; gap: 12px; }
  .unit__text { display: contents; }
  .unit__desc { order: 1; } .unit__price { order: 2; } .unit__specs { order: 3; } .unit__more { order: 4; margin-top: auto; }
  /* the rate a step down on tablet and phone (2026-09-29, by request):
     .t-price's 28 → the .t-price-sm size, 20 */
  .unit__price .t-price { font-size: var(--size-20); }
  /* Location's point names were 32 at every width — above the 28 section
     headline here; they take the in-content step (2026-09-29) */
  .locpt__name { font-size: var(--size-20); }
  /* section heads STACK here and the note reads left (2026-09-29, by
     request) — hung right, it sat oddly under a left headline */
  .sec-head { grid-template-columns: 1fr; align-items: start; }
  .sec-head__note { justify-self: start; text-align: left; }
  /* the same two-by-two for every card (2026-09-29 audit): as a wrapping
     row, one card broke onto two lines ("Studio Extra") and the rest did
     not. Layout + area on the first line, the balcony on the second. */
  .unit__specs { display: grid; grid-template-columns: repeat(2, max-content); gap: 8px 24px; padding: 0; }
  .amen__shot { height: clamp(320px, 56vh, 520px); }
  /* three columns cannot hold here: one column, centred, a hairline
     between the three instead of beside them */
  .tiers { grid-template-columns: minmax(0, 1fr); max-width: 560px; margin-inline: auto; row-gap: 20px; }
  .tier { aspect-ratio: 4 / 5; }   /* 3:4 at 560 wide stood 747 tall */
  .bookband, .contact { grid-template-columns: 1fr; }
  /* stacked, the form gets clear air from the contact list above it
     (2026-09-29, by request) */
  .contact { row-gap: clamp(56px, 12vw, 80px); }
  #p-urban:checked ~ .plans__bodies [data-plan="urban"],
  #p-primo:checked ~ .plans__bodies [data-plan="primo"],
  #p-one:checked   ~ .plans__bodies [data-plan="one"],
  #p-two:checked   ~ .plans__bodies [data-plan="two"],
  #p-grande:checked ~ .plans__bodies [data-plan="grande"] {
    grid-template-columns: 1fr;
    /* stacked: the PLAN first, the description under it, close
       (2026-09-29, by request — 125 apart, text first, before) */
    gap: 32px;
  }
  .plans__figure { order: -1; }
}

/* The burger takes over at ≤1200 (2026-09-30): the client's labels —
   "The collection", two dropdowns, "360° view" — no longer fit one row
   beside the wordmark and Book now below that. It was ≤1080. */
@media (max-width: 1200px) {
  .nav        { grid-template-columns: 1fr auto auto; }
  .nav__links { display: none; }
  .nav__burger { display: flex; }
  /* The burger opens the same links as a panel under the bar. The script
     also forces .nav--solid while it is open, so the bar and the panel are
     one white surface. */
  /* FULL SCREEN (2026-09-30, by request): the menu fills the window under
     the bar (--nav-h, set by js/nav.js) and ends on Check availability,
     pinned to the foot. It sits behind the bar's own wordmark and burger
     (z-index -1 inside the bar), which stay on top of it. */
  .nav.is-menu .nav__links {
    display: flex; flex-direction: column; align-items: flex-start; gap: 0;
    position: fixed; inset: 0; z-index: -1;
    justify-self: stretch; align-self: stretch;   /* grid alignment applies to fixed children too: without these it shrank to its content, centred */
    padding: calc(var(--nav-h, 93px) + 8px) var(--frame-x) calc(24px + env(safe-area-inset-bottom));
    background: var(--white);
  }
  .nav.is-menu .nav__links .nav__menu-cta {
    display: flex; justify-content: center; align-self: stretch;
    margin-top: auto; padding-block: 16px; color: var(--ivory);
  }
  .nav.is-menu .nav__links .nav__menu-cta::after { display: none; }   /* a button, not an underlined link */
  html:has(.nav.is-menu), html:has(.nav.is-menu) body { overflow: hidden; }
  /* the floating booking bar and the residence page's sticky bar sit above the nav; they stand down while the menu is open */
  html:has(.nav.is-menu) .book:not(.book--card):not(.book--inline), html:has(.nav.is-menu) .sbar { visibility: hidden; }   /* the page behind holds still */
  .nav.is-menu .nav__links a { padding-block: 14px; color: var(--ink); opacity: 1; }
  /* longer with the two groups open: the panel scrolls rather than run
     off a short phone */
  .nav.is-menu .nav__links { overflow-y: auto; }   /* a short phone scrolls the menu, never the page */
  /* in the menu, "360° view" follows the word; the groups open IN PLACE,
     their links indented under the name */
  .nav__loc { top: 0; }
  .nav__links .nav__loc::after { bottom: 0; }
  .nav__tag { position: static; display: inline; margin: 0 0 0 10px; color: var(--gold-700); }
  .nav__group { align-self: stretch; }
  .nav.is-menu .nav__drop { padding-block: 14px; color: var(--ink); opacity: 1; }
  .nav__drop::after { display: none; }
  .nav__sub, .nav__group.is-open .nav__sub {
    position: static; display: none; min-width: 0; padding: 0 0 8px 20px;
    background: none; -webkit-backdrop-filter: none; backdrop-filter: none; box-shadow: none;
    opacity: 1; visibility: visible; translate: none; transition: none;
  }
  .nav__group.is-open .nav__sub { display: flex; }
  .nav__sub::before { display: none; }
  .nav.is-menu .nav__links .nav__sub a { padding-block: 10px; opacity: .85; }
  .nav.is-menu .nav__more-btn { padding-block: 10px; }
  /* the account moves into the menu: its bar icon goes, the menu row shows */
  .nav__acct { display: none; }
  .nav.is-menu .nav__menu-acct { display: block; align-self: stretch; margin-top: 8px; padding-top: 22px; border-top: var(--hairline); }
}
/* 1081–1280: the three-up collection cards */
@media (min-width: 1081px) and (max-width: 1280px) {
  /* the THREE-UP collection cards stack their body here, as on tablet —
     beside the spec column their text ran ~110px wide, a word or two a
     line (2026-09-29 audit) */
  .units > :nth-child(n + 3) .unit__body { display: flex; flex-direction: column; align-items: flex-start; gap: 12px; }
  .units > :nth-child(n + 3) .unit__text { display: contents; }
  .units > :nth-child(n + 3) .unit__desc { order: 1; }
  .units > :nth-child(n + 3) .unit__price { order: 2; }
  .units > :nth-child(n + 3) .unit__specs { order: 3; display: grid; grid-template-columns: repeat(2, max-content); gap: 8px 24px; padding: 0; }
  .units > :nth-child(n + 3) .unit__more { order: 4; }
}
/* 1201–1280: the full link row, but the side columns are narrow — the 265
   wordmark met the links, so it holds at 230 (padding 19 keeps the bar 93). */
@media (min-width: 1201px) and (max-width: 1280px) {
  .nav       { padding-block: 19px; }   /* 93 around the 54px Book now (20 when it was 46) */
  .nav__logo { width: 230px; height: 52.34px; }
}
/* 1201–1400: the account goes icon-only — with the client's longer labels
   "Your stays" + Book now wrapped onto two lines up to ~1370 (2026-09-30;
   it was 1081–1280). The link keeps its aria-label, so the name is read. */
@media (min-width: 1201px) and (max-width: 1400px) {
  .nav__acct { padding-inline: 4px; }
  .nav__acct span { position: absolute; width: 1px; height: 1px; overflow: hidden;
                    clip-path: inset(50%); white-space: nowrap; }
}
@media (max-width: 720px) {
  .nav__cta   { display: none; }
  .nav        { grid-template-columns: 1fr auto;   /* brand · burger (the account is in the menu) */
                padding-block: 20px; }               /* the burger sets the height here, as before */
  .nav__end   { display: none; }                   /* holds only the hidden icon and Book now here */
  .nav__logo  { width: 173px; height: 39.37px; }   /* 150 + 15%, 2026-09-25 */
}

@media (max-width: 720px) {
  .bookband__act .btn { width: 100%; justify-content: center; }
  .units > :nth-child(n) { grid-column: span 6; }   /* one card per row */
  .units { row-gap: 64px; }   /* one per row: more air between rooms (2026-09-29, by request — 48) */
  .sec-head { grid-template-columns: 1fr; align-items: start; }
  :root { --gutter: 24px; --section-y: 72px; }
  .footer__top { grid-template-columns: 1fr; }
  .field-row { grid-template-columns: 1fr; }
  /* Headlines that carry their own <br> stop being balanced down here.
     balance evens line lengths across the WHOLE block, forced breaks
     included, so at 390 it was pulling "Room to work." onto two lines even
     though the clause fitted — turning a specified 2-line headline into 3.
     The <br> already says where the break goes; nothing else should. */
  .hero__title, .sec-head h2, .bookband h3 {
    text-wrap: normal;
  }
  /* Heading sizes step down in tokens.css now (the responsive scale). The
     one exception: the band's quote is a .t-h3 carrying a whole sentence,
     and at the mobile 28 it read as body copy — it keeps a 32 step. */
  .band__quote { font-size: var(--size-32); }
}


/* ══ CUSTOM PICKERS ═════════════════════════════════════════════════════
   A native <select>'s open list and a native date input's calendar are drawn
   by the OS, not the page — no stylesheet reaches them. That is why both
   looked like system chrome while their closed states matched the site.
   So both are REPLACED here, and the native control stays in the DOM as the
   value it writes to: if this script never runs, the page still works with
   the browser's own pickers. Nothing below is decoration on top of a native
   widget; it is the widget.
   ═══════════════════════════════════════════════════════════════════════ */

/* the native control, kept for its value and as the no-JS fallback */
.is-enhanced { position: absolute; width: 1px; height: 1px; padding: 0;
               margin: -1px; overflow: hidden; clip-path: inset(50%); border: 0; }

.pick { position: relative; }
.pick__btn {
  display: flex; align-items: center; justify-content: space-between; gap: 14px;
  width: 100%; padding: 0; font: inherit; color: var(--ink);
  background: none; border: 0; cursor: pointer; text-align: left;
}
.pick__btn[aria-expanded="true"] .pick__chev { transform: rotate(180deg); }
.pick__chev {
  flex: none; width: 17px; height: 17px; color: var(--muted);
  transition: transform var(--duration) var(--ease-out);
}

/* The panel both the list and the calendar drop into.
   FROSTED WHITE (2026-09-24, by request), replacing the --sand panel with
   its .22 border. White at .90 over the same blur(14px) as the booking bar
   (.78 first — raised by request): the footage shows through, but the panel
   still reads light and lifted —
   §03's "white means lifted": the default hairline plus a shadow, tighter
   than the card's so it reads over photography as well as over ivory.
   The calendar's muted weekday letters and disabled days sit on it, so it
   should not go much lower. The shadow drops down; panels that open UPWARD (the booking
   bar) turn it round below, or it would fall on the bar. */
.pick__panel {
  --shadow-panel: 0 16px 40px -16px rgba(var(--shadow-rgb), .18),
                  0 2px 8px rgba(var(--shadow-rgb), .06);
  position: absolute; z-index: 40; top: calc(100% + 10px); left: 0; min-width: 100%;
  background: rgba(255, 255, 255, .90);
  backdrop-filter: blur(14px);
  -webkit-backdrop-filter: blur(14px);
  border: var(--hairline);
  box-shadow: var(--shadow-panel);
  padding: 8px; opacity: 0; visibility: hidden; transform: translateY(-4px);
  /* Closing is the quick one; opening takes --dur-enter below. */
  transition: opacity var(--dur-quick) var(--ease-out),
              transform var(--dur-quick) var(--ease-out),
              visibility 0s linear var(--dur-quick);
}
.pick.is-open .pick__panel {
  opacity: 1; visibility: visible; transform: none;
  transition: opacity var(--dur-enter) var(--ease-out),
              transform var(--dur-enter) var(--ease-out),
              visibility 0s;
}

/* ── list ─────────────────────────────────────────────────────────────── */
.pick__list { display: flex; flex-direction: column; max-height: 280px; overflow-y: auto; }
.pick__opt {
  display: flex; align-items: center; justify-content: space-between; gap: 16px;
  padding: 11px 14px; font: inherit; color: var(--ink); white-space: nowrap;
  background: none; border: 0; cursor: pointer; text-align: left;
  transition: background-color var(--dur-quick) var(--ease-out);
}
.pick__opt:hover, .pick__opt.is-cursor { background: rgba(14, 43, 33, .06); }
.pick__opt[aria-selected="true"] { color: var(--green); }
.pick__opt[aria-selected="true"]::after { content: ""; flex: none;
  width: 6px; height: 6px; background: var(--gold); }

/* ── calendar ───────────────────────────────────────────────────────────
   LAID OUT AFTER raffles.com, inspected live 2026-09-23. What was taken:
   two months side by side, Monday-first, the month name centred over its
   own grid with one pair of arrows at the outer edges, a hairline under the
   weekday row, a CONTINUOUS range band, and a single "Clear dates" in the
   foot with no Apply — the second click commits and the panel closes.
   Square throughout, as everywhere else in this direction. The reference's
   "Special Rates & Accessibility" drawer is deliberately not here.        */
.cal { padding: 24px 26px 18px; }

/* Prev and next sit at the OUTER edges and move the pair. The month names
   are drawn by the columns below, so this row is arrows plus a spacer. */
.cal__head {
  display: grid; grid-template-columns: auto 1fr auto;
  align-items: center;
}
.cal__nav {
  display: grid; place-items: center; width: 34px; height: 34px;
  background: none; border: 0; cursor: pointer; color: var(--green);
  transition: opacity var(--dur-quick) var(--ease-out),
              background-color var(--dur-quick) var(--ease-out),
              scale var(--dur-quick) var(--ease-out);
}
.cal__nav:hover:not([disabled]) { background: rgba(14, 43, 33, .07); }
.cal__nav[disabled] { opacity: .22; cursor: default; }
.cal__nav svg { width: 16px; height: 16px; }
.cal__nav--prev svg { transform: scaleX(-1); }

.cal__months { display: grid; grid-template-columns: 1fr 1fr; gap: 40px; }
/* A fixed floor so the panel does not resize between a 5-row and a 6-row
   month — Raffles holds the same height all year, and a panel that jumps
   while you page through it is the thing that reads as unfinished. */
.cal__col { min-width: 238px; min-height: 252px; }
/* PHONES — stack the two months. The same rule sat in the ≤720 block near
   the booking bar, ABOVE these base rules, so the base won and the calendar
   stayed two months wide (586px at 390) on every page. It must come after. */
@media (max-width: 720px) {
  .cal__months { grid-template-columns: 1fr; gap: 24px; }
  .cal__col { min-width: 0; }
}
/* TABLET (721–1080): the three fields fill the row, so the action takes a
   full row of its own under them instead of a small box left over */
@media (min-width: 721px) and (max-width: 1080px) {
  .book:not(.book--card) .book__go { flex: 1 1 100%; margin-top: 0; min-height: 48px; }
}
/* no panel is ever wider than the screen it opens on */
.pick__panel { max-width: calc(100vw - 2 * var(--gutter)); }
.cal__cap {
  color: var(--green); text-align: center;
  padding-bottom: 12px; margin-top: -34px;   /* rides up into the arrow row */
}

.cal__grid { display: grid; grid-template-columns: repeat(7, 1fr); }
/* The hairline under the weekday row, as the reference draws it: it
   separates the key from the data without boxing anything. */
.cal__dow {
  display: grid; place-items: center; height: 30px;
  color: var(--muted); text-transform: uppercase;
  border-bottom: var(--hairline); margin-bottom: 8px;
}
/* Full-width rectangular cells with NO gap, so a selected run joins into one
   unbroken band instead of a row of separate chips. */
.cal__day {
  position: relative; display: grid; place-items: center;
  height: 36px; font: inherit; color: var(--ink);
  background: none; border: 0; cursor: pointer;
  /* NO transition. paint() repaints the range on every hover; any fade here
     left the preview band trailing the cursor across the grid. */
}
.cal__day:hover:not([disabled]) { background: rgba(14, 43, 33, .08); }
.cal__day[disabled] { color: rgba(95, 95, 85, .28); cursor: default; }
.cal__day.is-empty { pointer-events: none; }
.cal__day.in-range { background: rgba(200, 164, 97, .20); }
/* The run still being hovered — lighter, because it is not chosen yet. */
.cal__day.is-preview { background: rgba(200, 164, 97, .10); }
.cal__day.is-end   { background: var(--green); color: var(--ivory); }
.cal__day.is-end:hover { background: var(--green); }

/* Clear sits bottom-left as a plain underlined link, the readout beside it.
   No Apply — see the note in the page script. */
.cal__foot {
  display: flex; align-items: center; justify-content: space-between;
  gap: 20px; padding-top: 16px; margin-top: 14px; border-top: var(--hairline);
}
.cal__clear {
  background: none; border: 0; padding: 0; cursor: pointer;
  color: var(--muted); text-decoration: underline; text-underline-offset: 4px;
  transition: color var(--duration) var(--ease-out),
              opacity var(--duration) var(--ease-out);
}
.cal__clear:hover:not([disabled]) { color: var(--green); }
.cal__clear[disabled] { opacity: .35; cursor: default; text-decoration: none; }
.cal__readout { color: var(--muted); }
/* <b> only to keep the two legs addressable — the type system is one weight,
   so it must not inherit the browser's bold. */
.cal__leg { font-weight: var(--weight-regular); }
.cal__leg.is-picking { color: var(--green); box-shadow: 0 1px 0 var(--gold); }

/* ── guests stepper ─────────────────────────────────────────────────────
   Two counts cannot live in a flat list, which is what adding Children
   needed. Same panel shell as the calendar, different contents.          */
.gst { padding: 6px 4px; min-width: 312px; }
.gst__row {
  display: flex; align-items: center; justify-content: space-between; gap: 28px;
  padding: 16px 14px;
}
.gst__row + .gst__row { border-top: var(--hairline); }
.gst__label { display: flex; flex-direction: column; gap: 2px; }
/* The 2-adult ceiling and the child age are stated HERE, in the picker, not
   discovered on the results page — the brief is explicit about that. */
.gst__note { color: var(--muted); }
.gst__step { display: flex; align-items: center; gap: 4px; }
.gst__btn {
  display: grid; place-items: center; width: 34px; height: 34px;
  font: inherit; font-size: var(--size-16); line-height: 1;
  color: var(--green); background: none;
  border: 1px solid rgba(14, 43, 33, .28); cursor: pointer;
  transition: background-color var(--dur-quick) var(--ease-out),
              color var(--dur-quick) var(--ease-out),
              opacity var(--dur-quick) var(--ease-out),
              scale var(--dur-quick) var(--ease-out);
}
.gst__btn svg { width: 14px; height: 14px; }
.gst__btn:hover:not([disabled]) { background: var(--green); color: var(--ivory); }
.gst__btn[disabled] { opacity: .28; cursor: default; }
.gst__n { min-width: 30px; text-align: center; color: var(--ink); }
.gst__foot {
  display: flex; align-items: center; justify-content: space-between; gap: 20px;
  padding: 16px 14px 8px; margin-top: 4px; border-top: var(--hairline);
}
.gst__sum { color: var(--muted); }
/* Done is the commit, so it is the one outlined control in this panel —
   the same rest/hover pair as every other button on a light ground. */
.gst__done {
  padding: 10px 24px; background: none; cursor: pointer;
  border: 1px solid rgba(14, 43, 33, .28); color: var(--green);
  transition: background-color var(--dur-quick) var(--ease-out),
              color var(--dur-quick) var(--ease-out),
              border-color var(--dur-quick) var(--ease-out),
              scale var(--dur-quick) var(--ease-out);
}
.gst__done:hover { background: var(--green); color: var(--ivory); border-color: var(--green); }

/* the field whose end the next click will set */
.pick--cal.is-picking .pick__btn { box-shadow: 0 1px 0 var(--gold); }

/* ── the two date fields share ONE calendar ───────────────────────────
   Check in and Check out are two ends of a single range, so they get a
   single panel (2026-09-22) — two calendars side by side asked the visitor
   to work out which one they were looking at. The panel is hosted by an
   empty .pick inside the bar rather than by either field, so it anchors to
   the BAR and not to whichever field was clicked. */
.pick--calhost { position: static; }
/* .book { position: relative } used to live here to give the calendar host
   a containing block. It is gone: .book is position: fixed now, which is a
   containing block already, and the relative rule came LATER in the file so
   it was quietly winning and un-fixing the bar. */

/* EVERY panel in the bar anchors to the BAR's top edge, not to the little
   wrapper around its own value (2026-09-23). The Guests list was resolving
   against .pick — a ~26px box sitting mid-field — so `bottom: 100%` put its
   lower edge halfway up the field and it overlapped the bar. Making .pick
   static inside the bar and hanging the panel off .book__field, which spans
   the FULL bar height, lands every panel on the same line as the calendar's:
   flush on the bar, aligned to the field that opened it. */
.book .book__field { position: relative; }
.book .book__field .pick { position: static; }

/* On the hero the bar sits low, so a panel dropping DOWN ran past the hero
   and over the section below. Every panel in the bar opens upward instead:
   it lands on the photograph, which is what the panel is meant to cover. */
/* 12px OFF THE BAR (2026-09-24, by request). It sat flush from
   2026-09-23, after raffles.com, and read as stuck to the bar once the
   panel went white; now it floats as its own layer, like the other
   pickers (10px below their field). */
.book .pick__panel {
  top: auto; bottom: calc(100% + 12px); transform: translateY(4px);
  --shadow-panel: 0 -16px 40px -16px rgba(var(--shadow-rgb), .18),
                  0 -2px 8px rgba(var(--shadow-rgb), .06);
}
.book .pick.is-open .pick__panel { transform: none; }
/* The seam-filler is gone with the gap it used to bridge. */
.pick--calhost .pick__panel { left: 0; min-width: 0; }

/* The long-stay line in the calendar foot (brief §7.3): an invitation, so
   it reads as information — gold rule, green text, no alarm colour. */
.cal__foot { flex-wrap: wrap; }
.cal__long {
  flex-basis: 100%; order: -1; margin: 0 0 12px; padding: 10px 14px;
  background: rgba(200, 164, 97, .16); border-left: 2px solid var(--gold); color: var(--green);
}
.cal__long[hidden] { display: none; }

/* ══ REVEAL ON SCROLL, after raffles.com (2026-09-30, by request) ══════════
   Raffles' .c-appeared, read off its stylesheet: opacity 0 and 10% of its
   own height lower, to in place, over .6s, once a quarter is in view
   (js/reveal.js marks the pieces and takes the marks off on arrival).
   `translate`, not transform, so it composes with every transform and
   press-scale the pieces already carry. The site's decelerate, not
   Raffles' default ease. */
@keyframes rv-in {
  from { opacity: 0; translate: 0 10%; }
  to   { opacity: 1; translate: 0 0; }
}
.rv { opacity: 0; }
.rv.is-in { animation: rv-in .6s var(--ease-out) both; }

/* ══ PAGE TRANSITION, after belarosa-chalet.ch (js/transition.js, 2026-10-01) ═
   Their timings, sampled off their page. ARRIVING: a --green curtain with the
   wordmark covers the page; the mark is drawn (below); at 1.03s the curtain
   lifts in .75s, the mark riding up inside it and fading. LEAVING: .2s after
   the click the curtain rises from below in .7s to cover the page; the next
   page loads at 1s. Curtain speed and curve exactly theirs (below). Green from the first moment, both
   ways (2026-10-01, by request): Belarosa's pale veil under the curtain is
   left out. Only the curtain takes clicks, so the page answers as soon as
   it has lifted. No transition under reduced motion (the script adds none). */
.pt { position: fixed; inset: 0; z-index: 2000; pointer-events: none; display: none; }
.pt.is-intro, .pt.is-leaving { display: block; }
.pt__curtain { position: absolute; inset: 0; }
.pt__curtain { background: var(--green); pointer-events: auto; }
.pt__mark { position: absolute; inset: 0; display: grid; place-items: center; }
/* DRAWN SOLID (2026-10-01, by request): each of the wordmark's twelve shapes
   grows along its own course, already filled — js/transition.js gives each a
   mask whose wide white stroke (.pt__pen) runs along its outline, its dash
   growing 0 → 1 on pathLength 1, .7s each, starting .02s apart left to right — whole by
   ~.97s, just before the curtain lifts (.8 / .03 first, then .5 — too quick
   — and .25, all the same day). Size
   180–300 (by request; 480 was too big, 230 the first). Leaving shows no
   mark; the next page draws it. */
.pt__logo {
  width: clamp(180px, 20vw, 300px); height: auto; aspect-ratio: 149.4 / 34; display: block;
  color: var(--ivory); --logo-accent: var(--gold);
}
.pt__pen {
  /* the dash GROWS from nothing (0 1 → 1 0) rather than sliding in by
     offset: a zero dash with butt caps draws nothing at all, where an
     offset of 1 left specks on the green (2026-10-01) */
  /* butt caps: a round cap draws a dot at every start before the pen moves
     (2026-10-12, by request — "dari polosan dulu"); the SOVANA mark's round
     tips are reached by running the pen past each end (transition.js) */
  fill: none; stroke: #fff; stroke-width: 3; stroke-linecap: butt;
  stroke-dasharray: 0 1;
}
.pt.is-intro .pt__pen { animation: pt-draw .7s cubic-bezier(.65, 0, .35, 1) calc(.05s + var(--i, 0) * .02s) both; }
/* the first tips of the shapes read as dust on plain green — the mark fades
   in as it grows, so the screen starts plain green (by request) */
.pt.is-intro .pt__logo { animation: pt-in .25s linear .08s both; }
.pt.is-intro .pt__mark    { animation: pt-out .35s linear 1.03s both; }   /* rides up with the curtain */
/* Belarosa's curtain EXACTLY (2026-10-01, by request — "sama persis, smooth"),
   fitted to their samples: arriving it lifts at 1.03s over .75s on
   cubic-bezier(.25, 1, .4, 1); leaving it rises .2s after the click over .7s
   on cubic-bezier(.25, 1, .5, 1). */
.pt.is-intro .pt__curtain { animation: pt-lift .75s cubic-bezier(.25, 1, .4, 1) 1.03s both; }
.pt.is-leaving .pt__curtain { animation: pt-rise .7s cubic-bezier(.25, 1, .5, 1) .2s both; }
@keyframes pt-draw { from { stroke-dasharray: 0 1; } to { stroke-dasharray: 1 0; } }
@keyframes pt-in   { from { opacity: 0; } to { opacity: 1; } }
@keyframes pt-out  { from { opacity: 1; } to { opacity: 0; } }
@keyframes pt-lift { from { translate: 0 0; } to { translate: 0 -101%; } }
@keyframes pt-rise { from { translate: 0 101%; } to { translate: 0 0; } }

/* ══ PARALLAX on the large photographs, after belarosa-chalet.ch ═══════════
   (2026-10-01, by request; anuchome.com's fixed 80px before, 2026-09-30.)
   Belarosa's numbers, sampled while scrolling: the picture travels DOWN its
   frame linearly, from 10% of the frame's height above centre to 10% below,
   while the frame crosses the window from its foot to its head — a move in
   proportion to the frame (±72px on a 717px one), slower than the page.
   js/parallax.js marks each picture .px and sets --p 0 → 1. The picture is
   120% of its frame, so the 20% of travel never shows an edge: at --p 0 it
   sits 20% of the frame above, at 1 flush with the top (16.667% of its OWN
   height = 20% of the frame's). Every frame it sits in already clips.
   Specificity (0,1,2) so it wins over each picture's own height: 100%.
   The 1px inner ring those pictures carried moves to the FRAME — on the
   taller picture it would fall outside. */
html img.px {
  height: 120%;
  translate: 0 calc((var(--p, .5) - 1) * 16.6667%);
  outline: 0;
}
.unit__media::after, .amen__shot::after, .ocard__media::after,
.row__media:not(.row__media--plan)::after {
  content: ""; position: absolute; inset: 0; pointer-events: none;
  outline: 1px solid oklch(0 0 0 / .1); outline-offset: -1px;
}
.ocard__media, .row__media { position: relative; }

/* ══ SMOOTH SCROLLING (Lenis, light — js/smooth.js, 2026-09-30) ═══════════
   Lenis's own stylesheet, one change: it switches iframes off for the whole
   time smoothing is on, which would leave the Google map unusable; here only
   WHILE a scroll is running, so a wheel over the map still scrolls the page. */
html.lenis, html.lenis body { height: auto; }
.lenis:not(.lenis-autoToggle).lenis-stopped { overflow: clip; }
.lenis [data-lenis-prevent] { overscroll-behavior: contain; }
.lenis.lenis-scrolling iframe { pointer-events: none; }

/* ══ THE CALENDAR ON PHONES — a full-screen sheet (2026-09-30, by request,
   after Airbnb / Wander). picker.js moves the calendar into .cal-sheet at
   ≤720: head (× · "Your dates" · the range and its nights), one weekday row
   pinned, twelve months stacked in a scroller, a pinned foot (Clear dates ·
   Done). White and square like every panel; above the nav and the bar. */
.cal-sheet {
  position: fixed; inset: 0; z-index: 1000;
  display: none; flex-direction: column;
  background: var(--white); color: var(--ink);
}
.cal-sheet.is-shown { display: flex; }
html.has-sheet, html.has-sheet body { overflow: hidden; }   /* the page behind holds still */
.cal-sheet__head {
  display: flex; align-items: center; gap: 8px;
  padding: 12px var(--gutter) 12px 12px; border-bottom: var(--hairline);
}
.cal-sheet__x {
  flex: none; display: grid; place-items: center; width: 44px; height: 44px;
  background: none; border: 0; color: var(--green); cursor: pointer;
}
.cal-sheet__x svg { width: 22px; height: 22px; }
.cal-sheet__x:active { scale: .96; }
.cal-sheet__title { margin: 0; color: var(--green); }
.cal-sheet__readout { margin: 2px 0 0; }
.cal-sheet .cal {
  flex: 1; min-height: 0; padding: 0;
  display: flex; flex-direction: column;
}
.cal-sheet .cal__dowrow {
  display: grid; grid-template-columns: repeat(7, 1fr);
  padding: 0 var(--gutter); border-bottom: var(--hairline);
}
.cal-sheet .cal__dowrow .cal__dow { border-bottom: 0; margin: 0; height: 36px; }
.cal-sheet .cal__scroll {
  flex: 1; min-height: 0; overflow-y: auto; overscroll-behavior: contain;
  -webkit-overflow-scrolling: touch;
  padding: 0 var(--gutter) 24px;
}
.cal-sheet .cal__col { min-width: 0; min-height: 0; padding-top: 28px; }
/* the month name reads LEFT above its grid, not up in an arrow row */
.cal-sheet .cal__cap { margin-top: 0; text-align: left; padding-bottom: 12px; }
.cal-sheet .cal__day { height: 46px; }   /* a thumb-sized cell */
.cal-sheet .cal__foot {
  flex-wrap: wrap; margin: 0; gap: 12px 20px;
  padding: 14px var(--gutter) calc(14px + env(safe-area-inset-bottom));
  border-top: var(--hairline); background: var(--white);
}
.cal-sheet .cal__long { flex: 1 1 100%; margin: 0; }
.cal-sheet .cal__done { align-self: center; min-width: 150px; justify-content: center; }
.cal-sheet .cal__done[disabled] { opacity: .35; cursor: default; }
