/* ============================================================
   Selah — section layouts
   ============================================================ */

/* ============================================================
   1 · HERO
   ============================================================ */

/* The hero text never sits on the photograph. It was tried, measured, and
   abandoned: white type over a sunlit window reads 1.3:1, and the only
   scrim strong enough to fix it dims the skyline into mud — which is the
   one thing the photo is selling. So the type always has paper under it,
   stacked beneath the photo on phones and beside it on desktop. That also
   means one colour context instead of two: ink on cream, everywhere. */
.hero {
  --hero-fg:       var(--ink);
  --hero-fg-muted: var(--stone);
  --hero-rule:     var(--sand);

  position: relative;
  min-height: 100svh;              /* svh: mobile browser chrome can't cause a jump */
  display: flex;
  flex-direction: column;
  overflow: hidden;
  background: var(--paper);
}

.hero__media {
  position: relative;
  flex: none;
  width: 100%;
  z-index: 0;
}

/* The crop is pure geometry: object-fit:cover reveals
   (container aspect / 1.5) of the source width. The skyline lives at
   x 0-46% and the bed at x 28-90%, so holding both needs ~52%. A 100svh
   phone viewport gives 31% — which is why the photo is never full-height
   on a phone. At these pane sizes the aperture is 57-69%. */
.hero__img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: 42% 50%;
}

/* Only the wordmark sits on the photo now, so the scrim has one job: make
   it legible. It lands on a pure white ceiling, so it needs ~0.46 to clear
   3:1 — anything less and the brand name is the least readable text on the
   page. The ceiling is the least interesting part of the frame, so this
   costs nothing; it is fully clear before the window starts. */
.hero__scrim {
  position: absolute;
  inset: 0;
  background: linear-gradient(to bottom,
    rgba(22,18,14,0.60) 0%,
    rgba(22,18,14,0.52) 16%,
    rgba(22,18,14,0.12) 30%,
    rgba(22,18,14,0)    42%);
}

/* Fine grain. On large displays this reads as film texture rather
   than upscaling softness — the reason the 1440px source holds up. */
.hero__grain {
  position: absolute;
  inset: 0;
  opacity: 0.16;
  mix-blend-mode: overlay;
  pointer-events: none;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='140' height='140'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='140' height='140' filter='url(%23n)'/%3E%3C/svg%3E");
}

/* The paper ground the text sits on. Fills the width beneath the photo on
   phones; becomes the left column beside it on desktop. */
.hero__panel {
  position: relative;
  z-index: 1;
  flex: 1;
  display: flex;
  align-items: center;
  background: var(--paper);
}

/* The wordmark is the one thing still over the photograph. */
.hero__top {
  position: absolute;
  top: 0;
  left: 50%;
  transform: translateX(-50%);
  z-index: 2;
  width: var(--shell-wide);
  padding-top: var(--space-l);
}

.hero__bottom {
  position: relative;
  z-index: 1;
  width: 100%;
  padding-inline: 1.25rem;
  padding-block: var(--space-m);
  padding-bottom: calc(var(--space-m) + env(safe-area-inset-bottom, 0px));
}

.wordmark {
  font-family: var(--font-display);
  font-size: var(--step-1);
  font-weight: 400;
  letter-spacing: 0.34em;
  text-transform: uppercase;
  color: var(--white);
  text-decoration: none;
  display: inline-block;
  text-shadow: 0 1px 12px rgba(0,0,0,0.4);
}

.hero__eyebrow {
  font-size: var(--step--2);
  font-weight: 500;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--hero-fg-muted);
  margin-bottom: var(--space-s);
}

/* Sized so each authored line holds on one line. The longest line,
   "Where stillness", runs ~6.6em in Fraunces — much shorter than the
   headline this replaced, so it carries a larger size. 10.5vw fills
   ~81% of the measure at 390px and ~85% at 320px, leaving margin for
   device rasterization differences and the wider fallback serif during
   font swap. The ceiling here is vertical, not horizontal: the CTA has
   to stay above the fold, which is what the media reserve below pays for. */
.hero__headline {
  font-size: clamp(1.9rem, 10.5vw, 3.2rem);
  color: var(--hero-fg);
  line-height: 1.06;
  letter-spacing: -0.028em;
  margin-bottom: var(--space-s);
  font-variation-settings: 'SOFT' 0, 'WONK' 1;
}

.hero__headline span { display: block; }

/* The second line is the payoff — let it sit slightly lighter.
   .vh is not a headline line, so exclude it. */
.hero__headline span + span:not(.vh) { opacity: 0.9; }

/* ---- Credential strip -------------------------------------------
   Rating, then what the stay actually is. Each row carries its own
   hairline above it, so either can be absent (Eastwood may have no
   rating yet) without leaving a stranded border or an empty box.
   Deliberately no pill, no fill, no radius: a badge reads
   promotional, a ruled row reads like a fact. */

.hero__trust,
.hero__facts {
  border-top: 1px solid var(--hero-rule);
  padding-top: var(--space-xs);
  margin-bottom: var(--space-xs);
  list-style: none;
  padding-inline: 0;
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--space-3xs) var(--space-s);
  /* Without a cap the rule runs the full width of the shell and slices
     straight across the photograph. It should read as a strip under the
     text, not a line drawn over the bed. */
  max-width: 32rem;
}

.hero__facts { margin-bottom: var(--space-m); }

.hero__facts li + li::before {
  content: "·";
  margin-inline-end: var(--space-s);
  color: var(--hero-fg-muted);
  opacity: 0.5;
}

/* Set in the display serif. The same number in a bold sans reads as a
   badge; in Fraunces it reads as a fact. That is the whole difference
   between premium and promotional here. */
.hero__rating {
  display: inline-flex;
  align-items: center;
  gap: 0.34em;
  font-family: var(--font-display);
  font-size: var(--step-1);
  line-height: 1;
  color: var(--hero-fg);
}

.hero__rating-num {
  display: inline-flex;
  align-items: center;
  gap: 0.3em;
}

/* Laurels either side of the number — the same composition Airbnb uses,
   drawn in this site's own stroke idiom. currentColor, so no new brass. */
.hero__laurel {
  width: 0.82em;
  height: 1.37em;              /* the 24:40 viewBox, kept undistorted */
  flex: none;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.6;
  stroke-linecap: round;
  stroke-linejoin: round;
  opacity: 0.75;
}

.hero__laurel--r { transform: scaleX(-1); }

/* The label says what the badge is called; this says what it means, which
   is the part that actually persuades. */
.hero__trust-note {
  margin-top: calc(var(--space-3xs) * -1);
  margin-bottom: var(--space-xs);
  max-width: 32rem;
  font-size: var(--step--2);
  line-height: 1.5;
  color: var(--hero-fg-muted);
}

/* Not brass. Brass is reserved for the CTA — it is the only thing on
   the page allowed to look clickable. */
.hero__star {
  width: 0.68em;
  height: 0.68em;
  flex: none;
  fill: currentColor;
  stroke: none;
}

/* One flex item, not two — so it wraps as a unit under the rating on a
   phone instead of overflowing the panel. */
.hero__trust-meta {
  font-size: var(--step--2);
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--hero-fg-muted);
}

.hero__facts {
  font-size: var(--step--1);
  color: var(--hero-fg-muted);
  margin-top: 0;
}

/* Naming the destination costs one quiet line and removes the surprise
   of being sent off-site — which is a measurable bounce trigger. */
.hero__cta-note {
  margin-top: var(--space-2xs);
  font-size: var(--step--2);
  letter-spacing: 0.06em;
  color: var(--hero-fg-muted);
}

/* On paper the heavy on-photo shadow reads as grime. */
.hero .cta { box-shadow: var(--shadow-cta); }

.hero__scroll-cue {
  position: absolute;
  left: 50%;
  bottom: var(--space-m);
  transform: translateX(-50%);
  z-index: 1;
  width: 1px;
  height: 46px;
  background: linear-gradient(to bottom, rgba(255,255,255,0) 0%, rgba(255,255,255,0.7) 100%);
}

/* ---- Hero entrance ------------------------------------------
   Pure CSS, no JS gate. If the stylesheet loads the sequence runs; if it
   somehow does not, the content is simply there. That is sturdier than a
   JS-added class, and the reduced-motion block in base.css collapses the
   whole thing (it zeroes animation-delay as well as duration, so nothing
   sits invisible waiting for a delay that will never pay out).

   The photograph is deliberately excluded — it is the LCP element and
   must paint immediately. It gets only a slow settle out of a 3% scale,
   which costs nothing at first paint. */

@keyframes heroRise {
  from { opacity: 0; transform: translate3d(0, 14px, 0); }
  to   { opacity: 1; transform: none; }
}

@keyframes heroMaskRise {
  from { transform: translateY(105%); }
  to   { transform: none; }
}

@keyframes heroSettle {
  from { transform: scale(1.03); }
  to   { transform: none; }
}

/* `backwards`, never `both`. A forwards fill would pin transform:none on
   these elements permanently, which silently kills the CTA's hover lift —
   an animation's fill outranks a normal declaration. With `backwards` the
   keyframes hold only during the delay, then the element hands control
   back to its own styles.

   The wordmark rather than .hero__top is animated for the same class of
   reason: on phones .hero__top is centred with translateX(-50%), and
   animating transform on it throws the wordmark half its width to the
   right for the duration. */
.hero__top .wordmark,
.hero__eyebrow,
.hero__trust,
.hero__trust-note,
.hero__facts,
.hero .cta,
.hero__cta-note {
  animation: heroRise 800ms var(--ease-settle) backwards;
}

/* The hero is not a scroll-reveal target, so nothing ever adds .is-visible
   to it. Its masks therefore need a *resting* state of "risen" — otherwise
   the backwards fill hands them back to the base translateY(105%) the
   moment the entrance finishes and the headline drops out of sight. */
.hero__headline .mask__i {
  transform: none;
  animation: heroMaskRise 900ms var(--ease-settle) backwards;
}

.hero__img {
  animation: heroSettle 2000ms var(--ease-settle) backwards;
}

/* The sequence. Roughly 1.5s to fully settle. */
.hero__top .wordmark           { animation-delay: 0ms; }
.hero__headline .mask:nth-of-type(1) .mask__i { animation-delay: 120ms; }
.hero__headline .mask:nth-of-type(2) .mask__i { animation-delay: 220ms; }
.hero__eyebrow                 { animation-delay: 340ms; }
.hero__trust                   { animation-delay: 420ms; }
.hero__trust-note              { animation-delay: 480ms; }
.hero__facts                   { animation-delay: 540ms; }
.hero .cta                     { animation-delay: 620ms; }
.hero__cta-note                { animation-delay: 720ms; }

/* ============================================================
   1a · HERO — stacked (phones and portrait tablets)
   Photo on top, paper beneath. The photo takes 54svh where there is
   room, but never so much that the panel cannot seat its content: the
   CTA must stay above the fold, because the sticky CTA only appears
   once the hero has scrolled out. The 400px reserve is the measured
   panel height, not a guess.
   ============================================================ */

@media (max-width: 999px) {
  /* 425px reserved for the panel, up from 400 — the larger headline is
     paid for out of the photo, not out of the fold. */
  .hero__media {
    height: clamp(180px, min(54svh, 100svh - 425px), 460px);
  }

  .hero .cta { width: 100%; }

  /* The panel edge already implies more below, and a white cue would be
     invisible on paper. */
  .hero__scroll-cue { display: none; }
}

/* Short viewports — small older phones, and any phone held in landscape.
   The panel content is a fixed height, so the photo yields rather than
   pushing the CTA past the fold. Tightening the panel beats dropping
   content or crushing the photo to a sliver. */
@media (max-width: 999px) and (max-height: 660px) {
  .hero__media { height: clamp(140px, 100svh - 380px, 280px); }

  /* Sizing the headline off width alone means a landscape phone gets an
     oversized headline in a 390px-tall viewport. Cap it against the height
     as well; the horizontal fit is never the binding constraint here. */
  /* Small-and-short phones get no share of the size increase — there is
     no vertical room to pay for it without pushing the CTA under. */
  .hero__headline { font-size: clamp(1.5rem, min(8vw, 7.5vh), 2.5rem); }

  .hero__bottom {
    padding-block: var(--space-s);
    padding-bottom: calc(var(--space-s) + env(safe-area-inset-bottom, 0px));
  }

  .hero__eyebrow,
  .hero__headline,
  .hero__trust-note { margin-bottom: var(--space-2xs); }

  .hero__facts { margin-bottom: var(--space-s); }
}

/* ============================================================
   1b · HERO — desktop
   Paper column beside the photograph rather than beneath it. The photo
   is left completely undimmed: at this aperture (~62% of source width)
   it holds the window, the skyline and the bed at once.
   ============================================================ */

/* Also claims wide-but-short viewports — a phone in landscape. 390px of
   height cannot seat a photo above ~320px of text, but it seats them
   side by side comfortably. Source order matters: this block follows the
   stacked ones, so it overrides them where both match. */
@media (min-width: 1000px), (max-height: 560px) and (min-aspect-ratio: 3/2) {
  .hero {
    display: grid;
    grid-template-columns: minmax(min(400px, 52%), 44%) 1fr;
    grid-template-rows: 100svh;
  }

  .hero__media {
    grid-column: 2;
    grid-row: 1;
    height: 100%;
  }

  /* Nothing is over the photo except the scroll cue. */
  .hero__scrim { display: none; }

  .hero__top,
  .hero__panel {
    grid-column: 1;
    grid-row: 1;
  }

  /* Now on paper, so it takes the ink treatment and loses the shadow it
     needed against the photograph. */
  .hero__top {
    position: static;
    transform: none;
    width: auto;
    align-self: start;
    padding: var(--space-l) clamp(var(--space-l), 4vw, var(--space-2xl)) 0;
  }

  .hero__top .wordmark {
    color: var(--ink);
    text-shadow: none;
  }

  .hero__panel { align-self: center; }

  .hero__bottom {
    padding-inline: clamp(var(--space-l), 4vw, var(--space-2xl));
    padding-block: var(--space-l);
  }

  /* The column is the measure now, so the headline is sized to it rather
     than to the viewport: ~8.9em at this optical size against ~500px of
     column at 1440px. */
  /* Capped against height as well as width, so a landscape phone does not
     get a headline that consumes the whole viewport. */
  .hero__headline {
    font-size: clamp(1.6rem, min(4.4vw, 7vh), 4.4rem);
    margin-bottom: var(--space-m);
  }

  .hero__eyebrow  { margin-bottom: var(--space-m); }
  .hero__facts    { margin-bottom: var(--space-l); }
  .hero .cta      { width: auto; }

  /* Into the photo column, where a white cue reads. */
  .hero__scroll-cue {
    position: static;
    grid-column: 2;
    grid-row: 1;
    align-self: end;
    justify-self: center;
    transform: none;
    margin-bottom: var(--space-m);
  }
}

/* Landscape phones again: the column layout fits, but only with the
   panel's internal spacing tightened to match the available height. */
@media (max-height: 560px) and (min-aspect-ratio: 3/2) {
  .hero__bottom { padding-block: var(--space-s); }

  /* A harder height cap than the column rule uses. Scoped here rather than
     lowering that rule's vh term, which would shrink the headline on normal
     desktops where there is plenty of height to spare. */
  .hero__headline { font-size: clamp(1.4rem, min(4.4vw, 6vh), 2.4rem); }

  .hero__eyebrow,
  .hero__headline,
  .hero__trust-note,
  .hero__facts { margin-bottom: var(--space-2xs); }

  .hero__scroll-cue { display: none; }
}

/* ============================================================
   2 · BRAND INTRO
   The one section that is mostly empty. Resist filling it.
   ============================================================ */

.intro { padding-block: var(--space-3xl); }

.intro__grid {
  display: grid;
  gap: var(--space-xl);
  align-items: center;
}

@media (min-width: 900px) {
  .intro__grid {
    grid-template-columns: 1fr 0.78fr;
    gap: clamp(var(--space-l), 4.5vw, var(--space-2xl));
  }
}

.intro__word {
  font-family: var(--font-display);
  font-size: var(--step-5);
  line-height: 0.94;
  letter-spacing: -0.03em;
  margin-bottom: var(--space-xs);
  font-variation-settings: 'SOFT' 0, 'WONK' 1;
}

.intro__pron {
  font-size: var(--step--1);
  color: var(--stone);
  font-style: italic;
  margin-bottom: var(--space-2xs);
}

.intro__def {
  font-size: var(--step-0);
  color: var(--stone);
  padding-bottom: var(--space-l);
  margin-bottom: var(--space-l);
  border-bottom: 1px solid var(--sand);
  max-width: 28rem;
}

.intro__body p {
  font-size: var(--step-1);
  line-height: 1.6;
  max-width: var(--measure);
  color: var(--ink-soft);
}

.intro__body p + p { margin-top: var(--space-m); }

.intro__footnote {
  margin-top: var(--space-l);
  font-size: var(--step--1);
  color: var(--stone);
  font-style: italic;
}

.intro__figure { margin: 0; }

/* No aspect-ratio, no object-fit: the width/height attributes carry the
   real ratio, so the photo is shown whole at every width. The 4/3 mobile
   crop this replaces was discarding half the image. */
.intro__figure img {
  width: 100%;
  height: auto;
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-lift);
}

/* ============================================================
   3 · GALLERY
   Mobile: horizontal snap-scroll.
   Desktop: CSS columns — true masonry with mixed aspect ratios,
   which is what makes the irregular photo set look deliberate.
   ============================================================ */

.gallery__head {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-s);
  margin-bottom: var(--space-xl);
}

/* -- mobile: swipe rail -- */
.gallery__items {
  display: flex;
  /* The one landscape photo is much shorter than the portraits; centring
     it in the rail reads as intentional rather than dropped. */
  align-items: center;
  gap: var(--space-s);
  overflow-x: auto;
  scroll-snap-type: x mandatory;
  -webkit-overflow-scrolling: touch;
  scrollbar-width: none;
  padding-bottom: var(--space-xs);
  /* Bleed to the screen edge so it reads as a rail, not a box.
     Deliberately not vw-based: 100vw includes the scrollbar, which
     pushed the page 15px wider than the viewport on desktop Windows. */
  margin-inline: -1.25rem;
  padding-inline: 1.25rem;
}

.gallery__items::-webkit-scrollbar { display: none; }

.gallery__item {
  flex: 0 0 78%;
  scroll-snap-align: center;
  margin: 0;
}

.gallery__btn {
  display: block;
  width: 100%;
  padding: 0;
  border: 0;
  background: none;
  cursor: zoom-in;
  border-radius: var(--radius-lg);
  overflow: hidden;
}

/* Natural ratio, always. The width/height attributes reserve the correct
   space before load, so there is no layout shift and nothing to crop.
   An earlier comment here claimed the set mixed portraits and landscapes
   and so needed a uniform crop to look deliberate — after the reshoot the
   set is uniform (11 of 13 are 2:3) and the crop was manufacturing the
   irregularity it claimed to fix. */
.gallery__btn img {
  width: 100%;
  height: auto;
  background: var(--paper-warm);
  transition: transform 1200ms var(--ease), filter var(--dur-ui) var(--ease);
}

/* ---- Photo settle -------------------------------------------
   Photos come to rest out of a slight over-scale rather than sliding on.

   Driven by a custom property rather than by competing transform rules:
   a plain `.reveal.is-visible … img { transform: none }` would out-specify
   the hover scale below and kill it, and would strand every photo at 1.045
   when JS is off (.is-visible never arrives). Flipping one variable avoids
   both traps and leaves `transform` free for hover to own. */
.reveal            { --photo-scale: 1.045; }
.reveal.is-visible { --photo-scale: 1; }
.no-js .reveal     { --photo-scale: 1; }

.reveal img {
  transform: scale(var(--photo-scale, 1));
  transition: transform var(--dur-enter) var(--ease-settle);
  transition-delay: var(--reveal-delay, 0ms);
}

/* Hover only where a pointer actually exists — on touch these would
   latch on after a tap and stay stuck. */
@media (hover: hover) {
  .gallery__btn:hover img {
    transform: scale(1.028);
    filter: brightness(1.03);
  }
}

.gallery__btn:focus-visible {
  outline: 2px solid var(--brass);
  outline-offset: 3px;
}

.gallery__hint {
  margin-top: var(--space-m);
  font-size: var(--step--2);
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--stone);
}

/* -- desktop: row-wise grid --
   Deliberately NOT CSS columns. Columns flow top-to-bottom, which
   scatters the running order and put the bathroom in the opening
   row. A grid keeps the sequence authored in the JSON, so the first
   three photos a visitor sees are the three we chose. */
@media (min-width: 760px) {
  .gallery__items {
    display: grid;
    grid-template-columns: repeat(2, 1fr);
    /* Tiles keep their own height rather than stretching to the tallest
       in the row — required now that heights come from the photographs. */
    align-items: start;
    gap: var(--space-m);
    overflow: visible;
    margin-inline: 0;
    padding-inline: 0;
  }
  .gallery__item { flex: none; }
  .gallery__item--wide { grid-column: span 2; }
  .gallery__hint { display: none; }
}

@media (min-width: 1100px) {
  .gallery__items { grid-template-columns: repeat(3, 1fr); }
}

/* ============================================================
   4 · AMENITIES
   ============================================================ */

.amenities__grid {
  display: grid;
  gap: var(--space-l) var(--space-m);
  grid-template-columns: 1fr;
  margin-top: var(--space-xl);
}

@media (min-width: 620px)  { .amenities__grid { grid-template-columns: repeat(2, 1fr); } }
@media (min-width: 1000px) { .amenities__grid { grid-template-columns: repeat(4, 1fr); } }

.amenity__icon {
  width: 26px;
  height: 26px;
  margin-bottom: var(--space-s);
  color: var(--brass-deep);
  stroke: currentColor;
  stroke-width: 1.5;
  stroke-linecap: round;
  stroke-linejoin: round;
  fill: none;
  transform-origin: left center;
  transition: transform var(--dur-ui) var(--ease-settle);
}

@media (hover: hover) {
  .amenity:hover .amenity__icon { transform: scale(1.1); }
}

.amenity__label {
  font-family: var(--font-display);
  font-size: var(--step-1);
  margin-bottom: var(--space-3xs);
}

.amenity__detail {
  font-size: var(--step--1);
  color: var(--stone);
  line-height: 1.55;
  max-width: 24rem;
}

/* ---- The complete list, collapsed --------------------------
   A native <details>. Collapsed it costs one quiet line; open it answers
   the specific questions (parking? rice cooker? monthly rates?) that
   would otherwise send someone away to find out. */

.amenities__full {
  margin-top: var(--space-xl);
  border-top: 1px solid var(--sand);
}

.amenities__full > summary {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-s);
  padding-block: var(--space-m);
  cursor: pointer;
  list-style: none;                 /* the default triangle, removed */
  font-family: var(--font-display);
  font-size: var(--step-1);
  color: var(--ink);
  transition: color var(--dur-ui) var(--ease);
}

.amenities__full > summary::-webkit-details-marker { display: none; }

.amenities__full > summary:focus-visible {
  outline: 2px solid var(--brass);
  outline-offset: 4px;
}

@media (hover: hover) {
  .amenities__full > summary:hover { color: var(--brass-deep); }
}

.amenities__chev {
  width: 22px;
  height: 22px;
  flex: none;
  fill: none;
  stroke: var(--stone);
  stroke-width: 1.5;
  stroke-linecap: round;
  stroke-linejoin: round;
  transition: transform var(--dur-ui) var(--ease-settle);
}

.amenities__full[open] .amenities__chev { transform: rotate(180deg); }

.amenities__full-body {
  padding-bottom: var(--space-l);
}

.amenities__full-note {
  font-size: var(--step--2);
  color: var(--stone);
  margin-bottom: var(--space-l);
}

.amenities__full-body {
  display: grid;
  gap: var(--space-l) var(--space-m);
  grid-template-columns: 1fr;
}

@media (min-width: 620px)  { .amenities__full-body { grid-template-columns: repeat(2, 1fr); } }
@media (min-width: 1000px) { .amenities__full-body { grid-template-columns: repeat(4, 1fr); } }

/* The note spans the row above the groups. */
.amenities__full-note { grid-column: 1 / -1; margin-bottom: 0; }

.amenities__group > ul {
  list-style: none;
  padding: 0;
  margin: var(--space-2xs) 0 0;
}

.amenities__group li {
  font-size: var(--step--1);
  color: var(--stone);
  line-height: 1.5;
  padding-block: var(--space-3xs);
}

.amenities__group-name {
  font-size: var(--step--2);
  font-weight: 500;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  font-family: var(--font-body);
  color: var(--ink);
}

/* ============================================================
   5 · LOCATION
   ============================================================ */

.location__grid {
  display: grid;
  gap: var(--space-l);
}

@media (min-width: 900px) {
  .location__grid {
    grid-template-columns: 1fr 1fr;
    gap: clamp(var(--space-l), 4vw, var(--space-2xl));
    align-items: start;
  }
}

.location__body p {
  max-width: var(--measure);
  color: var(--ink-soft);
}

.location__body p + p { margin-top: var(--space-m); }

.landmarks {
  list-style: none;
  padding: 0;
  margin: 0;
  border-top: 1px solid var(--sand);
  max-width: var(--measure);
}

/* On one column the list needs its own breathing room; beside the copy it
   already has the grid gap. */
@media (max-width: 899px) {
  .landmarks { margin-top: var(--space-l); }
}

.landmark {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-m);
  padding-block: var(--space-s);
  padding-inline: var(--space-2xs);
  margin-inline: calc(var(--space-2xs) * -1);
  border-bottom: 1px solid var(--sand);
  transition: background var(--dur-ui) var(--ease);
}

.landmark__name {
  font-size: var(--step-0);
  min-width: 0;
  transition: transform var(--dur-ui) var(--ease-settle);
}

/* A static list that acknowledges the pointer. The row is not clickable,
   so this stays a wash and a nudge — nothing that implies a link. */
@media (hover: hover) {
  .landmark:hover { background: var(--paper); }
  .landmark:hover .landmark__name { transform: translateX(4px); }
}

.landmark__time {
  font-size: var(--step--1);
  color: var(--stone);
  white-space: nowrap;
  font-variant-numeric: tabular-nums;
}

/* -- photo row --
   Three across beneath the copy, each at its true ratio. Previously these
   sat in a narrow right-hand column with the first forced to 3/2, which
   threw away 47% of the Venice photo and left a void beside the text. */
.location__photos {
  display: grid;
  gap: var(--space-s);
  grid-template-columns: 1fr 1fr;
  margin-top: var(--space-xl);
  align-items: start;
}

/* Stacked full-width on a phone these ran to three screens on their own,
   so the lead photo takes the full width and the other two pair up. */
.location__photos figure:first-child { grid-column: 1 / -1; }

@media (min-width: 640px) {
  .location__photos { grid-template-columns: repeat(3, 1fr); }
  .location__photos figure:first-child { grid-column: auto; }
}

.location__photos figure { margin: 0; }

.location__photos img {
  width: 100%;
  height: auto;
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-lift);
}

/* -- click-to-load map: no third-party request until asked --
   Full width beneath the two columns. Sitting it inside the left
   column left a large dead area under the photo stack. */
.map {
  margin-top: var(--space-2xl);
  border: 1px solid var(--sand);
  border-radius: var(--radius-lg);
  overflow: hidden;
  background: var(--paper-warm);
}

.map__placeholder {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--space-2xs);
  width: 100%;
  min-height: 190px;
  padding: var(--space-l);
  border: 0;
  background: none;
  cursor: pointer;
  text-align: center;
  color: var(--stone);
  transition: background var(--dur-ui) var(--ease);
}

.map__placeholder:hover { background: var(--sand); }

.map__placeholder strong {
  font-family: var(--font-display);
  font-size: var(--step-1);
  font-weight: 400;
  color: var(--ink);
}

.map__placeholder span { font-size: var(--step--1); }

.map iframe {
  display: block;
  width: 100%;
  height: 340px;
  border: 0;
}

/* ============================================================
   6 · GUEST VOICES
   Typographic pull-quotes, not cards.
   ============================================================ */

.reviews__grid {
  display: grid;
  gap: var(--space-l);
  margin-top: var(--space-l);
}

@media (min-width: 860px) {
  .reviews__grid {
    grid-template-columns: repeat(3, 1fr);
    gap: var(--space-l);
  }
}

.review { margin: 0; }

.review__mark {
  font-family: var(--font-display);
  font-size: var(--step-3);
  line-height: 1;
  color: var(--sand);
  display: block;
  margin-bottom: var(--space-2xs);
  transition: color var(--dur-ui) var(--ease);
}

@media (hover: hover) {
  .review:hover .review__mark { color: var(--brass); }
}

.review__quote {
  font-family: var(--font-display);
  font-size: var(--step-1);
  line-height: 1.45;
  letter-spacing: -0.012em;
  color: var(--ink);
}

.review__by {
  margin-top: var(--space-s);
  font-size: var(--step--2);
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--stone);
  font-style: normal;
}

.rating {
  display: inline-flex;
  align-items: baseline;
  gap: var(--space-2xs);
  font-family: var(--font-display);
  font-size: var(--step-2);
}

.rating small {
  font-family: var(--font-body);
  font-size: var(--step--1);
  color: var(--stone);
  letter-spacing: 0.04em;
}

/* ============================================================
   7 · CLOSING CTA BAND
   ============================================================ */

/* The emptiest block on the page before this — 61% air around a single
   line and a button. It does not need full section padding. */
.closing {
  background: var(--ink);
  color: var(--paper);
  text-align: center;
  padding-block: var(--space-2xl);
}

.closing__line {
  font-family: var(--font-display);
  font-size: var(--step-3);
  color: var(--paper);
  margin-bottom: var(--space-l);
  font-variation-settings: 'SOFT' 0, 'WONK' 1;
}

.closing__sub {
  color: rgba(250,247,242,0.62);
  font-size: var(--step--1);
  margin-top: var(--space-m);
}

/* ============================================================
   STICKY CTA
   Fades in once the hero is out of view.
   ============================================================ */

.sticky-cta {
  position: fixed;
  z-index: 90;
  opacity: 0;
  visibility: hidden;
  transform: translateY(12px);
  transition: opacity 420ms var(--ease),
              transform 420ms var(--ease),
              visibility 420ms;
}

.sticky-cta.is-shown {
  opacity: 1;
  visibility: visible;
  transform: none;
}

/* mobile: full-width bar, thumb-reachable, clears the home indicator */
@media (max-width: 767px) {
  .sticky-cta {
    left: 0; right: 0; bottom: 0;
    padding: var(--space-xs) var(--space-s);
    padding-bottom: calc(var(--space-xs) + env(safe-area-inset-bottom, 0px));
    background: rgba(250,247,242,0.94);
    backdrop-filter: blur(12px);
    -webkit-backdrop-filter: blur(12px);
    border-top: 1px solid var(--sand);
  }
  .sticky-cta .cta { width: 100%; }
  /* stop the bar covering the last of the footer */
  body { padding-bottom: 0; }
  .footer { padding-bottom: calc(var(--space-2xl) + 72px); }
}

/* desktop: compact pill, bottom-right */
@media (min-width: 768px) {
  .sticky-cta {
    right: var(--space-l);
    bottom: var(--space-l);
  }
  .sticky-cta .cta { font-size: var(--step--1); padding: 0.95em 1.6em; }
}

/* ============================================================
   8 · FOOTER
   ============================================================ */

.footer {
  background: var(--ink);
  color: rgba(250,247,242,0.72);
  padding-block: var(--space-2xl);
  font-size: var(--step--1);
}

.footer__top {
  display: grid;
  gap: var(--space-l);
  padding-bottom: var(--space-l);
  border-bottom: 1px solid rgba(250,247,242,0.14);
}

@media (min-width: 760px) {
  .footer__top {
    grid-template-columns: 1fr auto;
    align-items: end;
  }
}

.footer .wordmark {
  color: var(--paper);
  margin-bottom: var(--space-2xs);
  text-shadow: none;
}

.footer__tagline {
  font-family: var(--font-display);
  font-size: var(--step-1);
  color: rgba(250,247,242,0.86);
}

.footer__teaser {
  color: var(--brass);
  letter-spacing: 0.02em;
}

.footer__bottom {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-s) var(--space-l);
  justify-content: space-between;
  padding-top: var(--space-l);
  color: rgba(250,247,242,0.5);
  font-size: var(--step--2);
}

.footer a { color: inherit; text-decoration: underline; text-underline-offset: 3px; }
.footer a:hover { color: var(--paper); }

/* ============================================================
   LIGHTBOX
   ============================================================ */

.lightbox {
  position: fixed;
  inset: 0;
  z-index: 200;
  display: none;
  align-items: center;
  justify-content: center;
  background: rgba(20, 17, 13, 0.94);
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
  padding: clamp(1rem, 4vw, 3rem);
}

.lightbox[open],
.lightbox.is-open { display: flex; }

.lightbox__img {
  max-width: 100%;
  max-height: 82vh;
  width: auto;
  object-fit: contain;
  border-radius: var(--radius);
  box-shadow: 0 20px 60px rgba(0,0,0,0.5);
}

.lightbox__caption {
  position: absolute;
  left: 50%;
  bottom: clamp(1rem, 3vw, 2rem);
  transform: translateX(-50%);
  max-width: min(90%, 46rem);
  text-align: center;
  color: rgba(255,255,255,0.72);
  font-size: var(--step--1);
}

.lightbox__btn {
  position: absolute;
  display: grid;
  place-items: center;
  width: 48px;
  height: 48px;
  border: 0;
  border-radius: 50%;
  background: rgba(255,255,255,0.1);
  color: var(--white);
  cursor: pointer;
  transition: background var(--dur-ui) var(--ease);
}

.lightbox__btn:hover      { background: rgba(255,255,255,0.2); }
.lightbox__btn:focus-visible { outline: 2px solid var(--white); outline-offset: 2px; }

.lightbox__btn svg { width: 22px; height: 22px; stroke: currentColor; stroke-width: 1.6; fill: none; }

.lightbox__close { top: clamp(1rem, 3vw, 2rem); right: clamp(1rem, 3vw, 2rem); }
.lightbox__prev  { left: clamp(0.5rem, 2vw, 2rem);  top: 50%; transform: translateY(-50%); }
.lightbox__next  { right: clamp(0.5rem, 2vw, 2rem); top: 50%; transform: translateY(-50%); }

@media (max-width: 560px) {
  .lightbox__prev { left: var(--space-2xs); }
  .lightbox__next { right: var(--space-2xs); }
}

body.is-locked { overflow: hidden; }
