/* ============================================================
   VALLETTA VINTAGE — v2
   Kinfolk register, all sans. Switzer only.
   Vast space, small precise type, photography first.
   ============================================================ */

:root {
  --bg: #fbf9f4;
  --paper: #fbf9f4;   /* current page paper — Malta-light themes update this; hero letters track it */
  --ink: #181611;
  --grey: #8d877c;
  --terra: #a4582f;
  --hair: rgba(24, 22, 17, 0.12);
  --hair-soft: rgba(24, 22, 17, 0.07);
  --pad: clamp(20px, 5vw, 84px);
  --ease: cubic-bezier(0.25, 0.8, 0.25, 1);
}

* { margin: 0; padding: 0; box-sizing: border-box; }

html { scroll-behavior: smooth; }

body {
  background: var(--bg);
  color: var(--ink);
  font-family: "Switzer", "Helvetica Neue", Helvetica, Arial, sans-serif;
  font-weight: 400;
  font-size: 15px;
  line-height: 1.75;
  letter-spacing: 0.005em;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  overflow-x: hidden;
}
/* form controls don't inherit font-family — pin them to the body sans so Menu/Close
   buttons and arrows aren't the OS system font (two typefaces only). Specific button
   rules that set var(--display) still override this. */
button, input, select, textarea, .burger, .mmenu-close { font-family: inherit; }


::selection { background: var(--ink); color: var(--bg); }

img { display: block; max-width: 100%; }

/* unified soft warm grade */
.ph {
  filter: sepia(0.1) saturate(0.92) contrast(1.01);
  background: #efece4;
}

a { color: inherit; text-decoration: none; }

/* ---------- type ---------- */

.label {
  font-size: 10px;
  font-weight: 500;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--ink);
}
.label--grey { color: var(--grey); }

.cap {
  font-size: 11.5px;
  line-height: 1.7;
  letter-spacing: 0.03em;
  color: var(--grey);
}

h1, h2, h3, .h {
  font-weight: 300;
  letter-spacing: -0.01em;
  line-height: 1.15;
}

.h-xl { font-size: clamp(34px, 5.4vw, 76px); line-height: 1.08; }
.h-lg { font-size: clamp(27px, 3.6vw, 50px); }
.h-md { font-size: clamp(21px, 2.4vw, 31px); line-height: 1.3; }

.body-lg {
  font-size: clamp(17px, 1.5vw, 21px);
  line-height: 1.7;
  font-weight: 300;
}

.quiet { color: var(--grey); }

p + p { margin-top: 1.4em; }

/* text link */
.tlink {
  font-size: 10px;
  font-weight: 500;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  padding-bottom: 4px;
  border-bottom: 1px solid var(--hair);
  transition: border-color 0.5s;
}
.tlink:hover { border-color: var(--ink); }
/* tlink used on a <button> (e.g. booking back-links) — strip default button chrome */
button.tlink { background: none; border: 0; border-bottom: 1px solid var(--hair); color: inherit; font-family: inherit; cursor: pointer; }
button.tlink:focus { outline: none; }
button.tlink:focus-visible { outline: none; border-bottom-color: var(--ink); }

/* ---------- header ---------- */

.head {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  z-index: 100;
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: center;
  padding: 26px var(--pad);
  transition: background 0.7s var(--ease), padding 0.7s var(--ease), color 0.4s;
}
.head.on-dark { color: #fbf9f4; }
.head.scrolled {
  background: rgba(251, 249, 244, 0.97);
  -webkit-backdrop-filter: blur(10px);
  backdrop-filter: blur(10px);
  padding-top: 16px;
  padding-bottom: 16px;
  color: var(--ink);
  border-bottom: 1px solid var(--hair-soft);
}

.head .left { display: flex; gap: 34px; }
.head .right { display: flex; gap: 34px; justify-content: flex-end; }
.head a:not(.wordmark) {
  font-size: 10px;
  font-weight: 500;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  opacity: 0.75;
  transition: opacity 0.3s;
}
.head a:not(.wordmark):hover, .head a.active { opacity: 1; }

.wordmark {
  font-size: 13px;
  font-weight: 500;
  letter-spacing: 0.42em;
  text-transform: uppercase;
  text-align: center;
  white-space: nowrap;
}

.burger {
  display: none;
  background: none;
  border: 0;
  cursor: pointer;
  color: inherit;
  font-size: 10px;
  font-weight: 500;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  z-index: 202;
  justify-self: end;
}

.menu {
  position: fixed;
  inset: 0;
  z-index: 200;
  background: var(--bg);
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  gap: 8px;
  visibility: hidden;
  opacity: 0;
  transition: opacity 0.5s var(--ease), visibility 0s 0.5s;
}
body.menu-open .menu { visibility: visible; opacity: 1; transition-delay: 0s; }
body.menu-open { overflow: hidden; }
.menu a {
  font-size: clamp(26px, 7vw, 40px);
  font-weight: 300;
  letter-spacing: 0.02em;
  padding: 6px 0;
  opacity: 0;
  transform: translateY(14px);
  transition: opacity 0.6s var(--ease), transform 0.6s var(--ease);
}
body.menu-open .menu a { opacity: 1; transform: none; }
.menu .label--grey { margin-top: 30px; }

/* ---------- hero ---------- */

.hero {
  position: relative;
  /* dvh, not svh — the same fix `.door`, `.mmenu` and `.story-page .hero` already carry and
     this one was missed. svh is the viewport with Safari's toolbars at their LARGEST, so once
     iOS collapses the bar to the compact pill the real viewport grows, the hero stays short,
     and the paper of the next section shows through underneath as a band (Duncan 2026-07-27,
     iPhone). dvh follows the viewport as the bar comes and goes. svh first as the fallback
     for browsers without dvh. */
  height: 100svh;
  height: 100dvh;
  min-height: 540px;
  overflow: hidden;
  color: #fbf9f4;
}
/* index hero: the poster is the first frame WAITING behind the video, so first-time
   visitors never see blank cream while the 16MB video buffers (Duncan 2026-07-19).
   :has(video) scopes it to the index hero — house-page heroes use their own <img>. */
/* No paper lip under the FILM (Hanna 2026-09-10: "can we remove the white line/space
   underneath the video please?"). The lip is the deliberate 44px band of page colour the
   rest of the site's heroes carry — `--vv-lip: 0px` is the escape hatch already built for
   exactly this, so the apartment and Story heroes are untouched. */
.hero:has(video) { --vv-lip: 0px; }
.hero:has(video) {
  background-image: url(../assets/hero-poster-20260721d.jpg);
  background-size: cover;
  background-position: center;
  background-repeat: no-repeat;
}
.hero video {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
}
.hero::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 1;
  background: linear-gradient(to top, rgba(20, 16, 10, 0.38), rgba(20, 16, 10, 0.04) 40%, rgba(20, 16, 10, 0.18));
}
.hero-foot {
  position: absolute;
  left: var(--pad);
  right: var(--pad);
  bottom: 34px;
  z-index: 2;
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 20px;
  font-size: 11px;
  letter-spacing: 0.06em;
  color: rgba(251, 249, 244, 0.92);
}
.hero-foot .label { color: inherit; }

/* §4 — apartment hero: lift the caption clear of the giant name so text never sits
   on top of the big title. --door-h is measured live by the name-fitter (js/main.js)
   from the ACTUAL rendered height; the clamp is only a no-JS fallback. (Hanna —
   "text still overlaps... on mobile too") */
.hero:has(.door-name) .hero-foot { bottom: calc(var(--door-h, calc(0.74 * clamp(64px, 13.5vw, 210px))) + 16px); }

/* home hero (video, no giant name): drop the dark scrim behind the line — Chris asked
   to remove the "black box" in the video text. A soft shadow keeps it legible. */
.hero:not(:has(.door-name))::before { background: none; }
.hero:not(:has(.door-name)) .hero-foot .label { text-shadow: 0 1px 18px rgba(0, 0, 0, 0.55); }

/* story hero — the landing-page DOOR, open state, rebuilt in CSS (Duncan 2026-07-23:
   "take the EXACT settings from the landing page — stop measuring and guessing").
   Same construction main.js renderDoor() uses for the #houses doors: the photo stops a
   band short of the viewport bottom, band = gap + caption + gap with the doors' own
   --vv-gap: 40px, caption verbatim from .door-meta--l .cap (16.5px/1.5, ink, 26em) on
   the doors' ink column calc(clamp(40px,6vw,96px)+2px), name on the photo one gap above
   its bottom edge. Flexbox does the measuring the door JS does per-frame, so the band is
   gap + text + gap BY CONSTRUCTION at any line count — no hardcoded height to drift. */
/* Story arrival = ONE screen split by flexbox: the photo (.sframe) takes every pixel the
   caption strip doesn't need; the strip is padding + one line + padding BY CONSTRUCTION,
   so it is always thin at any window size (Duncan 2026-07-23, final reference). */
.story-page .hero {
  --vv-lip: 0px;   /* the strip below IS the lip — disable the generic clipped lip (escape hatch, see ~2955) */
  height: 100svh; height: 100dvh; min-height: 540px;
  display: flex; flex-direction: column;
  background: var(--paper);
}
.story-page .hero::before { content: none; }   /* scrim moves to the frame, not the strip */
.story-page .sframe { position: relative; flex: 1 1 auto; min-height: 0; overflow: hidden; }
.story-page .sframe::after {
  content: ""; position: absolute; inset: 0; z-index: 1;
  background: linear-gradient(to top, rgba(20,16,10,0.35), rgba(20,16,10,0.05) 45%, rgba(20,16,10,0.15));
}
.story-page .sframe .door-name { z-index: 2; }   /* "Story" bottom-left ON the photo */
.hero-lip-line {
  /* the thin strip: one line at the door-caption size, equal air above and below,
     left-aligned with the title (both at var(--pad)) */
  flex: none;
  color: var(--ink);
  font-size: 16.5px;
  line-height: 1.5;
  /* no nowrap: on a phone too narrow for one line it wraps like the door captions do —
     the text can NEVER clip off-screen (Duncan 2026-07-23, iPhone). The strip is built
     from the text, so a wrapped line just makes it a touch taller. */
  padding: clamp(18px, 3.5vw, 26px) var(--pad);
  margin: 0;
}

/* ---------- spacing ---------- */

.wrap { padding-left: var(--pad); padding-right: var(--pad); }
.s-xl { padding-top: clamp(64px, 10vh, 120px); padding-bottom: clamp(64px, 10vh, 120px); }
.s-lg { padding-top: clamp(80px, 13vh, 160px); padding-bottom: clamp(56px, 8vh, 100px); }
.s-md { padding-top: clamp(50px, 8vh, 100px); padding-bottom: clamp(50px, 8vh, 100px); }

/* ---------- statement ---------- */

.statement { text-align: center; }
.statement .label--grey { display: block; margin-bottom: 38px; }
.statement .h-lg { max-width: 21em; margin: 0 auto; }
.statement .cap { margin-top: 38px; }

/* ---------- 12-col editorial grid ---------- */

.grid {
  display: grid;
  grid-template-columns: repeat(12, 1fr);
  gap: clamp(14px, 2vw, 28px);
  align-items: start;
}

.figure { position: relative; }
.figure a { display: block; overflow: hidden; }
.figure img { width: 100%; object-fit: cover; transition: transform 1.8s var(--ease); }
.figure a:hover img { transform: scale(1.03); }

/* §5/§9 — focal point: an image marked with a focal point keeps that spot in frame
   when cropped. Set per image in the editor; --focal-m gives mobile its own point. */
.has-focal { object-position: var(--focal, 50% 50%); }
@media (max-width: 860px) { .has-focal { object-position: var(--focal-m, var(--focal, 50% 50%)); } }
.figcap {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 16px;
  padding-top: 12px;
  margin-top: 2px;
  border-top: 1px solid var(--hair-soft);
}
.figcap .name {
  font-size: 12px;
  font-weight: 500;
  letter-spacing: 0.2em;
  text-transform: uppercase;
}
.figcap .cap { text-align: right; }

/* spread placements */
.col-a { grid-column: 1 / span 7; }
.col-b { grid-column: 9 / span 4; padding-top: clamp(60px, 12vw, 170px); }
.col-c { grid-column: 6 / span 7; }
.col-d { grid-column: 1 / span 4; padding-top: clamp(60px, 12vw, 170px); }
.col-text { grid-column: 1 / span 4; padding-top: 8px; }
.col-text-r { grid-column: 9 / span 4; padding-top: 8px; }
.story-copy { grid-column: 7 / span 5; }   /* homepage story prose (collapses to full width on mobile via #story rule) */

.ratio-43 { aspect-ratio: 4 / 3; }
.ratio-34 { aspect-ratio: 3 / 4; }
.ratio-11 { aspect-ratio: 1 / 1; }
.ratio-169 { aspect-ratio: 16 / 9; }

/* house spread text block */
.house-note h3 {
  font-size: 13px;
  font-weight: 500;
  letter-spacing: 0.3em;
  text-transform: uppercase;
  margin-bottom: 18px;
}
.house-note p { font-size: 14px; color: var(--grey); max-width: 26em; }
.house-note .meta {
  list-style: none;
  margin: 22px 0 26px;
}
.house-note .meta li {
  font-size: 11px;
  letter-spacing: 0.08em;
  color: var(--grey);
  padding: 7px 0;
  border-top: 1px solid var(--hair-soft);
}
.house-note .meta li:last-child { border-bottom: 1px solid var(--hair-soft); }

/* ---------- full-bleed moment ---------- */

.still {
  position: relative;
  height: min(92svh, 900px);
  overflow: hidden;
}
.still img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
}
.still-cap {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 40px;
  z-index: 2;
  text-align: center;
  color: rgba(251, 249, 244, 0.95);
  font-size: 13px;
  font-weight: 400;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  text-shadow: 0 1px 16px rgba(0, 0, 0, 0.55);   /* legible even over bright areas of the photo */
}
.still::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 1;
  background: linear-gradient(to top, rgba(20, 16, 10, 0.5), rgba(20, 16, 10, 0.06) 42%, rgba(20, 16, 10, 0.14));
}

/* ---------- index list (small, quiet) ---------- */

.qlist { max-width: 620px; margin: 0 auto; }
.qlist .row {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 24px;
  padding: 16px 0;
  border-top: 1px solid var(--hair-soft);
}
.qlist .row:last-child { border-bottom: 1px solid var(--hair-soft); }
.qlist .row .n { font-size: 13.5px; font-weight: 500; letter-spacing: 0.04em; }
.qlist .row .cap { text-align: right; }

/* ---------- suites (scala) ---------- */

.suite-spread { padding-top: clamp(70px, 12vh, 140px); }
.suite-head {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 20px;
  margin-bottom: clamp(26px, 4vh, 44px);
}
.suite-head h2 {
  font-size: 14px;
  font-weight: 500;
  letter-spacing: 0.34em;
  text-transform: uppercase;
}
.suite-head .cap { text-align: right; }

/* ---------- strip ---------- */

.strip {
  display: flex;
  gap: clamp(12px, 1.6vw, 22px);
  overflow-x: auto;
  scroll-snap-type: x mandatory;
  padding: 0 var(--pad) 18px;
  scrollbar-width: none;
  cursor: grab;
}
.strip::-webkit-scrollbar { display: none; }
.strip img {
  height: clamp(280px, 52svh, 540px);
  width: auto;
  flex: none;
  scroll-snap-align: center;
}

/* ---------- footer ---------- */

.foot {
  border-top: 1px solid var(--hair-soft);
  text-align: center;
  padding: clamp(60px, 10vh, 110px) var(--pad) 40px;
}
.foot .wordmark { display: block; margin-bottom: 30px; }
.foot nav { display: flex; justify-content: center; gap: 30px; flex-wrap: wrap; margin-bottom: 44px; }
.foot nav a {
  font-size: 10px;
  font-weight: 500;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  opacity: 0.7;
  transition: opacity 0.3s;
}
.foot nav a:hover { opacity: 1; }
.foot .cap { color: var(--grey); }

/* ---------- reveals: slow pure fades ---------- */

[data-fade] {
  opacity: 0;
  transition: opacity 1.6s var(--ease);
  transition-delay: var(--d, 0s);
}
[data-fade].in { opacity: 1; }

@media (prefers-reduced-motion: reduce) {
  [data-fade] { opacity: 1; transition: none; }
  html { scroll-behavior: auto; }
}

/* ---------- responsive ---------- */

@media (max-width: 860px) {
  .head { grid-template-columns: 1fr auto; }
  .head .left, .head .right { display: none; }
  .wordmark { text-align: left; }
  .burger { display: block; }
  .grid { grid-template-columns: repeat(6, 1fr); }
  .col-a, .col-c { grid-column: 1 / -1; }
  .col-b, .col-d { grid-column: 2 / -1; padding-top: 0; margin-top: 14px; }
  .col-text, .col-text-r { grid-column: 1 / -1; padding-top: 0; margin-top: 20px; }
  .figcap .cap { display: none; }
  .qlist .row { flex-direction: column; gap: 2px; }
  .qlist .row .cap { text-align: left; }
}

/* ============================================================
   NAVIGATION AS ARCHITECTURE
   ============================================================ */

/* ---------- floor indicator (fixed, right) ---------- */
/* REMOVED at client request (Chris + Hanna, 2026-07-13: "the levels/lines on the
   side don't work, they're a distraction"). Hidden rather than deleted so the
   markup + styling can be restored in one line if wanted later. */
/* Round-1 asked for the right scroll-nav to come off the MAIN page; it was applied
   site-wide, which also switched off the guide's section index (nav.lift on desktop,
   mirrored into the footer as .fi-index on phone by main.js) — leaving explore with no
   way to navigate at all (Hanna 2026-07-16: "should be more structured so it's easier
   to navigate"). Scoped to everything EXCEPT the guide; every other page is unchanged. */
/* Round-1: the right scroll-nav comes off the MAIN page. 2026-07-16 carved an
   exception so Explore could keep it as an index; 2026-07-17 that nav was removed
   from Explore outright (Chris hates it) and the collapsible titles became the
   index instead — so the rule is site-wide again, as originally asked. */
.lift, .fi-index { display: none !important; }

.lift {
  position: fixed;
  right: clamp(16px, 2.6vw, 40px);
  top: 50%;
  transform: translateY(-50%);
  z-index: 90;
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 0;
  pointer-events: none;
}
.lift a {
  pointer-events: auto;
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 7px 0;
  font-size: 9px;
  font-weight: 500;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--grey);
  opacity: 0.55;
  transition: opacity 0.5s, color 0.5s, letter-spacing 0.5s;
}
.lift a::after {
  content: "";
  width: 14px;
  height: 1px;
  background: currentColor;
  transition: width 0.5s var(--ease);
}
.lift a.here {
  opacity: 1;
  color: var(--ink);
  letter-spacing: 0.3em;
}
.lift a.here::after { width: 30px; }
.lift.on-dark a { color: rgba(251, 249, 244, 0.7); }
.lift.on-dark a.here { color: #fbf9f4; }

/* ---------- the plan (menu as architectural drawing) ---------- */

.plan {
  position: fixed;
  inset: 0;
  z-index: 200;
  background: var(--bg);
  display: flex;
  flex-direction: column;
  visibility: hidden;
  opacity: 0;
  transition: opacity 0.55s var(--ease), visibility 0s 0.55s;
  overflow-y: auto;
}
body.plan-open .plan { visibility: visible; opacity: 1; transition-delay: 0s; }
body.plan-open { overflow: hidden; }

.plan-head {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 26px var(--pad);
}
.plan-close {
  background: none;
  border: 0;
  cursor: pointer;
  font-family: inherit;
  font-size: 10px;
  font-weight: 500;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--ink);
}

.plan-body {
  flex: 1;
  display: grid;
  place-items: center;
  padding: 20px var(--pad) 60px;
}

.plan-grid {
  display: grid;
  grid-template-columns: repeat(12, minmax(0, 56px));
  grid-auto-rows: minmax(52px, 8.5vh);
  gap: 0;
  width: 100%;
  max-width: 880px;
}

.room {
  position: relative;
  border: 1px solid var(--hair);
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  padding: 12px 14px;
  background: var(--bg);
  transition: background 0.5s var(--ease), color 0.5s;
  overflow: hidden;
}
.room .label { letter-spacing: 0.26em; transition: color 0.4s; }
.room .cap { transition: color 0.4s; }
.room img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  opacity: 0;
  transition: opacity 0.7s var(--ease);
}
.room:hover img { opacity: 1; }
.room:hover .label, .room:hover .cap { color: #fbf9f4; position: relative; z-index: 2; }
.room:hover::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 1;
  background: linear-gradient(to top, rgba(20, 16, 10, 0.45), transparent 55%);
}

/* room placements — loose plan of the collection */
.room--scala { grid-column: 1 / span 5; grid-row: 1 / span 3; }
.room--altana { grid-column: 6 / span 4; grid-row: 1 / span 2; }
.room--explore { grid-column: 10 / span 3; grid-row: 1 / span 4; }
.room--doma { grid-column: 6 / span 4; grid-row: 3 / span 2; }
.room--retro { grid-column: 1 / span 5; grid-row: 4 / span 1; }
.room--story { grid-column: 1 / span 3; grid-row: 5 / span 1; }
.room--checkin { grid-column: 4 / span 4; grid-row: 5 / span 1; }
.room--book { grid-column: 8 / span 5; grid-row: 5 / span 1; background: var(--ink); color: var(--bg); }
.room--book .label { color: var(--bg); }
.room--book:hover { background: #2e2a22; }

.plan-foot {
  text-align: center;
  padding: 0 var(--pad) 34px;
}

@media (max-width: 860px) {
  .lift { display: none; }
  .plan-grid { grid-template-columns: repeat(6, 1fr); grid-auto-rows: 64px; }
  .room--scala { grid-column: 1 / -1; grid-row: 1 / span 2; }
  .room--altana { grid-column: 1 / span 3; grid-row: 3 / span 1; }
  .room--doma { grid-column: 4 / span 3; grid-row: 3 / span 1; }
  .room--retro { grid-column: 1 / -1; grid-row: 4 / span 1; }
  .room--explore { grid-column: 1 / span 3; grid-row: 5 / span 1; }
  .room--story { grid-column: 4 / span 3; grid-row: 5 / span 1; }
  .room--checkin { grid-column: 1 / span 3; grid-row: 6 / span 1; }
  .room--book { grid-column: 4 / span 3; grid-row: 6 / span 1; }
}

/* ============================================================
   v2.1 — logo, true-colour grade, full-bleed plan, motion
   ============================================================ */

/* photography untouched — unify only barely */
.ph { filter: saturate(1.03) contrast(1.01); }

/* logo mark */
.wordmark { display: inline-flex; align-items: center; justify-content: center; gap: 13px; }
/* transition only after .ready (added post-first-paint) — an always-on filter transition
   here animated the load snap-to-white through black (the "black VV glitch") */
.logo-mark { height: 24px; width: auto; display: block; }
.ready .logo-mark { transition: filter 0.4s; }
.foot .logo-mark { height: 40px; margin: 0 auto 22px; }

/* ---------- plan, full bleed ---------- */
.plan-head { position: absolute; top: 0; left: 0; right: 0; z-index: 5; pointer-events: none; }
.plan-head .plan-close { pointer-events: auto; }
.plan-head .label { display: none; }
.plan-body { position: absolute; inset: 0; padding: 0; display: block; }
.plan-grid {
  display: grid;
  width: 100%;
  height: 100%;
  max-width: none;
  grid-template-columns: repeat(12, 1fr);
  grid-template-rows: repeat(6, 1fr);
  gap: 0;
}
.plan-foot { display: none; }
.room { border: 0; box-shadow: inset -1px -1px 0 var(--hair); padding: 22px 26px; }
.room .label { font-size: 12px; letter-spacing: 0.3em; }
.room--scala   { grid-column: 1 / 6;   grid-row: 1 / 4; }
.room--altana  { grid-column: 6 / 10;  grid-row: 1 / 3; }
.room--explore { grid-column: 10 / 13; grid-row: 1 / 5; }
.room--doma    { grid-column: 6 / 10;  grid-row: 3 / 5; }
.room--retro   { grid-column: 1 / 6;   grid-row: 4 / 7; }
.room--story   { grid-column: 6 / 9;   grid-row: 5 / 7; }
.room--checkin { grid-column: 9 / 11;  grid-row: 5 / 7; }
.room--book    { grid-column: 11 / 13; grid-row: 5 / 7; }

@media (max-width: 860px) {
  .plan-grid { grid-template-columns: repeat(2, 1fr); grid-template-rows: repeat(5, 1fr); }
  .room--scala   { grid-column: 1 / 3; grid-row: 1 / 2; }
  .room--altana  { grid-column: 1 / 2; grid-row: 2 / 3; }
  .room--doma    { grid-column: 2 / 3; grid-row: 2 / 3; }
  .room--retro   { grid-column: 1 / 3; grid-row: 3 / 4; }
  .room--explore { grid-column: 1 / 2; grid-row: 4 / 5; }
  .room--story   { grid-column: 2 / 3; grid-row: 4 / 5; }
  .room--checkin { grid-column: 1 / 2; grid-row: 5 / 6; }
  .room--book    { grid-column: 2 / 3; grid-row: 5 / 6; }
}

/* ---------- mobile menu: text-only paper index (JS-injected, JS-gated) ----------
   Client 2026-07-24: text only — the four houses big in the VV face, then EXPLORE /
   STORY / BOOK as the small labels they were, a hairline between every row, all on
   paper. The photo-grid version is saved at git tag `menu-photo-grid-2026-07-24`
   (+ _BACKUPS/menu-photo-grid/) for the expected revert. */
.mmenu { display: none; }
@media (max-width: 860px) {
  /* when the injected menu is ready, it replaces the old plan grid */
  body.mmenu-ready .plan { display: none !important; }
  .mmenu {
    display: flex; flex-direction: column;
    min-height: 100%;
    /* dvh, not svh: with `inset: 0` AND a fixed svh height the page showed through under
       the last band as iOS collapsed Safari's toolbar (Hanna's phone, 2026-07-21). */
    position: fixed; inset: 0; z-index: 120; height: 100svh; height: 100dvh;
    /* Duncan 2026-07-27: sit the whole stack lower — it was starting hard against the top,
       tight under CLOSE and the status bar. box-sizing is border-box, so this comes out of
       the four flex:1 house rows (~7px each) and never off the fixed utility rows, and it
       cannot overflow 100dvh. CLOSE is absolutely positioned against the padding box, so it
       stays where it is and the gap below it opens up. */
    padding-top: calc(env(safe-area-inset-top, 0px) + 26px);
    background: var(--paper);
    opacity: 0; visibility: hidden; pointer-events: none;
    transition: opacity 0.4s ease;
  }
  body.plan-open .mmenu { opacity: 1; visibility: visible; pointer-events: auto; }
  .mmenu-close {
    position: absolute; top: 16px; right: 18px; z-index: 9;
    background: none; border: 0; cursor: pointer; font-family: inherit;
    color: var(--ink); font-size: 11px; letter-spacing: 0.22em;
    text-transform: uppercase; opacity: 0.85;
  }

  /* ============================================================================
     WORDMARK ON THE MENU — client 2026-08-16
     ----------------------------------------------------------------------------
     REVERT: flip MENU_MARK to false in js/main.js (one line). That drops the <a> AND
     the `.mmenu--mark` class, so every rule below stops applying and the panel returns
     to EXACTLY the 2026-07-27 geometry — 26px top pad, CLOSE at 16/18, seven bands.
     Nothing above this block was changed for it. Full revert: git tag
     menu-no-wordmark-2026-08-16.
     ----------------------------------------------------------------------------
     The mark keeps the VV in view while the menu is open. It is a SECOND, static copy of
     the logo, NEVER the header's: that one is a black PNG shown white by
     `brightness(0) invert(1)` with a gated transition, and on paper it must simply be ink
     (see the wordmark black-flash note). Plain <img>, not `.logo-mark`, so none of that
     filter machinery can reach it.
     Its height comes out of the four house rows — they are flex:1, the three utility rows
     are not — so the whole stack pushes down and nothing can overflow 100dvh.
     ============================================================================ */
  /* the stack starts at the HEADER's own mobile offset (18px) instead of the 26px breather
     that stood in for it, so the wordmark does not jump when the menu opens */
  .mmenu--mark { padding-top: calc(env(safe-area-inset-top, 0px) + 18px); }
  /* One dial for the mark and for the CLOSE that centres on it. 110px is the header's own
     mobile logo height, so on a normal phone the wordmark does not change size when the
     menu opens; it only gives ground on a short screen (SE, 667px), where a fixed 110 took
     the house names down to 45px and cost the apartments the dominance they are meant to
     have. */
  .mmenu--mark { --mmark: clamp(84px, 13vh, 110px); }
  /* CLOSE sits where MENU was: same column (var(--pad)) and centred on the mark, so the tap
     point does not move between opening and closing, and it clears the logo */
  .mmenu--mark .mmenu-close {
    top: calc(env(safe-area-inset-top, 0px) + 18px + var(--mmark) / 2);
    right: var(--pad); transform: translateY(-50%);
  }
  .mmenu-mark {
    flex: 0 0 auto; display: block; line-height: 0;
    margin: 0 auto; padding: 0 var(--pad) 20px;
    /* every other row on this panel is separated by the page hairline (client 2026-07-24),
       so the mark takes one too — it reads as the top row of the index, not as something
       floating above it */
    border-bottom: 1px solid var(--hair);
    width: 100%; box-sizing: border-box;
  }
  .mmenu-mark img {
    height: var(--mmark); width: auto; display: block; margin: 0 auto;
    /* full ink, NOT the header's 0.8 — that value exists to sit the mark back behind
       photography. On paper, beside ink house names, 0.8 just reads grey. */
    opacity: 1;
  }
  /* CLOSE moves to where MENU was — same column (var(--pad)) and centred on the mark, so
     the tap point does not move between opening and closing, and it clears the logo. */
  /* one column: four tall house rows, three short utility rows */
  .mgrid { flex: 1 1 auto; min-height: 0; display: flex; flex-direction: column; }
  .mgrid .mroom { flex: 1 1 0; min-height: 0; }
  .mstrip { flex: 0 0 auto; display: flex; flex-direction: column; }
  /* the three utility rows grow with their label (+13%, Duncan 2026-07-27); the four house
     rows are flex:1 so they give up exactly that height between them */
  .mstrip .mroom { flex: 0 0 auto; height: clamp(61px, 9vh, 81px); }
  .mroom {
    position: relative; overflow: hidden;
    display: flex; align-items: center; justify-content: center; text-decoration: none;
    background: none;
    -webkit-tap-highlight-color: transparent;
  }
  /* a line between ALL rows (client 2026-07-24) — the page hairline, on paper */
  .mgrid .mroom + .mroom,
  .mstrip .mroom { border-top: 1px solid var(--hair); }
  /* house names: the VV face, ink on paper, auto-fitted to a shared size by fitM() */
  .mnm {
    position: relative; z-index: 2; color: var(--ink);
    font-family: var(--display); font-weight: 400;
    text-transform: uppercase; letter-spacing: var(--display-track);
    line-height: 1; white-space: nowrap; font-size: 40px;
    transform: translateY(var(--ty, 0)) scale(1);
    transform-origin: center;
    text-shadow: 0 0 0 rgba(255, 255, 255, 0);
    transition: transform 0.3s cubic-bezier(0.2, 0.7, 0.2, 1), color 0.3s ease,
                text-shadow 0.3s ease;
  }
  /* utility labels (Explore/Story/Book): exactly as they were — small Switzer Light, ink */
  .mroom--util .mnm {
    font-family: "Switzer", "Helvetica Neue", Helvetica, Arial, sans-serif;
    font-weight: 300; font-size: 15.7px; letter-spacing: 0.14em; margin-right: -0.14em;   /* 12 → 13.8 (2026-07-24) → 15.7: another ~14% up (Duncan 2026-07-27) */
  }
  /* PRESS (Duncan 2026-07-27) — a real key, not a tint. The old state was a flat 6% wash
     eased over 0.3s in BOTH directions, which is why it read as nothing: a press has to
     SNAP down and RELEASE slow. Three things move together, all inside the locked paper/
     ink palette: the panel darkens and sinks (inset shadow at the top lip), the row takes
     a hair of travel, and the letters deboss — full ink with a paper-white highlight under
     each stroke, so the name looks stamped INTO the page rather than dimmed out. */
  /* Duncan 2026-07-27c: the wide soft version read as a GRADIENT, not a press, and was too
     heavy. What actually says "pressed" on flat paper is a flat even tone change plus one
     short tight shadow under the TOP edge only — the lip you'd see under a raised hairline.
     The long fall was the wrong instrument; small and crisp is the tell. Keep it quiet. */
  .mroom::after {
    content: ""; position: absolute; inset: 0; z-index: 1;
    background: rgba(24, 22, 17, 0.075);
    box-shadow: inset 0 4px 6px -4px rgba(24, 22, 17, 0.34);
    opacity: 0; transition: opacity 0.3s ease;
  }
  .mroom.is-press::after,
  .mroom:active::after { opacity: 1; transition-duration: 0.06s; }
  .mroom.is-press .mnm,
  .mroom:active .mnm {
    transform: translateY(calc(var(--ty, 0px) + 1px)) scale(0.99);
    color: #0f0d09;
    text-shadow: 0 1px 0 rgba(255, 255, 255, 0.42);
    transition-duration: 0.06s;
  }
  @media (prefers-reduced-motion: reduce) {
    /* keep the tone + deboss (the feedback), drop the travel */
    .mroom.is-press .mnm,
    .mroom:active .mnm { transform: translateY(var(--ty, 0)); }
  }
}

/* ---------- motion (parallax) toggle ---------- */
.motion-toggle {
  display: none;
  position: fixed;
  left: clamp(16px, 2.6vw, 40px);
  bottom: 22px;
  z-index: 95;
  background: none;
  border: 0;
  cursor: pointer;
  font-family: inherit;
  font-size: 9px;
  font-weight: 500;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--grey);
  opacity: 0.7;
  transition: opacity 0.3s;
}
.motion-toggle:hover { opacity: 1; }

.figure img, .still img { will-change: transform; }
.still img { transition: none; }

/* hero foot two-line left block */
.hero-foot .lines { display: flex; flex-direction: column; gap: 4px; }
.hero-foot .lines .cap { color: rgba(251, 249, 244, 0.7); }

/* close pinned right */
.plan-head { display: flex; justify-content: flex-end; padding: 22px 26px; }
.room:not(:has(img)):hover::after { content: none; }
.room:not(:has(img)):hover .label, .room--story:hover .cap, .room--checkin:hover .cap { color: var(--ink); }

/* logo mark only, centre stage */
.head .logo-mark { height: 44px; }
.head.scrolled .logo-mark { height: 34px; }
.foot .logo-mark { height: 52px; }

/* logo at 250% */
.head .logo-mark { height: 128px; }
.head.scrolled .logo-mark { height: 70px; }
.head { padding-top: 18px; padding-bottom: 18px; }
.head.scrolled { padding-top: 10px; padding-bottom: 10px; }
.foot .logo-mark { height: 130px; }

/* no rule under the scrolled header */
.head.scrolled { border-bottom: 0; }

/* header floats — no banner ever */
.head.scrolled {
  background: transparent;
  -webkit-backdrop-filter: none;
  backdrop-filter: none;
}

/* ============================================================
   v2.2 — fixed logo size + live black/white detection
   ============================================================ */
.head, .head.scrolled { padding-top: 18px; padding-bottom: 18px; }
.head .logo-mark, .head.scrolled .logo-mark { height: 110px; }
/* the big VV fades once you scroll into content — it was sitting on top of cards
   (Hanna, booking page). Mobile has always done this; now desktop matches. */
.head.scrolled .wordmark { opacity: 0; pointer-events: none; }
/* homepage: keep the logo through the whole scroll (Duncan) — the doors are full-bleed
   images, so it never sits on top of content the way the booking cards did */
@media (min-width: 861px) {
  body:has(#houses) .head.scrolled .wordmark { opacity: 1; pointer-events: auto; }
}

/* colour is driven only by what's behind each zone */
.head, .head.on-dark { color: var(--ink); }
.head .left a, .head .right a, .burger { color: var(--ink); }
.head .left.over-img a, .head .right.over-img a, .burger.over-img { color: #fbf9f4; }
/* No filter transition until the page has settled. The wordmark is a black PNG shown
   white via `brightness(0) invert(1)`; with a transition, the first-paint colour change
   ANIMATED black→white over 0.7s and that fade was the "black VV glitch" on the landing
   (Hanna 2026-07-23). `.ready` (added on load, one frame later) re-enables the transition
   so genuine section crossings still ease — but load and the seed snap instantly to white. */
.head .logo-mark { filter: none; }
.ready .head .logo-mark { transition: filter 0.7s var(--ease); }
.head .wordmark.lit .logo-mark { filter: brightness(0) invert(1); }   /* flat pure white, no halo */
/* logo flips white while a featured door is expanded (dark image showing) */
body:has(#houses .door.open) .head .logo-mark { filter: brightness(0) invert(1); }

/* parallax window: frame holds the ratio, image oversizes and moves inside it */
.pframe { overflow: hidden; position: relative; }
.pframe img {
  width: 100%;
  height: 120%;
  object-fit: cover;
  position: relative;
  top: -10%;
  transition: none;
}

/* ============================================================
   V3 — DOORWAYS
   Each house is a doorway you walk through.
   ============================================================ */

/* NO PIN. Each house is exactly one screen and scrolls past like any other section.
   It used to be a 170vh block with a 100svh sticky pane inside, so the viewport HELD while
   you scrolled through the extra 70vh — which reads as the page stopping. Chris/Hanna via
   Duncan 2026-07-21: "remove the stop on the open door animation, it just keeps scrolling
   — the client felt it was stopping and disrupting the flow." The aperture still opens as
   the house travels up the screen (openFloor in main.js ties it to that travel), so nothing
   is lost by dropping the pin — and the whole section is now 3 screens instead of 5.
   (History: 230vh → 170vh on 2026-07-17 for the same complaint. This finishes the job.) */
.door { height: 100svh; height: 100dvh; position: relative; }
.door-stick {
  position: relative;
  top: 0;
  /* dvh, not svh. svh is the SMALL viewport — the screen with Safari's toolbars showing.
     The moment iOS collapses the toolbar the visible screen gets taller than this frame,
     and the page background below it reads as an enormous bottom border (seen on Hanna's
     phone, 2026-07-21). dvh follows the viewport as the toolbar comes and goes, so the
     frame always fills the screen. svh first as the fallback for browsers without dvh. */
  height: 100svh;
  height: 100dvh;
  overflow: hidden;
}
.door-frame {
  position: absolute;
  inset: 0;
  /* JS animates the aperture via clip-path */
  clip-path: inset(16svh 36vw 16svh 36vw);
  will-change: clip-path;      /* keep the aperture on its own layer so the per-frame repaint stays off the main thread (Hanna jitter, 2026-07-20) */
  contain: paint;
}
.door-frame img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  transform: scale(1.08) translateZ(0);
  backface-visibility: hidden;
}
.door-meta {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  transition: opacity 0.5s var(--ease);
}
.door-meta--l { left: var(--pad); text-align: left; }
.door-meta--r { right: var(--pad); text-align: right; }
.door-meta h3 {
  font-size: 13px;
  font-weight: 500;
  letter-spacing: 0.3em;
  text-transform: uppercase;
  margin-bottom: 10px;
}
.door-meta .cap { display: block; max-width: 15em; }
.door-meta--l .cap { margin-left: 0; }
.door-hint {
  position: absolute;
  bottom: 30px;
  left: 0;
  right: 0;
  text-align: center;
  transition: opacity 0.5s var(--ease);
}
/* landing-video scroll cue (Chris 2026-07-13: suggest scroll-down after the video) */
.arrival-cue {
  display: inline-flex; align-self: center; align-items: center; color: #fff; text-decoration: none; z-index: 4;
  filter: drop-shadow(0 2px 12px rgba(0,0,0,0.55));
  animation: vv-cue-bob 2.4s ease-in-out infinite;
}
.arrival-cue .chev { display: block; width: 30px; height: 16px; }
@keyframes vv-cue-bob { 0%, 100% { transform: translateY(-2px); } 50% { transform: translateY(2px); } }
body.motion-off .arrival-cue { animation: none; }
.door-name {
  position: absolute;
  left: var(--pad);
  bottom: 34px;
  color: var(--paper);
  font-size: clamp(30px, 4.4vw, 64px);
  font-weight: 300;
  letter-spacing: 0.02em;
  opacity: 0;
  transition: opacity 0.5s var(--ease);
  z-index: 2;
}
.door-enter {
  position: absolute;
  right: var(--pad);
  bottom: 44px;
  z-index: 2;
  color: var(--paper);
  border-bottom-color: color-mix(in srgb, var(--paper) 50%, transparent);
  opacity: 0;
  transition: opacity 0.5s var(--ease);
}
.door.open .door-name, .door.open .door-enter { opacity: 1; }
.door.open .door-meta, .door.open .door-hint { opacity: 0; }

@media (max-width: 860px) {
  /* image sits higher with a clear band beneath; caption sits neatly below the photo
     (bigger + more legible), then fades as the card unfolds on scroll */
  /* shorter still on a phone, where the same scroll cost feels longer (thumb, not wheel) */
  .door { height: 100svh; height: 100dvh; }   /* no pin on phone either */
  .door-frame { clip-path: inset(20svh 16vw 34svh 16vw); }
  .door-meta--r { display: none; }
  .door-meta--l {
    top: auto; bottom: clamp(40px, 6vh, 74px); transform: none;
    left: calc(clamp(40px, 6vw, 96px) + 2px); right: var(--pad);   /* in line with the door name's ink (Duncan 2026-07-19) */
  }
  .door-meta--l h3 { font-size: 16px; margin-bottom: 10px; }
  .door-meta--l .cap { font-size: 16.5px; line-height: 1.5; max-width: 26em; color: var(--ink); }
  /* keep the caption below the big title when the door opens — colour transitions to
     cream via a plain CSS fade (Duncan 2026-07-19). Text only; timing untouched. */
  .door-meta--l .cap { transition: color 0.75s cubic-bezier(0.25, 0, 0.25, 1); }   /* soft ease-out, not harsh, not late (Duncan 2026-07-20) */
  .door.open .door-meta--l { opacity: 1; }
  .door.open .door-meta--l h3 { opacity: 0; transition: opacity 0.4s var(--ease); }
  .door.open .door-meta--l .cap { color: var(--paper); text-shadow: 0 1px 14px rgba(0,0,0,0.5); }
  /* Duncan 2026-07-19: keep the caption that's already there over the photo in the page
     colour when the door opens — WITHOUT its title (the door name already shows). JS
     skips .door-meta--l on mobile so it doesn't fade out; CSS holds it and recolours. */
}

/* ============================================================
   V3 — MALTA LIGHT
   The paper keeps Malta time, quietly.
   ============================================================ */
html[data-light="dawn"]   body { background: #f6f5f3; }
html[data-light="day"]    body { background: #fbf9f4; }
html[data-light="golden"] body { background: #fcf5e9; }
html[data-light="dusk"]   body { background: #f7f1e6; }
html[data-light="night"]  body { background: #f2eee5; }
body { transition: background 2s linear; }

/* slightly deeper light steps so the day reads in a demo */
html[data-light="dawn"]   body { background: #f4f4f2; }
html[data-light="golden"] body { background: #fdf2e0; }
html[data-light="dusk"]   body { background: #f6eedd; }
html[data-light="night"]  body { background: #efeade; }

/* keep the hero letters the exact colour of the paper (never pure white) */
html[data-light="dawn"]   body { --paper: #f4f4f2; }
html[data-light="day"]    body { --paper: #fbf9f4; }
html[data-light="golden"] body { --paper: #fdf2e0; }
html[data-light="dusk"]   body { --paper: #f6eedd; }
html[data-light="night"]  body { --paper: #efeade; }

/* explore list with drifting thumbnails */
.qlist--ph { max-width: 720px; }
.qlist--ph .row {
  display: grid;
  grid-template-columns: 84px minmax(0, 1fr) auto;
  gap: 26px;
  align-items: center;
  padding: 14px 0;
}
.qthumb { width: 84px; }
.qthumb .pframe img { transition: none; }
@media (max-width: 860px) {
  .qlist--ph .row { grid-template-columns: 64px 1fr; }
  .qlist--ph .row .cap { grid-column: 2; text-align: left; }
  .qthumb { width: 64px; grid-row: span 2; }
}

/* ============================================================
   THE WALK — explore along a line, thumbnails floating off it
   ============================================================ */
.walk { position: relative; max-width: 1020px; margin: 0 auto; padding: 30px 0; }
.walk::before {
  content: "";
  position: absolute;
  left: 50%;
  top: 0;
  bottom: 0;
  width: 1px;
  background: var(--hair);
}
.walk-line {
  position: absolute;
  left: 50%;
  top: 0;
  width: 1px;
  height: 0;
  background: var(--ink);
  z-index: 1;
}

.stop {
  position: relative;
  display: grid;
  grid-template-columns: 1fr 1fr;
  column-gap: clamp(40px, 6vw, 90px);
  align-items: center;
  padding: clamp(34px, 6vh, 64px) 0;
}
/* tick where the stop meets the line */
.stop::before {
  content: "";
  position: absolute;
  left: 50%;
  top: 50%;
  width: 22px;
  height: 1px;
  background: var(--ink);
  transform: translate(-50%, -50%);
  z-index: 2;
}

/* photo floats one side, words the other — alternating */
.stop .stop-ph { justify-self: end; }
.stop .stop-info { text-align: left; }
.stop:nth-child(odd) .stop-ph { order: 2; justify-self: start; }
.stop:nth-child(odd) .stop-info { order: 1; text-align: right; justify-self: end; }

/* floating: varied sizes, varied vertical offsets */
.stop-ph.w-s { width: clamp(110px, 12vw, 150px); }
.stop-ph.w-m { width: clamp(150px, 16vw, 200px); }
.stop-ph.w-l { width: clamp(190px, 21vw, 260px); }
.stop:nth-child(2) .stop-ph { transform: translateY(-16px); }
.stop:nth-child(3) .stop-ph { transform: translateY(20px); }
.stop:nth-child(4) .stop-ph { transform: translateY(-10px); }
.stop:nth-child(5) .stop-ph { transform: translateY(26px); }
.stop:nth-child(6) .stop-ph { transform: translateY(-22px); }

.stop-info { max-width: 24em; }
.stop-info h3 { margin-bottom: 8px; }
.stop-info .cap { display: block; max-width: 28em; }

@media (max-width: 860px) {
  .walk::before, .walk-line { left: 12px; }
  .stop, .stop:nth-child(odd) {
    grid-template-columns: 1fr;
    row-gap: 14px;
    padding-left: 44px;
  }
  .stop::before { left: 12px; transform: translate(0, -50%); width: 16px; }
  .stop .stop-ph, .stop:nth-child(odd) .stop-ph { order: 1; justify-self: start; transform: none; }
  .stop .stop-info, .stop:nth-child(odd) .stop-info { order: 2; text-align: left; justify-self: start; }
}

/* ============================================================
   V4 — OBJECT STORIES (hotspots on the photography)
   ============================================================ */
.spot {
  position: absolute;
  z-index: 5;
  width: 30px;
  height: 30px;
  margin: -15px 0 0 -15px;
  border-radius: 50%;
  border: 0;
  background: transparent;
  cursor: pointer;
  padding: 0;
}
.spot::before {
  content: "";
  position: absolute;
  inset: 8px;
  border-radius: 50%;
  border: 1px solid rgba(251, 249, 244, 0.95);
  background: rgba(24, 22, 17, 0.28);
  transition: transform 0.4s var(--ease), background 0.4s;
}
.spot:hover::before, .spot.open::before { transform: scale(1.35); background: rgba(24, 22, 17, 0.5); }
.spot-card {
  position: absolute;
  z-index: 6;
  bottom: calc(100% + 12px);
  left: 50%;
  transform: translateX(-50%) translateY(6px);
  width: 215px;
  background: var(--bg);
  padding: 14px 16px;
  font-size: 12px;
  line-height: 1.65;
  letter-spacing: 0.02em;
  color: var(--ink);
  box-shadow: 0 10px 34px rgba(24, 22, 17, 0.14);
  opacity: 0;
  visibility: hidden;
  transition: opacity 0.4s var(--ease), transform 0.4s var(--ease), visibility 0s 0.4s;
  pointer-events: none;
  text-align: left;
}
.spot-card .label { display: block; margin-bottom: 5px; font-size: 9px; }
.spot.open .spot-card, .spot:hover .spot-card {
  opacity: 1;
  visibility: visible;
  transform: translateX(-50%) translateY(0);
  transition-delay: 0s;
}
.spot-card.below { bottom: auto; top: calc(100% + 12px); }

/* ============================================================
   V4 — A DAY AT SCALA (the page's light follows the hours)
   ============================================================ */
.day { max-width: 1020px; margin: 0 auto; }
.day-row {
  display: grid;
  grid-template-columns: 7rem minmax(0, 1fr) minmax(0, 240px);
  gap: clamp(24px, 4vw, 60px);
  align-items: center;
  min-height: 58vh;
  border-top: 1px solid var(--hair-soft);
}
.day-row:nth-child(even) { grid-template-columns: 7rem minmax(0, 240px) minmax(0, 1fr); }
.day-row:nth-child(even) .day-line { order: 3; text-align: right; }
.day-row:nth-child(even) .day-ph { order: 2; }
.day-time {
  font-size: clamp(20px, 2.2vw, 28px);
  font-weight: 300;
  font-variant-numeric: tabular-nums;
  color: var(--grey);
}
.day-line { font-size: clamp(18px, 2vw, 26px); font-weight: 300; line-height: 1.4; max-width: 17em; }
.day-ph { width: 100%; max-width: 240px; }
@media (max-width: 860px) {
  .day-row, .day-row:nth-child(even) { grid-template-columns: 4.5rem 1fr; min-height: 46vh; }
  .day-ph, .day-row:nth-child(even) .day-ph { grid-column: 2; order: 0; max-width: 180px; }
  .day-row:nth-child(even) .day-line { order: 0; text-align: left; }
}

/* ============================================================
   V4 — MARGIN NOTES (the guest book, in the margins)
   ============================================================ */
.mnote {
  position: absolute;
  max-width: 15em;
  font-size: 12.5px;
  font-style: italic;
  line-height: 1.7;
  color: var(--grey);
  z-index: 3;
}
.mnote small { display: block; font-style: normal; font-size: 9px; letter-spacing: 0.18em; text-transform: uppercase; margin-top: 8px; color: var(--ink-40, rgba(24,22,17,.4)); }
@media (max-width: 1100px) { .mnote { display: none; } }

/* ============================================================
   V4 — LIVING CITY LINE
   ============================================================ */
.city-live { margin-bottom: 26px; }

/* ============================================================
   V4.1 — THE STORY FLOWS (draggable years)
   ============================================================ */
.flow-wrap { position: relative; margin-left: calc(-1 * var(--pad)); margin-right: calc(-1 * var(--pad)); }   /* full-bleed: timeline cuts at the viewport edge, not the wrap padding (Duncan 2026-07-18) */
.flow {
  display: flex;
  gap: clamp(30px, 4vw, 70px);
  overflow-x: auto;
  scroll-snap-type: x proximity;
  padding: 40px var(--pad) 30px;
  scrollbar-width: none;
  cursor: grab;
  position: relative;
}
/* the thread runs on past the last year and feathers out — the story isn't finished */
.era--tail { flex: none; width: clamp(120px, 16vw, 240px); position: relative; padding-top: 110px; }
.era--tail::after { display: none; }
.era--tail::before {
  content: "";
  position: absolute;
  top: 88px;
  left: calc(-1 * clamp(30px, 4vw, 70px));
  right: 0;
  height: 1px;
  background: linear-gradient(to right, var(--hair) 0, var(--hair) 30%, transparent 100%);
}
.flow::-webkit-scrollbar { display: none; }
.era {
  flex: none;
  width: clamp(220px, 24vw, 320px);
  scroll-snap-align: center;
  position: relative;
  padding-top: 110px;
}
/* the thread the years hang from */
.era::before {
  content: "";
  position: absolute;
  top: 88px;
  left: calc(-1 * clamp(30px, 4vw, 70px));
  right: 0;
  height: 1px;
  background: var(--hair);
}
.era::after {
  content: "";
  position: absolute;
  top: 84px;
  left: 0;
  width: 9px;
  height: 9px;
  border-radius: 50%;
  border: 1px solid var(--ink);
  background: var(--bg);
}
.era-year {
  position: absolute;
  top: 18px;
  left: -4px;
  font-size: clamp(38px, 4.4vw, 60px);
  font-weight: 300;
  color: var(--ink);
  letter-spacing: -0.01em;
  line-height: 1;
}
.era p { font-size: 13.5px; color: var(--grey); margin: 16px 0 0; max-width: 22em; }
.era .figure { margin-top: 22px; }
.era:nth-child(odd) .figure { margin-top: 38px; }
.era--next .era-year { color: var(--grey); font-style: italic; font-size: clamp(26px, 3vw, 40px); top: 40px; }
.flow-hint { text-align: center; margin-top: 10px; }

/* ============================================================
   V4.2 — THE BIG NAMES (Zodiak serif, display only)
   ============================================================ */
:root { --serif: "Switzer", "Helvetica Neue", Helvetica, Arial, sans-serif; }   /* was Zodiak; site is two typefaces only — VV Display + Switzer (Duncan 2026-07-18). Token name kept so all var(--serif) users resolve to the sans. */

/* VV Display — wooden-letter display face (from VV_FONT_SVG), used on main headings */
@font-face {
  font-family: "VV Display";
  src: url("../assets/fonts/VV-Display.woff2?v=3") format("woff2"),
       url("../assets/fonts/VV-Display.ttf?v=3") format("truetype");
  font-weight: 400;
  font-display: swap;
}
/* FOFT: never flash the serif fallback at title size. The big display titles stay
   hidden until "VV Display" is actually applied (js/main.js adds .vv-fonts-ready on
   document.fonts.ready — the same signal the title-fitter waits for, so what you see
   always matches what the fitter measured). A JS timeout reveals them regardless, so a
   font failure can never leave a title blank. (Duncan: old font flashed on hard refresh.) */
html:not(.vv-fonts-ready) .door-name,
html:not(.vv-fonts-ready) .suite-head h2,
html:not(.vv-fonts-ready) .cta-next h2,
html:not(.vv-fonts-ready) .gsec h2,
/* (footer headline is Switzer now, so it no longer waits on the display font) */
/* ── VV DISPLAY CONTROLS ─────────────────────────────────────────
   To revert titles to the old serif: set  --display: var(--serif);
   Kerning + how much of the page the title fills are here too.      */
:root {
  --display: "VV Display", "Switzer", "Helvetica Neue", Arial, sans-serif;   /* VV Display; sans fallback (never Zodiak) */
  --display-track: -0.06em;                    /* kerning */
  --display-fill: 1.40;                         /* width safety-cap (long words won't bleed past this) */
  --display-maxh: 0.82;                         /* hero title HEIGHT — every title this tall, never overflows */
  --name-size: clamp(88px, 12vw, 190px);       /* in-page apartment name size (intro name + next-home, matched) */
}
/* mobile only: title fits the screen width instead of bleeding 40% off both edges.
   Desktop is limited by height (--display-maxh), so this does NOT change desktop titles. */
@media (max-width: 860px) {
  :root { --display-fill: 0.86; }   /* phone: title centred with a small even margin, no ink clipping */
}

/* footer headline only — wooden face (font now has , and .). Scoped so the mid-page .fi-title stays serif. */
/* The footer headline is set like the page statements ("Stay long enough to watch the
   light change.") — Switzer light, sentence case — not the wooden display face
   (Duncan 2026-07-21: "use this everywhere including footer main text). */
.foot-ink .fi-title {
  font-family: inherit;
  letter-spacing: normal;
  text-transform: none;
  font-weight: 300;
  font-size: clamp(27px, 3.6vw, 50px);   /* == .h-lg, the statement scale */
  line-height: 1.15;
  max-width: 16em;
}

/* doorway names span the page, Kinfolk-scale */
.door-name {
  font-family: var(--display);
  font-weight: 400;
  left: 0;
  right: 0;
  bottom: -0.05em;
  text-align: center;
  /* conservative fallback; the JS fitter overrides font-size inline to fill the page. */
  font-size: clamp(40px, 11vw, 210px);
  line-height: 1;
  letter-spacing: var(--display-track);
  text-transform: uppercase;
  white-space: nowrap;
  overflow: hidden;
}
.door-enter { bottom: auto; top: 96px; }

/* suite / intro apartment names — wooden display face (all-caps, single word) */
.suite-head h2 {
  font-family: var(--display);
  font-weight: 400;
  font-size: var(--name-size);
  line-height: 0.95;
  letter-spacing: var(--display-track);
  text-transform: uppercase;
}

/* the years flow in the same voice */
.era-year { font-family: var(--serif); font-weight: 300; }

/* ============================================================
   V4.2 — INK END OF PAGE (weight + SEO)
   ============================================================ */
.foot-ink {
  background: #14110c;
  color: #f4efe6;
  margin-top: clamp(24px, 3.5vh, 44px);
  overflow: hidden;
}
.foot-ink a { color: inherit; }
/* Four columns: description, then SLEEP / VISIT / CONTACT beside it. (Full-width
   paragraph was tried 2026-07-21 and reverted — Duncan: "go to what we had before".) */
.fi-top {
  display: grid;
  grid-template-columns: minmax(0, 1.5fr) repeat(3, minmax(0, 1fr));
  gap: clamp(30px, 4vw, 60px);
  padding-top: clamp(70px, 12vh, 130px);
  padding-bottom: clamp(50px, 8vh, 90px);
}
.fi-title {
  grid-column: 1 / -1;
  font-family: var(--serif);
  font-weight: 300;
  font-size: clamp(40px, 6.5vw, 96px);
  line-height: 1.05;
  letter-spacing: 0.01em;
  max-width: 11em;
  margin-bottom: clamp(20px, 4vh, 50px);
}
.fi-desc { font-size: 14px; line-height: 1.8; color: rgba(244, 239, 230, 0.66); max-width: 30em; }
.fi-index { display: none; }   /* desktop uses the fixed .lift; mobile mirrors it into the footer */
.fi-col .label { display: block; color: rgba(244, 239, 230, 0.5); margin-bottom: 16px; }
.fi-col ul { list-style: none; }
.fi-col li { margin-bottom: 9px; }
.fi-col li a {
  font-size: 13.5px;
  color: rgba(244, 239, 230, 0.85);
  transition: color 0.3s;
}
.fi-col li a:hover { color: #fff; }
.foot-ink .city-live { text-align: center; color: rgba(244, 239, 230, 0.6); padding: 0 var(--pad); margin: 0 0 30px; }
.fi-legal {
  display: flex;
  justify-content: space-between;
  gap: 16px;
  flex-wrap: wrap;
  padding: 20px var(--pad);
  border-top: 1px solid rgba(244, 239, 230, 0.14);
  font-size: 11px;
  letter-spacing: 0.06em;
  color: rgba(244, 239, 230, 0.5);
}
.fi-mark {
  font-family: var(--serif);
  font-weight: 300;
  font-size: clamp(72px, 13.5vw, 215px);
  line-height: 1;
  letter-spacing: 0.02em;
  text-transform: uppercase;
  text-align: center;
  white-space: nowrap;
  margin-bottom: -0.18em;
  color: #f4efe6;
  user-select: none;
}
@media (max-width: 860px) {
  /* PHONE: the open photo leaves the same band of page colour the desktop does, and the
     line sits in it in black — not cream over the picture (Duncan 2026-07-21: "add a
     border on the bottom, same background colour, and place the text on top in black").
     This overrides the 2026-07-19 cream-on-open recolour further up. */
  #houses .door.open .door-meta--l .cap {
    color: var(--ink);
    text-shadow: none;
  }
  /* The small letterspaced "A L T A N A" above the line goes — in EVERY state, open or
     closed. First attempt scoped this to `.door.open`, which left it showing on the
     closed door, which is exactly where Duncan kept seeing it. His words: "the small
     ALTANA above the description should never be there." The big title carries the name. */
  #houses .door-meta--l h3 { display: none !important; }

  .fi-top { grid-template-columns: 1fr 1fr; }
  .fi-title { grid-column: 1 / -1; }
  /* footer headline (VV Display): the clamp floors at 40px on phone — too small; give it size */
  /* left, not centred — it sits on the same column as the footer copy beneath it
     (Duncan 2026-07-21: "move THE ALTERNATIVE line to the left") */
  .foot-ink .fi-title { font-size: clamp(27px, 7vw, 40px); line-height: 1.15; max-width: none; text-align: left; }
  .fi-desc { grid-column: 1 / -1; }
  /* the floor "scale" — brought into the footer here since the fixed .lift is hidden on mobile */
  .fi-index { display: flex; flex-wrap: wrap; gap: 12px 18px; align-items: center; justify-content: center; grid-column: 1 / -1;
    margin: 16px 0 30px; padding-bottom: 30px; border-bottom: 1px solid rgba(244, 239, 230, 0.12); }
  .fi-index a { font-size: 9px; letter-spacing: 0.18em; text-transform: uppercase; color: rgba(244, 239, 230, 0.55);
    text-decoration: none; display: flex; align-items: center; gap: 7px; }
  .fi-index a::after { content: ""; width: 12px; height: 1px; background: rgba(244, 239, 230, 0.35); }
  .fi-index a:last-child::after { display: none; }
}

/* fit the big name to the page width, crop at the very bottom edge */
.fi-mark { font-size: clamp(40px, 9.6vw, 158px); letter-spacing: 0; margin-bottom: -0.16em; }

/* footer ends on the mark itself */
.fi-logo {
  display: block;
  height: clamp(150px, 24vw, 330px);
  width: auto;
  margin: clamp(40px, 8vh, 90px) auto clamp(50px, 9vh, 100px);
  filter: invert(1) brightness(1.85);
}

/* door names: JS fits them edge to edge */
.door-name {
  line-height: 0.82;
  bottom: -0.11em;   /* sit letters on the base (hero pages + index, matches #houses) */
  letter-spacing: var(--display-track);
  padding: 0;
}

/* one mark at a time */
.wordmark { transition: opacity 0.5s var(--ease); }
.wordmark.cede { opacity: 0; pointer-events: none; }

/* ============================================================
   V4.3 — full site: guide lists + check-in
   ============================================================ */
.gsec { max-width: 880px; margin: 0 auto; padding-bottom: clamp(50px, 9vh, 90px); }
/* Set like the page statements — Switzer light, sentence case — not the wooden display
   face (Duncan 2026-07-21, same move as the footer headline). */
.gsec h2 {
  font-family: inherit;
  font-weight: 300;
  font-size: clamp(30px, 3.6vw, 50px);
  text-transform: none;
  letter-spacing: normal;
  margin-bottom: clamp(24px, 4vh, 44px);
}
/* editorial image beside each guide-section title (client: "one or two next to the title") */
.gsec-head { display: flex; align-items: flex-end; justify-content: space-between; gap: clamp(20px, 4vw, 56px); margin-bottom: clamp(24px, 4vh, 44px); }
.gsec-head h2 { margin-bottom: 0; }
.gsec-fig { flex: none; width: clamp(120px, 20vw, 230px); margin: 0; align-self: flex-end; }
.gsec-fig img { width: 100%; height: auto; display: block; aspect-ratio: 5 / 4; object-fit: cover; border-radius: 2px; }
@media (max-width: 860px) {
  .gsec-head { gap: 16px; align-items: flex-end; }
  .gsec-fig { width: clamp(96px, 26vw, 130px); }
}
.gsec .row {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 26px;
  padding: 15px 0;
  border-top: 1px solid var(--hair-soft);
}
.gsec .row .n { font-size: 14px; font-weight: 500; letter-spacing: 0.03em; flex: none; }
.gsec .row .cap { text-align: right; }
.gsec .gnote { font-style: italic; color: var(--grey); font-size: 13px; margin: 26px 0 6px; }

/* check-in */
.steps { display: flex; gap: 0; border: 1px solid var(--hair); border-radius: 2px; overflow: hidden; width: fit-content; margin: 0 auto clamp(40px, 7vh, 70px); }
.steps button {
  font-family: inherit;
  font-size: 10px;
  font-weight: 500;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  background: none;
  border: 0;
  color: var(--grey);
  padding: 13px 22px;
  cursor: pointer;
  transition: background 0.4s, color 0.4s;
}
.steps button.done { color: var(--ink); }
.steps button.current { background: var(--ink); color: var(--bg); }
.ck-panel { display: none; max-width: 640px; margin: 0 auto; }
.ck-panel.current { display: block; animation: ckIn 0.6s var(--ease); }
@keyframes ckIn { from { opacity: 0; transform: translateY(14px); } }
.field { margin-bottom: 26px; }
.field label { display: block; font-size: 10px; font-weight: 500; letter-spacing: 0.2em; text-transform: uppercase; color: var(--grey); margin-bottom: 9px; }
.field input, .field select {
  width: 100%;
  background: transparent;
  border: 0;
  border-bottom: 1px solid var(--hair);
  font-family: inherit;
  font-size: 16px;
  color: var(--ink);
  padding: 9px 0;
  border-radius: 0;
  outline: none;
  transition: border-color 0.4s;
}
.field input:focus, .field select:focus { border-color: var(--ink); }
.field input::placeholder { color: rgba(24, 22, 17, 0.35); }
/* required-field validation (monochrome — emphasis via weight + a solid ink rule, no colour) */
.field.invalid input, .field.invalid select { border-color: var(--ink); border-bottom-width: 2px; }
.field.invalid label { color: var(--ink); }
.field.invalid label::after { content: " — required"; font-weight: 400; opacity: 0.55; }
.field.invalid .upload { border-style: solid; border-color: var(--ink); }
.field-row { display: grid; grid-template-columns: 1fr 1fr; gap: 36px; }
.upload { border: 1px dashed var(--hair); padding: 40px 24px; text-align: center; color: var(--grey); font-size: 13.5px; cursor: pointer; transition: border-color 0.4s; }
.upload:hover { border-color: var(--ink); }
.upload.has-file { border-style: solid; border-color: var(--ink); color: var(--ink); }
.secure-note { display: flex; gap: 12px; align-items: flex-start; margin-top: 20px; font-size: 12px; line-height: 1.7; color: var(--grey); }
/* 2px is the standard corner on this site, and every button now uses it. Duncan
   2026-09-10: "make all buttons rounded corners standard!!! ... BOOK button is right -
   this SEE AVAILABLE HOMES is too round." This was the 100px full pill, which is what
   made it the odd one out. Circular icon controls (the month arrows, the guest stepper)
   and the calendar day cells stay round: they are circles by design, not rectangles with
   a radius. */
.pillbtn {
  font-family: inherit; font-size: 10px; font-weight: 500; letter-spacing: 0.2em; text-transform: uppercase;
  border: 1px solid var(--ink); background: var(--ink); color: var(--bg);
  border-radius: 2px; padding: 14px 30px; cursor: pointer; transition: background 0.4s, color 0.4s;
}
.pillbtn:hover { background: transparent; color: var(--ink); }
.pillbtn--ghost { background: transparent; color: var(--ink); margin-right: 10px; }
@media (max-width: 700px) { .field-row { grid-template-columns: 1fr; gap: 0; } .steps button { padding: 12px 13px; } }

/* ============================================================
   V4.4 — the walk floats
   ============================================================ */
.stop-ph .pframe {
  animation: bob var(--bobd, 7.5s) ease-in-out infinite alternate;
  animation-delay: var(--bobo, 0s);
  will-change: transform;
}
@keyframes bob {
  from { transform: translateY(-8px); }
  to { transform: translateY(10px); }
}
.stop:nth-child(2) .pframe { --bobd: 6.4s; --bobo: -1.2s; }
.stop:nth-child(3) .pframe { --bobd: 8.2s; --bobo: -3.1s; }
.stop:nth-child(4) .pframe { --bobd: 7s;   --bobo: -2.2s; }
.stop:nth-child(5) .pframe { --bobd: 8.8s; --bobo: -0.6s; }
.stop:nth-child(6) .pframe { --bobd: 6.8s; --bobo: -4s; }
body.motion-off .stop-ph .pframe { animation: none; }
.stop-ph { will-change: transform; transition: none; }

/* an open door is an entrance */
.door.open .door-stick { cursor: pointer; }
.door.open .door-name { pointer-events: none; }
.door-enter {
  font-size: 11px;
  padding-bottom: 6px;
  border-bottom-color: rgba(251, 249, 244, 0.7);
}
.door .door-stick { cursor: pointer; }

/* ============================================================
   V4.5 — THE PROMENADE (strips become a pinned travel)
   ============================================================ */
.prom { position: relative; }
.prom-pin {
  position: sticky;
  top: 0;
  height: 100svh;
  overflow: hidden;
  display: flex;
  align-items: center;
}
.prom-head {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  padding: 110px var(--pad) 0;
  z-index: 3;
}
.prom-track {
  display: flex;
  align-items: center;
  gap: clamp(28px, 4vw, 70px);
  padding: 0 var(--pad);
  will-change: transform;
}
.pr-item { flex: none; position: relative; }
.pr-frame { overflow: hidden; }
.pr-frame img {
  height: 100%;
  width: auto;
  display: block;
  will-change: transform;
}
.prom-count {
  position: absolute;
  bottom: 34px;
  left: var(--pad);
  z-index: 3;
  font-family: var(--serif);
  font-weight: 300;
  font-size: clamp(28px, 3vw, 44px);
  color: var(--ink);
  font-variant-numeric: tabular-nums;
}
.prom-count .of { color: var(--grey); font-size: 0.55em; letter-spacing: 0.08em; }

/* ============================================================
   V4.5 — THE LIGHTBOX (a photograph becomes the room)
   ============================================================ */
.pframe img, .strip img, .pr-frame img, .qthumb img { cursor: pointer; }
.lb {
  position: fixed;
  inset: 0;
  z-index: 3000;
  background: rgba(16, 13, 9, 0);
  visibility: hidden;
  transition: background 0.6s var(--ease), visibility 0s 0.6s;
}
.lb.on {
  background: rgba(16, 13, 9, 0.94);
  visibility: visible;
  transition-delay: 0s;
}
.lb img {
  position: absolute;
  transform-origin: top left;
  will-change: transform, opacity;
  cursor: auto;
  /* the photo must never intercept taps meant for Close / nav / backdrop — without
     this it sat on top (appended last) and swallowed taps, so Close took several
     presses on mobile (Hanna — "close button needed three presses") */
  pointer-events: none;
}
.lb-cap {
  position: absolute;
  left: var(--pad);
  bottom: 30px;
  color: rgba(244, 239, 230, 0.85);
  font-size: 11px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  opacity: 0;
  transition: opacity 0.5s 0.3s;
}
.lb.on .lb-cap { opacity: 1; }
.lb-count {
  position: absolute;
  right: var(--pad);
  bottom: 26px;
  color: rgba(244, 239, 230, 0.85);
  font-family: "Switzer", "Helvetica Neue", Helvetica, Arial, sans-serif;   /* plain sans, not the serif — Duncan 2026-07-18 */
  font-weight: 300;
  font-size: 15px;
  font-variant-numeric: tabular-nums;
  opacity: 0;
  transition: opacity 0.5s 0.3s;
}
.lb.on .lb-count { opacity: 1; }
.lb-close {
  position: absolute;
  top: 18px;
  right: calc(var(--pad) - 12px);
  z-index: 10;                 /* always above the nav zones and the photo */
  background: none;
  border: 0;
  color: rgba(244, 239, 230, 0.9);
  font-family: inherit;
  font-size: 10px;
  font-weight: 500;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  cursor: pointer;
  padding: 14px 16px;          /* a real tap target — was a tiny 10px hit area */
  opacity: 0;
  transition: opacity 0.5s 0.3s;
}
.lb.on .lb-close { opacity: 1; }
.lb-nav {
  position: absolute;
  top: 0;
  bottom: 0;
  width: 50%;
  background: none;
  border: 0;
  cursor: w-resize;
}
.lb-nav.next { right: 0; cursor: e-resize; }
.lb-nav.prev { left: 0; }
/* visible arrow glyphs so guests know photos can be changed (Hanna's note) */
.lb-nav span {
  position: absolute; top: 50%; transform: translateY(-50%);
  font-size: 34px; line-height: 1; font-weight: 300;
  color: rgba(251, 249, 244, 0.55);
  width: 56px; height: 56px; display: flex; align-items: center; justify-content: center;
  opacity: 0; transition: opacity .5s var(--ease), color .3s;
}
.lb-nav.prev span { left: 14px; }
.lb-nav.next span { right: 14px; }
.lb.on .lb-nav span { opacity: 1; }
.lb-nav:hover span { color: #fbf9f4; }
@media (max-width: 860px) { .lb-nav span { font-size: 40px; color: rgba(251,249,244,0.7); } }
body.lb-open { overflow: hidden; }
body.lb-open .head, body.lb-open .lift, body.lb-open .motion-toggle { opacity: 0; pointer-events: none; }
.head, .lift, .motion-toggle { transition: opacity 0.4s; }

/* ============================================================
   V4.6 — THE PASSAGE (galleries flow vertically, editorial)
   ============================================================ */
.passage { padding: clamp(60px, 11vh, 130px) 0 0; }
.passage .bleed { width: 100%; margin: 0 0 clamp(90px, 16vh, 200px); }
.passage .bleed .pframe { height: min(94svh, 60vw); }
.passage-pair { margin-bottom: clamp(90px, 16vh, 200px); align-items: center; }
.passage-pair .col-b, .passage-pair .col-d { padding-top: 0; transform: translateY(clamp(40px, 9vh, 90px)); }

/* pairs breathe at different tempos */
.passage-pair .col-a .pframe, .passage-pair .col-c .pframe {
  animation: bob 9s ease-in-out -2.4s infinite alternate;
}
.passage-pair .col-b .pframe, .passage-pair .col-d .pframe {
  animation: bob 6.6s ease-in-out -1.1s infinite alternate;
}
body.motion-off .passage-pair .pframe { animation: none; }
.passage-pair .figure { will-change: transform; }

/* house page heroes carry their name (doorway state hid it) */
.hero .door-name { opacity: 1; }

/* way back + bookable pill links */
.hero-back {
  position: absolute;
  top: 140px;
  left: var(--pad);
  z-index: 3;
  color: #fbf9f4;
  border-bottom-color: rgba(251, 249, 244, 0.55);
}
a.pillbtn { display: inline-block; text-decoration: none; }

/* walk photos: no idle bob — one calm drift only */
.stop-ph .pframe { animation: none; }

/* the years: smaller, same voice as the rest (the serif numerals read as another face) */
.era-year {
  font-family: inherit;
  font-weight: 300;
  font-size: clamp(22px, 2.4vw, 34px);
  letter-spacing: 0.04em;
  top: 34px;
}
.era { padding-top: 92px; }
.era--next .era-year { font-size: clamp(18px, 2vw, 26px); top: 40px; }

/* full-bleed stills never touch the section above */
.still { margin-top: clamp(90px, 16vh, 200px); }

/* hard paper band after the doorways — padding cannot collapse */
#houses { padding-bottom: clamp(40px, 6vh, 72px); }

/* closing mark, 15% smaller */
.fi-logo { height: clamp(128px, 20.4vw, 280px); }

/* the portrait: smaller on desktop, fuller on mobile */
.story-ph { grid-column: 2 / span 3; }
@media (max-width: 860px) {
  .story-ph { grid-column: 1 / -1; width: 92%; margin: 0 auto; }
}

/* ============================================================
   V4.7 — the explore cursor
   ============================================================ */
.vv-cursor {
  position: fixed;
  top: 0; left: 0;
  z-index: 2500;
  pointer-events: none;
  transform: translate(-50%, -50%) scale(0.6);
  opacity: 0;
  transition: opacity 0.4s var(--ease), transform 0.4s var(--ease);
  will-change: transform;
}
.vv-cursor.on { opacity: 1; transform: translate(-50%, -50%) scale(1); }
.vv-cursor-pill {
  display: inline-flex;
  align-items: center;
  gap: 0.7em;
  color: #fbf9f4;
  font-size: 11px;
  font-weight: 500;
  letter-spacing: 0.26em;
  text-transform: uppercase;
  white-space: nowrap;
  text-shadow: 0 1px 8px rgba(20, 16, 10, 0.35);
}
.vv-cursor-pill::after {
  content: "→";
  font-weight: 300;
  letter-spacing: 0;
}
@media (hover: none), (max-width: 860px) { .vv-cursor { display: none; } }

/* cursor pill stays centred on the pointer via its own translate */
.vv-cursor { transition: opacity 0.35s var(--ease); transform: none; }
.vv-cursor .vv-cursor-pill { transform: translate(-50%, -50%) scale(0.7); transition: transform 0.35s var(--ease); display: inline-flex; }
.vv-cursor.on .vv-cursor-pill { transform: translate(-50%, -50%) scale(1); }

/* ============================================================
   V4.8 — passages breathe: stronger size contrast, real step
   ============================================================ */
.passage { padding-top: clamp(80px, 14vh, 170px); }
.passage .bleed { margin-bottom: clamp(150px, 28vh, 360px); margin-top: clamp(40px, 8vh, 110px); }
.passage-pair { margin-bottom: clamp(150px, 28vh, 360px); align-items: start; }

/* big image: dominant. small image: clearly secondary + stepped far down */
.passage-pair .col-a { grid-column: 1 / span 6; }
.passage-pair .col-b { grid-column: 9 / span 3; }
.passage-pair .col-c { grid-column: 6 / span 6; }
.passage-pair .col-d { grid-column: 2 / span 3; }

@media (max-width: 860px) {
  .passage-pair .col-a, .passage-pair .col-b,
  .passage-pair .col-c, .passage-pair .col-d {
    grid-column: 1 / -1;
    transform: none !important;
    margin-bottom: clamp(40px, 8vh, 70px);
  }
  .passage-pair .col-b, .passage-pair .col-d { width: 70%; }
  .passage-pair { margin-bottom: clamp(50px, 10vh, 90px); }
}

/* ============================================================
   V4.9 — page transitions (arrival into a house)
   ============================================================ */
.vv-veil {
  position: fixed; inset: 0; z-index: 4000; background: var(--bg);
  opacity: 0; visibility: hidden; pointer-events: none;
  transition: opacity 0.5s var(--ease), visibility 0s 0.5s;
}
.vv-veil.show {
  opacity: 1; visibility: visible; pointer-events: all;
  transition: opacity 0.5s var(--ease);
}

/* entrance: the hero image blooms, the name settles.
   Hanna 2026-09-09: "Remove ZOOM from head images (the ones at the top) in the apartments.
   It looks jittery on some phones." The apartment heroes are STILLS (.hero > img), and a
   1.14 -> 1 scale on a full-bleed photo is a per-frame resample of a very large bitmap,
   which is what stutters on a mid-range phone. They now fade only. The HOME hero is a
   <video>, which the GPU is already scaling every frame anyway, so its bloom is untouched
   and the page still opens the way Duncan designed it. */
.vv-enter .hero > img,
.vv-enter .hero picture img { opacity: 0; }
.vv-enter .hero video { opacity: 0; transform: scale(1.14); }
/* the per-page --tx ink-centring nudge (inline in each page's HTML, in em) applies at EVERY
   width — the glyph ink sits right of the advance box by the same em-fraction at any size,
   so mid-width and desktop windows drift right without it (it moves the title, never resizes) */
.hero .door-name { transform: translateX(var(--tx, 0px)); }
.vv-enter .hero .door-name { opacity: 0; transform: translate(var(--tx, 0px), 46px); }
.vv-enter .hero .hero-foot,
.vv-enter .hero-back { opacity: 0; }

.vv-enter-go .hero > img,
.vv-enter-go .hero picture img {
  opacity: 1;
  transition: opacity 1.3s var(--ease);
}
.vv-enter-go .hero video {
  opacity: 1; transform: scale(1);
  transition: opacity 1.3s var(--ease), transform 1.9s var(--ease);
}
.vv-enter-go .hero .door-name {
  opacity: 1; transform: translateX(var(--tx, 0px));
  transition: opacity 1s var(--ease) 0.35s, transform 1.2s var(--ease) 0.35s;
}
.vv-enter-go .hero .hero-foot { opacity: 1; transition: opacity 1s var(--ease) 0.7s; }
.vv-enter-go .hero-back { opacity: 1; transition: opacity 1s var(--ease) 0.9s; }

@media (prefers-reduced-motion: reduce) {
  .vv-enter .hero > img, .vv-enter .hero picture img, .vv-enter .hero video,
  .vv-enter .hero .door-name { opacity: 1; transform: translateX(var(--tx, 0px)); }
  .vv-enter .hero .hero-foot, .vv-enter .hero-back { opacity: 1; transform: none; }
}

/* ============================================================
   V5.0 — A day at Scala, as one timeline on the line
   ============================================================ */
.daywalk { position: relative; max-width: 1100px; margin: 0 auto; padding: 20px 0 40px; }
.daywalk::before { content: ""; position: absolute; left: 50%; top: 0; bottom: 0; width: 1px; background: var(--hair); z-index: 0; }

.daystop { position: relative; z-index: 1; display: grid; grid-template-columns: 1fr 1fr; column-gap: clamp(40px, 6vw, 100px); align-items: center; min-height: 76vh; }
.daystop::before { content: ""; position: absolute; left: 50%; top: 50%; width: 20px; height: 1px; background: var(--ink); transform: translate(-50%, -50%); z-index: 2; }

.daystop .day-text { grid-column: 1; justify-self: end; text-align: right; max-width: 23em; }
.daystop .day-ph { grid-column: 2; justify-self: start; }
.daystop:nth-of-type(even) .day-text { grid-column: 2; justify-self: start; text-align: left; }
.daystop:nth-of-type(even) .day-ph { grid-column: 1; justify-self: end; }

.day-time { display: block; font-size: clamp(20px, 2.4vw, 30px); font-weight: 300; color: var(--grey); font-variant-numeric: tabular-nums; letter-spacing: 0.04em; margin-bottom: 16px; }
.day-line { font-size: clamp(18px, 2vw, 26px); font-weight: 300; line-height: 1.4; }
.day-do { margin-top: 20px; font-size: 13px; line-height: 1.7; color: var(--grey); max-width: 24em; }
.daystop .day-text { margin-left: auto; }
.daystop:nth-of-type(even) .day-text { margin-left: 0; margin-right: auto; }
.day-do span { display: inline; font-size: 10px; font-weight: 500; letter-spacing: 0.18em; text-transform: uppercase; color: var(--ink); margin-right: 0.4em; }
/* line the YOU-MIGHT block's edge up with the time + headline (Duncan 2026-07-18):
   its narrower max-width left its right edge 33px short on right-aligned stops. Push the
   block to the shared edge — right on odd stops, left on even (mirrored) stops. */
.daystop .day-do { margin-left: auto; }
.daystop:nth-of-type(even) .day-do { margin-left: 0; margin-right: auto; }

/* images float free of the grid: varied size + vertical offset */
.day-ph.w-s { width: clamp(150px, 17vw, 230px); }
.day-ph.w-m { width: clamp(200px, 23vw, 320px); }
.day-ph.w-l { width: clamp(270px, 32vw, 440px); }
.daystop .day-ph { opacity: 0; transition: opacity 1.3s var(--ease); will-change: transform, opacity; }
.daystop .day-ph.seen { opacity: 1; }
.daystop .day-ph img, .daystop .day-ph video { display: block; width: 100%; }
/* vertical (9:16) phone clips on the timeline — keep them narrower so they don't tower */
/* timeline video clips sized like the photo stops so they sit in the right column, clear of the line */
.day-ph.is-video { width: clamp(240px, 30vw, 440px) !important; max-width: 440px !important; }
.day-ph.is-video video { width: 100%; height: auto; }
.daystop:nth-of-type(1) .day-ph { --off: -34px; }
.daystop:nth-of-type(2) .day-ph { --off: 40px; }
.daystop:nth-of-type(3) .day-ph { --off: -20px; }
.daystop:nth-of-type(4) .day-ph { --off: 30px; }
.daystop:nth-of-type(5) .day-ph { --off: -40px; }
.daystop:nth-of-type(6) .day-ph { --off: 24px; }
.daystop .day-ph { transform: translateY(var(--off, 0px)); }

@media (max-width: 860px) {
  .daywalk::before { left: 12px; }
  .daystop, .daystop:nth-of-type(even) { grid-template-columns: 1fr; row-gap: 18px; padding-left: 40px; min-height: 56vh; }
  .daystop::before { left: 12px; transform: translate(0, -50%); width: 16px; }
  .daystop .day-text, .daystop:nth-of-type(even) .day-text { grid-column: 1; justify-self: start; text-align: left; margin: 0; }
  .daystop .day-ph, .daystop:nth-of-type(even) .day-ph { grid-column: 1; justify-self: start; transform: none !important; width: 70%; }
  /* keep the small / medium / large sizing on phone too — don't flatten to one width
     (the shared .day-ph max-width:180px cap is lifted here so the sizes can differ) */
  .daystop .day-ph.w-s, .daystop:nth-of-type(even) .day-ph.w-s { width: 44%; max-width: 200px; }
  .daystop .day-ph.w-m, .daystop:nth-of-type(even) .day-ph.w-m { width: 64%; max-width: 280px; }
  .daystop .day-ph.w-l, .daystop:nth-of-type(even) .day-ph.w-l { width: 88%; max-width: 380px; }
}

/* ============================================================
   V5.0 — crossfade arrival (chrome + title stay, image dissolves)
   ============================================================ */
.hero > img, .hero video, .hero picture { z-index: 0; }
.hero::before { z-index: 2; }
.hero .hero-inner, .hero .hero-foot, .hero .door-name { z-index: 3; }
.hero .hero-back { z-index: 4; }
.vv-xfade {
  position: absolute; inset: 0; z-index: 1;
  width: 100%; height: 100%; object-fit: cover;
  opacity: 1; transition: opacity 1.1s var(--ease);
}
.vv-xfade.gone { opacity: 0; }
.vv-hold { position: fixed; inset: 0; width: 100%; height: 100%; object-fit: cover; z-index: 3500; }

/* ============================================================
   V5.2 — next house (was unstyled, flush to edge)
   ============================================================ */
.next-house { border-top: 1px solid var(--hair-soft); }
.next-house a {
  display: block;
  padding: clamp(70px, 13vh, 150px) var(--pad);
  text-align: center;
  color: var(--ink);
}
.next-house .label { display: block; margin-bottom: 18px; color: var(--grey); }
.next-house h2 {
  font-family: var(--serif);
  font-weight: 300;
  font-size: clamp(48px, 11vw, 150px);
  letter-spacing: 0.03em;
  text-transform: uppercase;
  line-height: 1;
  transition: letter-spacing 0.7s var(--ease);
}
.next-house a:hover h2 { letter-spacing: 0.07em; }

/* ============================================================
   CMS preview mode — calmer page, edit highlight
   ============================================================ */
.cms-preview .vv-cursor, .cms-preview .motion-toggle { display: none !important; }
.cms-flash { outline: 2px solid var(--terracotta, #a4582f); outline-offset: 4px; transition: outline-color .3s; }

.cms-preview, html.cms-preview { scroll-behavior: auto !important; }

/* day-timeline video clips: crop inside the frame like the photos */
.day-ph .pframe { width: 100%; }
.day-ph .pframe video { width: 100%; height: 100%; object-fit: cover; display: block; }

/* day clips: larger so the movement reads */
.day-ph.clip { width: clamp(360px, 42vw, 600px); max-width: 600px; }
@media (max-width: 860px) { .day-ph.clip { width: 88% !important; } }

/* ============================================================
   BOOKING — branded flow on the TBF data layer
   ============================================================ */
.bk-body { background: var(--bg); }
/* Tried pulling the whole DATES step above the fold 2026-07-21 and REVERTED: even with
   the top space cut to the bone the footbar still landed 86px below a 900px-tall viewport
   (162px at 800). Two months of calendar plus the title block is simply taller than a
   laptop screen — the fix is a sticky footbar, not tighter spacing. Duncan: "if it's too
   much leave it for now, as i don't think it will fit." */
.bk-main { padding-top: clamp(140px, 22vh, 240px); min-height: 80vh; }

.bk-intro { text-align: center; max-width: 40em; margin: 0 auto; }
.bk-intro .label--grey { display: block; margin-bottom: 22px; }
.bk-intro h1 { font-size: clamp(23px, 2.7vw, 34px); }
.bk-intro .cap { margin-top: 18px; }
.bk-seeall { margin: 30px 0 6px; }
.bk-seeall .tlink { display: inline-block; }

/* progress rail */
.bk-rail {
  display: flex; justify-content: center; flex-wrap: wrap;
  gap: clamp(18px, 4vw, 54px);
  margin: clamp(34px, 6vh, 64px) auto clamp(28px, 5vh, 50px);
  border-bottom: 1px solid var(--hair-soft); padding-bottom: 22px; max-width: 760px;
}
.bk-dot {
  font-size: 10px; font-weight: 500; letter-spacing: 0.22em; text-transform: uppercase;
  color: var(--grey); position: relative; transition: color 0.4s;
}
.bk-dot::before { content: ""; display: inline-block; width: 5px; height: 5px; border-radius: 50%; background: var(--hair); margin-right: 10px; vertical-align: middle; transition: background 0.4s, transform 0.4s; }
.bk-dot.at { color: var(--ink); }
.bk-dot.at::before { background: var(--ink); transform: scale(1.5); }
.bk-dot.done { color: var(--ink); }
.bk-dot.done::before { background: var(--ink); }

.bk-shell { max-width: 1000px; margin: 0 auto; padding-bottom: clamp(60px, 12vh, 130px); }
.bk-step { display: none; animation: bkfade 0.6s var(--ease); }
.bk-step.on { display: block; }
@keyframes bkfade { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: none; } }

.bk-step-head { display: flex; align-items: center; justify-content: space-between; margin-bottom: 30px; }

/* calendar */
.bk-cal-nav { display: flex; align-items: center; justify-content: center; gap: 28px; margin-bottom: 26px; }
.bk-mo { font-family: inherit; font-size: 16px; width: 42px; height: 42px; border-radius: 50%; border: 1px solid var(--hair); background: transparent; color: var(--ink); cursor: pointer; transition: background 0.3s, color 0.3s; }
.bk-mo:hover { background: var(--ink); color: var(--bg); }
.bk-cals { display: grid; grid-template-columns: 1fr 1fr; gap: clamp(24px, 5vw, 72px); }
.bk-cal-head { text-align: center; margin-bottom: 16px; }
.bk-dow { display: grid; grid-template-columns: repeat(7, 1fr); gap: 2px; margin-bottom: 8px; }
.bk-dow span { text-align: center; font-size: 10px; letter-spacing: 0.12em; color: var(--grey); }
.bk-grid { display: grid; grid-template-columns: repeat(7, 1fr); gap: 2px; }
.bk-cell { position: relative; aspect-ratio: 1; border: none; background: transparent; font-family: inherit; font-size: 13px; color: var(--ink); cursor: pointer; border-radius: 50%; transition: background 0.25s, color 0.25s; display: flex; align-items: center; justify-content: center; }
.bk-cell.empty { visibility: hidden; cursor: default; }
.bk-cell:hover:not(.off):not(.empty) { background: var(--hair-soft); }
/* a small ink dot marks every bookable date (monochrome — palette lock) */
.bk-cell:not(.off):not(.empty):not(.a):not(.b):not(.mid):not(.co)::after {
  content: ""; position: absolute; left: 50%; bottom: 6px; transform: translateX(-50%);
  width: 5px; height: 5px; border-radius: 50%; background: var(--ink); opacity: 0.55;
}
.bk-cell.off { color: rgba(24,22,17,0.2); text-decoration: line-through; cursor: not-allowed; }
.bk-cell.mid { background: rgba(24, 22, 17, 0.07); border-radius: 0; }
.bk-cell.a, .bk-cell.b { background: var(--ink); color: var(--bg); }
.bk-cell.a { border-radius: 50% 0 0 50%; }
.bk-cell.b { border-radius: 0 50% 50% 0; }

.bk-legend { display: flex; justify-content: center; gap: clamp(18px, 3vw, 34px); margin-top: 26px; }
.bk-legend span { display: flex; align-items: center; gap: 8px; font-size: 9.5px; font-weight: 500; letter-spacing: 0.18em; text-transform: uppercase; color: var(--grey); }
.bk-legend i { width: 6px; height: 6px; border-radius: 50%; display: inline-block; }
.bk-legend .dot-open { background: var(--ink); opacity: 0.55; }
.bk-legend .bk-na-eg { color: rgba(24,22,17,0.2); text-decoration: line-through; font-weight: 500; letter-spacing: 0; font-size: 12px; }
.bk-legend-min { color: var(--grey); }

.bk-footbar { display: flex; align-items: center; justify-content: space-between; gap: 24px; flex-wrap: wrap; margin-top: 40px; padding-top: 26px; border-top: 1px solid var(--hair-soft); }
.bk-dates-bar { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; font-size: 14px; }
.bk-dates-bar strong { font-weight: 500; }
/* phone: stack the summary + button, both centred */
@media (max-width: 860px) {
  .bk-footbar { flex-direction: column; align-items: center; gap: 20px; }
  .bk-footbar .bk-dates-bar { justify-content: center; text-align: center; }
  .bk-footbar #bk-dates-next { align-self: center; }
}
.bk-arrow { color: var(--grey); }
.bk-nights { background: var(--ink); color: var(--bg); font-size: 10px; letter-spacing: 0.14em; text-transform: uppercase; padding: 5px 12px; border-radius: 100px; }
.pillbtn:disabled { opacity: 0.3; cursor: not-allowed; }
.pillbtn:disabled:hover { background: var(--ink); color: var(--bg); }

/* rooms */
.bk-rooms { display: grid; gap: 18px; }
/* `stretch`, not `start`: the body has to fill the card's height for the rate's
   `margin-top: auto` to push the price to the bottom, level with the photo
   (Duncan 2026-07-21: "move prices down"). */
.bk-room { display: grid; grid-template-columns: 220px 1fr; gap: 28px; align-items: stretch; text-align: left; background: transparent; border: 1px solid var(--hair-soft); border-radius: 2px; padding: 18px; cursor: pointer; font-family: inherit; color: var(--ink); transition: border-color 0.4s, box-shadow 0.4s; }
.bk-room:hover { border-color: var(--hair); box-shadow: 0 18px 50px -30px rgba(24,22,17,0.4); }
.bk-room-img { overflow: hidden; aspect-ratio: 1; align-self: center; }
.bk-room-img img { width: 100%; height: 100%; object-fit: cover; transition: transform 1.6s var(--ease); }
.bk-room:hover .bk-room-img img { transform: scale(1.05); }
.bk-room-body { display: flex; flex-direction: column; gap: 7px; padding: 6px 12px 6px 0; min-height: 100%; }
/* the VV display face, small — reads as the DO / RE / MI room titles do, not as body
   copy (Duncan 2026-07-21: "this should change to SCALA small font as is DO RE etc") */
.bk-room-name {
  font-family: var(--display);
  font-weight: 400;
  font-size: clamp(30px, 3.4vw, 44px);
  line-height: 0.95;
  /* The site-wide --display-track is -0.06em, drawn for names set BIG. At booking-card
     size that tight setting closes the counters up and "GALLERY" stops reading (Duncan
     2026-07-21: "make the kerning slightly wider on these two pages"). Positive tracking
     here only — the giant titles keep the locked value. */
  letter-spacing: -0.02em;   /* split the difference: -0.06em was unreadable, 0.015em too loose */
  text-transform: uppercase;
}
.bk-room-rate { margin-top: auto; padding-top: 12px; font-size: 16px; }
.bk-room-rate strong { font-weight: 500; }
.bk-room .tlink { align-self: flex-start; margin-top: 14px; }

/* stay */
.bk-stay { display: grid; grid-template-columns: 1.4fr 1fr; gap: clamp(30px, 5vw, 64px); align-items: start; }
.bk-stay-room { display: flex; gap: 20px; align-items: center; padding-bottom: 26px; border-bottom: 1px solid var(--hair-soft); margin-bottom: 28px; }
.bk-stay-room img { width: 120px; height: 120px; object-fit: cover; border-radius: 2px; }
.bk-stay-room h3 { margin: 4px 0; }
.bk-guests { display: grid; gap: 4px; margin-bottom: 36px; }
.bk-guest-row { display: flex; align-items: center; justify-content: space-between; padding: 16px 0; border-bottom: 1px solid var(--hair-soft); }
.bk-guest-row span:first-child { display: flex; flex-direction: column; }
.bk-step-ctrl { display: flex; align-items: center; gap: 18px; }
.bk-step-ctrl button { width: 36px; height: 36px; border-radius: 50%; border: 1px solid var(--hair); background: transparent; font-size: 17px; color: var(--ink); cursor: pointer; transition: background 0.3s, color 0.3s; font-family: inherit; }
.bk-step-ctrl button:hover { background: var(--ink); color: var(--bg); }
.bk-step-ctrl b { font-weight: 500; min-width: 18px; text-align: center; }
.bk-offers-wrap .label--grey { display: block; margin-bottom: 14px; }
.bk-offers { display: grid; gap: 10px; }
/* promo code (#1 — real codes come from Booking Factory; placeholder for now) */
.bk-promo-wrap { margin-top: 26px; }
.bk-promo-wrap .label--grey { display: block; margin-bottom: 14px; }
.bk-promo { display: flex; gap: 10px; }
.bk-promo input { flex: 1; min-width: 0; padding: 13px 14px; border: 1px solid var(--hair); border-radius: 2px; background: transparent; font: inherit; font-size: 14px; text-transform: uppercase; }
.bk-promo input::placeholder { text-transform: none; color: rgba(24,22,17,0.35); }
.bk-promo input:focus { border-color: var(--ink); outline: none; }
.bk-promo .pillbtn { flex: none; }
.bk-promo-hint { margin-top: 10px; color: var(--grey); }
.bk-promo-hint strong { color: var(--ink); font-weight: 500; letter-spacing: 0.03em; }
#bk-promo-msg { color: var(--grey); min-height: 1em; }
.bk-offer { display: flex; gap: 14px; align-items: flex-start; padding: 16px 18px; border: 1px solid var(--hair-soft); border-radius: 2px; cursor: pointer; transition: border-color 0.3s, background 0.3s; }
.bk-offer.on { border-color: var(--ink); }
.bk-offer.na { opacity: 0.4; cursor: not-allowed; }
.bk-offer input { margin-top: 4px; accent-color: var(--ink); }
.bk-offer span span { display: block; }

.bk-stay-right { background: transparent; border: 1px solid var(--hair-soft); padding: clamp(26px, 3vw, 40px); border-radius: 2px; position: sticky; top: 100px; }
.bk-stay-right .label--grey { display: block; margin-bottom: 20px; }
.bk-summary { display: grid; gap: 12px; }
.bk-line { display: flex; justify-content: space-between; gap: 16px; font-size: 14px; }
.bk-line span:last-child { white-space: nowrap; }
.bk-note { display: block; font-size: 12px; color: var(--grey); margin-top: -2px; }
.bk-total { border-top: 1px solid var(--hair); padding-top: 14px; margin-top: 6px; font-size: 17px; font-weight: 500; }
.bk-dep { font-weight: 500; }

/* details */
.bk-details { max-width: 620px; margin: 0 auto; }
.bk-form { display: grid; gap: 18px; }
.bk-field-row { display: grid; grid-template-columns: 1fr 1fr; gap: 18px; }
.bk-form label { display: flex; flex-direction: column; gap: 7px; font-size: 11px; letter-spacing: 0.12em; text-transform: uppercase; color: var(--grey); }
.bk-form input, .bk-form textarea { font-family: inherit; font-size: 15px; color: var(--ink); background: transparent; border: none; border-bottom: 1px solid var(--hair); padding: 9px 0; letter-spacing: normal; text-transform: none; transition: border-color 0.3s; }
.bk-form input:focus, .bk-form textarea:focus { outline: none; border-color: var(--ink); }
.bk-pay { margin-top: 18px; padding: 26px; background: #f4f1ea; border-radius: 2px; display: grid; gap: 18px; }
.bk-pay .label--grey { display: block; }
.bk-recap { padding: 20px 0; border-top: 1px solid var(--hair-soft); border-bottom: 1px solid var(--hair-soft); font-size: 14px; line-height: 1.7; margin: 8px 0; }

/* done */
.bk-done-body { text-align: center; padding: clamp(30px, 6vh, 70px) 0; }
.bk-done-body .label--grey { display: block; margin-bottom: 22px; }
.bk-done-card { display: inline-flex; gap: 18px; align-items: center; text-align: left; background: #f4f1ea; padding: 18px 22px; border-radius: 2px; margin-top: 40px; font-size: 14px; line-height: 1.6; }
.bk-done-card img { width: 90px; height: 90px; object-fit: cover; border-radius: 2px; }

@media (max-width: 820px) {
  .bk-cals { grid-template-columns: 1fr; gap: 40px; }
  .bk-stay { grid-template-columns: 1fr; }
  .bk-stay-right { position: static; }
  .bk-room { grid-template-columns: 110px 1fr; gap: 16px; padding: 12px; }
  .bk-room-body { padding: 0; }
  .bk-field-row { grid-template-columns: 1fr; gap: 18px; }
}

/* booking — room-first notice above the alternatives list */
.bk-notice { font-size: 14px; color: var(--ink); padding: 0 0 20px; margin-bottom: 6px; border-bottom: 1px solid var(--hair-soft); }

/* ============================================================
   CLIENT FEEDBACK — 2026-06-16
   ============================================================ */

/* §1 — logo a touch softer so the page reads first */
.head .logo-mark { opacity: 0.8; }

/* §1 — names rise in more smoothly on the homepage doorways */
#houses .door-name {
  transform: translate(var(--tx, 0px), 24px);   /* --tx = ink-centring from the fitter */
  transition: opacity 1s var(--ease), transform 1.1s var(--ease);
  bottom: -0.11em;   /* sit letters ON the base */
}
#houses .door.open .door-name { transform: translate(var(--tx, 0px), 0); }

/* §1 — the scroll hint is now a gentle arrow */
#houses .door-hint {
  font-size: 20px;
  line-height: 1;
  color: #fbf9f4;
  bottom: 26px;
  letter-spacing: 0;
  text-transform: none;
  animation: vv-bob 2.2s ease-in-out infinite;
}
@keyframes vv-bob { 0%, 100% { transform: translateY(0); } 50% { transform: translateY(7px); } }
body.motion-off #houses .door-hint { animation: none; }

/* §9 — mobile: centre the wordmark, hide it once scrolling, keep hero text legible */
@media (max-width: 860px) {
  .head { grid-template-columns: 1fr; justify-items: center; }
  .head .wordmark { justify-self: center; text-align: center; transition: opacity 0.4s var(--ease); }
  .burger { position: absolute; right: var(--pad); top: 50%; transform: translateY(-50%); }
  /* the transparent header band overlays the top ~146px and was swallowing taps meant for
     things beneath it (the "← All houses" link sits on the MENU line, outside the header).
     Let empty header space pass clicks through; its own controls stay tappable. */
  .head { pointer-events: none; }
  .head a, .head button { pointer-events: auto; }
  /* small breather between the last house frame and the footer — not the desktop band
     (the footer also carries its own margin-top; fold it into the one small gap) */
  #houses { padding-bottom: clamp(28px, 5vh, 48px); }
  .foot-ink { margin-top: 0; }
  /* Hanna 2026-09-09: "it needs to fade a bit later once you start scrolling in
     your mobile." Measured before changing anything: `.scrolled` flips at 40px and
     the mark was down to 0.01 opacity by 60px, so on a phone the logo vanished
     within a thumb-flick of the top. On mobile the fade now runs off its own
     class, set roughly a quarter of a screen down (js/main.js), while `.scrolled`
     keeps driving the header's padding exactly as before.
     The first rule undoes the site-wide `.head.scrolled` fade for phones only. */
  .head.scrolled .wordmark { opacity: 1; pointer-events: auto; }
  .head.mark-out .wordmark { opacity: 0; pointer-events: none; }
  /* left, not centred — the titles moved to the left on mobile, so this line follows them
     onto the same column (Duncan 2026-07-21: "make the text below left aligned") */
  .hero-foot {
    padding-bottom: 30px;
    justify-content: flex-start;
    text-align: left;
  }
  .hero-foot .label { text-shadow: 0 1px 12px rgba(0,0,0,0.45); }
  /* §4 — apartment hero on mobile: stack the two caption lines and sit them above the
     giant wordmark, no mid-screen scrim band */
  .hero:has(.door-name) .hero-foot {
    flex-direction: column; align-items: flex-start; gap: 5px;   /* left, matching the title */
    text-align: left;
    padding-bottom: 0; background: none;
    /* sit clear ABOVE the fitted title using its real height (--door-h from the JS fitter);
       the old fixed clamp estimate assumed a short title and overlapped tall ones (STORY) */
    bottom: calc(var(--door-h, 130px) + 18px);
  }
  /* tighten the big empty gap between the intro line and the first house section (client note) */
  .statement.s-xl { padding-bottom: clamp(40px, 6vh, 74px); }
  .suite-spread { padding-top: clamp(28px, 5vh, 52px); }
  /* homepage STORY on mobile: small photo (~40%) to one side with the caption beside it,
     then the full story text below — collapse the two-column grid (inline grid-column was
     unset on mobile, which threw the prose into a narrow sliver) */
  #story .grid { grid-template-columns: 1fr; row-gap: clamp(26px, 5vh, 46px); }
  #story .grid > * { grid-column: 1 / -1 !important; }
  #story .story-ph { width: 100%; margin: 0; display: flex; align-items: center; gap: clamp(16px, 5vw, 30px); }
  #story .story-ph > .pframe, #story .story-ph > img { width: 40%; flex: none; max-width: 200px; }
  #story .story-ph .figcap { flex: 1; margin: 0; padding-top: 0; border-top: 0; flex-direction: column; align-items: flex-start; gap: 5px; }
  #story .story-ph .figcap .cap { display: block; text-align: left; }
  #story .grid .body-lg { max-width: none; }
}

/* §2 — story: single prose column, centred photo + small caption */
.story-ph { width: clamp(280px, 38vw, 460px); margin: 0 auto; }
.story-cap {
  display: block; text-align: center; margin-top: 16px;
  font-size: 11px; font-weight: 500; letter-spacing: 0.2em;
  text-transform: uppercase; color: var(--grey);
}
.story-prose { max-width: 40em; margin: 0 auto; }
.story-prose .body-lg { margin-bottom: 1.5em; }
.story-prose p { color: var(--ink); }
.story-prose p:not(.body-lg) { font-size: 15px; color: #4a463f; }
/* §12 (round 2) — story body in two columns on desktop; the lede spans both */
@media (min-width: 861px) {
  .story-prose { max-width: 70em; column-count: 2; column-gap: clamp(40px, 5vw, 88px); }
  .story-prose .label--grey,
  .story-prose .body-lg { column-span: all; }
  .story-prose p:not(.body-lg) { break-inside: avoid; margin-bottom: 1.2em; }
}

/* §3 — remove footer logo watermark */
.fi-logo { display: none; }
/* §1 — right-side scroll nav: removed from the homepage (markup gone from index.html);
   kept on inner pages per Hanna. Mobile hide still handled by the existing media query. */

/* §3 — booking terms page */
.terms-main { max-width: 760px; margin: 0 auto; padding-top: clamp(140px, 20vh, 220px); padding-bottom: clamp(80px, 14vh, 160px); min-height: 70vh; }
.terms-head { text-align: center; margin-bottom: clamp(50px, 9vh, 96px); }
.terms-head .label--grey { display: block; margin-bottom: 18px; }
.terms-body { display: grid; gap: clamp(34px, 5vh, 56px); }
.term { display: grid; grid-template-columns: 56px 1fr; gap: clamp(18px, 3vw, 40px); align-items: start; }
.term-no { font-family: var(--serif); font-size: 22px; color: var(--grey); line-height: 1.2; }
.term h2 { margin-bottom: 10px; }
.term p { font-size: 15px; color: #4a463f; max-width: 34em; }
.terms-foot { text-align: center; margin-top: clamp(50px, 9vh, 90px); }
@media (max-width: 700px) { .term { grid-template-columns: 1fr; gap: 8px; } }

/* §4 — Scala apartments: text together beside its own 3-photo gallery, clearly separated */
.suite-spread { padding-top: clamp(56px, 10vh, 120px); padding-bottom: clamp(56px, 10vh, 120px); border-top: 1px solid var(--hair-soft); }
.suite-head { margin-bottom: clamp(26px, 4vh, 50px); }
.suite-head h2 { font-size: var(--name-size); font-weight: 400; }
/* "Shared spaces" is a SECTION title, not a house — the display face is reserved for
   apartment names (Duncan 2026-07-21). Both Scala and Altana mark that section
   id="shared", so this targets it without touching the markup and therefore without
   disturbing the CMS suites repeat, which binds names to .suite-head h2 BY POSITION.
   It cannot inherit --name-size: VV Display is heavily condensed and Switzer is not, so
   the same number renders about twice as wide and "SHARED SPACES" would tower over the
   apartment names it sits beneath — the opposite of the hierarchy. Sized as a section
   title instead, in the same Light cut as the menu's utility labels. */
#shared .suite-head h2 {
  font-family: "Switzer", "Helvetica Neue", Helvetica, Arial, sans-serif;
  font-weight: 300;
  font-size: clamp(28px, 3.4vw, 50px);
  letter-spacing: 0.03em;
  line-height: 1.15;
}
.suite-row { display: grid; grid-template-columns: 0.85fr 1.55fr; gap: clamp(28px, 5vw, 72px); align-items: start; }
.house-note .suite-floor { display: block; margin-bottom: 18px; color: var(--grey); }
.house-note p { max-width: 26em; }
.suite-gallery { display: grid; grid-template-columns: 1fr 1fr; gap: clamp(12px, 1.4vw, 18px); }
.suite-gallery .lead { grid-column: 1 / -1; }
.suite-gallery .figure { overflow: hidden; }
/* gallery photos open the full set in the lightbox — a mouse-following "Explore <room>"
   pill (js/main.js) signals it, matching the homepage doors. The native cursor hides
   while the pill is up; a gentle zoom is the secondary cue. */
.suite-gallery .figure { cursor: pointer; }
.suite-gallery.vv-cursoring, .suite-gallery.vv-cursoring .figure, .suite-gallery.vv-cursoring img { cursor: none; }
.suite-gallery .figure:hover img { transform: scale(1.03); }

/* §4f — one CTA, left; next house, right */
.suite-cta { display: flex; align-items: flex-end; justify-content: space-between; gap: 40px; flex-wrap: wrap; }
.suite-cta--next { justify-content: flex-end; }
/* phone: tighten the gap between "view next home" and the footer (was ~121px) */
@media (max-width: 860px) { .suite-cta--next { padding-bottom: clamp(30px, 5vh, 56px); } }
.cta-left h2 { font-weight: 300; }
.cta-next { text-align: right; }
.cta-next .label { display: block; color: var(--grey); margin-bottom: 10px; }
.cta-next h2 { font-family: var(--display); letter-spacing: var(--display-track); font-size: var(--name-size); font-weight: 400; line-height: 0.95; transition: opacity 0.35s var(--ease); }
.cta-next:hover h2 { opacity: 0.5; }

@media (max-width: 860px) {
  .suite-row { grid-template-columns: 1fr; gap: 22px; }
  .suite-cta { flex-direction: column; align-items: flex-start; gap: 28px; }
  .cta-next { text-align: left; }
}

/* §7 — Doma / Retro Pad: show 5 photos, the rest live only in the lightbox */
/* extras now shown in the grid too (Hanna — more photos in the galleries); lightbox still opens the full set */
/* extra photos are dropped from the visible grid (Chris — "3-4 photos, rest in the
   gallery") but stay in the lightbox set, reachable by tapping any photo and paging through */
.gallery-extra { display: none; }
.gallery-more { text-align: center; margin-top: clamp(22px, 4vh, 44px); color: var(--grey); }

/* §8 — booking terms block in the details step */
.bk-terms { margin-top: 4px; padding: 22px 0; border-top: 1px solid var(--hair-soft); }
.bk-terms .label--grey { display: block; margin-bottom: 12px; }
.bk-terms ul { list-style: none; display: grid; gap: 6px; margin-bottom: 14px; }
.bk-terms li { font-size: 13px; color: #4a463f; padding-left: 15px; position: relative; }
.bk-terms li::before { content: "·"; position: absolute; left: 3px; color: var(--grey); }

/* nearby-date suggestions when no homes are free for the chosen dates */
.bk-alts { display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: 12px; }
.bk-alt { display: flex; flex-direction: column; gap: 5px; align-items: flex-start; text-align: left;
  padding: 16px 18px; background: none; border: 1px solid var(--hair); border-radius: 2px; cursor: pointer;
  font: inherit; color: var(--ink); transition: border-color .25s, background .25s; }
.bk-alt:hover { border-color: var(--ink); background: rgba(24,22,17,.03); }
.bk-alt-dates { font-size: 17px; }

/* ============================================================
   Visual-QA fixes (2026-06-20) — fixed-chrome overlap legibility
   ============================================================ */

/* The story page's right side-nav collides with the full-width horizontal
   timeline (a right-fixed nav over a horizontal strip can't avoid it). Hide it
   there — the story page reads fine without the section spy. */
.story-page .lift { display: none; }

/* Mobile MENU button (Hanna round 2): no pill — plain underlined CAPS. A soft
   text-shadow keeps it legible over both light content and dark heroes; the
   .over-img state already flips the text to white. */
@media (max-width: 860px) {
  .burger {
    background: none;
    border: 0;
    border-radius: 0;
    padding: 0;
    line-height: 1;
    text-decoration: underline;
    text-underline-offset: 4px;
    text-decoration-thickness: 1px;
    text-shadow: 0 1px 10px rgba(0, 0, 0, 0.3);
  }
  .burger.over-img {
    background: none;
    text-shadow: 0 1px 12px rgba(0, 0, 0, 0.55);
  }

  /* MENU sits on the same line as the "← All houses" link, and takes the same
     colour as the giant page title (the page's --paper cream, not flat white) */
  .head.on-dark .burger, .head .burger.over-img { color: var(--paper); }
  .hero-back { top: 68px; }

  /* giant page titles: keep them dead-centre on phone. The JS fitter sets --tx to
     translate the true glyph ink back to centre (text-align only centres the advance
     box, which the tight tracking + angled letterforms sit off-centre within). */
  .hero .door-name { left: 0; right: 0; text-align: center; transform: translateX(var(--tx, 0px)); }

  /* Explore section headings (Culture, Eat and drink…): the desktop clamp bottoms
     out at 40px on phone, too small for the display face — give them proper size */
  .gsec h2 { font-size: clamp(30px, 8vw, 40px); }
}

/* booking — embedded Stripe payment (no redirect) */
.bk-pay-wrap { margin: 26px 0 8px; padding-top: 22px; border-top: 1px solid var(--hair-soft); }
#bk-payment-element { min-height: 44px; }
.bk-pay-loading { display: block; padding: 12px 0; }
.bk-pay-error { color: #8a2b1e; font-size: 13px; margin-top: 10px; min-height: 0; }
.bk-pay-error:not(:empty) { margin-bottom: 4px; }
.bk-pay-note { font-size: 12px; text-align: center; margin-top: 12px; }

/* ============================================================================
   HERO TITLE — SIDE treatment  (client-approved, Chris + Duncan 2026-07-13)
   Supersedes the centred full-bleed title AND its ink-centring lock for the hero:
   the title is now left-aligned, so per-word --tx centring no longer applies here.
   Matched to the in-page room-title size (--name-size) and kerning so the hero
   name reads at the same weight as "RE / MI…" further down the page — smaller,
   offset to one side, vertically central, clearly inset off the edge.
   ============================================================================ */
.hero .door-name {
  left: clamp(40px, 6vw, 96px) !important; right: auto !important;
  bottom: auto !important; top: 50% !important;
  width: auto !important; white-space: nowrap !important;
  text-align: left !important; overflow: visible !important;
  font-size: var(--name-size) !important;          /* == .suite-head h2 room titles */
  letter-spacing: var(--display-track) !important; /* keep the title's own kerning */
  line-height: 0.95 !important;
  transform: translateY(calc(-50% + 20px)) !important; transform-origin: left center !important;
}
@media (max-width: 860px) {
  .hero .door-name { left: clamp(24px, 7vw, 50px) !important; }
}
/* entrance = pure fade IN PLACE, position never changes (Duncan: "comes in centred
   then jumps down"). Transform stays translateY(-50%) throughout, only opacity moves. */
.vv-enter .hero .door-name { opacity: 0 !important; transform: translateY(calc(-50% + 20px)) !important; }
.vv-enter-go .hero .door-name {
  opacity: 1 !important; transform: translateY(calc(-50% + 20px)) !important;
  transition: opacity 1s var(--ease) 0.35s;
}

/* homepage door titles (#houses) — same SIDE treatment as the house-page heroes
   (client 2026-07-13: "titles still wrong on the main page"). Small, left,
   vertically central, matched to the room-title size; the per-door meta overlay
   is hidden so it reads as clean as the hero. */
#houses .door-name {
  left: clamp(40px, 6vw, 96px) !important; right: auto !important;
  bottom: auto !important; top: 50% !important;
  width: auto !important; white-space: nowrap !important; text-align: left !important;
  overflow: visible !important;                       /* parity with the house-page heroes — no glyph clip */
  font-size: var(--name-size) !important; letter-spacing: var(--display-track) !important;
  line-height: 1.02 !important; padding: 0.08em 0 !important; color: var(--paper) !important;
  transform: translateY(calc(-50% + 20px)) !important; transform-origin: left center !important;
}
#houses .door.open .door-name { transform: translateY(calc(-50% + 20px)) !important; }

/* The small left-hand paragraph (.door-meta--l) is NOT hidden here any more.
   It was killed with display:none when the title moved left on 2026-07-13, on the
   assumption it would collide with it — but the two are never on screen together:
   the paragraph reads while the photo is still a small aperture, and .door-name
   only fades in once the aperture has passed it. Duncan 2026-07-16: "a very small
   paragraph on the left of the title parts — which stayed there till the image
   took over."
   Measured 2026-07-17: the paragraphs are gone by p≈0.25 (desktop) / p≈0.20
   (mobile) and the name's inline opacity flips at p≈0.46 — so the two are never
   on screen together, with room to spare. */

/* booking — split card fields (Stripe elements in-page, styled like our inputs) */
.bk-card-label { display: flex; flex-direction: column; gap: 7px; font-size: 11px; letter-spacing: 0.12em; text-transform: uppercase; color: var(--grey); }
.bk-card-el { border-bottom: 1px solid var(--hair); padding: 9px 0; transition: border-color 0.3s; }
.bk-card-el.StripeElement--focus { border-color: var(--ink); }
.bk-card-el.StripeElement--invalid { border-color: #8a2b1e; }
.bk-form select { font-family: inherit; font-size: 15px; color: var(--ink); background: transparent; border: none; border-bottom: 1px solid var(--hair); padding: 9px 0; letter-spacing: normal; text-transform: none; border-radius: 0; -webkit-appearance: none; appearance: none; }
.bk-form select:focus { outline: none; border-color: var(--ink); }

/* ============================================================================
   EXPLORE — the field guide, as click-to-open categories
   Brief (Hanna via Duncan, 2026-07-17): "the concept of titles that are
   collapsible — user clicks on restaurants and the restaurants appear below.
   Remove any visual text clutter and the text menu on the right — CHRIS HATES
   THAT. Photo wise — take all of them and instead of pinning them to the left
   in the same exact space, some slight different positioning so they come up in
   the same space, but they feel like photos being placed on top of each."

   Two consequences worth stating, because they undo earlier work on purpose:
   1. `nav.lift` is GONE from explore.html — the six titles ARE the index now,
      so the right-hand menu has nothing left to do. The site-wide
      `.lift,.fi-index{display:none}` rule therefore stands unscoped again —
      2026-07-16 had carved an explore exception out of it.
   2. The timeline treatment (.daywalk/.daystop reuse) is retired here. A
      timeline wants to be walked top-to-bottom; a directory wants to be picked
      from. Hanna got tired scrolling because we kept building the former.
   ========================================================================== */

.gsec { max-width: 1080px; margin: 0 auto; padding: 0 var(--pad); }
.gsec + .gsec { border-top: 1px solid var(--hair); }

/* --- the title, which is the control and the navigation at once --- */
.gsec-h { margin: 0; }
.gsec-toggle {
  width: 100%;
  display: flex; align-items: baseline; gap: clamp(14px, 2vw, 24px);
  background: none; border: 0; border-radius: 0;
  /* Tight. VV Display is ultra-condensed and its ink fills only part of the em box,
     so the line box itself adds dead space on top of any padding — which is why 20px
     still read as "too far apart". Padding is small AND the line box is trimmed. */
  padding: clamp(5px, 0.8vh, 9px) 0;
  font-family: inherit; color: var(--ink); text-align: left;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}
.gsec-title {
  /* MUST restate these three. A <button> does not inherit text-transform,
     letter-spacing or font-family from the h2 it sits in — and VV Display is an
     UPPERCASE-ONLY face (31 glyphs: A–Z , . '). Drop the uppercase and every
     lowercase letter silently falls back to Zodiak, leaving a VV Display capital
     stranded against a Zodiak word ("Culture" reads as a broken "(ulture").
     That is not a broken font — it is this rule going missing. Do not remove. */
  /* Switzer light, sentence case, like the page statements (Duncan 2026-07-21) — the
     uppercase-only caveat above applied to VV Display and no longer binds here. */
  font-family: var(--serif);
  text-transform: none;
  letter-spacing: -0.005em;
  font-size: clamp(18px, 2.16vw, 30px); font-weight: 300; line-height: 1.05;   /* 60% of the previous size, 2026-07-21 */
  transition: opacity 0.4s var(--ease);
}
.gsec-count {
  font-size: 11px; letter-spacing: 0.12em; color: var(--grey);
  font-variant-numeric: tabular-nums;
}
/* a hairline cross that becomes a minus — a chevron reads as "menu", this reads
   as "open" */
.gsec-mark { margin-left: auto; position: relative; width: 15px; height: 15px; flex: none; align-self: center; }
.gsec-mark::before, .gsec-mark::after {
  content: ""; position: absolute; left: 0; top: 50%;
  width: 15px; height: 1px; background: var(--ink);
  transition: transform 0.45s var(--ease), opacity 0.45s var(--ease);
}
.gsec-mark::after { transform: rotate(90deg); }
.gsec-toggle[aria-expanded="true"] .gsec-mark::after { transform: rotate(0deg); opacity: 0; }
@media (hover: hover) { .gsec-toggle:hover .gsec-title { opacity: 0.55; } }
.gsec-toggle:focus-visible { outline: 1px solid var(--ink); outline-offset: 3px; }

/* --- the panel --- */
.gsec-panel[hidden] { display: none; }
.gsec-inner { padding-bottom: clamp(38px, 6vh, 68px); }
.expl-list { list-style: none; margin: 0; padding: 0; }
.expl-row { padding: 0 0 clamp(18px, 2.4vh, 26px); }
.expl-row + .expl-row { padding-top: clamp(18px, 2.4vh, 26px); border-top: 1px solid var(--hair); }
.row-name { font-size: clamp(16px, 1.5vw, 20px); font-weight: 300; line-height: 1.4; margin: 0; }
.row-do { margin: 7px 0 0; font-size: 13px; line-height: 1.7; color: var(--grey); max-width: 34em; }
.gsec .gnote { font-style: italic; color: var(--grey); font-size: 13px; margin: 0 0 20px; }

/* --- the photos: one slideshow, hard left, page-level ---
   Duncan 2026-07-17, after three wrong attempts by me: "as they have them now" —
   i.e. copy vallettavintage.com/explore. Measured on her live page: all 13 photos
   occupy the SAME rectangle (x:0 y:285 381x508) and cycle one at a time. It is a
   slideshow bled off the left edge, and it is NOT tied to a section — it sits
   there whichever category is open ("photos not stuck to the section").

   His one change from hers: "not fixed on the same space — slightly off on each
   one appearing", so each slide carries its own small --px/--py nudge instead of
   every frame stamping the identical rectangle.

   Explicitly NOT: shadows, borders, angles, overlapping ("no fucking shadows —
   no photos overlapping"). Only one slide is ever visible, so nothing can overlap. */
.guide {
  /* Duncan 2026-07-17: "move culture and that whole column to the middle — so the
     photos get the left full side to play in as well." Two equal halves: the text
     starts at the centre line, and everything left of it belongs to the photos. */
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: clamp(24px, 3vw, 56px);
  /* Anchor both columns to the TOP, not centre (Duncan 2026-07-18: opening a long
     category made align-items:center drop the sticky photo to the middle/bottom of the
     tall row, far from the title). Top-aligned + the photo's `position:sticky` keeps it
     up near the open title and it follows as the list pushes the page down. */
  align-items: start;
  max-width: 1400px; margin: 0 auto;
  padding-right: var(--pad);
}
/* The stage is the whole left half. It is sized by HEIGHT (the photos are 3:4
   portrait — sizing by the width of a half-page cell would make them enormous),
   which leaves the slide free to roam horizontally across the rest of the half.
   Never clip: the roaming IS the effect. */
/* The stage: the left half. No left-bleed — the photo sits CENTRED in this area
   (Duncan 2026-07-17: "move them central"), not pinned to the window edge. */
.guide-photos {
  position: sticky; top: 15vh;
  height: 67vh;   /* 58vh -> 67vh = photos 15% bigger (Duncan 2026-07-17). Grow the STAGE,
                     not the slide's 84%: that keeps the room the offsets shift into. */
  overflow: visible;
}
.gslide {
  /* Centred in the stage, then nudged by its own small --px/--py.
     NOTHING here transitions except opacity. The offset is a PLACEMENT, not a
     movement: each photo simply arrives a few px off the last ("they appear there
     more or less in the same space but not fixed"). Animating this offset made the
     picture drift across the page — "why is the photo floating about" — so the
     transform must stay off the transition list.
     The nudges are wide (~±70px x, ~±36px y) and that is safe ONLY because the
     slideshow is sequential: one photo fades fully out before the next fades in, so
     there is never a second frame for an offset to read against. They were briefly
     cut to ±11px while the fade still crossfaded — at which point the variation
     stopped being visible at all ("photos seem to be just coming up on the same
     spot"). If anyone ever restores a crossfade, these have to shrink again. */
  position: absolute; top: 50%; left: 50%;
  height: 84%; width: auto; aspect-ratio: 3 / 4;   /* 84%, not 92% — leaves vertical room to shift into */
  margin: 0;
  transform: translate(calc(-50% + var(--px, 0px)), calc(-50% + var(--py, 0px)));
  opacity: 0;
  transition: opacity 1.1s var(--ease);   /* must match FADE in main.js */
  pointer-events: none;
  overflow: hidden;   /* the frame is fixed; the grow below happens INSIDE it */
}
.gslide img { width: 100%; height: 100%; display: block; object-fit: cover; transform: scale(1); }

/* A very slow, very slight grow while a photo is up (Duncan 2026-07-17: "should the
   photos grow a bit? very subtle"). 8% over 5s, linear — 3.5% was imperceptible (Duncan: "maybe too subtle"). Still slow enough that you never catch it
   moving, only sense the picture isn't dead.
   It scales the IMG inside a fixed, clipped frame — NOT the frame itself. Scaling the
   frame would move the photo's outline on the page, which is the "floating about"
   problem all over again. The outline never moves; only the crop breathes.
   Driven by .is-live, not .is-on: .is-on is dropped to START the fade-out, and if the
   animation hung off that, the image would snap back to 1.0 while still visible.
   .is-live is removed only once opacity has reached 0, so the reset is invisible. */
/* 7s, not 5s (Duncan 2026-09-10: "last a bit longer before fade for the ken burns effect
   to sink in"). The hold went up to 4.2s at the same time, so a photo is now on screen for
   about 6.5s including its fades — at 5s the grow finished early and the picture sat dead
   for the last beat. Same 8% travel, just spread across the whole time it is up. */
.gslide.is-live img { animation: photo-grow 7s linear both; }
@keyframes photo-grow {
  from { transform: scale(1); }
  to   { transform: scale(1.08); }
}
@media (prefers-reduced-motion: reduce) {
  .gslide.is-live img { animation: none; }
}
.gslide.is-on { opacity: 1; }
.gslide img { width: 100%; height: 100%; display: block; object-fit: cover; }

/* the six categories, right */
.guide-secs { min-width: 0; }
.guide-secs .gsec { max-width: none; margin: 0; padding: 0; }

@media (max-width: 860px) {
  /* phone: the slideshow leads, then the categories — it can't be sticky beside
     anything on a single column */
  .guide { grid-template-columns: 1fr; gap: 22px; padding-right: 0; }
  /* CENTRED on mobile (Duncan 2026-07-17). `margin: 0 auto` is what does it — the
     stage is narrower than the column, so without auto margins it hugs the left. */
  .guide-photos { position: relative; top: 0; height: auto; width: 54vw; aspect-ratio: 3 / 4; margin: 0 auto 6px; }   /* 78vw -> 54vw so CULTURE clears the fold (Duncan 2026-07-18) */
  .guide-secs { padding: 0 var(--pad); }
  .gsec-toggle { padding: 7px 0; }
  /* Titles ~2x on phone (Duncan 2026-07-17: "the titles need to be 200% bigger").
     The desktop clamp bottomed out at its 30px floor here — 4.2vw is only 16px at
     390, so every phone got the minimum. This gives ~62px at 390. */
  .gsec-title { font-size: clamp(52px, 16vw, 72px); }
  .gsec-count { align-self: flex-start; }
}

@media (prefers-reduced-motion: reduce) {
  .gsec-panel.is-open .print { animation: none; }
}

/* ─────────────────────────────────────────────────────────────────────────────
   DOORS OFF — the alternative landing, toggled at a keystroke (D) or ?doors=off.
   No aperture: each house shows its full image, and only the title (and its
   "walk in" link) fade up as the house arrives. main.js holds the doors fully
   open (e = 1) and clears the inline title opacity so this transition owns it.
   The LOCKED title centring (--tx from the fitter) is untouched here — this
   block changes opacity and reveal timing only, never position.
   ───────────────────────────────────────────────────────────────────────────── */
html.doors-off #houses .door.open .door-name,
html.doors-off #houses .door.open .door-enter { opacity: 0; }
html.doors-off #houses .door.title-in .door-name,
html.doors-off #houses .door.title-in .door-enter { opacity: 1; }
html.doors-off #houses .door-name {
  transition: opacity 1.4s var(--ease), transform 1.4s var(--ease);
  transform: translate(var(--tx, 0px), 18px);
}
html.doors-off #houses .door.title-in .door-name { transform: translate(var(--tx, 0px), 0); }
html.doors-off #houses .door-enter { transition: opacity 1.4s var(--ease) 0.15s; }
/* nothing left to hint at — the image is already whole */
html.doors-off #houses .door-hint { display: none; }

/* The left-hand paragraph reads bottom-left OVER the photo, on desktop too — the same
   treatment the phone already gets when a door opens (Duncan 2026-07-21: "the
   explanation text bottom left as per mobile on desktop too"). Heading dropped, caption
   in cream with a soft shadow so it holds over any photo. Desktop only: below 860px the
   existing mobile block already does exactly this and must keep owning it. */
@media (min-width: 861px) {
  /* Both modes now: the left block lives in the paper band under the OPEN photo and
     appears with the title. JS drives the opacity (0 until fully open), so these rules
     only own where it sits and how it looks. */
  #houses .door-meta--r { opacity: 0; }
  #houses .door-meta--l {
    top: auto;
    bottom: 3.6svh;              /* inside the paper band the photo leaves below itself */
    transform: none;
    left: var(--pad);
    transition: opacity 1.4s var(--ease), color 0.75s cubic-bezier(0.25, 0, 0.25, 1);
  }
  /* display:none, not opacity:0 — an invisible heading still occupies its line, and that
     empty line WAS the strip of paper above the text Duncan wanted rid of (2026-07-21).
     The band is measured from this element, so a hidden-but-present h3 inflated it. */
  #houses .door-meta--l h3 { display: none; }
  #houses .door-meta--l .cap {
    /* The desktop default is the 11.5px micro-caption, sized for a small paragraph beside
       a closed aperture. Against a full-bleed photo at 1440 that reads as a footnote, so
       it steps up to the phone's proportions and beyond (Duncan 2026-07-21). Letter-
       spacing relaxes with the size — the wide tracking belongs to the small caps look. */
    /* Matched to the closed-door paragraph Duncan pointed at (2026-07-21): same quiet
       weight and colour value, just rendered in cream because it now sits on a photo.
       Switzer 300 is already loaded (fontshare 300,400,500) — the weight is what was
       reading as "bold", not the size. Cream held slightly off full strength for the
       same reason the closed version uses --grey rather than --ink. */
    font-weight: 300;
    font-size: clamp(15px, 1.15vw, 17px);
    line-height: 1.6;
    letter-spacing: 0.02em;
    /* wide enough to sit on ONE line at normal desktop widths — neater, as asked. Longest
       caption is Doma's ~91 characters; this wraps gracefully rather than overflowing on
       a narrow laptop instead of being forced with nowrap. */
    max-width: min(82ch, 66vw);   /* 72ch clipped Altana's 96-character line by ~4px */
    /* The line now sits BELOW the photo on page colour, not over the picture (Duncan
       2026-07-21). In ink, matching the phone — "in black as per the mobile version". */
    color: var(--ink);
    text-shadow: none;
  }
  /* JS sets `left` to the title's ink edge; keep it from being fought by the base rule */
  #houses .door-meta--l { right: auto; }

  /* The scrim is gone with the text: nothing sits on the photograph now except the title,
     which is large, cream and high-contrast on its own. The picture is left clean. */

  /* Title stays vertically centred, exactly as built — LOCKED (Duncan 2026-07-21,
     after trying it dropped to the photo's bottom edge). Do not re-raise. */
}

/* Phone gets the same treatment: photo leaves a band of page colour, the line sits in it.
   The mobile block above recolours the caption to cream ON OPEN for the doors-ON layout,
   where it genuinely does sit over the photo — that has to be undone here, since in
   doors-off it no longer does. */
@media (max-width: 860px) {
  /* black on the band, matching doors-on (Duncan 2026-07-21) — phone only; desktop keeps
     the quieter grey it was signed off with */
  html.doors-off #houses .door.open .door-meta--l .cap,
  html.doors-off #houses .door-meta--l .cap {
    color: var(--ink);
    text-shadow: none;
  }
  html.doors-off #houses .door.open .door-meta--l h3 { opacity: 0; }
  html.doors-off #houses .door-meta--l {
    bottom: 3.2svh;
    opacity: 1;
  }
  /* Title untouched here too — stays vertically centred. */
}

@media (prefers-reduced-motion: reduce) {
  html.doors-off #houses .door-name,
  html.doors-off #houses .door-enter { transition: none; }
}

/* the mode read-out, so it is obvious which version is on screen */
.doors-toast {
  position: fixed;
  left: 50%;
  bottom: 26px;
  transform: translateX(-50%) translateY(6px);
  z-index: 9000;
  padding: 8px 16px;
  border-radius: 100px;
  background: rgba(20, 18, 16, 0.82);
  color: #f6f2ec;
  pointer-events: none;
  opacity: 0;
  transition: opacity 0.35s ease, transform 0.35s ease;
}
.doors-toast.show { opacity: 1; transform: translateX(-50%) translateY(0); }


/* ─────────────────────────────────────────────────────────────────────────────
   PHONE — door title & caption placement.
   MUST live at the END of the stylesheet. The `#houses .door-name` block further up
   is entirely `!important`, and `#houses .door.open .door-name` there sets a
   translateY. Media queries add NO specificity, so an identical selector inside
   @media earlier in the file LOSES to it — which is exactly why the first attempt
   moved the title but left a stray transform pulling it ~35px back up.
   ───────────────────────────────────────────────────────────────────────────── */
@media (max-width: 860px) {
  /* The space BELOW the title (down to the bottom edge of the photo) and the space
     ABOVE the paragraph (that edge down to the text) are the same value, so they are
     equal by construction at any line count or screen height — not a measured guess.
     --vv-band is published by main.js, so JS and CSS cannot disagree about the band. */
  #houses .door { --vv-gap: 40px; }   /* lip ~25% deeper, 2026-07-21 */
  #houses .door-name,
  #houses .door.open .door-name {
    top: auto !important;
    /* main.js sets `bottom` inline per door (the band is measured from that door's own
       caption); this is only the fallback before the first frame runs */
    bottom: calc(var(--vv-band, 13svh) + var(--vv-gap)) !important;
    transform: none !important;
    font-size: calc(clamp(88px, 12vw, 190px) * 1.14) !important;
  }
  /* The caption's vertical position is set by main.js every frame: it tracks the photo's
     current bottom edge, --vv-gap below it, in every state. Anchoring it here as well
     (bottom while closed, top once open) is what made it jump at the moment the door
     finished opening. Left to JS on purpose — do not re-add a CSS anchor. */
}


/* A reached step in the booking crumb is a real control — say so on hover/focus. Steps
   ahead of you keep the plain read-out look (Duncan 2026-07-21: "DATES HOME STAY
   DETAILS should be clickable as well"). */
.bk-dot.done { cursor: pointer; }
.bk-dot.done:hover { opacity: 0.62; }
.bk-dot.done:focus-visible { outline: 1px solid var(--ink); outline-offset: 4px; }

/* ---------- check-in: Stripe card field + badge (2026-07-21) ---------- */
.ck-card-el { padding: 14px 16px; border: 1px solid rgba(17,17,17,.18); border-radius: 2px; background: #fff; }
.ck-card-el.StripeElement--focus { border-color: rgba(17,17,17,.5); }
.ck-card-error { min-height: 0; margin: 8px 0 0; color: #8a2b2b; font-size: 13.5px; opacity: 0; transition: opacity .2s; }
.ck-card-error.on { opacity: 1; }
.ck-secure { display: flex; align-items: center; justify-content: space-between; gap: 16px; flex-wrap: wrap; }
.ck-stripe-badge { display: inline-flex; align-items: center; gap: 5px; padding: 4px 9px; border: 1px solid rgba(17,17,17,.14);
  border-radius: 100px; color: var(--grey); font-size: 10.5px; letter-spacing: .04em; text-decoration: none; white-space: nowrap; }
.ck-stripe-badge b { font-weight: 500; color: #111; }
.ck-stripe-badge:hover { border-color: rgba(17,17,17,.3); }


/* lip token lives outside the phone block so DESKTOP gets it too (JS reads --vv-gap) */
#houses .door { --vv-gap: 40px; }

/* ─────────────────────────────────────────────────────────────────────────────
   A lip under the hero film, so the landing shows there is something below it
   (Duncan 2026-07-21). Same device as the houses: the media stops short and a
   strip of page colour shows beneath. Landing only — :not(:has(.door-name))
   excludes the apartment heroes, which carry a giant title instead.
   ───────────────────────────────────────────────────────────────────────────── */
/* Just enough to show an edge — it is a cue, not a band. 104/120 read as a big empty gap
   on a phone (Duncan 2026-07-21: "it's too big the gap"). Applied to EVERY hero, not just
   the landing: any page with content below the fold now shows page colour under its
   picture so you can see there is more ("make sure we have the lip across the board").
   Escape hatch: a hero that should stay truly full-bleed can set --vv-lip: 0px. */
/* Trimmed again 2026-07-27 (Duncan, iPhone: "much slimmer, it's a bit chunky here for no
   reason on the video"): 68/52 → 44/26. Kept proportional across desktop and phone because
   the lip is meant to read as one device site-wide ("across the board"). */
.hero { --vv-lip: 44px; }
@media (max-width: 860px) { .hero { --vv-lip: 26px; } }
/* height must be CALCULATED for the media: a replaced element with height:auto takes its
   INTRINSIC height rather than stretching between top and bottom, which collapsed the
   video to 219px on a phone. The scrim is not replaced, so `bottom` is enough for it. */
/* !important because the apartment heroes carry the sizing INLINE in the HTML
   (style="position:absolute;inset:0;...height:100%..."), which otherwise wins and left
   those pages with no lip at all. */
.hero video,
.hero img,
.hero picture {
  height: calc(100% - var(--vv-lip)) !important;
  bottom: auto !important;
}
.hero::before { bottom: var(--vv-lip); }
/* The lip is page colour and NOTHING may paint into it. The entrance animation scales the
   hero image to 1.14, and `overflow: hidden` on .hero clips to the whole section — lip
   included — so the growing image bled a soft halo across the band while it settled
   (Duncan 2026-07-21). clip-path cuts the section at the lip line, so every image effect
   inside the hero — scale, blur, crossfade — stops there. */
.hero { clip-path: inset(0 0 var(--vv-lip) 0); }
/* the poster background sits BEHIND the video and would paint into the lip — keep it out
   of the strip and let page colour fill it */
.hero:has(video) {
  background-color: var(--paper);
  background-size: 100% calc(100% - var(--vv-lip));
  background-position: top center;
}
/* keep the caption line and the giant title on the picture, clear of the lip */
.hero:not(:has(.door-name)) .hero-foot { bottom: calc(var(--vv-lip) + 22px); }
.hero:has(.door-name) .hero-foot {
  bottom: calc(var(--vv-lip) + var(--door-h, calc(0.74 * clamp(64px, 13.5vw, 210px))) + 16px);
}


/* The wordmark is knocked out to white over a photo, and on a pale hero (SCALA's limestone
   staircase) that means white on white — Duncan 2026-07-21: "wheres the logo". It is there;
   it just vanishes. A soft shadow gives it an edge on light pictures and is invisible on
   dark ones. Same reasoning as the hero caption's shadow. */
.head.on-dark .logo-mark {
  filter: brightness(0) invert(1) drop-shadow(0 1px 10px rgba(0, 0, 0, 0.45));
}

/* Big statement lines break evenly instead of dropping a single orphan word ("scale.")
   onto its own line — Duncan 2026-07-21. */
.fi-title,
.foot-ink .fi-title,
.statement .h-lg,
.h-lg {
  text-wrap: balance;
}


/* "Shared spaces" is a section label, not a room name — set it in sentence case rather
   than the letterspaced caps the room titles (DO / RE / MI) use (Duncan 2026-07-21). */
#shared .suite-head h2 {
  text-transform: none;
  letter-spacing: 0.02em;
}


/* The STORY splash title is set in the secondary face (Switzer light, sentence case),
   not the VV display face — Duncan 2026-07-21 ("change story in secondary font, on main
   splash"). Scoped to the class on that page's h1, so every other hero is untouched. */
/* `.hero .door-name` (two classes) beats a bare `.door-name--sans`, even with
   !important on both — so this has to carry the same specificity. */
/* Sits a touch high of centre — the sans face reads lighter and lower in the frame than
   the display caps the centring was tuned for (Duncan 2026-07-21: "STORY should be pushed
   up a bit, doesn't sit right there"). Desktop only; the phone title is bottom-left. */
@media (min-width: 861px) {
  .hero .door-name--sans { transform: translateY(calc(-50% - 42px)) !important; }
}
.hero .door-name--sans,
.door-name--sans {
  font-family: var(--serif) !important;
  font-weight: 300 !important;
  text-transform: none !important;
  letter-spacing: -0.01em !important;
  font-size: clamp(44px, 7vw, 96px) !important;
  line-height: 1.05 !important;
}


/* phone: the explore list at the same 60% step as desktop (another mobile rule was
   sizing the title span to 16vw) */
@media (max-width: 860px) {
  .gsec-title { font-size: clamp(20px, 5.2vw, 28px) !important; line-height: 1.15; }
}


/* The chosen-home block on the Stay step: the three lines are one unit — house, room,
   dates — set as a tight column beside the photo rather than drifting apart down the
   height of the image (Duncan 2026-07-21: "fix this section for the text sizes and
   positions"). */
.bk-stay-room { align-items: center; gap: clamp(18px, 2vw, 26px); }
.bk-stay-room > div {
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: 6px;
  min-width: 0;
}
.bk-stay-room .label--grey { margin: 0; }
.bk-stay-room .bk-room-name {
  font-size: clamp(34px, 3.4vw, 46px);
  line-height: 0.9;
  margin: 0;
}
.bk-stay-room .cap { margin: 2px 0 0; color: var(--grey); }
.bk-stay-room img { width: clamp(96px, 10vw, 132px); height: clamp(96px, 10vw, 132px); }


/* NOTE 2026-07-21: hiding the booking wordmark was tried to lift the "See available homes"
   button above the fold and made NO difference — the header is fixed, so it takes no flow
   space and removing the mark moves nothing (1083px → 1081px). The screenshot that looked
   higher was simply scrolled: `.head.scrolled .wordmark` fades on scroll. The DATES step is
   ~180px taller than a 900px viewport; only a sticky footbar or a shorter calendar fixes
   it. Do not re-try the logo. */

/* dates step: the party size, visible so it can never filter the search invisibly (2026-07-21) */
.bk-dates-guests { display: flex; align-items: center; gap: 14px; }
.bk-step-ctrl--sm button { width: 28px; height: 28px; font-size: 14px; }
.bk-step-ctrl--sm { gap: 12px; }
@media (max-width: 860px) { .bk-footbar .bk-dates-guests { justify-content: center; } }

/* Booking flow only: shrink the logo band and the deep top pad. Both together pushed the
   primary action ("See available homes" / "Continue" / "Book") below the fold on every
   step, so the guest had to scroll to find out what to do next — Duncan 2026-07-21:
   "remove the logo and go always for the second screen so we see the full picture
   including the SEE AVAILABLE and book buttons across the whole process", then, better:
   "maybe have a smaller vv on top that doesn't push the page down too much".
   Scoped to .bk-compact, NOT .bk-body — checkin.html shares .bk-body and is untouched.

   NOTE: .head is `position: fixed`, so the logo NEVER pushed the page content down — the
   99px top pad did, and that is what was cut. The W's size here is therefore a free
   aesthetic choice with no layout cost; dial it to taste.

   The W stays PRESENT and merely smaller. It must never be display:none here: .head is
   `grid-template-columns: 1fr auto 1fr`, so removing it collapses the middle column and
   drags SLEEP/BOOK in towards the centre — which is exactly what happened when it was
   first tried, and Duncan caught it on sight. */
/* Hanna 2026-09-09: "In BOOK section to keep LOGO the same size as on the website."
   The override that held it at 44px is gone, so the Book page now inherits the site's
   110px like every other page. Safe to do: .head is `position: fixed`, so the mark's
   height costs the booking flow no vertical space (see the note above), and
   `.head.scrolled .wordmark` already fades it out the moment you scroll into the cards.
   Do NOT replace this with display:none if it ever needs hiding again — .head is
   `grid-template-columns: 1fr auto 1fr` and removing the mark collapses the middle
   column, dragging SLEEP/BOOK toward the centre. */
.bk-compact .bk-intro .label--grey { margin-bottom: 14px; }
.bk-compact .bk-intro .cap { margin-top: 12px; }
/* Measured, not guessed: at 1440x900 the stack ran to 970px, so the action sat 70px below
   the fold. These five gaps give back 90px — each ~25-30% tighter, nothing restyled — and
   land the footbar at ~880px with room to spare. Only the booking flow is affected. */
/* The gap under the W has to do two contradictory jobs: breathe on a tall window (Duncan
   2026-07-21: "you still have some space to leave a gap from under logo") while keeping
   CONTINUE above the fold on a 900px one, where there are only 20px spare. So it is driven
   by the height ACTUALLY available rather than a fixed value: 72px at 900px, growing by
   0.45px per extra pixel of viewport, capped at 200. 900->72, 1000->117, 1130->175. */
.bk-compact .bk-main { padding-top: clamp(72px, calc(72px + (100vh - 900px) * 0.45), 200px); }
.bk-compact .bk-rail { margin-top: 36px; margin-bottom: 30px; }
.bk-compact .bk-cal-nav { margin-bottom: 18px; }
.bk-compact .bk-legend { margin-top: 18px; }
.bk-compact .bk-footbar { margin-top: 26px; }


/* STORY on phone: the title is the SANS face, which the VV-Display fitter never measures,
   so --door-h stays 0 and the two hero captions ("THE STORY — SINCE 2012" / "AN ARCHITECT
   …") dropped onto the title (Hanna 2026-07-23). Lift the caption block clear of it — the
   title is ~44px tall and sits a lip+gap off the bottom, so clear that plus the caption's
   own two lines. */
@media (max-width: 860px) {
  .hero:has(.door-name--sans) .hero-foot {
    bottom: calc(var(--vv-lip) + 44px + 48px) !important;
  }
}

/* ============================================================
   BOOK BAR — mobile only (Hanna's item 06, Duncan's design 2026-09-09)
   "a black bar across the bottom - with BOOK <ROOM NAME> which changes as you
   scroll along with a nice fade - not the full thickness of a big button but
   enough to make it clickable."

   TO SWITCH IT OFF: one line, `BOOKBAR_ON` at the top of the block in js/main.js.
   Nothing else in the site depends on it.

   It changes the height and spacing of NOTHING. `position: fixed` keeps it out of
   flow, so no page padding was touched and no section moved. The trade is that it
   sits over the last 52px of whatever is on screen, which is the deal with any
   permanent bar.

   z-index 90: above the page, BELOW the header (100), the menu (120), the
   lightbox (3000) and the navigation veil (4000).
   ============================================================ */
.bookbar {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 90;
  display: none;
  align-items: center;
  justify-content: center;
  /* Slim, but 52px of face is still a comfortable thumb target, and the safe-area
     inset is added BELOW that so the iPhone home indicator never eats into it. */
  height: 52px;
  padding-bottom: env(safe-area-inset-bottom, 0px);
  box-sizing: content-box;
  background: var(--ink);
  color: var(--bg);
  text-decoration: none;
  opacity: 0;
  transform: translateY(100%);
  transition: opacity 0.5s var(--ease), transform 0.5s var(--ease);
}
@media (max-width: 860px) { .bookbar { display: flex; } }
.bookbar.in { opacity: 1; transform: none; }

/* THE WORD "BOOK" NEVER ANIMATES. Only the room name changes.

   A SIMPLE, LONG OPACITY FADE. No movement of any kind. Duncan 2026-09-09: "a simple
   longer fade between each fucking name not a rotating countdown like alphabet."
   An earlier build slid the old name up while the new one rose from below — that reads
   as an odometer, which is exactly wrong here. The name now just fades out, changes
   while it is invisible, and fades back in. Half a second each way.

   The slot's width is reserved at runtime to the widest name on the page (js/main.js),
   so DO -> RE -> MI never reflows the line and BOOK does not move by a pixel. */
.bookbar-label {
  display: inline-flex;
  align-items: center;
  font-family: inherit;
  font-size: 10px;
  font-weight: 500;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  white-space: nowrap;
  max-width: calc(100vw - 48px);
}
.bookbar-word { flex: none; }
.bookbar-room {
  flex: none;
  margin-left: 0.62em;
  overflow: hidden;
  text-overflow: ellipsis;
  transition: opacity 0.5s ease;
}
.bookbar-room.swap { opacity: 0; }
/* one label on the page and nothing to swap to — drop the reserved slot and the gap */
.bookbar--single .bookbar-room { display: none; }

.bookbar:active { background: #241f19; }
.bookbar:focus-visible { outline: 2px solid var(--bg); outline-offset: -4px; }

/* step aside for whatever owns the screen */
body.plan-open .bookbar,
body.lb-open .bookbar { opacity: 0; pointer-events: none; transform: translateY(100%); }

@media (prefers-reduced-motion: reduce) {
  .bookbar { transition: opacity 0.2s linear; transform: none; }
  .bookbar.in { transform: none; }
  body.plan-open .bookbar, body.lb-open .bookbar { transform: none; }
  /* nothing to strip: the name only ever changes opacity, which is already
     vestibular-safe. It keeps its full duration. */
}

/* ============================================================
   BOOK PAGE, PHONE — make room for the full-size mark
   Regression caught 2026-09-09 while shooting the mobile screens. Restoring the
   site-size logo on the Book page (Hanna's item 08) made the fixed header 146px
   tall on a phone (18 + 110 + 18), but `.bk-compact .bk-main` still had a 72px
   top floor tuned for the old 44px mark — so the W was drawn straight over
   "Reservations · direct" and the top of "Choose your dates."
   Measured, not guessed: logo bottom sat at 128px, the label's top at 72px, and
   elementFromPoint at the label returned .logo-mark.
   COST, stated plainly: this pushes the dates step ~96px further down a screen
   that the CSS above already records as too short for it. If that proves worse
   than the big mark is worth, the alternative is a smaller mark on THIS page at
   phone width — which is what Hanna asked us to change, so it is Duncan's call,
   not a silent revert.
   ============================================================ */
/* Clear the mark at EVERY width, not just on phones — the first patch was scoped to
   860px and desktop had exactly the same 56px overlap (measured at 1728x897: logo ink
   ending at 128px, "Reservations - direct" starting at 72px, elementFromPoint at the
   label returning .logo-mark).

   140px, not the header's full 146px height: `.head` has NO background, so the only
   thing that can collide is the ink. The mark ends at 128px, so 140 clears it by 12 and
   costs the page 6px less than clearing the empty box would. MENU sits top RIGHT and the
   intro is centred in a 40em column, so those never meet.

   The space this costs is paid back below, so the action still lands above the fold on a
   900px laptop. */
.bk-compact .bk-main { padding-top: 140px !important; }

/* Paying for the clearance. Each of these is a gap on the BOOKING FLOW ONLY, nothing
   restyled and nothing resized elsewhere on the site. */
.bk-compact .bk-intro h1 { font-size: clamp(21px, 2.2vw, 29px); }
.bk-compact .bk-rail { margin-top: 24px; margin-bottom: 22px; }

/* ============================================================
   THE PER-APARTMENT BOOK LINK — outlined in the site's own language
   Hanna: "could make these a bit more prominent." Duncan: "keep the same colour but
   make a full outline around it", then "make the outline of the buttons the same as
   the rest - i dont think they are black less rounded."

   He was right on both. Read off the live site rather than guessed:
     - every outlined control here uses `var(--hair)` — ink at 12% — or `--hair-soft`
       at 7%. Nothing on the site carries a solid black outline.
     - 2px is the site's radius for a rectangle: 10 uses against 6 of the 100px pill,
       and the pill is reserved for the filled primary button.
   So the frame is a hairline at 2px, and it darkens to ink on hover exactly the way
   `.tlink:hover` already does everywhere else. The box alone is the prominence Hanna
   asked for; it does not need weight on top.

   Scoped to the booking links inside an apartment block, so ALL HOUSES / SEE NEXT keep
   their plain underline.
   ============================================================ */
.suite-spread a.tlink[href*="booking.html?room="] {
  display: inline-block;
  padding: 12px 22px;
  border: 1px solid var(--hair);
  border-radius: 2px;
  transition: border-color 0.5s var(--ease);
}
.suite-spread a.tlink[href*="booking.html?room="]:hover { border-color: var(--ink); }

/* PHONE: LEFT, aligned with the text above it. Hanna 2026-09-10: "It looks good in the
   browser; I would still align it with the text though in mobile version."
   This reverses the right-hand placement Duncan asked for earlier the same day ("closer
   to the user thumb") — his call, relayed from her. Left is also the default, so the
   rule that pushed it right is simply gone rather than overridden. */

/* ============================================================
   BOOKING — the action bar is pinned on phones; GUESTS is not in it
   Duncan 2026-09-10: "we are missing the bottom button", then "i dont think that [the
   guests control] needs to be stuck to the bottom of the mobile page - just keep the
   main button so on any mobile its not hidden."

   The guest stepper was moved OUT of the bar in booking.html and now sits above it in
   the page, where it scrolls away like ordinary content. What is pinned is the bar
   holding the date summary and the button — the two things you need in order to act.

   Pinning the button ALONE was tried and is wrong: with the summary still in the flow,
   the floating button landed straight on top of "Arrive 15 Sep - choose your departure"
   at the top of the page, because that line's natural position is in the same band the
   pinned button occupies. A bar owns that band instead, so nothing is ever covered.

   This exists because two instructions collide: the note at ~3159 records Duncan's
   2026-07-21 rule that the primary action must never sit below the fold, and the fix
   then was to shrink the logo to 44px — which Hanna has since asked to undo. Pinning
   means the action no longer depends on the page fitting, on any handset.

   Phones only: the desktop row already sits in view (verified 866px of 900).
   ============================================================ */
/* Only when the phone script has lifted this row out of the pinned bar and up to the
   search controls. In the bar, on desktop, it is untouched. */
.bk-dates-guests.is-topside {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 14px;
  margin: 0 0 22px;
}

@media (max-width: 860px) {
  .bk-compact .bk-footbar {
    position: sticky;
    bottom: 0;
    z-index: 60;
    margin-top: 22px;
    padding-top: 16px;
    padding-bottom: calc(14px + env(safe-area-inset-bottom, 0px));   /* clears the home indicator */
  }
  /* The bar sits in a padded column, so a plain background would leave the page showing
     through at its edges. This paints the full width behind it; z-index -1 keeps it
     behind the bar's own content while staying inside the bar's stacking context, so it
     can never cover the calendar above. */
  .bk-compact .bk-footbar::after {
    content: "";
    position: absolute;
    inset: 0;
    /* --paper, NOT --bg. Hanna 2026-09-17, golden hour: a white band appeared behind the
       booking bar on her phone. The page changes colour through the day (dawn, day, golden,
       dusk, night each set body background AND --paper together), but this backdrop was
       painted with the fixed --bg, so at any hour except plain day it sat lighter than the
       page around it. --paper always matches the page it is sitting on. */
    background: var(--paper);
    /* the paper runs edge to edge as a shadow: a shadow never widens the page, so the
       phone can no longer be dragged sideways (the old -50vw box did exactly that) */
    box-shadow: 0 0 0 100vmax var(--paper);
    clip-path: inset(0 -100vmax);
    z-index: -1;
  }
  /* so the last row of content can scroll clear of the bar instead of hiding under it */
  .bk-compact .bk-step.on { padding-bottom: 20px; }
}

/* ============================================================
   EXPLORE — centred list, photographs BOTH sides. DESKTOP ONLY.
   Hanna's item 05, specified by Duncan 2026-09-10: "make all text centred ... photos
   come up on both sides in the same style different positions and different images on
   each side - one at a time on either side - NOT MOBILE just desktop", and "all fed
   from the same CMS and order".

   Three columns instead of two: photographs, the list, photographs. The middle column
   is capped in ch rather than fr so the measure stays readable as the window grows —
   at 1400px wide a third of the page is a very long line to centre.

   The right-hand stage is BUILT IN JS, not written into the markup, and it reads its
   pictures from the left stage at the moment each one comes up. Two reasons, both hard:
   the editor keys Hanna's photo fields to `data-fig` ids that must stay unique on the
   page, so a duplicated block would break her editing; and reading the src live means
   whatever she publishes appears on both sides, in her order, without a second copy to
   keep in step. See js/main.js.

   Mobile keeps the single column it already had — the rule below is min-width only, and
   the script removes the second stage under 861px.
   ============================================================ */
@media (min-width: 861px) {
  .guide { grid-template-columns: 1fr minmax(0, 46ch) 1fr; }
  /* TRULY centred. The grid carried `padding-right: var(--pad)` and nothing on the left,
     left over from the two-column design where the text ran to the page gutter. With
     three columns that asymmetry threw the whole thing 36px off centre — measured, not
     eyeballed: page centre 720, list centre 684. */
  .guide { padding-right: 0; }
  /* Sized for the three-column page. 84% of the stage was drawn when a photo had half the
     page; in a third of it that came to 380px wide against a 370px column, so it printed
     over the list. Cut to 60%, then back up 15% at Duncan's ask on 2026-09-10 — 69% comes
     to ~312px wide, which still clears the column with the lean offsets applied. */
  .gslide { height: 69%; }

  /* The BLOCK is centred; the rows inside it are not. Hanna 2026-09-10, asked whether she
     wanted a fixed width with the +s at a common far end: "Yes ... Like it was but in the
     middle of the page." So the list keeps the left-aligned reading it always had — title
     at the start of the line, count beside it, the + at the far end — and it is the whole
     fixed-width column that has moved to the middle of the page.
     This deliberately reverses the centred-text pass of an hour earlier: her answer is the
     later one and it is specific. The column is a fixed 46ch, so every row is the same
     width and every + lands on the same vertical line. */

  /* the second stage is the same object as the first — same size, same stickiness,
     same fade. Only the placements inside it differ, and those come from the script. */
  .guide-photos--r { grid-column: 3; }
}

/* ============================================================
   "A DAY AT ..." — the client picks the media size
   Hanna 2026-09-10: "is it possible to add an option to choose the size of photo in
   'day experience'? Photos look small in comparison to videos."

   She was right about the cause. Every stop was BORN with a rhythm class (w-s / w-m /
   w-l, rotating small-medium-large down the timeline), but a video ignores it —
   `.day-ph.is-video` forces 240-440px with !important. So dropping a film into a stop
   built as "small" made it three times the size of the photo that had been there, and
   she had no way to even it up.

   These sizes already existed; she simply could not reach them. `data-size` is the
   explicit choice, set from the editor, and it beats BOTH the baked class and the video
   rule. Crucially it only applies once she picks something: nothing on the live site
   moves until she does. The phone keeps its own percentage widths, as the w-* classes
   already do down there.
   ============================================================ */
.day-ph[data-size="s"]  { width: clamp(150px, 17vw, 230px) !important; max-width: none !important; }
.day-ph[data-size="m"]  { width: clamp(200px, 23vw, 320px) !important; max-width: none !important; }
.day-ph[data-size="l"]  { width: clamp(270px, 32vw, 440px) !important; max-width: none !important; }
.day-ph[data-size="xl"] { width: clamp(320px, 40vw, 560px) !important; max-width: none !important; }

@media (max-width: 860px) {
  .day-ph[data-size="s"]  { width: 44% !important;  max-width: 200px !important; }
  .day-ph[data-size="m"]  { width: 64% !important;  max-width: 280px !important; }
  .day-ph[data-size="l"]  { width: 88% !important;  max-width: 380px !important; }
  .day-ph[data-size="xl"] { width: 100% !important; max-width: 460px !important; }
}

/* ============================================================
   PHONE: the hero media must not resize as Safari's toolbar moves
   Hanna 2026-09-11: "The video and head image are zooming in/out, we need to remove it
   in Mobile version too."

   This is NOT the entrance bloom — that was removed from the stills on 09-09 and never
   ran twice anyway. Read off her screen recording: between frames the Safari chrome
   collapses, and because `.hero` is `100dvh`, the DYNAMIC viewport unit, the section
   grows and shrinks with it. The media inside is `object-fit: cover`, so every one of
   those height changes re-crops the picture. Scrolling on iOS therefore zooms the hero
   in and out continuously.

   The section stays on dvh — it has to, or the paper of the next section shows through
   underneath as a band once the bar collapses, which is the bug dvh was brought in to
   fix in the first place (see the note on .hero). Instead the MEDIA is pinned to `lvh`,
   the largest viewport, which never changes as the bar moves. It is taller than the
   section while the bar is showing and the overflow is clipped, so nothing moves on
   screen and the hero's own foot line stays exactly where it was.
   ============================================================ */
@media (max-width: 860px) {
  /* Hanna 2026-09-11 (Doma): "There is that line which keeps moving". Pinning only the
     MEDIA was half a fix: the SECTION was still 100dvh with overflow hidden, so whenever
     the toolbar was showing it cut the taller photo at its own bottom, and that cut rode
     up and down with the toolbar. Now the section AND the media take one height, measured
     once when the page opens (--vv-hero-h, set in main.js, re-measured only when the WIDTH
     changes, i.e. rotation). Toolbar movement no longer reaches either, so the edge is
     fixed to the page. Without JS it falls back to svh/lvh, which are also static. */
  .hero,
  .story-page .hero {
    height: var(--vv-hero-h, 100svh) !important;
  }
  .hero video,
  .hero img,
  .hero picture {
    height: calc(var(--vv-hero-h, 100lvh) - var(--vv-lip)) !important;
  }
  /* And the picture's BOTTOM EDGE must not move either. Hanna 2026-09-11: "Bottom of the
     picture is moving when you start scrolling, could you pls remove it too."
     The edge was being cut by the section's clip-path, `inset(0 0 var(--vv-lip) 0)`, which
     is measured from the SECTION — and the section is `100dvh`, so it grew and shrank with
     the toolbar and dragged the cut line with it.
     Dropping the clip lets the media's own fixed height define the edge instead, so it now
     sits a constant distance from the top of the page whatever the toolbar does. What is
     left underneath is paper, against the paper of the next section, so the leftover strip
     changing height is invisible — there is no line there to watch.
     The clip was originally added to stop the entrance scale bleeding a halo across the
     lip. That scale is gone from the stills, so there is nothing left to bleed. */
  .hero { clip-path: none; }
}

/* The "All houses" link is gone at EVERY width. Hanna 2026-09-11 dropped it on the phone
   (the menu lists every home); 2026-09-14: "we can remove 'all houses' button since it's
   just the same as SLEEP", which sits in the nav on every page. The markup stays in the
   four apartment pages so the link can come back with one rule. */
.hero .hero-back { display: none; }

/* Hanna 2026-09-11: "After STORY lands the title jumps up". The arrival fade holds every
   hero title at the house-page position, translateY(-50% + 20px). The Story title RESTS
   42px above centre instead (the sans-face nudge above), so the moment the arrival classes
   came off it snapped up about 62px. Hold it at its own resting position through the
   arrival too, so it only fades. Desktop only: on the phone the title is pinned by main.js. */
@media (min-width: 861px) {
  .vv-enter .hero .door-name--sans,
  .vv-enter-go .hero .door-name--sans { transform: translateY(calc(-50% - 42px)) !important; }
}
