/* Storefront styling, built from the LuxeEstates Bali v2 design canvas
   (luxe/design/canvas/LuxeEstates-Bali-v2.dc.html). Hand-written, no build
   step, no preprocessor (ARCHITECTURE.md, "one process, no build step";
   issue #44).

   Tokens, base rules and the shared primitives (eyebrow, brand, header and
   navigation, buttons, chips, tags, notes, the empty state, the footer) come
   from design.css — a vendored copy of luxe/design/design.css, loaded before
   this file. What is here is the storefront's own: the header as the canvas
   floats it, the menu overlay, the hero, the search bar, the trust strip,
   the villa cards, the editorial band, the quotes, the Instagram grid, the
   owners card, the footer's columns, the catalog filters, the villa page,
   the calendar and the reserve step.

   The v2 canvas draws three widths — a phone at 390, a tablet between 40rem
   and 64rem, and a desktop at 1440 on a 12-column, 24px-gutter, 1200px grid —
   so this file is mobile-first with those two breakpoints.

   Every value below comes from the canvas. Do not invent new ones — change the
   canvas first, then mirror it here (luxe/design/README.md). */

:root {
  /* The canvas's page margins: 16 on a phone, 32 on a tablet, and on a
     desktop 24 around a centred 1200 (which is 120 at 1440). */
  --gutter-page: 16px;
  /* The phone hero was 576 and put the first villa photograph below the fold
     on a 390 screen — most of a screenful of animated cream before anything
     of the collection (issue #195). The canvas's Home phone artboard now
     draws it at 480; the copy is bottom-aligned, so the height comes off the
     animation and not off the words. */
  --hero-h: 480px;
  --hero-fade: 148px;
}

@media (min-width: 40rem) {
  :root { --gutter-page: 32px; --hero-h: 600px; }
}

@media (min-width: 64rem) {
  :root { --gutter-page: 24px; --hero-h: 640px; --hero-fade: 120px; }
}

.pad { padding-inline: 20px; }

/* The centred column every section sits in above the page gutter. */
.wrap, .links-page,
.trust, .collection, .quotes, .gram, .owners, .owners-page, .contact-page, .guide-page,
.page-head, .chips, .applied, .note, .empty, .stack, .searchbar {
  width: 100%;
  max-width: calc(var(--container) + 2 * var(--gutter-page));
  margin-inline: auto;
  padding-inline: var(--gutter-page);
}

/* ── Header ────────────────────────────────────────────────── */

/* The canvas's frosted header. On the landing it floats transparent over the
   hero and takes this ground after 56px of scroll (site.js adds .is-solid);
   without JavaScript it stays transparent there, which the hero's own fade
   keeps legible. */
.site-header {
  min-height: var(--header-h);
  padding: 0 var(--gutter-page);
  background: var(--frost-header);
  -webkit-backdrop-filter: blur(16px);
  backdrop-filter: blur(16px);
  border-bottom: 1px solid var(--line);
  transition: background 0.3s var(--ease), border-color 0.3s var(--ease);
}

.site-header[data-float]:not(.is-solid) {
  background: transparent;
  border-bottom-color: transparent;
  -webkit-backdrop-filter: none;
  backdrop-filter: none;
}

/* On a phone the menu button is the navigation. */
.site-nav { display: none; }

.menu-btn {
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  justify-content: center;
  gap: 5px;
  width: 44px;
  height: 44px;
  margin-right: -10px;
}

.menu-btn span { display: block; width: 20px; height: 1.5px; background: var(--ink); }
.menu-btn span + span { width: 13px; }

/* ── Menu overlay ──────────────────────────────────────────── */

/* Opens under :target (the header button links to #menu) and under .is-open
   (site.js). .is-closed is site.js's guard for a :target that outlives a
   replaceState. */
.menu {
  position: fixed;
  inset: 0;
  z-index: 60;
  display: none;
  flex-direction: column;
  overflow-y: auto;
  background: var(--frost);
  -webkit-backdrop-filter: blur(18px);
  backdrop-filter: blur(18px);
}

.menu:target:not(.is-closed),
.menu.is-open { display: flex; }

body:has(.menu:target:not(.is-closed)),
body:has(.menu.is-open) { overflow: hidden; }

.menu__bar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  min-height: var(--header-h);
  padding: 0 var(--gutter-page);
}

.menu__close {
  display: grid;
  place-items: center;
  width: 34px;
  height: 34px;
  border: 1px solid var(--line-strong);
  border-radius: 50%;
  background: var(--card);
  color: var(--ink);
}

.menu__body {
  flex: 1;
  display: flex;
  flex-direction: column;
  width: 100%;
  max-width: calc(var(--container) + 2 * var(--gutter-page));
  margin-inline: auto;
  padding: 20px calc(var(--gutter-page) + 12px) 44px;
}

.menu__body > .eyebrow { animation: sIn 0.5s var(--ease) both; }

.menu__primary {
  display: flex;
  flex-direction: column;
  gap: 18px;
  margin: 22px 0 0;
  padding: 0;
  list-style: none;
  counter-reset: menu;
}

.menu__primary li {
  display: flex;
  align-items: baseline;
  gap: 14px;
  counter-increment: menu;
  animation: sIn 0.5s var(--ease) both;
}

/* The canvas numbers the primary destinations in gold. */
.menu__primary li::before {
  content: counter(menu, decimal-leading-zero);
  font-family: var(--display);
  font-size: 13px;
  color: var(--gold-ink);
}

.menu__primary li:nth-child(1) { animation-delay: calc(1 * var(--stagger)); }
.menu__primary li:nth-child(2) { animation-delay: calc(2 * var(--stagger)); }
.menu__primary li:nth-child(3) { animation-delay: calc(3 * var(--stagger)); }
.menu__primary li:nth-child(4) { animation-delay: calc(4 * var(--stagger)); }

.menu__primary a {
  font-family: var(--display);
  font-size: var(--fs-display);
  line-height: 1.1;
  color: var(--ink);
}
.menu__primary a:hover, .menu__primary a[aria-current="page"] { color: var(--accent); }

.menu__areas { margin-top: 34px; padding-top: 26px; border-top: 1px solid var(--line-strong); animation: sIn 0.5s var(--ease) calc(5 * var(--stagger)) both; }
.menu__areas .eyebrow { display: block; margin-bottom: 14px; }

.menu__areas ul,
.menu__secondary {
  display: flex;
  flex-wrap: wrap;
  gap: 14px 24px;
  margin: 0;
  padding: 0;
  list-style: none;
  font-size: var(--fs-smallcaps);
  letter-spacing: 0.16em;
  text-transform: uppercase;
  font-weight: 600;
}

.menu__areas a, .menu__secondary a { color: var(--ink-soft); }
.menu__areas a:hover, .menu__secondary a:hover { color: var(--ink); }

.menu__secondary { margin-top: 26px; padding-top: 26px; border-top: 1px solid var(--line-strong); animation: sIn 0.5s var(--ease) calc(6 * var(--stagger)) both; }
.menu__areas + .menu__secondary { border-top: 0; padding-top: 0; }

.menu__foot {
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  gap: 8px 16px;
  margin: auto 0 0;
  padding-top: 32px;
  font-size: 12px;
  color: var(--ink-muted);
  animation: sIn 0.5s var(--ease) calc(7 * var(--stagger)) both;
}

/* ── Hero ──────────────────────────────────────────────────── */

.hero {
  position: relative;
  display: flex;
  min-height: var(--hero-h);
  /* Runs under the header rather than starting below it. */
  margin-top: calc(-1 * var(--header-h));
}

.hero__media {
  position: absolute;
  inset: 0 0 var(--hero-fade);
  overflow: hidden;
  /* The ground the hero animation paints, so a frame that has not drawn yet
     — or a browser with JS off — shows the same colour rather than a hole. */
  background: var(--ground);
}

.hero__media canvas { width: 100%; height: 100%; display: block; }

/* Fade the animation into the page so the copy sits on flat ground — the
   canvas does this rather than a scrim behind the text. */
.hero__veil {
  position: absolute;
  inset: 0;
  background: linear-gradient(180deg,
    rgba(250, 247, 240, 0) 42%,
    rgba(250, 247, 240, 0.85) 66%,
    var(--ground) 74%);
  pointer-events: none;
}

.hero__inner {
  position: relative;
  z-index: 1;
  align-self: flex-end;
  width: 100%;
}

.hero__copy { padding: 0 20px 16px; }

.hero__copy .eyebrow {
  display: block;
  margin-bottom: 10px;
  color: var(--ink-soft);
  animation: sIn 0.5s var(--ease) both;
}

.hero h1 {
  font-size: var(--fs-display);
  line-height: 1.06;
  letter-spacing: 0.005em;
  animation: sIn 0.5s var(--ease) 0.08s both;
}

.hero__row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 12px 20px;
  margin-top: 14px;
}

/* The canvas's Superhost mark: a gold pill that pops in after the copy. */
.superhost {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  padding: 5px 11px 5px 8px;
  border: 1px solid var(--gold);
  border-radius: var(--r-pill);
  font-size: 10.5px;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  font-weight: 600;
  color: var(--gold-ink);
  white-space: nowrap;
  animation: pop 0.55s var(--ease) var(--pop-delay) both;
}

.superhost { text-decoration: none; }
.superhost:hover { color: var(--ink); }
.superhost svg { display: block; }

/* The score the mirror can back, set apart from the claim it sits beside:
   the words are the operator's own standing, the figure is Airbnb's reviews
   averaged. Marcellus and no letter-spacing so the number reads as a number
   rather than as more of the uppercase label. */
.superhost__sep { opacity: 0.5; }

.superhost__score {
  font-family: var(--display);
  font-size: 13px;
  font-weight: 400;
  letter-spacing: 0;
  text-transform: none;
}

.hero .lede {
  flex-basis: 100%;
  margin: 0;
  max-width: 300px;
  font-size: var(--fs-body);
  line-height: 1.55;
  color: var(--ink-soft);
  text-wrap: pretty;
  animation: sIn 0.5s var(--ease) 0.16s both;
}

/* ── Search card that overlaps the hero ────────────────────── */

.searchbar {
  position: relative;
  z-index: 2;
  /* Two rows on a phone. One row put Check-in, Check-out, Guests and the
     button in ~90px each: the label wrapped to CHECK- / IN, and the date
     control clipped to "mm/c" under a native calendar glyph. */
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr) auto;
  grid-template-areas:
    "in out go"
    "guests guests go";
  align-items: center;
  column-gap: 0;
  row-gap: 0;
  padding: 10px 8px 8px 16px;
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  box-shadow: var(--lift);
  /* The padding shorthand is the card's own inset and wipes the column
     gutter, so on a phone the card ran into the screen edge (issue #309). */
  width: auto;
  margin-inline: var(--gutter-page);
}

/* .searchbar is a centred column; on a phone the canvas gives it the card
   gutter, which is the page gutter, so the column rule above already fits. */
.searchbar__field { position: relative; min-width: 0; padding: 6px 0; }
.searchbar__field--checkin { grid-area: in; padding-right: 12px; }
.searchbar__field--checkout { grid-area: out; padding-left: 12px; }
.searchbar__field--guests {
  grid-area: guests;
  margin-top: 4px;
  padding-top: 8px;
  border-top: 1px solid var(--line);
}

/* A hairline between the two dates. Guests sits under them, so it has a
   rule of its own rather than a vertical one. */
.searchbar__field--checkout::before {
  content: "";
  position: absolute;
  left: 0;
  top: 50%;
  width: 1px;
  height: 34px;
  transform: translateY(-50%);
  background: var(--line);
}

/* Area is a desktop field on the canvas. */
.searchbar__field--area { display: none; }

.searchbar label {
  display: block;
  margin-bottom: 3px;
  font-size: 10.5px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--ink-muted);
  font-weight: 600;
  white-space: nowrap;
}

.searchbar select,
.searchbar input[type="date"] {
  width: 100%;
  padding: 0;
  border: 0;
  background: transparent;
  color: var(--ink);
  font-family: inherit;
  font-size: 15px;
  font-weight: 500;
  letter-spacing: 0;
  cursor: pointer;
}

/* A select can drop its chrome. A date input cannot: appearance:none leaves
   Chromium's calendar glyph as a black box over the numbers, and the numbers
   themselves stay in the browser's own face — the chunky bit next to
   Marcellus. Keep the control, paint it in our type, and shrink the glyph. */
.searchbar select {
  appearance: none;
  -webkit-appearance: none;
  font-weight: 600;
}

.searchbar input[type="date"] {
  width: auto;
  max-width: 100%;
  min-height: 1.4em;
  font-variant-numeric: tabular-nums;
}

.searchbar input[type="date"]::-webkit-datetime-edit,
.searchbar input[type="date"]::-webkit-datetime-edit-fields-wrapper,
.searchbar input[type="date"]::-webkit-datetime-edit-text,
.searchbar input[type="date"]::-webkit-datetime-edit-month-field,
.searchbar input[type="date"]::-webkit-datetime-edit-day-field,
.searchbar input[type="date"]::-webkit-datetime-edit-year-field {
  font-family: inherit;
  font-weight: inherit;
  letter-spacing: 0;
  padding: 0;
  color: inherit;
}

.searchbar input[type="date"]::-webkit-calendar-picker-indicator {
  width: 16px;
  height: 16px;
  margin: 0 0 0 6px;
  padding: 0;
  opacity: 0.45;
  cursor: pointer;
}

.searchbar__go {
  grid-area: go;
  align-self: center;
  flex-shrink: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
  width: 46px;
  height: 46px;
  margin-left: 10px;
  border: 0;
  border-radius: var(--r-pill);
  background: var(--accent);
  color: var(--warm);
  font: inherit;
  font-size: var(--fs-body);
  font-weight: 600;
  cursor: pointer;
  transition: background 0.15s ease;
}

.searchbar__go:hover { background: var(--accent-deep); }
.searchbar__go svg { flex-shrink: 0; }
/* Visually hidden on a phone (the arrow is the button), never display:none:
   the SVG is aria-hidden, so this span is the button's only name. */
.searchbar__label {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  border: 0;
  overflow: hidden;
  white-space: nowrap;
  clip-path: inset(50%);
}

/* ── Trust strip ───────────────────────────────────────────── */

/* A phone has two rows: the rating, then the channel wordmarks. */
.trust {
  display: flex;
  flex-direction: column;
  gap: 10px;
  margin-top: 22px;
  padding-inline: calc(var(--gutter-page) + 4px);
}

/* One row per channel (the canvas's {{platformScore}} slot), stacked on a
   phone. Each row is a claim a guest can go and check on that channel, so the
   channel's own name leads it rather than trailing the number. */
.trust__scores {
  display: flex;
  flex-direction: column;
  gap: 7px;
  margin: 0;
  padding: 0;
  list-style: none;
}

.trust__score { display: flex; align-items: center; gap: 8px; margin: 0; }

.trust__channel {
  min-width: 5.5em;
  font-size: 10.5px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  font-weight: 600;
  color: var(--ink-quiet);
}

/* Two layers, not five gold glyphs: the fill is a copy of the same stars
   clipped to the share of five the average actually is (components.js
   #renderStars), over a soft-gold track. Both carry the same metrics — a
   different size or letter-spacing on either would show the track through
   the fill, off by a fraction of a glyph. */
.trust__score .stars {
  position: relative;
  display: inline-block;
  color: var(--gold-soft);
  font-size: 12px;
  letter-spacing: 0.1em;
}

.trust__score .stars__fill {
  position: absolute;
  top: 0;
  left: 0;
  overflow: hidden;
  white-space: nowrap;
  color: var(--gold);
  font-size: inherit;
  letter-spacing: inherit;
}
.trust__score b { font-family: var(--display); font-size: 18px; font-weight: 400; }
.trust__count { font-size: 11.5px; color: var(--ink-quiet); }

.trust__channels {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px 14px;
  margin: 0;
  padding: 0;
  list-style: none;
  font-size: 11px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--ink-quiet);
  font-weight: 600;
}

.trust__channels li + li:not(.trust__terms)::before {
  content: "";
  display: inline-block;
  width: 3px;
  height: 3px;
  margin-right: 14px;
  border-radius: 50%;
  background: var(--line-strong);
  vertical-align: middle;
}

/* auto on a wrapping phone row shoves "No platform fees" onto its own
   line, flush right. It belongs after Direct, in the same run of words. */
.trust__terms { margin-left: 0; color: var(--ink-muted); letter-spacing: 0.1em; }

/* The wordmarks open our own page on each channel. They keep the strip's
   quiet colour and only say they are links when a pointer finds them. */
.trust a { color: inherit; text-decoration: none; }
.trust a:hover { color: var(--ink); text-decoration: underline; text-underline-offset: 3px; }

/* ── Section head ──────────────────────────────────────────── */

.section-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px;
  padding: 0 4px 14px;
}

.section-head .eyebrow { display: block; }
.section-head h2 { margin-top: 6px; font-size: 24px; }
.section-head .more {
  display: inline-block;
  padding-block: 10px;
  font-size: var(--fs-link);
  font-weight: 600;
  color: var(--accent);
  white-space: nowrap;
}

.collection { margin-top: 30px; }
/* On a phone the canvas heads the rail with the eyebrow alone — but "Fifteen
   houses. One standard." is the section's heading, and `display: none` takes
   it out of the accessibility tree and the document outline with it, leaving
   the rail unnamed for anyone navigating by heading (issue #195). Clipped
   instead: the canvas's picture is unchanged, the heading is still there. */
.collection .section-head h2 {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  overflow: hidden;
  white-space: nowrap;
  clip-path: inset(50%);
}
.collection .note { margin: 0 0 14px; }

/* ── Villa cards ───────────────────────────────────────────── */

/* Landing on a phone: a horizontal rail that snaps and bleeds to the screen
   edge, so the collection reads as a set rather than a grid competing with
   the hero. */
.rail {
  display: flex;
  gap: 14px;
  margin: 0 calc(-1 * var(--gutter-page));
  padding: 2px var(--gutter-page) 8px;
  list-style: none;
  overflow-x: auto;
  scroll-snap-type: x mandatory;
  scrollbar-width: none;
}

.rail::-webkit-scrollbar { display: none; }
.rail .villa { width: 236px; flex-shrink: 0; scroll-snap-align: start; }
.rail .villa__media { height: 158px; border-radius: 18px; }
/* The clamp is on .villa__name for every card; the rail adds the reserved
   height, so a row of cards does not go ragged. */
.rail .villa__name {
  font-size: 16.5px;
  min-height: calc(2 * 1.25em);
  line-height: 1.25;
}
.rail .villa__head { margin-top: 10px; gap: 8px; }
.rail .villa__price { font-size: 12.5px; }
.rail .villa__price .per { display: none; }
.rail .villa__meta { margin-top: 2px; font-size: 11.5px; }
.rail .villa__demand { margin-top: 8px; padding-top: 7px; font-size: 10.5px; gap: 4px 8px; }
.rail .villa__signal { margin-left: 0; }

/* Collection: one column of tall cards on a phone. */
.stack {
  display: flex;
  flex-direction: column;
  gap: 30px;
  list-style: none;
}

.stack .villa__media { height: 232px; border-radius: 22px; }
.stack .villa__name { font-size: var(--fs-card); }
.stack .villa__line { padding-inline: 4px; }

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

.villa__media img { width: 100%; height: 100%; object-fit: cover; }

.villa__tag {
  position: absolute;
  top: 12px;
  left: 12px;
  padding: 5px 12px;
  border-radius: var(--r-pill);
  background: var(--frost-chip);
  -webkit-backdrop-filter: blur(8px);
  backdrop-filter: blur(8px);
  font-size: 10px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  font-weight: 600;
  color: var(--ink-soft);
  pointer-events: none;
}

.villa__head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 10px;
  margin-top: 12px;
}

/* Two lines rather than one with an ellipsis, and never a third. Real villa
   names do not fit a card on one line — "Uluwatu Cliff Ho…", "Seminyak Garden
   …" — and the truncated half is the part that distinguishes one villa from
   the next, which is the whole job of a card in a browsing rail. A third line
   is what the audit found (issue #195): with the neighbourhood off the name
   (components.js splitCardName) two lines hold the names the collection has,
   and the clamp is the guard for the one that grows past them. */
.villa__name {
  font-family: var(--display);
  color: var(--ink);
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
  line-clamp: 2;
  overflow: hidden;
}

a.villa__name:hover { color: var(--accent); }

.villa__price { white-space: nowrap; font-size: 14px; font-weight: 700; }
.villa__price .per,
.villa__price .from { font-size: 11px; font-weight: 400; color: var(--ink-quiet); }

.villa__meta { margin: 3px 0 0; font-size: 12px; color: var(--ink-quiet); }

/* The canvas's demand line under a gold hairline: rating, stays, and what the
   calendar says. */
.villa__demand {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 4px 10px;
  margin: 10px 0 0;
  padding-top: 9px;
  border-top: 1px solid var(--gold-soft);
  font-size: 11.5px;
  color: var(--ink-soft);
}

.villa__rating { color: var(--gold-ink); }
.villa__rating b { font-weight: 600; }
.villa__stays { color: var(--ink-muted); }
.villa__signal { margin-left: auto; }

/* A month is a price, not a sentence of figures. The rack rate is struck,
   the monthly rate is the display face, and the percent is a small-caps note. */
.villa__stay {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 4px 8px;
  margin: 8px 0 0;
  color: var(--ink-soft);
  font-size: 13px;
  line-height: 1.35;
}
.villa__stay-price {
  font-family: var(--display);
  font-size: 20px;
  line-height: 1.1;
  letter-spacing: 0;
  color: var(--ink);
}
.villa__stay-was {
  color: var(--ink-muted);
  text-decoration: line-through;
  text-decoration-thickness: 1px;
}
.villa__stay-off {
  font-size: 10.5px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  font-weight: 600;
  color: var(--gold-ink);
}
.villa__stay-note { color: var(--ink-muted); font-size: 12px; }

/* The booked heading was the one block not in the column, so it sat on the
   screen edge while the cards beside it were inset. */
.catalog-booked {
  width: 100%;
  max-width: calc(var(--container) + 2 * var(--gutter-page));
  margin: 36px auto 0;
}
.catalog-booked > .eyebrow {
  display: block;
  margin: 0 0 16px;
  padding-inline: var(--gutter-page);
}

/* ── Filters (catalog) ─────────────────────────────────────── */

.page-head { padding-top: 22px; padding-bottom: 4px; }
/* Canvas page titles: 30 on a phone, 40 on a tablet, 48 on a desktop.
   One size at every width left the collection heading looking like a card title. */
.page-head h1 { font-size: var(--fs-title); }
.page-head .lede { margin: 6px 0 0; font-size: 13px; color: var(--ink-quiet); }

/* Wrapping, not a hidden-scrollbar rail: on a phone the rail cut the last
   chip mid-word and two areas were never seen without a swipe nobody was
   told to make. With four short area names it is one row on a desktop. */
.chips {
  flex-wrap: wrap;
  gap: 8px;
  padding-top: 16px;
  padding-bottom: 18px;
}

/* ── Editorial band ────────────────────────────────────────── */

.band { margin-top: 28px; padding: 32px var(--gutter-page) 34px; background: var(--soft); }
.band__inner { max-width: var(--container); margin-inline: auto; padding-inline: 4px; }
.band .eyebrow { display: block; margin-bottom: 10px; }
.band h2 { margin: 0 0 22px; font-size: var(--fs-block); line-height: 1.2; }

.band ol {
  display: flex;
  flex-direction: column;
  gap: 16px;
  margin: 0;
  padding: 0;
  list-style: none;
  counter-reset: promise;
}

.band li {
  display: flex;
  gap: 14px;
  align-items: baseline;
  font-size: var(--fs-body);
  line-height: 1.5;
  color: var(--ink-2);
}

/* The numerals are the structure here: the canvas numbers these because they
   are a stated set of four promises, not decoration. */
.band li::before {
  content: counter(promise, decimal-leading-zero);
  counter-increment: promise;
  min-width: 26px;
  font-family: var(--display);
  font-size: 19px;
  color: var(--gold);
}

/* ── What guests say ───────────────────────────────────────── */

.quotes { margin-top: 38px; padding-inline: calc(var(--gutter-page) + 4px); }
.quotes > .eyebrow { display: block; margin-bottom: 14px; }
.quotes__grid { display: flex; flex-direction: column; gap: 22px; }

.quote { margin: 0; padding-top: 18px; border-top: 1px solid var(--line); }
.quote blockquote {
  margin: 0;
  font-family: var(--display);
  font-size: 18px;
  line-height: 1.45;
  text-wrap: pretty;
}
.quote figcaption { display: flex; align-items: center; gap: 8px; margin-top: 10px; }
.quote__who {
  font-size: 10.5px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--ink-muted);
  font-weight: 600;
}
.quote .tag { font-size: 10.5px; letter-spacing: 0.1em; padding: 3px 8px; }

/* ── Instagram ─────────────────────────────────────────────── */

.gram { margin-top: 36px; }
.gram .section-head { padding-bottom: 14px; }

.gram__grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 3px;
  margin: 0;
  padding: 0;
  list-style: none;
}

.gram__grid li { aspect-ratio: 1; border-radius: var(--r-xs); overflow: hidden; background: var(--soft); }
.gram__grid a, .gram__grid img { display: block; width: 100%; height: 100%; }
.gram__grid img { object-fit: cover; }

/* ── Owners call to action ─────────────────────────────────── */

.owners {
  display: block;
  margin-top: 30px;
  padding: 26px 22px;
  border-radius: var(--r-xl);
  background: var(--accent);
  color: var(--warm);
  transition: background 0.15s ease;
}

/* .owners is a centred column, so the card's own edge is the gutter. */
.owners { width: auto; margin-inline: var(--gutter-page); max-width: var(--container); }

.owners:hover { background: var(--accent-deep); color: var(--warm); }
.owners .eyebrow { display: block; margin-bottom: 8px; color: var(--gold-soft); font-size: 10px; letter-spacing: 0.22em; }
.owners h2 { font-size: 24px; line-height: 1.2; color: var(--warm); }
.owners p { margin: 10px 0 16px; font-size: 13px; line-height: 1.55; color: var(--on-accent-muted); text-wrap: pretty; }

.owners .pill {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 10px 18px;
  border: 1px solid var(--on-accent-line);
  border-radius: var(--r-pill);
  font-size: var(--fs-link);
  font-weight: 600;
}

/* ── Empty state ───────────────────────────────────────────── */

.empty {
  margin-top: 24px;
  margin-bottom: 24px;
  width: auto;
  max-width: var(--container);
  /* The shared column rule pads the text but the border still sat on the
     screen edge. The gutter is a margin so the box itself is inset. */
  margin-inline: var(--gutter-page);
}
.empty__alt { display: inline-block; margin-top: 14px; font-size: 13px; }

/* ── For owners (issue #93) ───────────────────────────────────── */

/* .owners-page carries the page's column (the rule at the top of this file),
   so every block inside it is measured from that column rather than from the
   screen — which is what let the stats, the checklist, the fee and the form
   run edge to edge at 1440 while the heading stayed in a column (issue #195).
   On a phone the column's gutter is the 16px these blocks used to hard-code,
   so nothing about the phone moves. */

/* .page-head is a page column of its own everywhere else; inside .owners-page
   the column is already there, so it would otherwise indent the heading twice
   as far as everything under it. */
.owners-page .page-head { max-width: none; padding-inline: 0; }

/* The canvas's stat: a figure under a hairline, not a bordered strip. A strip
   is drawn for four of them; with the one number the mirror can actually back
   — the guest rating — it read as an empty box with a number in the middle of
   it (issue #195). */
.owners-stats {
  display: flex;
  gap: 24px;
  margin-top: 22px;
}

.owners-stats__cell {
  flex: 1;
  padding-top: 14px;
  border-top: 1px solid var(--line-strong);
}
.owners-stats__n { font-family: var(--display); font-size: 21px; }
.owners-stats__l {
  font-size: 10.5px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--ink-muted);
  font-weight: 600;
  margin-top: 3px;
}

.owners-checklist { margin-top: 28px; }
.owners-checklist .eyebrow {
  display: block;
  margin-bottom: 14px;
  font-size: 10.5px;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--ink-muted);
  font-weight: 600;
}
.owners-checklist ul { display: flex; flex-direction: column; gap: 12px; margin: 0; padding: 0; list-style: none; }
.owners-checklist li {
  position: relative;
  padding-left: 26px;
  font-size: 13.5px;
  line-height: 1.5;
  color: var(--ink-2);
}
.owners-checklist li::before {
  content: '✓';
  position: absolute;
  left: 0;
  color: var(--accent);
  font-weight: 700;
}

.owners-fee {
  margin-top: 24px;
  padding: 20px;
  background: var(--surface-soft);
  border-radius: 20px;
}
.owners-fee__n { margin: 0; font-family: var(--display); font-size: 22px; }
.owners-fee__s { margin: 6px 0 0; font-size: 12.5px; color: var(--ink-soft); line-height: 1.55; }

.owners-lead, .owners-alt { margin: 24px 0 0; }
.owners-alt { font-size: 12.5px; color: var(--ink-quiet); text-align: center; }
.owners-lead { scroll-margin-top: calc(var(--header-h) + 16px); }
/* The storefront's guest form. The contact page (ADR-0038) draws the same
   one, so each rule names both rather than the second copying the first. */
.owners-form__label, .contact-form__label { display: block; }
.owners-form__label > span,
.contact-form__label > span:first-child { display: block; margin-bottom: 4px; font-size: 12px; font-weight: 600; color: var(--ink-soft); }
.owners-form__hint, .contact-form__hint { margin: 2px 0 0; font-size: 11.5px; color: var(--ink-quiet); }
.contact-form__hint { display: block; }

.owners-form, .contact-form { display: flex; flex-direction: column; gap: 8px; }
.owners-form__field, .contact-form__field {
  width: 100%;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: 12px;
  padding: 13px 14px;
  font: inherit;
  font-size: 13.5px;
  color: var(--ink);
}
.owners-form__submit, .contact-form__submit { width: 100%; padding: 14px; margin-top: 4px; font-size: 13.5px; }
.owners-form__error, .contact-form__error { margin: 0 0 4px; font-size: 12.5px; color: var(--danger); }

/* Real visitors never see this field — a script filling in every input on
   the page does. Off-screen rather than display:none, which some scripts
   already skip. */
.owners-form__hp, .contact-form__hp {
  position: absolute;
  left: -9999px;
  width: 1px;
  height: 1px;
  overflow: hidden;
}

.owners-thanks {
  border: 1px solid var(--good-line);
  background: var(--good-bg);
  border-radius: 16px;
  padding: 18px;
  text-align: center;
}
.owners-thanks__t { margin: 0; font-size: 14px; font-weight: 700; color: var(--accent); }
.owners-thanks__s { margin: 6px 0 0; font-size: 12px; color: var(--ink-soft); }

/* ── Contact (ADR-0038) ───────────────────────────────────────── */

/* /contact: WhatsApp, the form and the address, under the page head. The
   form is the owners page's (the rules above name both); what is here is the
   page's own arrangement. .contact-page carries the column, like
   .owners-page, so the head inside it must not indent a second time. */
.contact-page .page-head { max-width: none; padding-inline: 0; }
.contact-about { margin: 12px 0 0; font-size: 12.5px; color: var(--ink-soft); }
.contact-about a { font-weight: 600; }

.contact-way, .contact-write { margin-top: 24px; scroll-margin-top: calc(var(--header-h) + 16px); }
.contact-way > .eyebrow, .contact-write > .eyebrow { display: block; margin-bottom: 10px; }
.contact-way p { margin: 0; font-size: 13.5px; line-height: 1.55; color: var(--ink-soft); text-wrap: pretty; }
.contact-way--mail a { font-weight: 600; }

/* WhatsApp is the one-tap way, so it sits on the soft panel the owners page
   gives its fee, with the button across the whole of it. */
.contact-way--wa { padding: 20px; background: var(--surface-soft); border-radius: 20px; }
.contact-way__go { width: 100%; margin-top: 14px; padding: 14px; font-size: 13.5px; }
.contact-way--wa .contact-way__num { margin-top: 10px; font-size: 12px; color: var(--ink-quiet); text-align: center; }

/* A phone zooms into any focused control set under 16px and does not zoom
   back out, and on a page that is one form that is every guest who taps a
   field. So these fields are 16px where the owners form's are 13.5. */
.contact-form__field { font-size: 16px; }
.contact-form__msg { resize: vertical; }
.contact-form__row > .contact-form__error { display: block; margin: 4px 0 0; }
.contact-form__small { margin: 4px 0 0; font-size: 12px; color: var(--ink-quiet); text-align: center; }

/* A note inside the page is a block of the page, not a page-wide banner. */
.contact-page .note { width: auto; max-width: none; margin: 0 0 4px; padding: 12px 16px; }
.contact-sent p { margin: 0; }
.contact-sent .contact-sent__t { font-size: 14px; font-weight: 700; }
.contact-sent .contact-sent__s { margin-top: 6px; font-size: 12px; }

/* Says which filter narrowed the collection and how to drop it. Deliberately
   quieter than .note below: this is orientation, that one is a warning about
   data a guest might act on. */
.applied {
  margin-bottom: 14px;
  font-size: 11.5px;
  line-height: 1.55;
  color: var(--ink-muted);
}
.applied a { color: var(--accent); }
/* ── Villa detail (v2 canvas, boards 10 and 11) ─────────────── */

/* The icon sprite is markup, not a picture: out of the flow entirely. */
.sprite { position: absolute; width: 0; height: 0; overflow: hidden; }

/* "Read more" appears six times on a long page. The visible label stays
   short; the suffix that says which block it opens is for a listener
   navigating by control, who otherwise hears six identical ones. */
.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  border: 0;
  overflow: hidden;
  white-space: nowrap;
  clip-path: inset(50%);
}

.villa-detail {
  width: 100%;
  max-width: calc(var(--container) + 2 * var(--gutter-page));
  margin-inline: auto;
  padding: 0 var(--gutter-page) 36px;
}

/* ── Gallery: one large photograph, four beside it ─────────── */

.villa-gallery { margin-top: 12px; }

.villa-hero {
  height: 248px;
  border-radius: var(--r-lg);
  overflow: hidden;
  background: var(--soft);
}

/* A villa with no photographs still has a page. The block keeps its shape so
   the layout below does not jump depending on what the channel syndicated. */
.villa-hero--empty { background: var(--soft); }

.villa-hero__img,
.villa-thumb__img { width: 100%; height: 100%; object-fit: cover; display: block; }

.villa-thumbs {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 8px;
  margin-top: 8px;
}

.villa-thumb {
  height: 72px;
  border-radius: var(--r-sm);
  overflow: hidden;
  background: var(--soft);
}

.villa-gallery__more { margin-top: 12px; }

.villa-gallery__rest {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 8px;
  margin-top: 12px;
}

.villa-gallery__rest .villa-thumb { height: 130px; }

/* A frame becomes a control only once gallery.js is running and there is a
   viewer to open; until then it is a plain <div> and looks like one. */
.is-zoomable { cursor: zoom-in; }
.is-zoomable:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }

/* ── The photograph viewer (issue #195) ────────────────────── */

/* A <dialog>, so the top layer, the backdrop, Escape and the focus trap are
   the browser's rather than ours. Closed, it is display:none — no layout, no
   focusable controls, and its <img> carries no address until one is shown.
   That `display: none` is written here rather than borrowed from the UA
   stylesheet's `dialog:not([open])`: a browser with no <dialog> — the same
   one gallery.js declines to bind to — parses it as an unknown inline
   element, and would paint four dead controls under the strip. */
.lightbox {
  display: none;
  width: 100%;
  max-width: 100%;
  max-height: 100%;
  height: 100%;
  margin: 0;
  padding: 0;
  border: 0;
  background: var(--frost);
  color: var(--ink);
}

.lightbox[open] {
  display: flex;
  flex-direction: column;
}

.lightbox::backdrop { background: rgba(28, 26, 21, 0.72); }

.lightbox__bar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: calc(10px + env(safe-area-inset-top, 0px)) var(--gutter-page) 10px;
  border-bottom: 1px solid var(--line);
}

.lightbox__count {
  font-size: var(--fs-smallcaps);
  letter-spacing: 0.1em;
  color: var(--ink-soft);
}

.lightbox__close {
  border: 0;
  background: none;
  color: var(--ink);
  font-size: 26px;
  line-height: 1;
  /* 44px: the tap target the audit asked for everywhere else on the page. */
  min-width: 44px;
  min-height: 44px;
  cursor: pointer;
}

/* The photograph takes whatever the bar and the nav leave, and is never
   cropped: a viewer that cuts the picture defeats the point of opening it. */
.lightbox__stage {
  flex: 1;
  min-height: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 12px var(--gutter-page);
}

.lightbox__img {
  max-width: 100%;
  max-height: 100%;
  width: auto;
  height: auto;
  object-fit: contain;
  border-radius: var(--r-sm);
}

.lightbox__nav {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 10px var(--gutter-page) calc(12px + env(safe-area-inset-bottom, 0px));
  border-top: 1px solid var(--line);
}

/* A disclosure styled as a button: the marker would sit inside the pill. */
.villa-gallery__more > summary,
.disclosure > summary { list-style: none; cursor: pointer; }
.villa-gallery__more > summary::-webkit-details-marker,
.disclosure > summary::-webkit-details-marker { display: none; }

/* One label opens it, the other closes it again: a disclosure that unfolds
   seventy amenities and offers no way back leaves a guest scrolling past
   them for ever (issue #195). */
.disclosure:not([open]) .disclosure__close { display: none; }
.disclosure[open] .disclosure__open { display: none; }

/* ── Body ──────────────────────────────────────────────────── */

/* The three blocks are written intro → booking → content, which is the order
   the canvas's tablet board asks for ("booking card inline after facts"), and
   from this pass the phone's order too.
   It used to be the reverse: the card went to the end, on the reading that a
   phone looks the house over first and reaches for the dates last. The
   screenshot audit measured what that costs — the card landed at y≈2860 of a
   3980px page, below every amenity and every review, so the one block a guest
   came to use was the one they had to scroll furthest for, and the sticky bar
   was the only thing that made it reachable at all (issue #195). The facts
   answer "is this the house", and the dates are the next question. */
.villa-body { display: flex; flex-direction: column; }
.villa-side { order: 1; }
.villa-main { order: 2; }

.villa-intro { padding-top: 22px; }
.villa-head .eyebrow { display: block; margin-bottom: 8px; }
.villa-head h1 { font-size: var(--fs-title); line-height: 1.05; letter-spacing: 0.005em; }

.villa-facts { margin: 8px 0 0; font-size: 12.5px; color: var(--ink-muted); }

/* The canvas's demand line, under the same gold hairline as the cards. */
.villa-demand {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 4px 10px;
  margin: 12px 0 0;
  padding-top: 10px;
  border-top: 1px solid var(--gold-soft);
  font-size: 11.5px;
  color: var(--ink-soft);
}

.villa-rating .star { color: var(--gold); }
.villa-rating b { font-weight: 600; }
.villa-rating span { color: var(--ink-muted); }
.villa-signal { margin-left: auto; }

/* ── Sections ──────────────────────────────────────────────── */

.villa-section { margin-top: 30px; }

.villa-section__head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px;
}

.villa-section > .eyebrow,
.villa-section__head .eyebrow { display: block; }

.villa-section__count { font-size: 11.5px; color: var(--ink-muted); white-space: nowrap; }
.villa-section__count .star { color: var(--gold); }

/* ── Read more, the canvas's own contract: no JavaScript ─────
   The toggle is a hidden checkbox and its label, not a <summary> wrapped
   around the paragraph: the text is text, and only "Read more" is a control
   (issue #195). The checkbox is clipped, never display:none, so it keeps its
   place in the tab order. */

.read-more { --clamp-lines: 6; margin-top: 14px; }

.rm-text {
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: var(--clamp-lines);
  line-clamp: var(--clamp-lines);
  overflow: hidden;
  margin: 0;
  font-size: var(--fs-body);
  line-height: 1.6;
  color: var(--ink-2);
  text-wrap: pretty;
}

.rm-check:checked ~ .rm-text {
  display: block;
  -webkit-line-clamp: unset;
  line-clamp: unset;
}

/* Sits over the last clamped line, so the text reads as continuing rather
   than as stopping mid-sentence. */
.rm-fade {
  display: block;
  position: relative;
  height: var(--fade-h);
  margin-top: calc(-1 * var(--fade-h));
  background: var(--fade-ground);
  pointer-events: none;
}

.rm-check:checked ~ .rm-fade { display: none; }

.rm-toggle {
  display: flex;
  align-items: center;
  width: fit-content;
  min-height: var(--hit);
  cursor: pointer;
  font-size: var(--fs-link);
  font-weight: 600;
  color: var(--accent);
}

.rm-check:not(:checked) ~ .rm-toggle .rm-close { display: none; }
.rm-check:checked ~ .rm-toggle .rm-open { display: none; }

/* The input is clipped, so the focus ring has to be drawn on the label a
   keyboard user is actually looking at. */
.rm-check:focus-visible ~ .rm-toggle {
  outline: 2px solid var(--accent);
  outline-offset: 3px;
  border-radius: 4px;
}

/* Text that is not clamped: the same type, without a control that would
   open nothing. */
.villa-copy {
  margin: 14px 0 0;
  font-size: var(--fs-body);
  line-height: 1.6;
  color: var(--ink-2);
  text-wrap: pretty;
}

/* ── Amenities ─────────────────────────────────────────────── */

.amenities {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 14px 16px;
  margin: 14px 0 0;
  padding: 0;
  list-style: none;
}

.amenity {
  display: flex;
  align-items: center;
  gap: 12px;
  min-height: 24px;
  font-size: 13px;
  line-height: 1.35;
  color: var(--ink-2);
}

.amenity__icon { flex-shrink: 0; color: var(--ink); }

.amenities-more { margin-top: 16px; }
.amenities-more[open] { margin-bottom: 4px; }

/* ── Where you'll be: the Canggu coast map (issue #381) ──────── */

.where {
  margin-top: 14px;
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  background: var(--card);
  overflow: hidden;
}

.where__body { padding: 18px; }
.where__area { margin: 0; font-family: var(--display); font-size: 19px; }
.where__note { margin: 4px 0 0; font-size: 12px; line-height: 1.5; color: var(--ink-muted); }
.where__copy .read-more { margin-top: 12px; }
.where__links { display: flex; flex-wrap: wrap; gap: 0 22px; margin: 0; }

.where__link {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  margin-top: 12px;
  min-height: var(--hit);
  font-size: var(--fs-link);
  font-weight: 600;
  color: var(--accent);
}

.where__places { padding: 4px 18px 20px; border-top: 1px solid var(--line); }
.where__sub { margin: 18px 0 4px; font-size: var(--fs-card); }

/* The map. Every colour is a token or a mix of two: the sea and the rice
   fields are the accent thinned into the ground, so the map reads as part
   of the page rather than as a window onto somebody else's. */
.coastmap { position: relative; margin: 0; }

.coastmap__filters {
  display: flex;
  gap: 6px;
  padding: 12px 14px;
  overflow-x: auto;
  scrollbar-width: none;
  border-bottom: 1px solid var(--line);
  background: var(--card);
}
.coastmap__filters::-webkit-scrollbar { display: none; }

.coastmap__filter {
  display: inline-flex;
  flex-shrink: 0;
  align-items: center;
  gap: 6px;
  min-height: 34px;
  padding: 0 12px;
  border: 1px solid var(--line-ghost);
  border-radius: var(--r-pill);
  background: var(--card);
  color: var(--ink-2);
  font: inherit;
  font-size: 12px;
  font-weight: 600;
  cursor: pointer;
}
.coastmap__filter[aria-pressed="true"] { border-color: var(--ink); background: var(--ink); color: var(--surface-warm); }
.coastmap__filter:hover { border-color: var(--gold-soft); }

.coastmap__frame { position: relative; background: var(--ground); }

.coastmap__svg {
  display: block;
  width: 100%;
  height: auto;
  cursor: grab;
  touch-action: pan-y;
  user-select: none;
  -webkit-user-select: none;
}
.coastmap__svg.is-dragging { cursor: grabbing; }

.cm-land { fill: var(--ground); }
.cm-rice { fill: color-mix(in srgb, var(--accent) 12%, var(--ground)); }
.cm-sea { fill: color-mix(in srgb, var(--accent) 20%, var(--surface-soft)); }
.cm-surf { fill: none; stroke: var(--card); stroke-width: 5px; stroke-opacity: 0.55; vector-effect: non-scaling-stroke; }
.cm-stream, .cm-river { fill: none; stroke: color-mix(in srgb, var(--accent) 22%, var(--surface-soft)); stroke-linecap: round; vector-effect: non-scaling-stroke; }
.cm-stream { stroke-width: 0.8px; }
.cm-river { stroke-width: 2px; }

.cm-road { fill: none; stroke-linecap: round; stroke-linejoin: round; vector-effect: non-scaling-stroke; }
.cm-road--minor-casing { stroke: var(--line-strong); stroke-width: 3.6px; }
.cm-road--minor { stroke: var(--card); stroke-width: 2px; }
.cm-road--major-casing { stroke: var(--gold-soft); stroke-width: 6px; stroke-opacity: 0.75; }
.cm-road--major { stroke: var(--card); stroke-width: 4px; }

/* Text keeps its size on screen while the map zooms under it: --cm-s is the
   width of the view over the width it opened at (public/areamap.js). */
.coastmap text { font-family: var(--body); paint-order: stroke; stroke: var(--ground); stroke-linejoin: round; }
.cm-roadlabel { font-size: calc(9.5px * var(--cm-s, 1)); fill: var(--ink-muted); stroke-width: calc(3px * var(--cm-s, 1)); letter-spacing: 0.02em; }
.cm-area {
  font-family: var(--display);
  font-size: calc(12px * var(--cm-s, 1));
  letter-spacing: 0.2em;
  fill: var(--ink-3);
  stroke-width: calc(3.5px * var(--cm-s, 1));
}
.cm-area--lg { font-size: calc(21px * var(--cm-s, 1)); letter-spacing: 0.24em; fill: var(--ink); }
.cm-sealabel {
  font-family: var(--display);
  font-size: calc(17px * var(--cm-s, 1));
  font-style: italic;
  letter-spacing: 0.12em;
  fill: color-mix(in srgb, var(--accent) 55%, var(--surface-soft));
  stroke: none;
}

.cm-zone__halo { fill: var(--accent); fill-opacity: 0.04; }
.cm-zone__area {
  fill: var(--accent);
  fill-opacity: 0.1;
  stroke: var(--accent);
  stroke-width: 1.5px;
  stroke-dasharray: 5 4;
  vector-effect: non-scaling-stroke;
}
.cm-zone__label { font-family: var(--display); font-size: calc(13px * var(--cm-s, 1)); letter-spacing: 0.04em; fill: var(--accent); stroke-width: calc(3px * var(--cm-s, 1)); }

.cm-pin, .cm-far { cursor: pointer; color: var(--surface-warm); outline: none; }
.cm-pin__body {
  transform: scale(var(--cm-s, 1));
  transform-box: fill-box;
  transform-origin: center;
  transition: transform 160ms var(--ease);
}
.cm-pin circle { fill: var(--ink); stroke: var(--card); stroke-width: 2; transition: fill 160ms var(--ease); }
.cm-pin:hover circle, .cm-pin:focus-visible circle { fill: var(--accent); }
.cm-pin.is-active circle { fill: var(--accent); stroke: var(--gold); stroke-width: 3; }
.cm-pin.is-active .cm-pin__body, .cm-pin:focus-visible .cm-pin__body { transform: scale(calc(var(--cm-s, 1) * 1.3)); }

.cm-far rect { fill: var(--card); stroke: var(--line-strong); }
.cm-far text { font-size: 11px; font-weight: 600; fill: var(--ink-2); stroke: none; }
.cm-far use { color: var(--gold-ink); }
.cm-far:hover rect, .cm-far:focus-visible rect { stroke: var(--gold); }
.cm-far.is-active rect { stroke: var(--gold); stroke-width: 2; }

/* The furniture that stays on screen whatever the zoom: the far places,
   north and the scale. Placed and sized by the script, at the view's corners. */
.cm-hud path { fill: none; stroke: var(--ink-muted); stroke-width: 1.4px; vector-effect: non-scaling-stroke; }
.cm-hud .cm-north path { fill: var(--ink-muted); stroke: none; }
.cm-hud text { font-size: 10px; font-weight: 600; fill: var(--ink-muted); stroke-width: 3px; }

.coastmap__zoom {
  position: absolute;
  top: 12px;
  right: 12px;
  display: flex;
  flex-direction: column;
  overflow: hidden;
  border: 1px solid var(--line-strong);
  border-radius: var(--r-sm);
  background: var(--frost-chip);
  box-shadow: var(--lift-sm);
}
.coastmap__zoom[hidden] { display: none; }
.coastmap__zoom button {
  width: 38px;
  height: 38px;
  border: 0;
  background: transparent;
  color: var(--ink-2);
  font: inherit;
  font-size: 18px;
  line-height: 1;
  cursor: pointer;
}
.coastmap__zoom button + button { border-top: 1px solid var(--line); }
.coastmap__zoom button:hover { background: var(--soft); color: var(--ink); }

/* The card a pin opens: over the sea, bottom left, on a wide map; under the
   map on a phone, where it would cover the pin it belongs to. */
.coastmap__card {
  position: relative;
  margin: 12px;
  padding: 16px 18px;
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  background: var(--card);
  box-shadow: var(--lift);
}
.coastmap__card[hidden] { display: none; }
.coastmap__card .place__name { padding-right: 28px; }
.coastmap__close {
  position: absolute;
  top: 8px;
  right: 8px;
  width: 32px;
  height: 32px;
  border: 0;
  border-radius: var(--r-pill);
  background: var(--soft);
  color: var(--ink-2);
  font-size: 18px;
  line-height: 1;
  cursor: pointer;
}

.coastmap__credit { padding: 8px 14px; font-size: 10.5px; line-height: 1.5; color: var(--ink-muted); border-top: 1px solid var(--line); }
.coastmap__credit a { color: inherit; text-decoration: underline; }

@media (min-width: 40rem) {
  .coastmap__card {
    position: absolute;
    left: 14px;
    bottom: 14px;
    width: min(330px, calc(100% - 28px));
    max-height: calc(100% - 28px);
    margin: 0;
    overflow: auto;
  }
}

/* The places, as the list prints them and the card copies them. */
.places { display: grid; gap: 4px 28px; }
.places__group[hidden] { display: none; }
.places__heading {
  display: flex;
  align-items: center;
  gap: 8px;
  margin: 18px 0 2px;
  font-family: inherit;
  font-size: var(--fs-caps);
  font-weight: 600;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--gold-ink);
}
.places__list { margin: 0; padding: 0; list-style: none; }
.place { padding: 12px 0; border-bottom: 1px solid var(--line); scroll-margin-top: calc(var(--header-h) + 16px); }
.place:target { background: linear-gradient(90deg, var(--warm), transparent); }
.place__cat { display: none; }
.place__name { margin: 0; font-family: var(--display); font-size: 17px; font-weight: 400; line-height: 1.25; }
.place__area { display: block; margin-top: 2px; font-size: 11.5px; color: var(--ink-muted); }
.place__note { margin: 6px 0 0; font-size: var(--fs-body); line-height: 1.55; color: var(--ink-2); }
.place__trip { margin: 6px 0 0; font-size: 12px; font-weight: 600; color: var(--accent); }
.place__tags { display: flex; flex-wrap: wrap; gap: 4px; margin: 8px 0 0; }
.place__tags span {
  padding: 2px 8px;
  border-radius: var(--r-pill);
  background: var(--soft);
  font-size: 10.5px;
  font-weight: 600;
  letter-spacing: 0.04em;
  color: var(--ink-3);
}
.place__links { display: flex; flex-wrap: wrap; gap: 4px 16px; margin: 4px 0 0; }
.place__link {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  min-height: 32px;
  padding: 0;
  border: 0;
  background: none;
  font: inherit;
  font-size: var(--fs-link);
  font-weight: 600;
  color: var(--accent);
  cursor: pointer;
}
.place__link--guide { color: var(--gold-ink); }
.place-icon { flex-shrink: 0; }

.coastmap__card .place__cat {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  margin-bottom: 8px;
  padding: 4px 10px;
  border-radius: var(--r-pill);
  background: var(--soft);
  font-size: var(--fs-caps);
  font-weight: 600;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--ink-3);
}
.coastmap__card .place__show { display: none; }

.places__more { margin-top: 2px; }
.places__more summary {
  display: inline-flex;
  align-items: center;
  min-height: var(--hit);
  font-size: var(--fs-link);
  font-weight: 600;
  color: var(--accent);
  cursor: pointer;
  list-style: none;
}
.places__more summary::-webkit-details-marker { display: none; }
.places__close, .places__more[open] .places__open { display: none; }
.places__more[open] .places__close { display: inline; }

/* ── Links (/links) ───────────────────────────────────────── */

.links-page { padding-bottom: 48px; }
.links-page .page-head { max-width: none; padding-inline: 0; }
.links-page .page-head .lede { max-width: 620px; font-size: var(--fs-body); line-height: 1.6; }
.links-page .page-head .eyebrow { display: block; margin-bottom: 8px; }

.links-section { margin-top: 40px; scroll-margin-top: calc(var(--header-h) + 12px); }
.links-section > .eyebrow { display: block; margin: 0 0 14px; }
.links-note { max-width: 560px; margin: -6px 0 16px; font-size: 13px; line-height: 1.55; color: var(--ink-quiet); }

/* Book direct: the owners card's accent, because it is the same kind of
   invitation — the one thing on the page we would rather you did. */
.links-direct {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: 16px;
  align-items: start;
  margin-top: 24px;
  padding: 24px 22px;
  border-radius: var(--r-xl);
  background: var(--accent);
  color: var(--warm);
  transition: background 0.15s ease;
}
.links-direct:hover { background: var(--accent-deep); color: var(--warm); }
.links-direct__icon {
  display: grid;
  place-items: center;
  width: 52px;
  height: 52px;
  border: 1px solid var(--on-accent-line);
  border-radius: 50%;
  color: var(--gold-soft);
}
.links-direct__copy { display: grid; gap: 6px; }
.links-direct .eyebrow { color: var(--gold-soft); }
.links-direct strong { font-family: var(--display); font-size: 24px; font-weight: 400; line-height: 1.2; }
.links-direct__copy > span:last-child { font-size: 13px; line-height: 1.55; color: var(--on-accent-muted); }
.links-direct .pill {
  grid-column: 2;
  justify-self: start;
  display: inline-flex;
  padding: 10px 18px;
  border: 1px solid var(--on-accent-line);
  border-radius: var(--r-pill);
  font-size: var(--fs-link);
  font-weight: 600;
}

/* The channels: one card each, the whole card the target. */
.link-cards { display: grid; gap: 12px; margin: 0; padding: 0; list-style: none; }
.link-card {
  display: grid;
  grid-template-columns: auto 1fr;
  grid-template-areas: "icon name" "icon what" "icon proof" "icon go";
  column-gap: 16px;
  row-gap: 4px;
  height: 100%;
  padding: 20px;
  border: 1px solid var(--line);
  border-radius: var(--r-card);
  background: var(--card);
  color: var(--ink-2);
  transition: border-color 0.15s ease, box-shadow 0.15s ease, transform 0.15s ease;
}
.link-card:hover { border-color: var(--gold-soft); box-shadow: var(--lift); transform: translateY(-2px); color: var(--ink-2); }
.link-card__icon {
  grid-area: icon;
  display: grid;
  place-items: center;
  width: 48px;
  height: 48px;
  border-radius: 50%;
  background: var(--soft);
  color: var(--accent);
}
.link-card:hover .link-card__icon { color: var(--gold-ink); }
.link-card__name { grid-area: name; font-family: var(--display); font-size: var(--fs-card); line-height: 1.15; color: var(--ink); }
.link-card__what { grid-area: what; font-size: 13px; line-height: 1.5; color: var(--ink-quiet); }
.link-card__proof { grid-area: proof; display: flex; align-items: center; gap: 8px; margin-top: 4px; font-size: 11.5px; color: var(--ink-quiet); }
.link-card__proof .stars { position: relative; display: inline-block; color: var(--gold-soft); font-size: 12px; letter-spacing: 0.1em; }
.link-card__proof .stars__fill { position: absolute; top: 0; left: 0; overflow: hidden; white-space: nowrap; color: var(--gold); font-size: inherit; letter-spacing: inherit; }
.link-card__proof b { font-family: var(--display); font-size: 18px; font-weight: 400; color: var(--ink); }
.link-card__go {
  grid-area: go;
  display: inline-flex;
  align-items: center;
  gap: 6px;
  margin-top: 10px;
  font-size: var(--fs-link);
  font-weight: 600;
  color: var(--accent);
}

/* Booking.com, house by house: the photograph, the facts, then the two ways
   to book it side by side — ours first. */
.link-villas { display: grid; gap: 10px; margin: 0; padding: 0; list-style: none; }
.link-villa {
  display: grid;
  grid-template-columns: 88px 1fr;
  grid-template-areas: "media text" "media actions";
  gap: 10px 14px;
  align-items: center;
  padding: 10px;
  border: 1px solid var(--line);
  border-radius: var(--r-card);
  background: var(--card);
}
.link-villa__media { grid-area: media; align-self: stretch; display: block; width: 88px; min-height: 66px; aspect-ratio: 4 / 3; overflow: hidden; border-radius: var(--r-sm); background: var(--soft); }
.link-villa__media img { display: block; width: 100%; height: 100%; object-fit: cover; }
.link-villa__blank { display: grid; place-items: center; height: 100%; color: var(--ink-faint); }
.link-villa__text { grid-area: text; align-self: end; display: grid; gap: 3px; min-width: 0; }
.link-villa__name { font-family: var(--display); font-size: 17px; line-height: 1.2; color: var(--ink); }
.link-villa__facts { font-size: 12px; color: var(--ink-muted); }
.link-villa__actions { grid-area: actions; align-self: start; display: flex; flex-wrap: wrap; gap: 8px; }
.link-villa__direct,
.link-villa__out {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  min-height: 36px;
  padding: 0 14px;
  border-radius: var(--r-pill);
  font-size: var(--fs-link);
  font-weight: 600;
}
.link-villa__direct { background: var(--accent); color: var(--warm); }
.link-villa__direct:hover { background: var(--accent-deep); color: var(--warm); }
.link-villa__out { border: 1px solid var(--line-ghost); color: var(--ink-2); }
.link-villa__out:hover { border-color: var(--gold-soft); color: var(--ink); }

/* Talk to us: three quiet tiles. */
.link-talk-list { display: grid; gap: 10px; margin: 0; padding: 0; list-style: none; }
.link-talk { display: flex; align-items: center; gap: 14px; height: 100%; padding: 16px 18px; border-radius: var(--r-md); background: var(--soft); color: var(--ink-2); }
.link-talk:hover { background: var(--bg-sink); color: var(--ink); }
.link-talk__icon { display: grid; place-items: center; flex: none; width: 40px; height: 40px; border-radius: 50%; background: var(--card); color: var(--accent); }
.link-talk strong { display: block; font-size: 13.5px; font-weight: 600; color: var(--ink); }
.link-talk strong + span { display: block; font-size: 12px; color: var(--ink-muted); overflow-wrap: anywhere; }

/* The footer's Links column carries the same glyphs, small. */
.site-footer__col.site-footer__col--links a { display: inline-flex; align-items: center; gap: 8px; }
.site-footer__col--links svg { flex: none; color: var(--ink-muted); }

@media (min-width: 40rem) {
  .links-direct { grid-template-columns: auto 1fr auto; align-items: center; padding: 28px 30px; }
  .links-direct .pill { grid-column: 3; }
  .link-cards { grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 16px; }
  .link-cards > li { min-width: 0; }
  .link-card {
    grid-template-columns: 1fr;
    grid-template-rows: auto auto auto 1fr auto;
    grid-template-areas: "icon" "name" "what" "proof" "go";
    row-gap: 6px;
  }
  .link-card__proof { align-self: start; }
  .link-card__icon { margin-bottom: 10px; }
  .link-card__go { padding-top: 10px; }
  .link-talk-list { grid-template-columns: repeat(auto-fit, minmax(14rem, 1fr)); }
  .link-villas { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

@media (min-width: 64rem) {
  .link-villa { grid-template-columns: 120px 1fr; }
  .link-villa__media { width: 120px; }
}

/* ── The Canggu guide (/guide) ────────────────────────────── */

.guide-page .page-head { max-width: none; padding-inline: 0; }
.guide-page .page-head .lede { max-width: 640px; font-size: var(--fs-body); line-height: 1.6; }
.guide-page .page-head .eyebrow { display: block; margin-bottom: 8px; }

.guide-index ul { display: flex; flex-wrap: wrap; gap: 8px; margin: 18px 0 0; padding: 0; list-style: none; }
.guide-index a {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  min-height: 36px;
  padding: 0 14px;
  border: 1px solid var(--line-ghost);
  border-radius: var(--r-pill);
  font-size: 12.5px;
  font-weight: 600;
  color: var(--ink-2);
}
.guide-index a span { color: var(--ink-muted); font-weight: 400; }
.guide-index a:hover { border-color: var(--gold-soft); color: var(--ink); }

.guide-map {
  margin-top: 22px;
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  background: var(--card);
  overflow: hidden;
  scroll-margin-top: calc(var(--header-h) + 12px);
}
.guide-from { display: flex; flex-wrap: wrap; gap: 8px; padding: 14px; border-bottom: 1px solid var(--line); }

.guide-places .places__heading { margin-top: 30px; font-size: var(--fs-smallcaps); }
.guide-places .places__group { scroll-margin-top: calc(var(--header-h) + 12px); }

.guide-stay { margin-top: 40px; padding: 24px; border-radius: var(--r-lg); background: var(--soft); }
.guide-stay ul { display: grid; gap: 10px; margin: 14px 0 0; padding: 0; list-style: none; }
.guide-stay a {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px;
  padding: 14px 16px;
  border-radius: var(--r-md);
  background: var(--card);
}
.guide-stay strong { font-family: var(--display); font-size: 19px; font-weight: 400; color: var(--ink); }
.guide-stay span { font-size: 12.5px; font-weight: 600; color: var(--accent); }

.guide-foot { margin: 28px 0 48px; font-size: 12.5px; color: var(--ink-muted); }
.guide-foot a { color: var(--accent); font-weight: 600; }

@media (min-width: 40rem) {
  .where .places { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .guide-places .places { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .guide-stay ul { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

/* On a desktop the map stays beside the list while it scrolls: the guide is
   read with both in view, the way a guest plans a day. */
@media (min-width: 64rem) {
  .guide-body {
    display: grid;
    grid-template-columns: minmax(0, 5fr) minmax(0, 7fr);
    gap: 32px;
    align-items: start;
    margin-top: 8px;
  }
  .guide-body .guide-map {
    position: sticky;
    top: calc(var(--header-h) + 16px);
    order: 2;
    margin-top: 22px;
  }
  .guide-body .guide-places { order: 1; }
  .guide-places .places { grid-template-columns: minmax(0, 1fr); }
  .guide-stay ul { grid-template-columns: repeat(4, minmax(0, 1fr)); }
}

@media (prefers-reduced-motion: reduce) {
  .cm-pin__body, .cm-pin circle { transition: none; }
}

/* ── Guest reviews ─────────────────────────────────────────── */

.villa-reviews { display: flex; flex-direction: column; gap: 20px; margin-top: 14px; }

.villa-review { margin: 0; padding-top: 16px; border-top: 1px solid var(--line); }
.villa-review figcaption { display: flex; align-items: center; flex-wrap: wrap; gap: 8px; }

.villa-review__who {
  font-size: 10.5px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--ink-muted);
  font-weight: 600;
}

.villa-review .tag { font-size: 10.5px; letter-spacing: 0.1em; padding: 3px 8px; }
.villa-review .read-more { margin-top: 10px; }
.villa-review .villa-copy { margin-top: 10px; }
.read-more--review .rm-text { font-size: var(--fs-body); }

/* The review's own score, and the month it was written. The date is pushed
   to the end of the line: it is the part a guest looks for last and skips
   over most often. */
.villa-review__score { font-size: 11.5px; font-weight: 600; color: var(--ink-2); }
.villa-review__score .star { color: var(--gold); }
.villa-review__lang { font-size: 11.5px; color: var(--ink-muted); }
.villa-review__at { margin-left: auto; font-size: 11.5px; color: var(--ink-muted); }

.reviews-more { margin-top: 20px; }
.reviews-more[open] { margin-bottom: 4px; }
.reviews-more .villa-reviews { margin-top: 20px; }

/* ── House rules ───────────────────────────────────────────── */

.villa-rules {
  margin: 14px 0 0;
  font-size: var(--fs-body);
  line-height: 1.6;
  color: var(--ink-2);
  text-wrap: pretty;
}

/* ── The phone's sticky reserve bar ────────────────────────── */

/* Sticky rather than fixed: it rides the bottom of the viewport and comes to
   rest at the end of the document, so it never covers the footer and needs no
   compensating padding anywhere. */
.villa-bar {
  position: sticky;
  bottom: 0;
  z-index: 30;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 12px var(--gutter-page) calc(12px + env(safe-area-inset-bottom, 0px));
  background: var(--frost-header);
  -webkit-backdrop-filter: blur(16px);
  backdrop-filter: blur(16px);
  border-top: 1px solid var(--line);
}

/* And it steps aside while the booking card it points at is on screen
   (calendar.js, issue #195): parked over the calendar, it is the same offer
   printed twice and a row of dates behind glass. `visibility` is switched at
   the end of the slide, so the bar leaves the tab order and the screen
   reader's page with it rather than staying a second "Check the dates" link
   pointing at what is already in view. */
.villa-bar {
  transition: transform 0.22s var(--ease), opacity 0.22s var(--ease), visibility 0s;
}

/* It fades and settles 6px rather than sliding its whole height away: a
   bottom-stuck bar translated past the viewport grows the page's scrollable
   area by exactly that much, and a page that gets taller when a bar hides is
   a page that bounces at the end of every scroll. */
.villa-bar.is-away {
  transform: translateY(6px);
  opacity: 0;
  visibility: hidden;
  transition: transform 0.22s var(--ease), opacity 0.22s var(--ease), visibility 0s linear 0.22s;
}

@media (prefers-reduced-motion: reduce) {
  .villa-bar, .villa-bar.is-away { transition: none; }
}

.villa-bar__price { font-size: 15px; font-weight: 700; }
.villa-bar__price .from,
.villa-bar__price .per { font-size: 11px; font-weight: 400; color: var(--ink-muted); }

/* ── Calendar (issue #47) ──────────────────────────────────── */

.villa-calendar {
  scroll-margin-top: calc(var(--header-h) + 12px);
  margin-top: 26px;
  border: 1px solid var(--line);
  border-radius: 20px;
  background: var(--surface);
  padding: 16px 14px 14px;
}

.cal-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 0 4px 12px;
}
.cal-month { font-family: var(--display); font-size: 16px; }

.cal-nav {
  width: 30px;
  height: 30px;
  border-radius: 50%;
  border: 1px solid var(--line-strong);
  background: var(--surface);
  color: var(--ink);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  cursor: pointer;
}
.cal-nav:disabled { color: var(--ink-faint); cursor: default; }
/* The visible circle stays 30px (26px for the stepper); the hit area is
   --hit through an invisible pseudo-element, the way a thumb expects. */
.cal-nav, .cal-guests__btn { position: relative; }
.cal-nav::after, .cal-guests__btn::after { content: ""; position: absolute; inset: -8px; }
.cal-nav:disabled::after, .cal-guests__btn:disabled::after { display: none; }
.cal-nav svg { display: block; }

/* minmax(0, 1fr), not 1fr: a bare 1fr is minmax(auto, 1fr), so a column's
   floor is the min-content of its widest price ("IDR 7,850,740.00") and seven
   priced columns overflow a phone-width card — the page then scrolls sideways
   the moment a guest taps a date. With a zero floor the grid holds. */
.cal-weekdays,
.cal-grid { display: grid; grid-template-columns: repeat(7, minmax(0, 1fr)); gap: 4px; }
.cal-weekdays {
  padding: 0 0 6px;
  font-size: 10.5px;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--ink-muted);
  font-weight: 600;
  text-align: center;
}

.cal-day {
  min-width: 0;
  height: 46px;
  border-radius: 10px;
  border: 1px solid transparent;
  background: none;
  font: inherit;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 1px;
  cursor: pointer;
  color: var(--ink);
}
.cal-day--blank { visibility: hidden; }
.cal-day__n { font-size: 13px; font-weight: 500; }
.cal-day__p {
  font-size: 10.5px;
  opacity: .8;
  max-width: 100%;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.cal-day--closed { cursor: default; }
.cal-day--closed .cal-day__n {
  font-weight: 400;
  color: var(--ink-muted);
  text-decoration: line-through;
}
/* A month the calendar could not load is not a month that is booked. */
.cal-day--unknown { cursor: default; }
.cal-day--unknown .cal-day__n {
  font-weight: 400;
  color: var(--ink-muted);
  text-decoration: none;
}
/* A day that has gone is not a day somebody booked: no strike-through. */
.cal-day--past .cal-day__n { text-decoration: none; }
/* Nor is a free night on which the host takes no arrivals. */
.cal-day--noin .cal-day__n { text-decoration: none; }
/* Nor a free night too short for its own minimum stay: not booked, not open. */
.cal-day--short { cursor: default; }
.cal-day--short .cal-day__n { text-decoration: none; color: var(--ink-muted); font-weight: 400; }

/* Check-out only: once a check-in is chosen, a booked day right after a free
   run opens as the morning that stay ends — the next guest arrives that
   afternoon. The evening half of the cell is filled on the diagonal, the way
   its night is taken, under a dashed rule, so it reads apart from an open day
   and from a booked one. Existing tokens only. */
.cal-day--out {
  border: 1px dashed var(--gold-soft);
  background: linear-gradient(135deg, transparent 50%, var(--surface-soft) 50%);
}
.cal-day--out .cal-day__n { color: var(--ink-soft); }

/* #eae3d0 is the canvas's own one-off in-range highlight — not one of the
   surface tokens, but taken verbatim from the design. */
.cal-day--inrange { background: #eae3d0; }

.cal-day--endpoint {
  background: var(--accent);
  color: var(--surface-warm);
}
.cal-day--endpoint .cal-day__n { font-weight: 700; }

.cal-summary {
  margin-top: 12px;
  background: var(--bg-sink);
  border-radius: 12px;
  padding: 11px 14px;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
}
.cal-range { font-size: 12.5px; font-weight: 600; }
.cal-range-sub { font-size: 12.5px; color: var(--ink-soft); }

/* The canvas's demand line under a gold hairline, here as the way out of a
   month with nothing left in it (issue #195). */
.cal-nextopen {
  margin: 12px 0 0;
  padding-top: 10px;
  border-top: 1px solid var(--gold-soft);
  font-size: 11.5px;
  color: var(--ink-soft);
}
.cal-nextopen__go {
  border: 0;
  background: none;
  padding: 4px 0;
  font: inherit;
  font-size: 11.5px;
  color: var(--ink-soft);
  cursor: pointer;
  text-decoration: underline;
  text-underline-offset: 3px;
}

.cal-note { font-size: 11px; color: var(--ink-quiet); margin: 10px 0 0; padding: 0 2px; }
.cal-quote__note { margin: 8px 0 0; font-size: 11.5px; color: var(--ink-quiet); line-height: 1.5; }
.cal-quote__sub { font-size: 11.5px; color: var(--ink-quiet); }

/* The stale note is designed for full-bleed page placement; nested in the
   card's own padding it only needs its own top margin. */
.villa-calendar .note { margin: 12px 0 0; }

/* ── Reserve (issue #48) ────────────────────────────────────── */

.cal-book { margin-top: 14px; }

.cal-guests {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
}
.cal-guests__label { font-size: 12.5px; font-weight: 600; }
.cal-guests__control { display: flex; align-items: center; gap: 12px; }
.cal-guests__btn {
  width: 26px;
  height: 26px;
  border-radius: 50%;
  border: 1px solid var(--line-strong);
  background: var(--surface);
  color: var(--ink);
  font: inherit;
  font-size: 14px;
  line-height: 1;
  cursor: pointer;
}
.cal-guests__btn:disabled { color: var(--ink-faint); cursor: default; }
.cal-guests__n { min-width: 12px; text-align: center; font-size: 14px; font-weight: 600; }

.cal-reserve {
  display: block;
  width: 100%;
  margin-top: 12px;
  padding: 13px;
  border: 0;
  border-radius: 999px;
  background: var(--accent);
  color: var(--surface-warm);
  font: inherit;
  font-size: 13px;
  font-weight: 600;
  cursor: pointer;
}
.cal-reserve:disabled { background: var(--surface-soft); color: var(--ink-faint); cursor: default; }

.cal-quote { margin-top: 12px; }
.cal-quote__line {
  display: flex;
  justify-content: space-between;
  font-size: 13px;
  color: var(--ink-2);
  padding: 6px 0;
  border-bottom: 1px solid var(--line);
}
.cal-quote__line:last-of-type { border-bottom: 0; }

/* The canvas's quote summary: the parts in a light column, the total set off
   above the button by its own rule and the only weight in the block. */
.cal-quote__line--total {
  border-bottom: 0;
  border-top: 1px solid var(--line-strong);
  margin-top: 4px;
  padding-top: 12px;
  font-size: 15px;
  font-weight: 700;
  color: var(--ink);
}
.cal-quote__line--off { color: var(--ink-muted); }

.cal-quote__book { display: block; width: 100%; text-align: center; margin-top: 12px; }
.cal-pay { margin-top: 12px; }
.cal-pay__label {
  display: block;
  margin: 10px 0 0;
  font-size: 11px;
  color: var(--ink-muted);
}
.cal-pay__in {
  display: block;
  width: 100%;
  box-sizing: border-box;
  margin-top: 4px;
  border: 1px solid var(--line-strong);
  border-radius: 12px;
  background: var(--surface);
  color: var(--ink);
  font: inherit;
  font-size: 16px;
  padding: 10px 12px;
}
.cal-pay__in[name="phone"],
.cal-concierge__wa {
  font-variant-numeric: tabular-nums;
  letter-spacing: 0.01em;
}
.cal-pay__status:empty { display: none; }
.cal-pay__status { margin: 8px 0 0; font-size: 12.5px; line-height: 1.45; color: var(--ink-soft); }
/* Change dates only exists where the quote hides the grid. On a phone the
   grid never leaves, so the button would be a control that does nothing. */
.cal-quote__change { display: none; }

/* The concierge step (issue #94) — the canvas's confirmation-screen chat with
   the villa manager, folded into the same card as the quote it follows. Its
   palette is the canvas's own note treatment: a warm sunk panel with a gold
   rule, so it reads as the manager speaking rather than as one more field. */
.cal-concierge {
  margin-top: 16px;
  padding: 14px;
  border-radius: 14px;
  border: 1px solid var(--line-strong);
  background: var(--bg-sink);
}
.cal-concierge__head { display: flex; flex-direction: column; gap: 3px; }
.cal-concierge__who {
  font-size: 10.5px;
  letter-spacing: .24em;
  text-transform: uppercase;
  color: var(--gold-ink);
  font-weight: 600;
}
.cal-concierge__sub { font-size: 12.5px; line-height: 1.5; color: var(--ink-soft); }

.cal-concierge__chips { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 12px; }
.cal-chip {
  border: 1px solid var(--line-strong);
  border-radius: 999px;
  background: var(--surface);
  color: var(--ink-2);
  font: inherit;
  font-size: 12px;
  padding: 7px 12px;
  cursor: pointer;
}
.cal-chip:hover { border-color: var(--gold-soft); color: var(--ink); }

.cal-concierge__label {
  display: block;
  margin: 12px 0 4px;
  font-size: 11px;
  color: var(--ink-muted);
}
.cal-concierge__msg,
.cal-concierge__wa,
.cal-concierge__name {
  display: block;
  width: 100%;
  box-sizing: border-box;
  border: 1px solid var(--line-strong);
  border-radius: 12px;
  background: var(--surface);
  color: var(--ink);
  font: inherit;
  font-size: 13px;
  padding: 10px 12px;
}
.cal-concierge__msg { resize: vertical; min-height: 60px; }

/* Honeypot, same treatment as the owners form's. */
.cal-concierge__hp {
  position: absolute;
  left: -9999px;
  width: 1px;
  height: 1px;
  opacity: 0;
}

.cal-concierge__send { display: block; width: 100%; text-align: center; margin-top: 12px; }
.cal-concierge__send:disabled { opacity: .6; cursor: default; }
.cal-concierge__status:empty { display: none; }

/* The canvas heads its booking card with the rate and the rating. */
.cal-price {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 10px;
  padding: 0 4px 14px;
}

.cal-price__n { font-size: 18px; font-weight: 700; white-space: nowrap; }
.cal-price__n .from,
.cal-price__n .per,
.cal-price__n .in { font-size: 11px; font-weight: 400; color: var(--ink-muted); }
/* "in September" wraps rather than pushing the rating off the row: the price
   is nowrap, the month it belongs to need not be. */
.cal-price__n .in { white-space: normal; }

.cal-price__r { font-size: 12.5px; color: var(--ink-soft); white-space: nowrap; }
.cal-price__r .star { color: var(--gold); }
.cal-price__r span { color: var(--ink-muted); }

/* ── Mirror staleness ──────────────────────────────────────── */

/* Advisory, not alarming — but never invisible: ARCHITECTURE.md treats a
   silently-shown vacancy that is actually booked as the worst failure here. */
.note { margin-bottom: 18px; width: auto; margin-inline: var(--gutter-page); max-width: var(--container); }

/* ── Footer ────────────────────────────────────────────────── */

.site-footer { margin-top: 36px; padding: 40px 0 46px; }

.site-footer__grid {
  display: flex;
  flex-direction: column;
  gap: 22px;
  width: 100%;
  max-width: calc(var(--container) + 2 * var(--gutter-page));
  margin-inline: auto;
  padding-inline: calc(var(--gutter-page) + 4px);
}

.site-footer .brand { display: block; font-size: 18px; margin-bottom: 8px; }
.site-footer__brand .fine { margin-top: 12px; }
/* The operator's own line sits under the guest-facing one, not apart from it. */
.site-footer__brand .fine + .fine { margin-top: 6px; }

.site-footer__h {
  display: block;
  margin-bottom: 8px;
  font-size: 10.5px;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  font-weight: 600;
  color: var(--ink-soft);
}

.site-footer__col ul {
  margin: 0;
  padding: 0;
  list-style: none;
  font-size: 12.5px;
  line-height: 1.8;
  color: var(--ink-quiet);
}
/* 24px tall at least: a 15px line is a target a thumb misses. */
.site-footer__col a { display: inline-block; padding-block: 3px; color: var(--ink-quiet); }
.menu__areas a, .menu__secondary a { display: inline-block; padding-block: 5px; }
.site-footer__col a:hover { color: var(--ink); }

/* ── Tablet ────────────────────────────────────────────────── */

@media (min-width: 40rem) {
  .hero__copy { padding: 0 var(--gutter-page) 20px; }
  .hero .lede { max-width: 560px; }
  .page-head h1 { font-size: var(--fs-display); }

  .trust { flex-direction: row; align-items: center; justify-content: space-between; }
  .trust__terms { margin-left: 0; }
  /* Room for the channels to sit beside each other rather than stacked. */
  .trust__scores { flex-direction: row; flex-wrap: wrap; gap: 10px 28px; }
  .trust__channel { min-width: 0; }

  /* Two-up cards on a tablet, no rail. */
  .rail {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 30px 24px;
    margin: 0;
    padding: 0;
    overflow: visible;
  }
  .rail .villa { width: auto; }
  .rail .villa__media { height: auto; aspect-ratio: 4 / 3; border-radius: var(--r-lg); }
  /* A grid, not a row of snapped cards: no height to reserve. The two-line
     clamp stays — a wider card holds more, not more lines. */
  .rail .villa__name { font-size: var(--fs-card); min-height: 0; line-height: 1.15; }
  .rail .villa__head { margin-top: 14px; gap: 10px; }
  .rail .villa__price { font-size: 14px; }
  .rail .villa__price .per { display: inline; }
  .rail .villa__meta { margin-top: 3px; font-size: 12.5px; }
  .rail .villa__demand { margin-top: 10px; padding-top: 9px; font-size: 11.5px; gap: 4px 10px; }
  .rail .villa__signal { margin-left: auto; }

  .stack {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 30px 24px;
  }
  .stack .villa__media { height: auto; aspect-ratio: 4 / 3; border-radius: var(--r-lg); }
  .stack .villa__line { padding-inline: 0; }

  .band { padding: 44px var(--gutter-page); }
  .band h2 { font-size: 32px; }

  .quotes__grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 24px; }

  .gram__grid { grid-template-columns: repeat(6, minmax(0, 1fr)); gap: 8px; }
  .gram__grid li { border-radius: 14px; }

  .site-footer__grid { flex-direction: row; flex-wrap: wrap; gap: 28px 40px; }
  .site-footer__brand { flex: 1 1 100%; }
  .site-footer__col { flex: 1 1 10rem; }

  /* Villa detail: still one column, and from here the canvas puts the
     booking card inline right after the facts — the order it is written in. */
  .villa-main, .villa-side { order: 0; }
  .villa-side { margin-top: 24px; }
  .villa-hero { height: 380px; }
  .villa-thumb { height: 110px; }
  .villa-gallery__rest .villa-thumb { height: 150px; }
  .villa-gallery__rest { grid-template-columns: repeat(4, minmax(0, 1fr)); }
  .villa-head h1 { font-size: 40px; }
  .amenities { grid-template-columns: repeat(3, minmax(0, 1fr)); }
  .villa-side { max-width: 30rem; }
}

/* ── Desktop ───────────────────────────────────────────────── */

@media (min-width: 64rem) {
  /* The header's content sits on the same 1200 column as the page. */
  .site-header,
  .menu__bar {
    padding-inline: max(var(--gutter-page), calc((100% - var(--container)) / 2));
  }

  .site-nav { display: flex; gap: 36px; }
  .menu-btn { margin-left: 24px; }

  .menu__body {
    display: grid;
    grid-template-columns: repeat(12, minmax(0, 1fr));
    grid-template-rows: auto 1fr auto;
    gap: 24px;
    padding: 64px var(--gutter-page) 48px;
  }
  .menu__body > .eyebrow { grid-column: 1 / -1; }
  .menu__primary { grid-column: 1 / span 5; grid-row: 2; margin-top: 0; gap: 22px; }
  .menu__primary li { gap: 18px; }
  .menu__primary li::before { font-size: 14px; }
  .menu__primary a { font-size: var(--fs-display-menu); line-height: 1.05; }
  .menu__areas { grid-column: 7 / span 3; grid-row: 2; margin-top: 0; padding-top: 0; border-top: 0; }
  .menu__areas ul, .menu__secondary { flex-direction: column; gap: 14px; }
  .menu__secondary { grid-column: 10 / span 3; grid-row: 2; margin-top: 0; padding-top: 0; border-top: 0; align-self: start; }
  .menu__secondary::before { content: "More"; display: block; margin-bottom: 4px; font-size: var(--fs-caps); letter-spacing: 0.24em; color: var(--ink-muted); }
  .menu__foot { grid-column: 1 / -1; grid-row: 3; padding-top: 22px; border-top: 1px solid var(--line-strong); font-size: var(--fs-link); }

  .hero__veil {
    background: linear-gradient(180deg,
      rgba(250, 247, 240, 0) 50%,
      rgba(250, 247, 240, 0.85) 74%,
      var(--ground) 82%);
  }

  .hero__inner {
    display: grid;
    grid-template-columns: repeat(12, minmax(0, 1fr));
    gap: 24px;
    align-items: end;
    max-width: calc(var(--container) + 2 * var(--gutter-page));
    margin-inline: auto;
    padding: 0 var(--gutter-page) 40px;
  }
  .hero__copy { grid-column: 1 / span 7; padding: 0; }
  .hero__copy .eyebrow { margin-bottom: 16px; }
  .hero h1 { font-size: var(--fs-display-xl); line-height: 1.02; }
  .hero__row { margin-top: 22px; flex-wrap: nowrap; }
  .superhost { padding: 6px 13px 6px 9px; font-size: 10px; }
  .superhost__score { font-size: 14px; }
  .hero .lede { flex-basis: auto; max-width: 420px; font-size: var(--fs-body-lg); line-height: 1.6; }

  .page-head h1 { font-size: var(--fs-title-lg); }
  .empty { margin-inline: auto; }

  /* The search bar overlaps the hero's foot, columns 1–8. */
  .searchbar {
    display: flex;
    grid-template-areas: none;
    align-items: center;
    grid-column: 1 / span 8;
    width: auto;
    max-width: none;
    margin: 36px 0 0;
    padding: 8px 8px 8px 24px;
  }
  .searchbar__field { flex: 1; padding: 8px 0; margin: 0; border: 0; grid-area: auto; }
  .searchbar__field--checkin { padding-right: 0; }
  .searchbar__field--checkout { padding-left: 0; }
  .searchbar__field--guests { flex: 1.2; margin-top: 0; padding-top: 8px; border-top: 0; }
  .searchbar__field--area { display: block; flex: 1.1; }
  .searchbar__field + .searchbar__field { margin-left: 20px; padding-left: 20px; }
  .searchbar__field + .searchbar__field::before {
    content: "";
    position: absolute;
    left: 0;
    top: 50%;
    width: 1px;
    height: 38px;
    transform: translateY(-50%);
    background: var(--line);
  }
  .searchbar label { margin-bottom: 4px; letter-spacing: 0.18em; }
  .searchbar select,
  .searchbar input[type="date"] { font-size: 15px; }
  .searchbar__go { width: auto; height: 50px; padding: 0 24px; margin-left: 20px; }
  .searchbar__label { position: static; width: auto; height: auto; margin: 0; overflow: visible; clip-path: none; }

  /* One hairlined row: the rating on the left, the channels on the right. */
  .trust {
    margin-top: 36px;
    padding: 22px 0;
    border-top: 1px solid var(--line);
    border-bottom: 1px solid var(--line);
    max-width: var(--container);
  }
  .trust__scores { gap: 10px 32px; }
  .trust__score { gap: 10px; }
  .trust__score .stars { font-size: 14px; letter-spacing: 0.12em; }
  .trust__score b { font-size: var(--fs-block); }
  .trust__count { font-size: 13px; }
  .trust__channels { gap: 22px; font-size: 11px; letter-spacing: 0.16em; }
  .trust__channels li + li::before { display: none; }

  .collection { margin-top: 64px; }
  .section-head { padding: 0 0 24px; }
  .section-head .eyebrow { margin-bottom: 8px; }
  .section-head h2 { margin-top: 0; font-size: var(--fs-section); }
  /* Unclipped: from here the canvas prints the heading the phone only speaks. */
  .collection .section-head h2 {
    position: static;
    width: auto;
    height: auto;
    margin: 0;
    overflow: visible;
    white-space: normal;
    clip-path: none;
  }
  .gram .section-head h2 { font-size: var(--fs-title); }

  /* Three-up on the canvas's 12-column grid. */
  .rail,
  .stack { grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 40px 24px; }
  .rail { gap: 24px; }

  .band { margin-top: 72px; padding: 72px var(--gutter-page); }
  .band__inner {
    display: grid;
    grid-template-columns: repeat(12, minmax(0, 1fr));
    gap: 24px;
    padding-inline: 0;
  }
  .band__title { grid-column: 1 / span 5; }
  .band .eyebrow { margin-bottom: 12px; }
  .band h2 { margin: 0; font-size: var(--fs-display); line-height: 1.12; }
  .band ol {
    grid-column: 7 / span 6;
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 28px 24px;
  }
  .band li { font-size: var(--fs-body-lg); line-height: 1.55; }
  .band li::before { min-width: 30px; font-size: var(--fs-card); }

  .quotes { margin-top: 72px; }
  .quotes > .eyebrow { margin-bottom: 24px; }
  .quotes__grid { grid-template-columns: repeat(3, minmax(0, 1fr)); }
  .quote { padding-top: 22px; }
  .quote blockquote { font-size: var(--fs-card); }
  .quote figcaption { margin-top: 14px; }

  .gram { margin-top: 72px; }
  .gram .section-head { padding-bottom: 20px; }

  .owners {
    display: grid;
    grid-template-columns: repeat(12, minmax(0, 1fr));
    gap: 24px;
    align-items: center;
    margin-top: 72px;
    margin-inline: auto;
    padding: 56px 64px;
    border-radius: var(--r-2xl);
  }
  .owners__copy { grid-column: 1 / span 7; }
  .owners .eyebrow { margin-bottom: 12px; font-size: var(--fs-caps); }
  .owners h2 { font-size: var(--fs-display); line-height: 1.12; }
  .owners p { margin: 14px 0 0; max-width: 520px; font-size: var(--fs-body-lg); line-height: 1.6; }
  .owners .pill { grid-column: 10 / span 3; justify-self: end; padding: 14px 24px; font-size: var(--fs-body); }

  /* ── For owners on the canvas's 12-column grid ────────────── */

  /* The canvas's "For owners" desktop board reads left to right: the promise
     in columns 1-7, what to do about it in 9-12, and the numbers as figures
     under hairlines. Until now the page had no desktop layout at all — the
     phone's blocks simply grew to the window, so the checklist, the fee and
     the lead form ran the full width while the heading sat in the page
     column (issue #195). */
  .owners-page {
    display: grid;
    grid-template-columns: repeat(12, minmax(0, 1fr));
    gap: 0 var(--gutter);
    align-items: start;
  }

  .owners-page .page-head { grid-column: 1 / span 7; }
  .owners-page .page-head h1 { font-size: var(--fs-title-lg); }
  .owners-page .page-head .lede { font-size: var(--fs-body-lg); line-height: 1.6; max-width: 520px; }

  .owners-stats { grid-column: 9 / span 4; margin-top: 32px; }
  .owners-stats__n { font-size: var(--fs-display); }
  .owners-stats__l { font-size: var(--fs-caps); letter-spacing: 0.18em; margin-top: 8px; }

  .owners-checklist { grid-column: 1 / span 7; margin-top: 48px; }
  .owners-checklist li { font-size: var(--fs-body-lg); line-height: 1.55; }

  .owners-side { grid-column: 9 / span 4; margin-top: 48px; }

  /* The contact page on the owners page's grid: the head and the form in
     columns 1-7, the quick ways in 9-12 beside the form. The last row is the
     flexible one, so a tall form grows it rather than pushing a gap in
     between WhatsApp and the address. Without a form the ways take the main
     column instead of leaving it empty. */
  .contact-page {
    display: grid;
    grid-template-columns: repeat(12, minmax(0, 1fr));
    grid-template-rows: auto auto 1fr;
    gap: 0 var(--gutter);
    align-items: start;
  }
  .contact-page .page-head { grid-column: 1 / span 7; grid-row: 1; }
  .contact-page .page-head h1 { font-size: var(--fs-title-lg); }
  .contact-page .page-head .lede { font-size: var(--fs-body-lg); line-height: 1.6; max-width: 520px; }
  .contact-write { grid-column: 1 / span 7; grid-row: 2 / span 2; margin-top: 32px; }
  .contact-way { grid-column: 9 / span 4; grid-row: 2; margin-top: 32px; }
  .contact-way--wa ~ .contact-way--mail { grid-row: 3; margin-top: 24px; }
  .contact-page--noform .contact-way { grid-column: 1 / span 7; grid-row: auto; }

  .site-footer { margin-top: 72px; padding: 40px 0 56px; }
  .site-footer__grid {
    display: grid;
    grid-template-columns: repeat(12, minmax(0, 1fr));
    gap: 24px;
    padding-inline: var(--gutter-page);
  }
  /* The canvas's columns moved one step left to make room for the small
     Links column: brand 1–4, Areas 5–6, Company 7–8, Contact 9–10, Links
     11–12. Without an areas column the others keep their places. */
  .site-footer__brand { grid-column: 1 / span 4; }
  .site-footer .brand { font-size: 19px; margin-bottom: 10px; }
  .site-footer__col--areas { grid-column: 5 / span 2; }
  .site-footer__col--company { grid-column: 7 / span 2; }
  .site-footer__col--contact { grid-column: 9 / span 2; }
  .site-footer__col--links { grid-column: 11 / span 2; }

  /* ── Villa detail, the canvas's desktop board ─────────────── */

  /* Gallery 1 + 4: the hero across six columns and two rows, four beside it.
     `display: contents` lets the four thumbs be grid items of the gallery
     rather than of their own strip, so one grid places all five. */
  .villa-gallery {
    display: grid;
    grid-template-columns: repeat(12, minmax(0, 1fr));
    grid-template-rows: 230px 230px;
    gap: 12px;
    margin-top: 24px;
  }

  .villa-hero { grid-column: 1 / span 6; grid-row: 1 / span 2; height: auto; border-radius: var(--r-xl); }
  .villa-thumbs { display: contents; }
  .villa-thumbs .villa-thumb { height: auto; border-radius: var(--r-xl); }
  .villa-thumbs .villa-thumb:nth-child(1) { grid-column: 7 / span 3; grid-row: 1; }
  .villa-thumbs .villa-thumb:nth-child(2) { grid-column: 10 / span 3; grid-row: 1; }
  .villa-thumbs .villa-thumb:nth-child(3) { grid-column: 7 / span 3; grid-row: 2; }
  .villa-thumbs .villa-thumb:nth-child(4) { grid-column: 10 / span 3; grid-row: 2; }
  .villa-gallery__more { grid-column: 1 / -1; margin-top: 0; }
  .villa-gallery__rest { grid-template-columns: repeat(6, minmax(0, 1fr)); }
  .villa-gallery__rest .villa-thumb { height: 160px; }

  /* Content in columns 1–7, the booking card sticky in 9–12. The intro sits
     above the content in the same seven columns, so the card starts level
     with the villa's name. */
  /* No `align-items: start` here, deliberately. The booking card sticks
     inside its own column, and a column sized to its content is a column
     with nothing to stick through: the card would scroll away with the
     intro. Stretched, the side spans both rows and the card rides the whole
     page. */
  .villa-body {
    display: grid;
    grid-template-columns: repeat(12, minmax(0, 1fr));
    gap: 0 var(--gutter);
  }

  .villa-intro { grid-column: 1 / span 7; grid-row: 1; align-self: start; padding-top: 32px; }
  .villa-main { grid-column: 1 / span 7; grid-row: 2; align-self: start; }
  .villa-side {
    grid-column: 9 / span 4;
    grid-row: 1 / span 2;
    align-self: stretch;
    max-width: none;
    margin-top: 0;
    padding-top: 32px;
  }

  .villa-calendar {
    position: sticky;
    top: calc(var(--header-h) + 24px);
    /* 1366×768: calendar + quote + concierge used to push Book now below
       the fold. The card scrolls inside the viewport instead. */
    max-height: calc(100vh - var(--header-h) - 48px);
    overflow-y: auto;
  }
  /* Once a price is on screen the month grid is what shoved the button
     down. Every control that moves that grid hides with it — a jump the
     guest cannot see would open the wrong month when they ask for the
     dates back. Change dates, which only exists at this width, brings
     the grid back. The price stays. */
  .villa-calendar.is-quoted .cal-head,
  .villa-calendar.is-quoted .cal-weekdays,
  .villa-calendar.is-quoted .cal-grid,
  .villa-calendar.is-quoted [data-cal-nextopen] { display: none; }
  .cal-quote__change { display: block; width: 100%; margin-top: 8px; }

  .villa-head h1 { font-size: var(--fs-title-lg); }
  .villa-facts { font-size: var(--fs-body-lg); }
  .villa-demand { font-size: 12.5px; }
  .villa-section { margin-top: 40px; }
  .amenities { grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 18px 20px; }
  .amenity { font-size: 13.5px; }
  .villa-reviews { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: 24px; }
  .villa-review { padding-top: 18px; }

  /* The sticky bar is the phone's answer to a booking card that is not on
     screen. On a desktop the card itself never leaves. */
  .villa-bar { display: none; }

  /* The viewer comes off the edges and becomes a card, the way every other
     panel on a desktop does. */
  .lightbox {
    width: min(96vw, calc(var(--container) + 2 * var(--gutter)));
    height: min(94vh, 900px);
    margin: auto;
    border-radius: var(--r-xl);
    overflow: hidden;
  }
}

/* iOS zooms the page when a focused control is under 16px. Phones and
   other coarse pointers get 16; a mouse keeps the canvas sizes. */
@media (pointer: coarse) {
  /* Under 16px, iOS zooms the page on focus and the layout appears to jump. */
  .searchbar input[type="date"],
  .searchbar select,
  .owners-form__field,
  .contact-form__field,
  .cal-concierge__msg,
  .cal-concierge__wa,
  .cal-concierge__name {
    font-size: 16px;
  }
}
