/* ==========================================================================
   Cap de Douma - Douma, Lebanon
   Design system. Implements DESIGN.md. Every value here is a token or
   derived from one. No magic numbers in page markup.
   ========================================================================== */

/* --------------------------------------------------------------------------
   1. Fonts (self-hosted, latin subset)
   -------------------------------------------------------------------------- */

@font-face {
  font-family: 'Marcellus';
  src: url('../fonts/marcellus.woff2') format('woff2');
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}

@font-face {
  font-family: 'Archivo';
  src: url('../fonts/archivo-var.woff2') format('woff2-variations'),
       url('../fonts/archivo-var.woff2') format('woff2');
  font-weight: 100 900;
  font-style: normal;
  font-display: swap;
}

/* --------------------------------------------------------------------------
   2. Tokens
   -------------------------------------------------------------------------- */

:root {
  accent-color: oklch(0.66 0.098 62);

  /* Canvas: the shade inside the rock */
  --rock-deep:      oklch(0.13 0.010 60);
  --rock:           oklch(0.19 0.012 60);
  --surface:        oklch(0.24 0.014 62);
  --surface-raised: oklch(0.29 0.016 64);

  /* Limestone: the light. Ink and photography only, never body background. */
  --limestone:      oklch(0.95 0.012 85);
  --limestone-dim:  oklch(0.81 0.014 80);
  /* 0.64 measured 4.37:1 against --surface at 14px, which is under the 4.5:1 AA
     floor for small text (axe, 2026-08-15). Raised until it passes. */
  --muted:          oklch(0.66 0.012 78);

  /* The single voltage */
  --bronze:         oklch(0.66 0.098 62);
  --bronze-dim:     oklch(0.52 0.078 60);
  --on-bronze:      oklch(0.15 0.010 60);

  /* Material secondaries, used contextually and sparingly */
  --olive:          oklch(0.54 0.058 118);
  --walnut:         oklch(0.36 0.042 52);

  --hairline:       oklch(0.32 0.010 60);
  --hairline-warm:  oklch(0.38 0.020 62);

  /* Type */
  --font-display: 'Marcellus', 'Times New Roman', serif;
  --font-body: 'Archivo', system-ui, -apple-system, 'Segoe UI', sans-serif;

  --t-hero:     clamp(2.75rem, 7.5vw, 5.5rem);
  --t-display:  clamp(2.25rem, 5vw, 3.75rem);
  --t-title:    clamp(1.5rem, 3vw, 2.25rem);
  --t-subtitle: clamp(1.125rem, 1.8vw, 1.375rem);
  --t-body:     clamp(1rem, 1.15vw, 1.0625rem);
  --t-small:    0.875rem;
  --t-marker:   0.75rem;

  /* Space. --s-1..--s-6 are small enough to stay fixed at any viewport.
     --s-7..--s-11 gate section rhythm (band padding, section-head gaps,
     footer blocks) and were fixed rem, same as desktop, at every width. On a
     390px phone that reads as dead black gutter between sections rather than
     breathing room — --s-11 alone was 192px top+bottom. Fluid like the type
     scale already is: each clamp reaches its old fixed value by roughly
     1000-1300px viewport width (wider tokens taper in later), and floors out
     at 60-70% of that value on a phone instead of matching it 1:1. */
  --s-1: 0.25rem;  --s-2: 0.5rem;   --s-3: 0.75rem;  --s-4: 1rem;
  --s-5: 1.5rem;   --s-6: 2rem;     --s-7: clamp(2rem, 1.5rem + 2vw, 3rem);
  --s-8: clamp(2.5rem, 1.5rem + 4vw, 4rem);
  --s-9: clamp(3rem, 1.5rem + 7vw, 6rem);
  --s-10: clamp(3.5rem, 1.5rem + 9vw, 8rem);
  --s-11: clamp(4rem, 1.5rem + 13vw, 12rem);

  --measure: 68ch;
  --gutter: clamp(1.25rem, 5vw, 4rem);
  --max-w: 1440px;
  --max-w-narrow: 42rem;

  /* Shape: zero radius everywhere except buttons (sm). Pill kept for future use. */
  --r-none: 0;
  --r-sm: 4px;
  --r-pill: 9999px;

  /* Motion */
  --ease-out-quart: cubic-bezier(0.25, 1, 0.5, 1);
  --ease-out-expo:  cubic-bezier(0.16, 1, 0.3, 1);
  --dur-fast: 180ms;
  --dur-mid:  320ms;
  --dur-slow: 620ms;

  /* Layers: semantic, never arbitrary */
  --z-base: 0;
  --z-raised: 10;
  --z-sticky: 100;
  --z-nav: 200;
  --z-backdrop: 300;
  --z-modal: 400;
  --z-toast: 500;
}

/* --------------------------------------------------------------------------
   3. Reset
   -------------------------------------------------------------------------- */

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

html {
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
  /* Native controls (date picker, select arrow, number spinners, scrollbars)
     render light chrome without this and look broken on the dark canvas. */
  color-scheme: dark;
}

body {
  margin: 0;
  background: var(--rock);
  color: var(--limestone-dim);
  font-family: var(--font-body);
  font-size: var(--t-body);
  font-weight: 400;
  line-height: 1.7;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  overflow-x: hidden;
}

h1, h2, h3, h4 {
  margin: 0;
  font-family: var(--font-display);
  font-weight: 400;
  color: var(--limestone);
  text-wrap: balance;
}

h1 { font-size: var(--t-hero);    line-height: 1.02; letter-spacing: -0.03em; }
h2 { font-size: var(--t-display); line-height: 1.08; letter-spacing: -0.025em; }
h3 { font-size: var(--t-title);   line-height: 1.15; letter-spacing: -0.02em; }
h4 { font-size: var(--t-subtitle);line-height: 1.35; letter-spacing: -0.01em; }

p {
  margin: 0 0 var(--s-4);
  max-width: var(--measure);
  text-wrap: pretty;
}

p:last-child { margin-bottom: 0; }

a { color: var(--bronze); text-decoration-thickness: 1px; text-underline-offset: 0.2em; }
a:hover { color: var(--limestone); }

img, picture, video, svg { display: block; max-width: 100%; height: auto; }

ul, ol { margin: 0; padding: 0; list-style: none; }

button, input, textarea, select { font: inherit; color: inherit; }

:focus-visible {
  outline: 2px solid var(--bronze);
  outline-offset: 2px;
}

::selection { background: var(--bronze); color: var(--on-bronze); }

/* Skip link */
.skip-link {
  position: absolute;
  left: var(--s-4);
  top: -100%;
  z-index: var(--z-toast);
  padding: var(--s-3) var(--s-5);
  background: var(--bronze);
  color: var(--on-bronze);
  font-weight: 600;
  text-decoration: none;
  transition: top var(--dur-fast) var(--ease-out-quart);
}
.skip-link:focus { top: var(--s-4); color: var(--on-bronze); }

/* --------------------------------------------------------------------------
   4. Layout primitives
   -------------------------------------------------------------------------- */

.wrap {
  width: 100%;
  max-width: var(--max-w);
  margin-inline: auto;
  padding-inline: var(--gutter);
}

.wrap--narrow { max-width: calc(var(--max-w-narrow) + var(--gutter) * 2); }

/* Section rhythm is varied by modifier, never uniform */
.band       { padding-block: var(--s-9); }
.band--tight{ padding-block: var(--s-8); }
.band--open { padding-block: var(--s-10); }
.band--vast { padding-block: var(--s-11); }

/* Interior page heads carry a title and one line. At --s-10 both sides that
   pushed the page's first photograph below the fold on a 900px viewport, so
   every page but the home page opened on text and empty canvas. */
.band--head { padding-block: var(--s-9) var(--s-6); }
.band--head + .band,
.band--head + .band--surface { padding-block-start: var(--s-7); }

.band--surface { background: var(--surface); }

/* --------------------------------------------------------------------------
   5. Typography helpers
   -------------------------------------------------------------------------- */

.lede {
  font-size: var(--t-subtitle);
  line-height: 1.6;
  color: var(--limestone);
}

.small { font-size: var(--t-small); }
.dim   { color: var(--muted); }

/* The marker system. Inscriptional capitals, Roman boundary-stone reference.
   Two permitted uses only: trail markers in the heritage story, and real
   data (altitude, date, coordinate). Never a generic section eyebrow. */
.marker {
  display: inline-block;
  font-family: var(--font-display);
  font-size: var(--t-marker);
  letter-spacing: 0.22em;
  line-height: 1.4;
  text-transform: uppercase;
  color: var(--bronze);
}

.marker--stone { color: var(--muted); }

/* --------------------------------------------------------------------------
   6. Navigation
   -------------------------------------------------------------------------- */

.nav {
  position: fixed;
  inset-block-start: 0;
  inset-inline: 0;
  z-index: var(--z-nav);
  height: 72px;
  display: flex;
  align-items: center;
  transition: background-color var(--dur-mid) var(--ease-out-quart),
              border-color var(--dur-mid) var(--ease-out-quart);
  border-block-end: 1px solid transparent;
}

.nav[data-scrolled='true'] {
  background: oklch(0.13 0.010 60 / 0.88);
  backdrop-filter: blur(12px);
  -webkit-backdrop-filter: blur(12px);
  border-block-end-color: var(--hairline);
}

.nav__inner {
  width: 100%;
  max-width: var(--max-w);
  margin-inline: auto;
  padding-inline: var(--gutter);
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-5);
}

.nav__brand {
  display: flex;
  align-items: center;
  gap: var(--s-3);
  /* The wordmark's own line-height is ~24px, short of the 44px touch-target
     floor (WCAG 2.5.5 / iOS-Android alike) on the busiest link on the page. */
  min-height: 44px;
  font-family: var(--font-display);
  /* Eighteen characters of tracked-out uppercase. Fluid so the wordmark clears
     the menu button at 320px without wrapping or shrinking on desktop. */
  font-size: clamp(0.8125rem, 2.6vw, 1.0625rem);
  letter-spacing: clamp(0.1em, 0.55vw, 0.18em);
  text-transform: uppercase;
  color: var(--limestone);
  text-decoration: none;
  white-space: nowrap;
}
.nav__brand:hover { color: var(--bronze); }

/* The mark: the two facing profiles cut into the outcrop, the shape the whole
   site's copy has been pointing at since the og:image alt text. The client's
   supplied artwork is a line drawing over a photograph; the photograph is
   moodboard, not an asset, and per the same rule that kept a photo crop out
   of this slot before (sandstone turns to mud at 26-42px on a dark bar), it
   never ships. What ships is the line work alone, isolated to its alpha
   channel (logo-mark.png, white strokes on transparent) and painted with
   background-color so it recolors from one token instead of a baked-in hex.
   mask-image reads that alpha the same way stroke="currentColor" reads a
   path — a raster stand-in for the same technique, kept until the mark is
   redrawn as vector. Fluid for the same reason the wordmark is: at 320px the
   mark, its gap and eighteen tracked-out characters all have to clear the
   menu button. */
.nav__mark {
  flex: none;
  width: clamp(26px, 7.5vw, 42px);
  aspect-ratio: 480 / 387;
  background-color: var(--bronze);
  -webkit-mask-image: url('/assets/img/logo-mark.png');
  mask-image: url('/assets/img/logo-mark.png');
  -webkit-mask-repeat: no-repeat;
  mask-repeat: no-repeat;
  -webkit-mask-size: contain;
  mask-size: contain;
  -webkit-mask-position: center;
  mask-position: center;
}

.nav__links {
  display: flex;
  align-items: center;
  gap: clamp(var(--s-4), 2vw, var(--s-6));
}

.nav__link {
  position: relative;
  font-size: var(--t-small);
  font-weight: 500;
  letter-spacing: 0.04em;
  color: var(--limestone-dim);
  text-decoration: none;
  padding-block: var(--s-2);
  white-space: nowrap;
}

.nav__link::after {
  content: '';
  position: absolute;
  inset-block-end: 0;
  inset-inline-start: 0;
  width: 100%;
  height: 1px;
  background: var(--bronze);
  transform: scaleX(0);
  transform-origin: left;
  transition: transform var(--dur-fast) var(--ease-out-quart);
}

.nav__link:hover { color: var(--limestone); }
.nav__link:hover::after,
.nav__link[aria-current='page']::after { transform: scaleX(1); }
.nav__link[aria-current='page'] { color: var(--limestone); }

.nav__toggle {
  display: none;
  width: 44px;
  height: 44px;
  padding: 0;
  background: none;
  border: 1px solid var(--hairline-warm);
  color: var(--limestone);
  cursor: pointer;
  align-items: center;
  justify-content: center;
}
.nav__toggle:hover { border-color: var(--bronze); color: var(--bronze); }

/* Language toggle. Both options are always shown rather than a single link to
   the other language: which language you are reading is then stated, not
   inferred from which flag is missing. The accessible name of each option is
   the language's own name; EN and FR are the visible shorthand. */
.langswitch {
  display: flex;
  align-items: center;
  gap: var(--s-1);
}

.langswitch__opt {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 44px;
  padding-inline: var(--s-2);
  font-size: var(--t-small);
  font-weight: 500;
  letter-spacing: 0.08em;
  color: var(--limestone-dim);
  text-decoration: none;
}

.langswitch__opt:hover { color: var(--limestone); }
.langswitch__opt[aria-current='true'] { color: var(--bronze); }

.langswitch__sep {
  color: var(--hairline-warm);
  font-size: var(--t-small);
  user-select: none;
}

/* --------------------------------------------------------------------------
   7. Buttons
   -------------------------------------------------------------------------- */

/* Uiverse.io by Voxybuns, reworked onto brand tokens. Outer element is the
   "outline" plate; .btn__top is the face that lifts on hover and presses flush
   on click. Each variant only overrides the four custom properties below, so
   the push mechanic lives in one place and never gets re-derived per variant. */
.btn {
  --btn-radius: var(--r-sm);
  --btn-outline: var(--rock-deep);
  /* Separate from --btn-outline (used for the face's border) because the two
     layers overlap almost completely: a transparent face does not reveal a
     thin shadow "lip", it reveals the plate's full background. Ghost needs
     that plate gone, while primary still wants it as the depth cue. */
  --btn-plate: var(--btn-outline);
  --btn-face: var(--bronze);
  --btn-face-hover: var(--limestone);
  --btn-ink: var(--on-bronze);
  --btn-ink-hover: var(--rock-deep);

  position: relative;
  display: inline-block;
  border: none;
  border-radius: var(--btn-radius);
  background: var(--btn-plate);
  padding: 0;
  font-size: var(--t-small);
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  text-decoration: none;
  cursor: pointer;
}

.btn__top {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--s-2);
  box-sizing: border-box;
  min-height: 44px;
  padding: var(--s-3) var(--s-6);
  border: 1px solid var(--btn-outline);
  border-radius: var(--btn-radius);
  background: var(--btn-face);
  color: var(--btn-ink);
  white-space: nowrap;
  transform: translateY(-0.2em);
  transition: transform var(--dur-fast) var(--ease-out-quart),
              background-color var(--dur-fast) var(--ease-out-quart),
              color var(--dur-fast) var(--ease-out-quart),
              border-color var(--dur-fast) var(--ease-out-quart);
}

.btn:hover .btn__top {
  transform: translateY(-0.33em);
  background: var(--btn-face-hover);
  color: var(--btn-ink-hover);
}
.btn:active .btn__top { transform: translateY(0); }

.btn--primary .btn__top { padding-inline: var(--s-7); }

.btn--ghost {
  --btn-outline: var(--hairline-warm);
  --btn-plate: transparent;
  --btn-face: transparent;
  --btn-face-hover: transparent;
  --btn-ink: var(--limestone);
  --btn-ink-hover: var(--bronze);
}
.btn--ghost:hover .btn__top { border-color: var(--bronze); }

/* Ghost button over photography needs its own scrim to stay readable */
.btn--on-photo {
  --btn-outline: oklch(0.13 0.010 60 / 0.72);
  --btn-face: oklch(0.13 0.010 60 / 0.55);
  --btn-face-hover: oklch(0.13 0.010 60 / 0.78);
  --btn-ink: var(--limestone);
  --btn-ink-hover: var(--limestone);
}
.btn--on-photo .btn__top {
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
  border-color: oklch(0.95 0.012 85 / 0.42);
}
.btn--on-photo:hover .btn__top { border-color: var(--limestone); }

/* The button's generous inline padding plus nowrap gives the longest label
   ("Message on WhatsApp") a min-content width of 321px, which overflowed a 320px
   viewport. Narrow screens trade some of the padding rather than the nowrap: a
   two-line button reads worse than a slightly tighter one. This has to come after
   the variants and match their specificity, because .btn--primary sets its own
   padding-inline and a bare .btn__top rule loses to .btn--primary .btn__top. */
@media (max-width: 360px) {
  .btn__top,
  .btn--primary .btn__top { padding-inline: var(--s-4); }
}

.btn__icon { width: 18px; height: 18px; flex: none; }

/* --------------------------------------------------------------------------
   8. Hero, scroll-scrub stage
   -------------------------------------------------------------------------- */

/* The stage is tall: scroll distance through it drives video currentTime.
   Height is expressed in dvh so the iOS URL bar does not shift the mapping. */
.stage {
  position: relative;
  height: 320dvh;
}

.stage--short { height: 100dvh; }

.stage__pin {
  position: sticky;
  inset-block-start: 0;
  height: 100dvh;
  overflow: hidden;
}

.stage__media {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: center 42%;
}

/* Poster sits above the video until the video paints its first frame.
   iOS Safari will not paint a seeked frame on a video that never played. */
.stage__poster {
  z-index: var(--z-raised);
  transition: opacity var(--dur-slow) var(--ease-out-quart);
}
.stage__poster[data-painted='true'] { opacity: 0; }

/* Two-layer photo treatment from DESIGN.md: scrim plus colour unifier */
/* Three layers. The vertical pair seats the copy and the nav; the horizontal one
   guarantees the copy column has a dark base no matter how bright the
   photograph behind it happens to be at that point in the clip. */
.stage__scrim {
  position: absolute;
  inset: 0;
  z-index: calc(var(--z-raised) + 1);
  background:
    linear-gradient(to right,
      oklch(0.11 0.010 60 / 0.82) 0%,
      oklch(0.11 0.010 60 / 0.58) 32%,
      oklch(0.11 0.010 60 / 0.12) 62%,
      transparent 84%),
    linear-gradient(to top,
      oklch(0.11 0.010 60 / 0.88) 0%,
      oklch(0.11 0.010 60 / 0.52) 34%,
      oklch(0.11 0.010 60 / 0.14) 64%,
      transparent 100%),
    linear-gradient(to bottom,
      oklch(0.11 0.010 60 / 0.62) 0%,
      transparent 30%);
  pointer-events: none;
}

.stage__tint {
  position: absolute;
  inset: 0;
  z-index: calc(var(--z-raised) + 1);
  background: var(--walnut);
  opacity: 0.12;
  mix-blend-mode: multiply;
  pointer-events: none;
}

.stage__copy {
  position: absolute;
  inset-block-end: 0;
  inset-inline: 0;
  z-index: calc(var(--z-raised) + 2);
  padding-block-end: calc(var(--s-9) + env(safe-area-inset-bottom));
}

.hero__title {
  max-width: 16ch;
  margin-block-end: var(--s-5);
}

.hero__sub {
  max-width: 46ch;
  margin-block-end: var(--s-6);
  font-size: var(--t-subtitle);
  color: var(--limestone);
}

.hero__actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-3);
}

/* Once the pair wraps, unequal button widths read as a layout fault rather
   than a hierarchy. Below the wrap point they share the row instead. */
@media (max-width: 620px) {
  .hero__actions > .btn { flex: 1 1 0; }
  .hero__actions > .btn .btn__top { padding-inline: var(--s-5); }
}

.hero__meta {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-2) var(--s-5);
  margin-block-end: var(--s-5);
}

/* Over photography the marker needs its own weight, not just colour */
.stage__copy .marker {
  color: var(--limestone);
  text-shadow: 0 1px 12px oklch(0.11 0.010 60 / 0.9);
}

.stage__copy .marker--stone { color: var(--limestone-dim); }

.stage__copy .hero__title,
.stage__copy .hero__sub {
  text-shadow: 0 2px 24px oklch(0.11 0.010 60 / 0.55);
}

/* --------------------------------------------------------------------------
   9. The twin-faces silhouette, secondary signature
   -------------------------------------------------------------------------- */

.faces-rule {
  display: block;
  width: 100%;
  max-width: 420px;
  height: auto;
  margin-inline: auto;
  color: var(--hairline-warm);
  opacity: 0.7;
}

/* --------------------------------------------------------------------------
   10. Editorial blocks
   -------------------------------------------------------------------------- */

/* Asymmetric split. Deliberately not 50/50. */
.split {
  display: grid;
  gap: var(--s-7) var(--s-8);
  align-items: center;
}

/* A grid item defaults to min-width: auto, so it refuses to shrink below its
   content's min-content width. The contact form's min-content is wider than the
   280px track at a 320px viewport, which pushed the whole page sideways. */
.split > * { min-width: 0; }

@media (min-width: 900px) {
  .split            { grid-template-columns: 5fr 7fr; gap: var(--s-9); }
  .split--flip      { grid-template-columns: 7fr 5fr; }
  .split--flip > :first-child { order: 2; }
}

.split__media { position: relative; }

.split__media img,
.split__media video {
  width: 100%;
  aspect-ratio: 4 / 5;
  object-fit: cover;
}

/* Video sits over its own poster image rather than owning the box alone, so a
   still frame is always in the served HTML for crawlers and reduced-motion
   visitors, and the clip only has to fade in once it actually paints. */
.split__media--clip img { position: relative; }

.split__media--clip video {
  position: absolute;
  inset: 0;
  opacity: 0;
  transition: opacity var(--dur-slow) var(--ease-out-quart);
}

.split__media--clip video[data-playing='true'] { opacity: 1; }

.split__media--wide img { aspect-ratio: 3 / 2; }

.prose > * + * { margin-block-start: var(--s-4); }
.prose h2 + p, .prose h3 + p { margin-block-start: var(--s-5); }

/* --------------------------------------------------------------------------
   11. Stay: unequal composition, not a uniform card grid
   -------------------------------------------------------------------------- */

.rooms {
  display: grid;
  gap: var(--s-6);
}

@media (min-width: 900px) {
  .rooms {
    grid-template-columns: repeat(2, 1fr);
    grid-auto-rows: min-content;
  }
  .rooms > :first-child { grid-column: 1 / -1; }
}

.room {
  display: flex;
  flex-direction: column;
  background: var(--surface);
  border: 1px solid var(--hairline);
  transition: border-color var(--dur-mid) var(--ease-out-quart),
              transform var(--dur-mid) var(--ease-out-quart);
}

.room:hover { border-color: var(--hairline-warm); transform: translateY(-2px); }

.room__media {
  position: relative;
  overflow: hidden;
  background: var(--rock-deep);
}

.room__media img {
  width: 100%;
  aspect-ratio: 3 / 2;
  object-fit: cover;
  transition: transform var(--dur-slow) var(--ease-out-quart);
}

.rooms > :first-child .room__media img { aspect-ratio: 21 / 9; }

.room:hover .room__media img { transform: scale(1.03); }

/* Three supporting shots under the lead photo — bed, bathroom, one more room
   of the house — not a second identical card grid, just a smaller row. */
.room__gallery {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 2px;
  background: var(--hairline);
}

.room__gallery img {
  display: block;
  width: 100%;
  aspect-ratio: 1 / 1;
  object-fit: cover;
}

.room__body {
  display: flex;
  flex-direction: column;
  gap: var(--s-3);
  padding: var(--s-6);
  flex: 1;
}

.room__specs {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-2) var(--s-5);
  font-size: var(--t-small);
  color: var(--muted);
}

.room__foot {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-4);
  margin-block-start: auto;
  padding-block-start: var(--s-4);
  border-block-start: 1px solid var(--hairline);
}

/* Placeholder slot for photography not yet supplied */
.placeholder {
  display: grid;
  place-items: center;
  gap: var(--s-2);
  aspect-ratio: 3 / 2;
  padding: var(--s-6);
  background:
    repeating-linear-gradient(
      -45deg,
      var(--surface) 0 12px,
      var(--surface-raised) 12px 24px);
  color: var(--muted);
  text-align: center;
}

.rooms > :first-child .placeholder { aspect-ratio: 21 / 9; }

/* --------------------------------------------------------------------------
   12. Experiences: horizontal scroll-snap rail, a different layout family
   -------------------------------------------------------------------------- */

.rail {
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: min(78vw, 340px);
  gap: var(--s-5);
  overflow-x: auto;
  scroll-snap-type: x mandatory;
  padding-block-end: var(--s-5);
  padding-inline: var(--gutter);
  margin-inline: calc(var(--gutter) * -1);
  /* The peek card at the trailing edge is deliberate — it signals there is
     more to scroll to — but a hard clip mid-word read as broken rather than
     intentional. Fade it instead. */
  -webkit-mask-image: linear-gradient(to right, black calc(100% - var(--gutter) - 2rem), transparent 100%);
  mask-image: linear-gradient(to right, black calc(100% - var(--gutter) - 2rem), transparent 100%);
  scrollbar-width: thin;
  scrollbar-color: var(--hairline-warm) transparent;
}

.rail > * { scroll-snap-align: start; }

.rail::-webkit-scrollbar { height: 3px; }
.rail::-webkit-scrollbar-track { background: var(--hairline); }
.rail::-webkit-scrollbar-thumb { background: var(--hairline-warm); }

.xp {
  display: flex;
  flex-direction: column;
  gap: var(--s-3);
  padding-block-start: var(--s-5);
  border-block-start: 1px solid var(--hairline-warm);
}

.xp__icon { width: 24px; height: 24px; color: var(--bronze); }
.xp__season { font-size: var(--t-small); color: var(--muted); }

/* --------------------------------------------------------------------------
   13. Heritage timeline: the one permitted sequence
   -------------------------------------------------------------------------- */

.timeline {
  display: grid;
  gap: var(--s-8);
  position: relative;
}

@media (min-width: 760px) {
  .timeline { grid-template-columns: 10rem 1fr; column-gap: var(--s-8); }
  .timeline__entry { display: contents; }
}

.timeline__entry { position: relative; }

.timeline__when {
  font-family: var(--font-display);
  font-size: var(--t-subtitle);
  color: var(--bronze);
  letter-spacing: 0.04em;
  /* Wraps rather than nowrap: a label longer than the 10rem column ("September
     and October" on /experiences.html) used to run straight over the heading
     beside it. Short dates still sit on one line. */
  text-wrap: balance;
}

.timeline__what {
  padding-block-end: var(--s-8);
  border-block-end: 1px solid var(--hairline);
}

.timeline__entry:last-child .timeline__what { border-block-end: 0; padding-block-end: 0; }

.timeline__what h3 { margin-block-end: var(--s-3); }

/* --------------------------------------------------------------------------
   14. Testimonials
   -------------------------------------------------------------------------- */

.quotes {
  display: grid;
  gap: var(--s-6);
}

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

.quote { display: flex; flex-direction: column; gap: var(--s-4); }

.quote blockquote {
  margin: 0;
  font-family: var(--font-display);
  font-size: var(--t-subtitle);
  line-height: 1.5;
  color: var(--limestone);
}

.quote figcaption {
  font-size: var(--t-small);
  color: var(--muted);
  margin-block-start: auto;
  padding-block-start: var(--s-4);
  border-block-start: 1px solid var(--hairline);
}

.quote__name { display: block; color: var(--limestone-dim); font-weight: 500; }

/* --------------------------------------------------------------------------
   15. FAQ
   -------------------------------------------------------------------------- */

.faq { border-block-start: 1px solid var(--hairline); }

.faq__item { border-block-end: 1px solid var(--hairline); }

.faq__q {
  width: 100%;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-5);
  min-height: 44px;
  padding-block: var(--s-5);
  background: none;
  border: 0;
  font-family: var(--font-display);
  font-size: var(--t-subtitle);
  color: var(--limestone);
  text-align: start;
  cursor: pointer;
}

.faq__q:hover { color: var(--bronze); }

.faq__sign {
  flex: none;
  width: 20px;
  height: 20px;
  color: var(--bronze);
  transition: transform var(--dur-mid) var(--ease-out-quart);
}

.faq__q[aria-expanded='true'] .faq__sign { transform: rotate(45deg); }

.faq__a {
  display: grid;
  grid-template-rows: 0fr;
  transition: grid-template-rows var(--dur-mid) var(--ease-out-quart);
}

.faq__a[data-open='true'] { grid-template-rows: 1fr; }

.faq__a > div { overflow: hidden; }

.faq__a p { padding-block-end: var(--s-5); }

/* --------------------------------------------------------------------------
   16. Forms
   -------------------------------------------------------------------------- */

.form { display: grid; gap: var(--s-5); }

.field { display: grid; gap: var(--s-2); }

.field label {
  font-size: var(--t-small);
  font-weight: 500;
  color: var(--limestone);
}

.field__hint { font-size: var(--t-small); color: var(--muted); }

.field input,
.field textarea,
.field select {
  width: 100%;
  min-height: 44px;
  padding: var(--s-3) var(--s-4);
  background: var(--rock-deep);
  border: 1px solid var(--hairline-warm);
  border-radius: var(--r-none);
  color: var(--limestone);
  transition: border-color var(--dur-fast) var(--ease-out-quart);
}

.field textarea { min-height: 8rem; resize: vertical; }

.field input::placeholder,
.field textarea::placeholder { color: var(--muted); opacity: 1; }

.field input:hover,
.field textarea:hover,
.field select:hover { border-color: var(--muted); }

.field input:focus-visible,
.field textarea:focus-visible,
.field select:focus-visible { border-color: var(--bronze); }

.field[data-invalid='true'] input,
.field[data-invalid='true'] textarea,
.field[data-invalid='true'] select { border-color: oklch(0.68 0.150 30); }

.field__error {
  font-size: var(--t-small);
  color: oklch(0.76 0.130 30);
}

.field__error:empty { display: none; }

.field--row { display: grid; gap: var(--s-5); }

@media (min-width: 620px) {
  .field--row { grid-template-columns: 1fr 1fr; }
}

/* Honeypot, visually hidden but not display:none so bots still fill it */
.hp {
  position: absolute;
  width: 1px; height: 1px;
  padding: 0; margin: -1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

.form__status {
  padding: var(--s-4) var(--s-5);
  border: 1px solid var(--hairline-warm);
  font-size: var(--t-small);
}

.form__status:empty { display: none; }
.form__status[data-state='ok']  { border-color: var(--olive); color: var(--limestone); }
.form__status[data-state='err'] { border-color: oklch(0.68 0.150 30); color: var(--limestone); }

/* --------------------------------------------------------------------------
   17. Map and Instagram
   -------------------------------------------------------------------------- */

.map {
  width: 100%;
  aspect-ratio: 16 / 9;
  border: 1px solid var(--hairline);
  filter: grayscale(0.4) contrast(1.05) brightness(0.85);
}

@media (min-width: 900px) { .map { aspect-ratio: 21 / 9; } }

.feed {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  gap: var(--s-3);
}

.feed__item {
  position: relative;
  display: block;
  overflow: hidden;
  background: var(--surface);
}

.feed__item img {
  width: 100%;
  aspect-ratio: 1;
  object-fit: cover;
  transition: transform var(--dur-slow) var(--ease-out-quart),
              opacity var(--dur-mid) var(--ease-out-quart);
}

.feed__item:hover img { transform: scale(1.04); opacity: 0.82; }

/* --------------------------------------------------------------------------
   18. Footer
   -------------------------------------------------------------------------- */

.footer { background: var(--rock-deep); border-block-start: 1px solid var(--hairline); }

/* The wordmark in the footer's first column. This lived as an inline style on
   index.html only, so the homepage footer had spacing the other twenty pages
   lacked. It belongs here, once, for all of them. */
.footer__brand { margin-block-end: var(--s-5); }

.footer__grid {
  display: grid;
  gap: var(--s-8);
}

@media (min-width: 760px) {
  .footer__grid { grid-template-columns: 1.4fr 1fr 1fr; gap: var(--s-8); }
}

@media (min-width: 1100px) {
  .footer__grid { grid-template-columns: 1.6fr 1fr 1fr 1.4fr; }
}

.footer__grid h2 {
  font-size: var(--t-small);
  font-family: var(--font-body);
  font-weight: 600;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--limestone);
  margin-block-end: var(--s-4);
}

.footer__list { display: grid; gap: var(--s-3); }

.footer__list a {
  color: var(--limestone-dim);
  text-decoration: none;
  font-size: var(--t-small);
}
.footer__list a:hover { color: var(--bronze); }

.footer__social { display: flex; gap: var(--s-3); }

.footer__social a {
  display: grid;
  place-items: center;
  width: 44px;
  height: 44px;
  border: 1px solid var(--hairline-warm);
  color: var(--limestone-dim);
}
.footer__social a:hover { border-color: var(--bronze); color: var(--bronze); }
.footer__social svg { width: 20px; height: 20px; }

.footer__bottom {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-4);
  margin-block-start: var(--s-8);
  padding-block-start: var(--s-5);
  border-block-start: 1px solid var(--hairline);
  font-size: var(--t-small);
  color: var(--muted);
}

.footer__legal { display: flex; flex-wrap: wrap; gap: var(--s-5); }
.footer__legal a { color: var(--muted); text-decoration: none; }
.footer__legal a:hover { color: var(--limestone); }

/* --------------------------------------------------------------------------
   19. Mobile booking bar
   -------------------------------------------------------------------------- */

/* On phones the nav collapses to a hamburger, which buries the only booking
   CTA behind a tap. WhatsApp is the actual conversion path for this property,
   so it gets a persistent rail. Desktop keeps the CTA in the nav and does not
   need one. */
.bookbar {
  position: fixed;
  inset-block-end: 0;
  inset-inline: 0;
  z-index: var(--z-sticky);
  display: none;
  padding: var(--s-3) var(--gutter) calc(var(--s-3) + env(safe-area-inset-bottom));
  background: oklch(0.13 0.010 60 / 0.92);
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
  border-block-start: 1px solid var(--hairline-warm);
}

.bookbar > .btn { width: 100%; }

/* 860px, not 899px: this must match the breakpoint at which .nav__toggle takes
   over and the nav CTA goes behind the hamburger. Any wider and the desktop
   nav's own "Book now" is on screen at the same time as the rail. */
@media (max-width: 860px) {
  :root {
    /* Rendered footprint of .bookbar's own padding, border and button at this
       breakpoint — independent of env(safe-area-inset-bottom), which .bookbar
       and .stage__copy each add separately for the notch. Named so any future
       change to the bar's padding/button size has one place to update. */
    --bookbar-h: 4.5rem;
  }
  .bookbar { display: block; }
  /* Clear the footer from underneath the rail. */
  .footer { padding-block-end: calc(var(--s-8) + env(safe-area-inset-bottom)); }
  /* Clear the hero and closing-CTA button rows from underneath the rail too —
     .stage__copy's own padding was sized for the safe-area notch alone and
     left the "Book now" / "See the houses" buttons ~24px into the bar. */
  .stage__copy { padding-block-end: calc(var(--s-9) + var(--bookbar-h) + env(safe-area-inset-bottom)); }
}

/* --------------------------------------------------------------------------
   20. Motion
   -------------------------------------------------------------------------- */

/* Reveals enhance an already visible default. Content is never gated behind
   a class-triggered transition, or it ships blank to headless renderers. */
@media (prefers-reduced-motion: no-preference) {
  .reveal {
    opacity: 0;
    transform: translateY(18px);
    transition: opacity var(--dur-slow) var(--ease-out-quart),
                transform var(--dur-slow) var(--ease-out-quart);
  }
  .reveal[data-seen='true'] { opacity: 1; transform: none; }

  .reveal--stagger > * {
    opacity: 0;
    transform: translateY(14px);
    transition: opacity var(--dur-slow) var(--ease-out-quart),
                transform var(--dur-slow) var(--ease-out-quart);
    transition-delay: calc(var(--i, 0) * 70ms);
  }
  .reveal--stagger[data-seen='true'] > * { opacity: 1; transform: none; }
}

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
  /* The scroll stage collapses to a single viewport showing the poster */
  .stage { height: 100dvh; }
  .stage__poster { opacity: 1 !important; }
  .stage__media[data-scrub] { display: none; }
}

/* --------------------------------------------------------------------------
   21. Utilities
   -------------------------------------------------------------------------- */

.visually-hidden {
  position: absolute;
  width: 1px; height: 1px;
  padding: 0; margin: -1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

.stack   { display: grid; gap: var(--s-5); }
.stack-lg{ display: grid; gap: var(--s-7); }
.cluster { display: flex; flex-wrap: wrap; gap: var(--s-4); align-items: center; }

/* Hairline between siblings in a stack. The rule only supplies the divider,
   so it composes with .split without either owning `display`. */
.divided > * + * {
  padding-block-start: var(--s-7);
  border-block-start: 1px solid var(--hairline);
}

/* The facilities this property does not have. Deliberately a scannable list
   and not a card grid: a guest has to be able to read the whole thing before
   they book, and every item is one line. */
.denials {
  display: grid;
  border-block-start: 1px solid var(--hairline);
}

@media (min-width: 760px) {
  .denials {
    grid-template-columns: 1fr 1fr;
    column-gap: var(--s-7);
  }
}

.denials > li {
  display: grid;
  gap: var(--s-1);
  padding-block: var(--s-4);
  border-block-end: 1px solid var(--hairline);
}

.denials b    { font-weight: 500; color: var(--limestone); }
.denials span { font-size: var(--t-small); color: var(--limestone-dim); }

/* The measure must resolve at the heading's own font size. Putting a ch
   max-width on the wrapper computes it against body text and crushes display
   headings into a narrow column. */
.section-head { margin-block-end: var(--s-8); }
.section-head > h1,
.section-head > h2 { max-width: 20ch; }
.section-head > p  { max-width: 52ch; }

/* --------------------------------------------------------------------------
   22. Responsive
   -------------------------------------------------------------------------- */

@media (max-width: 860px) {
  .nav__links {
    position: fixed;
    inset-block-start: 72px;
    inset-inline: 0;
    flex-direction: column;
    align-items: stretch;
    gap: 0;
    padding: var(--s-4) var(--gutter) calc(var(--s-6) + env(safe-area-inset-bottom));
    background: var(--rock-deep);
    border-block-end: 1px solid var(--hairline);
    transform: translateY(-8px);
    opacity: 0;
    visibility: hidden;
    transition: opacity var(--dur-mid) var(--ease-out-quart),
                transform var(--dur-mid) var(--ease-out-quart),
                visibility var(--dur-mid);
  }

  .nav__links[data-open='true'] { transform: none; opacity: 1; visibility: visible; }

  .nav__link { padding-block: var(--s-4); border-block-end: 1px solid var(--hairline); }
  .nav__link::after { display: none; }

  /* In the drawer the toggle is a row like the links above it, and each option
     gets a full 44px touch target. */
  .langswitch { padding-block: var(--s-3); border-block-end: 1px solid var(--hairline); }
  .langswitch__opt { min-width: 44px; }
  .nav__links .btn { margin-block-start: var(--s-5); }
  .nav__links .btn .btn__top { justify-content: center; }

  .nav__toggle { display: inline-flex; }
}

/* Below 860px (or on a touch device — matches assets/js/scripts.js's isPhone
   check) the hero never scrubs: no scroll-linked video, no 100+dvh scroll
   runway to sit through before the page starts. Same collapse-to-one-viewport
   pattern already used for prefers-reduced-motion, plus a slow Ken Burns
   drift on the poster so it still reads as alive rather than a flat photo. */
@media (max-width: 860px), (pointer: coarse) {
  .stage { height: 100dvh; }
  .stage__media[data-scrub] { display: none; }
  .stage__poster { opacity: 1 !important; }
}

@media (min-width: 701px) and (max-width: 860px) {
  /* Between 700 and 860px the <picture> still falls back to the desktop
     16:9 photo (the dedicated portrait crop only loads under 700px), so
     the poster needs the same rightward bias the video used to get. */
  .stage__poster { object-position: 64% center; }
}

@keyframes hero-kenburns {
  from { transform: scale(1); }
  to   { transform: scale(1.08); }
}

@media (max-width: 860px) and (prefers-reduced-motion: no-preference),
       (pointer: coarse) and (prefers-reduced-motion: no-preference) {
  .stage__poster {
    transform-origin: 60% 45%;
    animation: hero-kenburns 24s ease-in-out infinite alternate;
    will-change: transform;
  }
}

@media (max-width: 620px) {
  .hero__title { max-width: 12ch; }
  .footer__bottom { flex-direction: column; align-items: flex-start; }
}

/* Ultrawide: cap content, never stretch line lengths */
@media (min-width: 1800px) {
  :root { --gutter: clamp(2rem, 6vw, 7rem); }
}

@media print {
  .nav, .bookbar, .stage, .btn { display: none; }
  body { background: #fff; color: #000; }
}
