/* ==========================================================================
   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;

  /* Photograph frame (section 10a). A photograph on this canvas is a mounted
     print: a mat of --surface-raised, a warm hairline at the outer edge, a
     cooler hairline where the mat meets the image, and a layered warm shadow
     that lifts it off the rock. Two mat widths: --frame-mat for the large
     split photographs, --frame-mat-tight for the small Instagram tiles, which
     would look padded rather than mounted at the larger value. */
  --frame-mat:       clamp(0.5rem, 0.35rem + 0.6vw, 0.875rem);
  --frame-mat-tight: clamp(0.375rem, 0.3rem + 0.35vw, 0.625rem);
  --frame-shadow:
    0 1px 2px   oklch(0.11 0.010 60 / 0.30),
    0 8px 18px  oklch(0.11 0.010 60 / 0.22),
    0 24px 48px oklch(0.11 0.010 60 / 0.16);

  /* 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; }
}

/* The mat is padding, so the image's own box stays the aspect-ratio box and
   every existing ratio below still holds. The inner hairline is drawn by
   ::after rather than an outline on the image, so the one rule covers the
   poster and the video that sits over it in the --clip variant. */
.split__media {
  position: relative;
  padding: var(--frame-mat);
  background: var(--surface-raised);
  border: 1px solid var(--hairline-warm);
  box-shadow: var(--frame-shadow);
}

/* inset resolves against the padding box, so var(--frame-mat) lands exactly on
   the content edge: the hairline sits on the photograph, not on the mat. */
.split__media::after {
  content: '';
  position: absolute;
  inset: var(--frame-mat);
  border: 1px solid var(--hairline);
  pointer-events: none;
}

.split__media img,
.split__media video {
  /* Block, or the inline descender adds a few px under the image and the
     bottom of the mat reads thicker than the top. */
  display: block;
  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; }

/* Three corrections the frame forces on the clip, all because an absolutely
   positioned box resolves against the padding box, which now includes the mat:

   inset      inset: 0 would paint the clip over the mat.
   width      the inherited width: 100% above resolves against the padding box,
              making the clip two mat-widths too wide.
   ratio      the inherited aspect-ratio then beats the insets outright: with
              width auto the box is sized from the ratio and the insets are
              dropped, which is the 505x631 clip over a 477x596 poster.

   With the ratio off, both axes auto and all four insets set, the content box
   alone sizes the clip, so it lands on its poster whatever the poster's ratio. */
.split__media--clip video {
  position: absolute;
  inset: var(--frame-mat);
  /* Both axes stated outright. Leaving either auto hands sizing back to a
     ratio -- 4/5 from the rule above, or the clip's own intrinsic ratio under
     aspect-ratio: auto -- and a ratio beats the insets, which is what put a
     505x631 clip over a 477x596 poster. Percentages resolve against the
     padding box, so one mat comes off each side. */
  width: calc(100% - var(--frame-mat) * 2);
  height: calc(100% - var(--frame-mat) * 2);
  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; }

/* For a portrait source the default 4/5 box would crop away most of. Capped in
   height so a tall frame cannot outrun the prose beside it and leave a column
   of dead space. */
.split__media--tall img { aspect-ratio: 5 / 7; max-block-size: 34rem; }

.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; }
}

/* The <li> stretches to the row, the card inside it did not — so two cards
   side by side ended at different heights and their footers sat on different
   lines. Make the card fill its item. */
.rooms > li { display: flex; }
.rooms > li > .room { flex: 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 {
  /* Sits with the footer at the card floor, so the spec row and the button
     line up across two cards whose descriptions run to different lengths. */
  margin-block-start: auto;
  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);
  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 h3 { min-block-size: calc(var(--t-title) * 1.15 * 2); }

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

/* Paragraphs run to different lengths; without this the season notes sit at
   six different heights across the rail. Push them all to the card floor. */
.xp > .xp__season { margin-block-start: auto; }

/* --------------------------------------------------------------------------
   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);
}

/* The frame goes on the <li> and the link keeps overflow: hidden, so the hover
   scale below is clipped at the photograph's edge and never rides over the mat. */
.feed > li {
  padding: var(--frame-mat-tight);
  background: var(--surface-raised);
  border: 1px solid var(--hairline-warm);
  box-shadow: var(--frame-shadow);
}

.feed__item {
  position: relative;
  display: block;
  overflow: hidden;
  background: var(--surface);
  outline: 1px solid var(--hairline);
  outline-offset: -1px;
}

.feed__item img {
  display: block;
  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. Lightbox: the room photography at the size it was shot
   -------------------------------------------------------------------------- */

/* The photograph is the control. Reset only -- everything the thumbnail looks
   like still comes from .room__media / .room__gallery, so wrapping a picture
   changes no pixel until the pointer is on it. */
.shot {
  display: block;
  width: 100%;
  padding: 0;
  border: 0;
  background: none;
  cursor: zoom-in;
  position: relative;
  /* .room__media clips its overflow, so an outside ring would be cut off */
  outline-offset: -3px;
}

/* Corner brackets, bottom right, on hover and keyboard focus only. At rest the
   composition stays photographic: no permanent icon sitting on the picture. */
.shot::after {
  content: '';
  position: absolute;
  inset-block-end: var(--s-3);
  inset-inline-end: var(--s-3);
  width: 30px;
  height: 30px;
  background:
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='%23f0ebe1' stroke-width='1.5' stroke-linecap='square'%3E%3Cpath d='M2 6V2h4M14 10v4h-4'/%3E%3C/svg%3E")
    center / 16px 16px no-repeat,
    oklch(0.13 0.010 60 / 0.72);
  opacity: 0;
  transition: opacity var(--dur-fast) var(--ease-out-quart);
  pointer-events: none;
}

.shot:hover::after,
.shot:focus-visible::after { opacity: 1; }

/* The overlay itself. <dialog> carries the modality: focus stays inside it,
   Escape closes it, and the rest of the page goes inert, none of which is
   worth reimplementing in script. */
.lightbox {
  width: 100%;
  max-width: none;
  height: 100%;
  max-height: none;
  margin: 0;
  padding: var(--s-6) var(--gutter);
  border: 0;
  background: transparent;
  color: var(--limestone);
  overflow: hidden;
}

.lightbox::backdrop {
  background: oklch(0.09 0.008 60 / 0.94);
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
}

.lightbox__inner {
  display: grid;
  grid-template-rows: 1fr auto;
  gap: var(--s-4);
  width: 100%;
  height: 100%;
  place-items: center;
}

/* Flex, not grid: a percentage max-height on a grid item resolves against the
   grid area only in theory. In Chrome the photograph kept its full 1707px and
   ran off the bottom of the screen. A flex line sizes it correctly. */
.lightbox__figure {
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 0;
  height: 100%;
  margin: 0;
}

.lightbox__figure img {
  width: auto;
  height: auto;
  max-width: 100%;
  max-height: 100%;
}

.lightbox__meta {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--s-2);
  max-width: var(--measure);
  text-align: center;
}

.lightbox__caption {
  margin: 0;
  font-size: var(--t-small);
  line-height: 1.6;
  color: var(--limestone-dim);
}

.lightbox__count {
  margin: 0;
  flex: none;
  font-size: var(--t-marker);
  letter-spacing: 0.08em;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
}

/* Controls: 48px targets, above the picture, never over the caption. */
.lightbox__btn {
  position: absolute;
  display: grid;
  place-items: center;
  width: 48px;
  height: 48px;
  padding: 0;
  border: 1px solid transparent;
  background: oklch(0.13 0.010 60 / 0.66);
  color: var(--limestone);
  cursor: pointer;
  transition: background-color var(--dur-fast) var(--ease-out-quart),
              border-color var(--dur-fast) var(--ease-out-quart);
}

.lightbox__btn:hover { background: var(--surface); border-color: var(--hairline-warm); }
.lightbox__btn svg { width: 20px; height: 20px; }

.lightbox__btn--close { inset-block-start: var(--s-4); inset-inline-end: var(--gutter); }
.lightbox__btn--prev  { inset-inline-start: var(--s-2); inset-block-start: calc(50% - 24px); }
.lightbox__btn--next  { inset-inline-end: var(--s-2); inset-block-start: calc(50% - 24px); }

/* One picture in the set: the arrows would be decoration. */
.lightbox[data-single='true'] .lightbox__btn--prev,
.lightbox[data-single='true'] .lightbox__btn--next { display: none; }

html[data-lightbox-open] { overflow: hidden; }

@media (prefers-reduced-motion: no-preference) {
  .lightbox[open] { animation: lightbox-in var(--dur-mid) var(--ease-out-quart); }
  .lightbox[open]::backdrop { animation: fade-in var(--dur-mid) var(--ease-out-quart); }
}

@keyframes lightbox-in {
  from { opacity: 0; transform: scale(0.985); }
  to   { opacity: 1; transform: none; }
}

@keyframes fade-in {
  from { opacity: 0; }
  to   { opacity: 1; }
}

/* --------------------------------------------------------------------------
   21. 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; }
}

/* --------------------------------------------------------------------------
   22. 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; }

/* --------------------------------------------------------------------------
   23. 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); }
}

/* --------------------------------------------------------------------------
   24. Contact card (/contact)

   The page the QR code at the property opens. Phone first: one column, the
   four actions and the save button above the fold at 390x844, every target
   44px or more, and padding that clears the notch and the home indicator.
   On a wider screen the same column is mounted as a card on the rock, using
   the photograph-frame shadow, so it reads as a printed card, not a link list.
   -------------------------------------------------------------------------- */

.cardpage {
  --card-w: 28rem;
  min-height: 100dvh;
  display: flex;
  flex-direction: column;
  align-items: center;
  padding:
    max(var(--s-3), env(safe-area-inset-top))
    max(var(--gutter), env(safe-area-inset-right))
    max(var(--s-5), env(safe-area-inset-bottom))
    max(var(--gutter), env(safe-area-inset-left));
  background:
    radial-gradient(120% 55% at 50% 0%, oklch(0.26 0.030 62 / 0.6), transparent 70%),
    var(--rock);
}

.cardpage__bar,
.cardpage__card,
.cardpage__foot { width: 100%; max-width: var(--card-w); }

.cardpage__bar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-4);
}
.cardpage__bar .langswitch__opt { min-width: 44px; }
/* Undo the drawer row styling section 23 gives .langswitch under 860px:
   here it sits in the bar, not in a list of nav links. */
.cardpage__bar .langswitch { padding-block: 0; border: 0; }

.cardpage__share {
  display: inline-flex;
  align-items: center;
  gap: var(--s-2);
  min-height: 44px;
  padding-inline: var(--s-4);
  background: none;
  border: 1px solid var(--hairline-warm);
  border-radius: var(--r-sm);
  color: var(--limestone-dim);
  font-size: var(--t-small);
  font-weight: 500;
  letter-spacing: 0.04em;
  cursor: pointer;
  transition: color var(--dur-fast) var(--ease-out-quart),
              border-color var(--dur-fast) var(--ease-out-quart),
              transform var(--dur-fast) var(--ease-out-quart);
}
.cardpage__share[hidden] { display: none; }
.cardpage__share:hover { color: var(--bronze); border-color: var(--bronze); }
.cardpage__share:active { transform: translateY(1px); }

.cardpage__icon { width: 20px; height: 20px; flex: none; }

.cardpage__card {
  display: grid;
  gap: var(--s-7);
  padding-block: var(--s-5) var(--s-7);
}

/* Brand */
.cardpage__brand {
  display: grid;
  justify-items: center;
  gap: var(--s-3);
  text-align: center;
}

.cardpage__mark { width: clamp(4rem, 18vw, 5rem); }

.cardpage__name {
  font-size: var(--t-title);
  line-height: 1.1;
  letter-spacing: 0.14em;
  text-transform: uppercase;
}

.cardpage__tagline {
  margin: 0;
  max-width: 24ch;
  color: var(--limestone-dim);
  text-wrap: balance;
}

/* Quick actions: four equal tiles, WhatsApp carries the one bronze fill
   because a WhatsApp message is the site's single job (PRODUCT.md). */
.cardpage__actions { display: grid; gap: var(--s-4); }

.qa {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--s-3);
}

.qa__btn {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--s-2);
  min-height: 5.25rem;
  padding: var(--s-3) var(--s-2);
  border: 1px solid var(--hairline-warm);
  border-radius: var(--r-sm);
  background: var(--surface);
  color: var(--limestone);
  font-size: var(--t-small);
  font-weight: 600;
  letter-spacing: 0.08em;
  line-height: 1.3;
  text-align: center;
  text-transform: uppercase;
  text-decoration: none;
  transition: background-color var(--dur-fast) var(--ease-out-quart),
              border-color var(--dur-fast) var(--ease-out-quart),
              color var(--dur-fast) var(--ease-out-quart),
              transform var(--dur-fast) var(--ease-out-quart);
}
.qa__btn .cardpage__icon { width: 24px; height: 24px; color: var(--bronze); }
.qa__btn:hover { border-color: var(--bronze); color: var(--limestone); }
.qa__btn:active { transform: translateY(1px); }

.qa__btn--primary {
  background: var(--bronze);
  border-color: var(--bronze);
  color: var(--on-bronze);
}
.qa__btn--primary .cardpage__icon { color: currentColor; }
.qa__btn--primary:hover {
  background: var(--limestone);
  border-color: var(--limestone);
  color: var(--rock-deep);
}

/* Full width, and allowed to wrap: the label is long and a button that
   overflows a 320px screen is worse than one on two lines. */
.cardpage__save { display: block; }
.cardpage__save .btn__top {
  padding-inline: var(--s-4);
  letter-spacing: 0.04em;
  white-space: normal;
  text-align: center;
  text-wrap: balance;
}
.cardpage__save .btn__icon { color: var(--bronze); }

/* Detail rows: label over value, the whole row is the link */
.cardpage__section { display: grid; gap: var(--s-3); }

.cardpage__heading {
  font-size: var(--t-subtitle);
  line-height: 1.35;
  letter-spacing: -0.01em;
}

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

.cardpage__row {
  display: grid;
  grid-template-columns: 1fr auto;
  grid-template-areas: "label chev" "value chev";
  align-items: center;
  column-gap: var(--s-3);
  padding-block: var(--s-3);
  border-block-end: 1px solid var(--hairline);
  color: var(--limestone);
  text-decoration: none;
}

.cardpage__label {
  grid-area: label;
  display: flex;
  align-items: center;
  gap: var(--s-2);
  font-size: var(--t-small);
  letter-spacing: 0.04em;
  color: var(--muted);
}
.cardpage__label .cardpage__icon { width: 16px; height: 16px; }

.cardpage__value {
  grid-area: value;
  overflow-wrap: anywhere;
  transition: color var(--dur-fast) var(--ease-out-quart);
}

.cardpage__chevron {
  grid-area: chev;
  width: 18px;
  height: 18px;
  color: var(--muted);
  transition: color var(--dur-fast) var(--ease-out-quart),
              transform var(--dur-fast) var(--ease-out-quart);
}

.cardpage__row:hover { color: var(--limestone); }
.cardpage__row:hover .cardpage__value { color: var(--bronze); }
.cardpage__row:hover .cardpage__chevron { color: var(--bronze); transform: translateX(2px); }
.cardpage__row:focus-visible { outline-offset: 0; }

/* Back to the site */
.cardpage__explore {
  display: grid;
  justify-items: center;
  gap: var(--s-2);
}
.cardpage__explore .btn { width: 100%; }

.cardpage__more {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  font-size: var(--t-small);
  text-align: center;
}

.cardpage__foot {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 0 var(--s-5);
  margin-block-start: auto;
  padding-block-start: var(--s-3);
  border-block-start: 1px solid var(--hairline);
  font-size: var(--t-small);
  color: var(--muted);
}
.cardpage__foot p { margin: 0; }
.cardpage__foot .footer__legal a {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 44px;
  min-height: 44px;
}

/* "Link copied", when the share button fell back to the clipboard. Fixed so
   the empty live region never takes space in the layout. */
.cardpage__status {
  position: fixed;
  inset-inline: 0;
  inset-block-end: max(var(--s-5), env(safe-area-inset-bottom));
  z-index: var(--z-toast);
  width: max-content;
  max-width: calc(100% - var(--s-6));
  margin: 0 auto;
  font-size: var(--t-small);
  color: var(--on-bronze);
  pointer-events: none;
}
.cardpage__status:not(:empty) {
  padding: var(--s-2) var(--s-4);
  border-radius: var(--r-sm);
  background: var(--limestone);
  box-shadow: var(--frame-shadow);
}

@media (min-width: 640px) {
  .cardpage { justify-content: center; }

  .cardpage__card {
    margin-block: var(--s-4);
    padding: var(--s-7);
    background: var(--surface);
    border: 1px solid var(--hairline-warm);
    box-shadow: var(--frame-shadow);
  }
  .cardpage__card .qa__btn:not(.qa__btn--primary) { background: var(--surface-raised); }

  .cardpage__foot { margin-block-start: 0; border-block-start: 0; }
}

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