/* ==========================================================================
   Desa Hay Bali — stylesheet
   Palette and type chosen to match the original brand: warm bone paper,
   deep jungle green, hand-carved-wood clay accent.
   ========================================================================== */

:root {
  --bone:      #faf8f4;
  --bone-deep: #f2ede4;
  --sand:      #e3d9c9;
  --ink:       #22261f;
  --ink-soft:  #4d5347;
  --moss:      #34412f;
  --moss-deep: #222b1f;
  --clay:      #a3714b;
  --clay-deep: #8a5d3b;
  --white:     #ffffff;

  --font-display: "Cormorant Garamond", Georgia, "Times New Roman", serif;
  --font-body:    "Jost", "Futura", "Avenir Next", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;

  --wrap:      1240px;
  --wrap-text: 760px;
  --gutter:    clamp(1.25rem, 5vw, 4rem);
  --header-h:  84px;

  --ease: cubic-bezier(.22,.61,.36,1);
  --shadow-sm: 0 1px 2px rgba(34,38,31,.06), 0 4px 14px rgba(34,38,31,.06);
  --shadow-md: 0 10px 40px rgba(34,38,31,.14);
}

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

html {
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
  scroll-padding-top: calc(var(--header-h) + 1rem);
}

body {
  margin: 0;
  background: var(--bone);
  color: var(--ink);
  font-family: var(--font-body);
  font-size: 1.0625rem;
  font-weight: 300;
  line-height: 1.75;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  overflow-wrap: break-word;
}

body.is-locked { overflow: hidden; }

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

h1, h2, h3, h4 {
  font-family: var(--font-display);
  font-weight: 300;
  line-height: 1.15;
  letter-spacing: .005em;
  margin: 0 0 .6em;
  color: var(--ink);
}

h1 { font-size: clamp(2.4rem, 6vw, 4.1rem); }
h2 { font-size: clamp(1.9rem, 4.2vw, 3rem); }
h3 { font-size: clamp(1.5rem, 3vw, 2.1rem); }
h4 { font-size: clamp(1.2rem, 2.2vw, 1.45rem); }

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

a { color: inherit; text-decoration-thickness: 1px; text-underline-offset: .22em; }
a:hover { color: var(--clay-deep); }

strong, b { font-weight: 500; }

ul, ol { margin: 0 0 1.35em; padding-left: 1.25em; }
li { margin-bottom: .45em; }

hr {
  border: 0;
  border-top: 1px solid var(--sand);
  margin: clamp(3rem, 8vw, 6rem) 0;
}

/* Visible focus ring for keyboard users — the original site had none. */
:focus-visible {
  outline: 2px solid var(--clay);
  outline-offset: 3px;
  border-radius: 2px;
}

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

.skip-link {
  position: absolute;
  left: 1rem; top: -100px;
  z-index: 999;
  background: var(--moss);
  color: var(--white);
  padding: .75rem 1.25rem;
  font-size: .9rem;
  letter-spacing: .06em;
  text-transform: uppercase;
  text-decoration: none;
  transition: top .2s var(--ease);
}
.skip-link:focus { top: 1rem; color: var(--white); }

/* ----------------------------------------------------------- primitives */
.wrap {
  width: 100%;
  max-width: var(--wrap);
  margin-inline: auto;
  padding-inline: var(--gutter);
}
.wrap--text { max-width: calc(var(--wrap-text) + var(--gutter) * 2); }

.section { padding-block: clamp(3.5rem, 9vw, 7rem); }
.section--tight { padding-block: clamp(2.5rem, 6vw, 4.5rem); }
.section--sand { background: var(--bone-deep); }

/* Closing hairline for a section that shares the page background with the
   one after it. Drawn inside the section's last .wrap so it is inset to the
   content width rather than running edge to edge — a full-bleed rule reads
   as a divider between two pages, an inset one as the end of a block.
   Used on the welcome + awards block, which would otherwise run straight
   into the villa features with nothing but padding between them. */
/* The rule is what closes the block, so the section contributes no padding
   below it — otherwise the gap was the section's own bottom padding (up to
   7rem) plus the next section's top padding (up to 4.5rem), ~184px of dead
   space under a 1px line. With this at zero the rule sits between its own
   margin-top above and the following section's top padding below, which are
   near-identical clamps: about 64px / 72px at 1280px and 40px / 40px at
   390px. The line reads as centred between the two blocks. */
.section--ruled { padding-bottom: 0; }

/* Same hairline as .section--ruled, but as a real element dividing two blocks
   *inside* one section rather than closing it — so it takes air on both
   sides, not just above. Used between the villa/dining/values features and
   the reviews widget, which share the page background. Named block-rule to
   keep it distinct from .rule, the short clay mark under headings. */
.block-rule {
  height: 1px;
  border: 0;
  background: var(--sand);
  margin-block: clamp(2.5rem, 6vw, 4rem);
}

.section--ruled > .wrap:last-child::after {
  content: "";
  display: block;
  height: 1px;
  background: var(--sand);
  margin-top: clamp(2.5rem, 6vw, 4rem);
}
.section--moss { background: var(--moss); color: var(--bone); }
.section--moss h1, .section--moss h2, .section--moss h3 { color: var(--bone); }

.eyebrow {
  font-family: var(--font-body);
  font-size: .75rem;
  font-weight: 400;
  letter-spacing: .22em;
  text-transform: uppercase;
  color: var(--clay);
  margin: 0 0 1rem;
}
.section--moss .eyebrow { color: var(--sand); }

.lede { font-size: clamp(1.1rem, 1.9vw, 1.3rem); line-height: 1.7; color: var(--ink-soft); }

.center { text-align: center; }
.measure { max-width: 62ch; }
.center .measure { margin-inline: auto; }

.rule {
  width: 56px; height: 1px;
  background: var(--clay);
  border: 0;
  margin: 1.75rem 0;
}
.center .rule { margin-inline: auto; }

/* ---------------------------------------------------------------- button */
.btn {
  --btn-bg: var(--moss);
  --btn-fg: var(--bone);
  --btn-bd: var(--moss);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: .5em;
  background: var(--btn-bg);
  color: var(--btn-fg);
  border: 1px solid var(--btn-bd);
  padding: .95em 2.1em;
  font-family: var(--font-body);
  font-size: .8rem;
  font-weight: 400;
  letter-spacing: .16em;
  text-transform: uppercase;
  text-decoration: none;
  cursor: pointer;
  transition: background .25s var(--ease), color .25s var(--ease),
              border-color .25s var(--ease), transform .25s var(--ease);
}
.btn:hover {
  background: var(--moss-deep);
  border-color: var(--moss-deep);
  color: var(--bone);
  transform: translateY(-1px);
}
.btn--ghost { --btn-bg: transparent; --btn-fg: var(--ink); --btn-bd: var(--ink); }
.btn--ghost:hover { background: var(--ink); color: var(--bone); border-color: var(--ink); }
.btn--light { --btn-bg: transparent; --btn-fg: var(--bone); --btn-bd: rgba(250,248,244,.7); }
.btn--light:hover { background: var(--bone); color: var(--moss-deep); border-color: var(--bone); }
.btn--clay { --btn-bg: var(--clay); --btn-fg: var(--white); --btn-bd: var(--clay); }
.btn--clay:hover { background: var(--clay-deep); border-color: var(--clay-deep); color: var(--white); }
.btn--sm { padding: .7em 1.5em; font-size: .72rem; }

.link-arrow {
  display: inline-flex;
  align-items: center;
  gap: .5em;
  font-size: .8rem;
  letter-spacing: .16em;
  text-transform: uppercase;
  text-decoration: none;
  color: var(--clay-deep);
  border-bottom: 1px solid currentColor;
  padding-bottom: .3em;
  transition: gap .25s var(--ease);
}
.link-arrow::after { content: "\2192"; transition: transform .25s var(--ease); }
.link-arrow:hover { gap: .8em; color: var(--clay-deep); }

.btn-row {
  display: flex;
  flex-wrap: wrap;
  gap: .85rem;
  margin-top: 1.75rem;
}
.center .btn-row { justify-content: center; }

/* ---------------------------------------------------------------- header */
.site-header {
  position: fixed;
  inset: 0 0 auto 0;
  z-index: 100;
  height: var(--header-h);
  display: flex;
  align-items: center;
  transition: background .35s var(--ease), box-shadow .35s var(--ease), height .35s var(--ease);
}
.site-header__inner {
  width: 100%;
  max-width: var(--wrap);
  margin-inline: auto;
  padding-inline: var(--gutter);
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1.5rem;
}

.brand { display: block; flex: 0 0 auto; line-height: 0; }
.brand img { width: 148px; transition: filter .35s var(--ease); }

/* Transparent-over-hero state: invert the black logo to white. */
.site-header--transparent { background: transparent; }
.site-header--transparent .brand img { filter: brightness(0) invert(1); }
.site-header--transparent .nav__link,
.site-header--transparent .nav__toggle-label,
.site-header--transparent .burger { color: var(--bone); }
.site-header--transparent .burger span { background: var(--bone); }
/* Keeps the white bars readable where a hero photo is pale (sky, pool water). */
.site-header--transparent .burger { filter: drop-shadow(0 1px 2px rgba(0,0,0,.45)); }
.site-header--transparent .btn--header {
  color: var(--bone);
  border-color: rgba(250,248,244,.75);
}
.site-header--transparent .btn--header:hover {
  background: var(--bone); color: var(--moss-deep); border-color: var(--bone);
}

.site-header--solid {
  background: rgba(250,248,244,.96);
  box-shadow: 0 1px 0 rgba(34,38,31,.08);
}

/* backdrop-filter must stay desktop-only. An element with backdrop-filter
   becomes the containing block for its position:fixed descendants, and the
   mobile drawer (.nav) is a child of this header — so applying it below
   1001px collapses the open drawer from full-screen to the header's own
   height. Above 1001px there is no drawer, so the effect is safe there. */
@media (min-width: 1001px) {
  .site-header--solid { backdrop-filter: saturate(140%) blur(10px); }
}

/* ------------------------------------------------------------ navigation */
.nav { display: flex; align-items: center; gap: clamp(1rem, 2.2vw, 2rem); }

.nav__list {
  display: flex;
  align-items: center;
  gap: clamp(1rem, 2.2vw, 2rem);
  list-style: none;
  margin: 0; padding: 0;
}
.nav__item { position: relative; margin: 0; }

.nav__link,
.nav__toggle-label {
  display: inline-flex;
  align-items: center;
  gap: .4em;
  background: none;
  border: 0;
  padding: .5rem 0;
  font-family: var(--font-body);
  font-size: .78rem;
  font-weight: 400;
  letter-spacing: .14em;
  text-transform: uppercase;
  color: var(--ink);
  text-decoration: none;
  cursor: pointer;
  white-space: nowrap;
  transition: color .2s var(--ease);
}
.nav__link:hover, .nav__toggle-label:hover { color: var(--clay); }

.nav__link[aria-current="page"] { color: var(--clay); }

.nav__caret {
  width: 8px; height: 8px;
  border-right: 1px solid currentColor;
  border-bottom: 1px solid currentColor;
  transform: rotate(45deg) translate(-2px, -2px);
  transition: transform .25s var(--ease);
}
.nav__item--open > .nav__toggle-label .nav__caret { transform: rotate(-135deg) translate(-3px, -3px); }

.nav__panel {
  position: absolute;
  top: calc(100% + .55rem);
  left: 50%;
  transform: translate(-50%, -6px);
  min-width: 250px;
  background: var(--white);
  border: 1px solid var(--sand);
  box-shadow: var(--shadow-md);
  padding: .6rem 0;
  list-style: none;
  margin: 0;
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
  transition: opacity .22s var(--ease), transform .22s var(--ease), visibility .22s;
}
.nav__item--open > .nav__panel {
  opacity: 1;
  visibility: visible;
  pointer-events: auto;
  transform: translate(-50%, 0);
}

/* Hover bridge. The panel sits ~9px below the button, and travelling across
   that transparent strip used to count as leaving the menu, so the panel
   closed under the cursor. This pseudo-element fills the gap and belongs to
   the panel, so the pointer never leaves the menu item. */
@media (min-width: 1001px) {
  .nav__panel::before {
    content: "";
    position: absolute;
    left: 0;
    right: 0;
    bottom: 100%;
    height: .8rem;
  }
}
.nav__panel li { margin: 0; }

/* The Cabins panel ends with "All cabins" and "Gallery" below a hairline: they
   are the overview links, not two more room types. The rule is drawn as a
   border on the first item of that group rather than as a separate <li> or an
   <hr>, so there is nothing extra for a screen reader to announce and nothing
   for keyboard focus to land on. Selector carries `li` so it outranks the
   margin reset above without !important.

   --sand is the site's hairline in both themes (#e3d9c9 light, #3a3733 dark),
   so this needs no .theme-dark counterpart. Works in the mobile drawer too,
   where the panel loses its own border and background but the items do not. */
.nav__panel li.nav__panel-rule {
  border-top: 1px solid var(--sand);
  margin-top: .5rem;
  padding-top: .5rem;
}
.nav__panel a {
  display: block;
  padding: .6rem 1.4rem;
  font-size: .8rem;
  letter-spacing: .06em;
  color: var(--ink);
  text-decoration: none;
  transition: background .18s var(--ease), color .18s var(--ease);
}
.nav__panel a:hover { background: var(--bone-deep); color: var(--clay-deep); }
.nav__panel a[aria-current="page"] { color: var(--clay); }

.btn--header { padding: .75em 1.6em; font-size: .72rem; }

/* burger */
.burger {
  display: none;
  flex-direction: column;
  justify-content: center;
  gap: 5px;
  width: 44px; height: 44px;
  margin-right: -10px;
  background: none;
  border: 0;
  cursor: pointer;
  color: var(--ink);
}
.burger span {
  display: block;
  width: 24px; height: 1px;
  background: currentColor;
  transition: transform .3s var(--ease), opacity .2s var(--ease);
}
.burger[aria-expanded="true"] span:nth-child(1) { transform: translateY(6px) rotate(45deg); }
.burger[aria-expanded="true"] span:nth-child(2) { opacity: 0; }
.burger[aria-expanded="true"] span:nth-child(3) { transform: translateY(-6px) rotate(-45deg); }

/* mobile drawer */
@media (max-width: 1000px) {
  :root { --header-h: 70px; }
  .burger { display: flex; }
  .brand img { width: 124px; }

  .nav {
    position: fixed;
    inset: var(--header-h) 0 0 0;
    display: block;
    background: var(--bone);
    padding: 1.5rem var(--gutter) 3rem;
    overflow-y: auto;
    opacity: 0;
    visibility: hidden;
    transform: translateY(-8px);
    transition: opacity .28s var(--ease), transform .28s var(--ease), visibility .28s;
  }
  .nav[data-open="true"] { opacity: 1; visibility: visible; transform: none; }

  .nav__list { display: block; }
  .nav__item { border-bottom: 1px solid var(--sand); }

  .nav__link, .nav__toggle-label {
    width: 100%;
    justify-content: space-between;
    padding: 1.05rem 0;
    font-size: .88rem;
    color: var(--ink) !important;
  }

  .nav__panel {
    position: static;
    transform: none;
    min-width: 0;
    border: 0;
    box-shadow: none;
    background: transparent;
    padding: 0 0 .75rem .9rem;
    display: none;
    opacity: 1;
    visibility: visible;
    pointer-events: auto;
  }
  .nav__item--open > .nav__panel { display: block; transform: none; }
  .nav__panel a { padding: .6rem 0; font-size: .85rem; }
  .nav__panel a:hover { background: none; }

  .btn--header { display: flex; width: 100%; margin-top: 1.75rem; }

  /* The burger deliberately has no colour override here. The JS only applies
     .site-header--transparent while the drawer is CLOSED (see syncHeader in
     main.js), so the shared rule above already gives us white bars over the
     hero photo and dark bars once the header turns solid or the drawer opens. */
}

/* ------------------------------------------------------------------ hero */
.hero {
  position: relative;
  display: flex;
  align-items: flex-end;
  min-height: min(86vh, 780px);
  padding: calc(var(--header-h) + 3rem) 0 clamp(3rem, 8vw, 6rem);
  color: var(--bone);
  overflow: hidden;
  background: var(--moss-deep);
}
.hero--short { min-height: min(62vh, 560px); }
.hero--tall  { min-height: min(94vh, 900px); }

.hero__media {
  position: absolute;
  inset: 0;
  z-index: 0;
}
.hero__media img {
  width: 100%; height: 100%;
  object-fit: cover;
  object-position: center;
}
/* Hero video. Sits between the poster photo and the gradient overlay: all
   three are z-index auto inside .hero__media, so paint order is DOM order and
   ::after counts as the last child, which keeps the gradient on top. The
   element ships with no src — see the comment in index.html — and main.js
   adds .is-playing only once frames are actually rendering, so a stalled or
   blocked load simply leaves the photo showing. */
.hero__video {
  position: absolute;
  inset: 0;
  width: 100%; height: 100%;
  object-fit: cover;
  object-position: center;
  opacity: 0;
  transition: opacity .9s ease;
  pointer-events: none;
}
.hero__video.is-playing { opacity: 1; }
@media (prefers-reduced-motion: reduce) {
  .hero__video { display: none; }
}

.hero__media::after {
  content: "";
  position: absolute;
  inset: 0;
  /* Scrim ramp. The waist sits at 34% because that is where the headline lands.
     See "The hero scrim" in PAGE-STRUCTURE.md: this was retuned from
     .42 / .06 / .62 after measuring the WORST pixel under the headline rather
     than the average. Re-measure the worst pixel if a hero image is swapped. */
  background:
    linear-gradient(to bottom, rgba(20,24,18,.60) 0%, rgba(20,24,18,.45) 34%, rgba(20,24,18,.72) 100%);
}
.hero__inner { position: relative; z-index: 1; width: 100%; }
.hero h1, .hero h2, .hero p { color: var(--bone); }
.hero h1 { text-shadow: 0 1px 30px rgba(0,0,0,.35); margin-bottom: .35em; }
.hero__lede { max-width: 46ch; font-size: clamp(1rem, 1.6vw, 1.2rem); text-shadow: 0 1px 20px rgba(0,0,0,.4); }
.hero .eyebrow { color: var(--sand); }

/* Vertical placement only. The hero's own padding stays asymmetric — header
   height plus 3rem at the top, the shared clamp at the bottom — so the block
   lands a little below true centre, which offsets the transparent header
   lying over the top of the image. Roughly 52% down on desktop, 55% on
   phones; the gap is the clamp resolving to different values, and both read
   as centred. */
.hero--middle { align-items: center; }

/* Horizontal centring only. These two modifiers are deliberately orthogonal:
   --middle places the block, --center centres the text inside it. This rule
   used to also set align-items: center, a second and conflicting way to say
   what --middle says. */
.hero--center { text-align: center; }
.hero--center .hero__lede { margin-inline: auto; }
.hero--center .btn-row { justify-content: center; }

/* page header for interior pages without a photo hero */
.page-head { padding: calc(var(--header-h) + clamp(3rem, 7vw, 5.5rem)) 0 clamp(2rem, 5vw, 3.5rem); }

/* ----------------------------------------------------------- booking bar */
/* The Cloudbeds <cb-property-date-picker> on the homepage. No band any more:
   the card straddles the bottom edge of the hero, half on the photo and half
   on the page, so the search sits as high up the page as it can without
   covering the hero copy.

   The pull is half the card's height, and those heights are not guesses —
   they are the same 52px / 58px this stylesheet already reserves on
   cb-property-date-picker:not(:defined) to stop the layout jumping when the
   Cloudbeds script upgrades the element. If the card's height ever changes,
   both places move together.

   z-index lifts the card over the hero image; it stays well under
   .site-header, and under the calendar popover, which the widget renders in
   a portal on <body> at z-index 1500. */
.booking-bar {
  position: relative;
  z-index: 5;
  background: transparent;
  padding-block: 0;
  margin-top: -26px;
}
@media (max-width: 699px) {
  .booking-bar { margin-top: -29px; }
}

.booking-bar__fallback { margin: 0; text-align: center; }

/* ------------------------------------------------- date picker, both homes */
/* The same <cb-property-date-picker> appears in two places: the homepage band
   above, and the drawer that "Book a room" opens (.booking-panel, built by
   main.js). Every rule below is grouped across both — scoping them to
   .booking-bar alone is what once left the drawer's Search button stretched
   into an ellipse on phones.

   Two mechanisms are in play, and the order they appear in matters:

   1. TOKENS. The engine ships a ~560-token design system as CSS custom
      properties (`--booking-engine-*`) which it resolves at use time, so
      redefining one on an ancestor retunes the widget from outside with no
      !important and nothing to break. Preferred whenever a token exists.
   2. DIRECT RULES, needing !important, because the engine's own styles carry
      id-level specificity —
      `:is(#cb-bookingengine, .cb-bookingengine-root, reach-portal) .d-xxxxxx`.
      Keyed to `.cb-search-card` and `data-testid` values, which are stable
      across releases; never to the generated `d-*` hashes beside them.

   Only tokens the widget uses unambiguously are overridden. Scale tokens like
   `radii-full` and `neutral-900` are left alone: they are shared across the
   whole engine, and repainting them would leak into places we cannot see.

   Nothing here touches reservation.html's full engine. Delete the section and
   the picker reverts to Cloudbeds' own look rather than breaking. */
/* Set at :root, not on the two containers, for two reasons: the calendar
   popover is rendered in a portal attached to <body>, outside both, and the
   full engine on reservation.html needs the same treatment. The names are
   namespaced, so this leaks nowhere.

   colors-primary-500 differs by surface out of the box — the engine reads the
   property's configured brand colour from Cloudbeds (#4C4848) while the
   standalone picker falls back to a mint green. That mismatch is why the bar
   and the engine never matched. Pinning it here settles both. */
:root {
  --booking-engine-fonts-body: var(--font-body);
  --booking-engine-fonts-heading: var(--font-body);
  --booking-engine-colors-primary-500: var(--moss);
  --booking-engine-colors-primary-600: var(--moss-deep);
}

/* Flatten the widget's floating white pill into one squared surface, the way
   every other card and button on the site is drawn: 1px sand rule, no radius,
   no drop shadow. */
.booking-bar .cb-search-card,
.booking-panel .cb-search-card {
  background: var(--white) !important;
  border: 1px solid var(--sand) !important;
  border-radius: 0 !important;
  box-shadow: none !important;
}
.booking-bar .cb-search-card > div,
.booking-panel .cb-search-card > div,
.booking-bar [data-testid="property-date-picker-date-picker"],
.booking-panel [data-testid="property-date-picker-date-picker"] {
  background: transparent !important;
}
/* The Search button. `--booking-engine-colors-primary-500` does NOT paint it.
   Established by testing against the deployed site on 26 Aug 2026: pinning that
   token at :root, even with !important, leaves the button on Cloudbeds' own
   mint #32C0A0. The note further up claiming the token settles both surfaces
   was true of an older engine release and is not true now — the token still
   usefully tunes other surfaces, so it stays, but the button needs a direct
   rule.

   Keyed to the data-testid, which is stable across releases, never to the
   `d-1gls782` hash beside it. The descendant selector is required because the
   label's colour is set on a child element, not on the button: without it the
   text stays #1C1B1A and lands at 1.3:1 on the green. With it, 8.3:1.

   Theme-neutral on purpose — the button should be brand green on the light
   pages and the dark ones alike, and #f5efe6 reads on both values of --moss. */
.booking-bar [data-testid="property-date-picker-search-button"],
.booking-panel [data-testid="property-date-picker-search-button"] {
  background: var(--moss) !important;
}
.booking-bar [data-testid="property-date-picker-search-button"],
.booking-bar [data-testid="property-date-picker-search-button"] *,
.booking-panel [data-testid="property-date-picker-search-button"],
.booking-panel [data-testid="property-date-picker-search-button"] * {
  color: #f5efe6 !important;
}

.booking-bar [data-testid$="-checkin-input"],
.booking-bar [data-testid$="-checkout-input"],
.booking-panel [data-testid$="-checkin-input"],
.booking-panel [data-testid$="-checkout-input"] {
  background: transparent !important;
  border-radius: 0 !important;
  color: var(--ink) !important;
}

/* Search reads as a .btn: moss, squared, uppercase and letterspaced. */
.booking-bar [data-testid="property-date-picker-search-button"],
.booking-panel [data-testid="property-date-picker-search-button"] {
  border-radius: 0 !important;
  color: #f5efe6 !important;   /* literal, not var(--bone): the dark theme
                                  redefines --bone to #1c1b1a, which would put
                                  the label at 1.5:1 on --moss. Matches the
                                  descendant rule above, which was already
                                  literal. */
  font-size: .72rem !important;
  letter-spacing: .16em;
  text-transform: uppercase;
}

.booking-bar cb-property-date-picker,
.booking-panel cb-property-date-picker { display: block; width: 100%; }

/* Reserve height while the custom element is still unupgraded, so nothing
   jumps when the Cloudbeds script defines it. */
.booking-bar cb-property-date-picker:not(:defined),
.booking-panel cb-property-date-picker:not(:defined) {
  display: block;
  min-height: 52px;
}
@media (max-width: 699px) {
  .booking-bar cb-property-date-picker:not(:defined),
  .booking-panel cb-property-date-picker:not(:defined) { min-height: 58px; }
}

/* Every width. The widget wraps its control row in 1.5em of vertical
   padding, which is what made the bar look tall on phones (~93px for a 43px
   row). */
.booking-bar .cb-search-card,
.booking-panel .cb-search-card { padding-block: .375rem !important; }
.booking-bar .cb-search-card > div,
.booking-panel .cb-search-card > div { padding-block: 0 !important; }

/* Desktop only: cap the width and drop the type to 15px so the pill matches
   the old site at ~544x52. Left off on phones, where the row needs the full
   width and 17px controls stay comfortably tappable.

   Button size is in rem, not em, on purpose: it has its own smaller
   letterspaced font-size above, so em would shrink it to about 28px tall. */
@media (min-width: 700px) {
  .booking-bar .cb-search-card,
  .booking-panel .cb-search-card {
    max-width: 34rem;
    margin-inline: auto;
    font-size: .9375rem;
    padding-inline: 1.5em !important;
  }
  .booking-bar [data-testid="property-date-picker-search-button"],
  .booking-panel [data-testid="property-date-picker-search-button"] {
    height: 2.25rem !important;
    min-height: 0 !important;
    padding: 0 1.25rem !important;
  }
}

/* Phones only: the widget switches the date fields to its `md` size token
   (0.875em, about 15px), which in Jost at the site's light body weight reads
   noticeably smaller than it did in Poppins. Pin them to body size and normal
   weight. 17px also clears iOS's 16px zoom-on-focus threshold. Costs no
   height: the 44px Search circle already sets the row. */
@media (max-width: 699px) {
  .booking-bar [data-testid$="-checkin-input"],
  .booking-bar [data-testid$="-checkout-input"],
  .booking-panel [data-testid$="-checkin-input"],
  .booking-panel [data-testid$="-checkout-input"] {
    font-size: 1.0625rem !important;
    font-weight: 400 !important;
  }
}

/* Phones only: the widget drops the "Search" label for an icon button, which
   keeps its intrinsic width while the trimmed padding takes away its height —
   so it renders as an ellipse unless pinned square. 2.75rem is 44px, Apple's
   minimum comfortable tap target. The 50% radius has to out-rank the squared
   corners set above, which is why this block comes last. */
@media (max-width: 699px) {
  .booking-bar [data-testid="property-date-picker-search-button"],
  .booking-panel [data-testid="property-date-picker-search-button"] {
    flex: 0 0 auto !important;
    width: 2.75rem !important;
    height: 2.75rem !important;
    min-width: 0 !important;
    min-height: 0 !important;
    padding: 0 !important;
    border-radius: 50% !important;
  }
}

/* Full booking engine on reservation.html. Same rule as the bar above — style
   the container, never the widget's internals.

   The engine is the whole page: full-bleed edge to edge, sitting directly
   under the fixed header with the footer immediately after it, matching the
   old Squarespace reservation page. No .wrap — the engine centres and
   constrains its own content.

   padding-top clears the fixed header; the min-height then makes the section
   fill exactly the rest of the first screen, so the footer starts at the fold
   even before the engine has loaded. In practice the engine's own content is
   far taller and drives the real page height. */
.booking-engine-page {
  padding-top: var(--header-h);
  min-height: calc(100vh - var(--header-h));
}
.booking-engine { min-height: calc(100vh - var(--header-h)); }
.booking-engine cb-immersive-experience { display: block; width: 100%; }
.booking-engine__fallback {
  max-width: 60ch;
  margin-inline: auto;
  padding: clamp(2rem, 6vw, 4rem) var(--gutter);
  text-align: center;
}

/* The engine renders its own dark bar with the Cloudbeds property logo and
   name at the left, directly under our site header — two Desa Hay logos
   stacked. The old Squarespace site suppressed both, so match it.

   The 84x84 logo CONTAINER is deliberately left in place: the bar is a CSS
   grid and removing the column shifts the date pill off centre. Hiding only
   the image inside it is exactly what the old site does. */
.booking-engine [data-testid="property-logo"] img,
.booking-engine [data-testid="property-name"] { display: none !important; }

/* The engine's own toolbar is hardcoded to the property's brand grey, with no
   token behind it — keyed here to `.cb-header`, its stable class, never to the
   generated hash beside it. Its contents are already white, so moss is safe.

   The proper fix lives in Cloudbeds, not here: setting the property's brand
   colour to #34412F would drive this bar, the accent tokens and the derived
   tints (the "View offers" strip is one, still grey because it is a hashed
   class with no token) all at once, and this rule could then be deleted. */
.booking-engine .cb-header { background: var(--moss) !important; }

/* --------------------------------------------------------- booking panel */
/* Drawer opened by "Book a room" / "Check availability", holding the same
   Cloudbeds date picker as the homepage bar. Built and injected by the
   `booking panel` section of main.js; without JS the links navigate to
   reservation.html as normal, so nothing here is load-bearing.

   Anchored to the top rather than centred, deliberately. The widget's
   calendar is a 466px fixed-position popover that opens below the input; a
   centred dialog pushes it past the fold, where a locked body cannot scroll
   to it. Dropping the sheet from the top leaves room for the calendar.

   z-index sits BELOW .site-header (100) so the header stays visible and the
   drawer reads as sliding out from under it. The calendar portal renders at
   z-index 1500, above both. */
.booking-panel { position: fixed; inset: 0; z-index: 90; display: none; }
.booking-panel[data-open="true"] { display: block; }

.booking-panel__backdrop {
  position: absolute;
  inset: 0;
  background: rgba(20, 24, 18, .55);
}

.booking-panel__sheet {
  position: absolute;
  left: 0; right: 0; top: 0;
  background: var(--bone-deep);
  padding: calc(var(--header-h) + 1.1rem) var(--gutter) 1.4rem;
  box-shadow: var(--shadow-md);
  text-align: center;
}

.booking-panel__inner { max-width: 40rem; margin-inline: auto; }

.booking-panel__close {
  position: absolute;
  top: calc(var(--header-h) + .1rem);
  right: var(--gutter);
  padding: .5rem;
  background: none;
  border: 0;
  color: var(--ink);
  font-size: 1.05rem;
  line-height: 1;
  cursor: pointer;
}

.booking-panel .eyebrow { margin-bottom: .7rem; }

/* Reserve the picker's height so the drawer does not jump when the Cloudbeds
   bundle finishes loading on first open. Matches the compact pill it becomes,
   not the widget's untrimmed height. */
.booking-panel__picker { min-height: 52px; }
@media (max-width: 699px) {
  .booking-panel__picker { min-height: 58px; }
}

.booking-panel__note {
  margin: .9rem 0 0;
  font-size: .8rem;
  color: var(--ink-soft);
}

/* ----------------------------------------------------------- alternating */
.feature {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  align-items: center;
  gap: clamp(2rem, 5vw, 5rem);
}
.feature + .feature { margin-top: clamp(3.5rem, 8vw, 6.5rem); }
.feature--flip .feature__media { order: 2; }

.feature__media { position: relative; }
.feature__media img {
  width: 100%;
  aspect-ratio: 4 / 3;
  object-fit: cover;
}
.feature__body { max-width: 52ch; }

@media (max-width: 860px) {
  .feature { grid-template-columns: 1fr; gap: 1.75rem; }
  .feature--flip .feature__media { order: 0; }
}

/* ---------------------------------------------------------------- cards */
.cards {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 290px), 1fr));
  gap: clamp(1.5rem, 3vw, 2.5rem);
}
.card {
  display: flex;
  flex-direction: column;
  background: var(--white);
  border: 1px solid var(--sand);
  text-decoration: none;
  color: inherit;
  transition: transform .3s var(--ease), box-shadow .3s var(--ease);
}
a.card:hover { transform: translateY(-4px); box-shadow: var(--shadow-md); color: inherit; }
.card__media { overflow: hidden; }
.card__media img {
  width: 100%;
  aspect-ratio: 3 / 2;
  object-fit: cover;
  transition: transform .6s var(--ease);
}
a.card:hover .card__media img { transform: scale(1.045); }
.card__body { padding: 1.6rem 1.6rem 1.9rem; flex: 1 1 auto; display: flex; flex-direction: column; }
.card__body h3 { margin-bottom: .5rem; }
.card__body p { font-size: .97rem; color: var(--ink-soft); }
.card__body .link-arrow { margin-top: auto; align-self: flex-start; }

/* The standout line on the cabin comparison cards. The label is a small caps
   block above the value rather than an inline "Standout feature:" run-in: it
   matches the label/value pattern the room pages already use in .spec-strip,
   and it keeps the value on its own line so the seven cards can be scanned
   down the column. Both colours are tokens, so this works in either theme:
   --clay-deep on the card surface is 6.96:1 dark and 5.66:1 light. Note it is
   clay-DEEP, not clay: plain --clay is only 4.18:1 on a white card, which fails
   AA at this size. (.eyebrow uses plain --clay at .75rem site-wide and has the
   same problem on light pages - worth a separate look.) */
.card__standout {
  margin-top: .9rem;
  color: var(--ink);
  font-size: .97rem;
}
.card__standout span {
  display: block;
  font-family: var(--font-body);
  font-size: .68rem;
  letter-spacing: .18em;
  text-transform: uppercase;
  color: var(--clay-deep);
  margin-bottom: .25rem;
}

/* -------------------------------------------------------------- amenities */
.amenities {
  columns: 2;
  column-gap: clamp(1.5rem, 4vw, 3rem);
  list-style: none;
  padding: 0;
  margin: 1.5rem 0 0;
}
.amenities li {
  break-inside: avoid;
  position: relative;
  padding-left: 1.5rem;
  margin-bottom: .7rem;
  font-size: .97rem;
  color: var(--ink-soft);
}
.amenities li::before {
  content: "";
  position: absolute;
  left: 0; top: .72em;
  width: 7px; height: 7px;
  border-radius: 50%;
  background: var(--clay);
}
@media (max-width: 700px) { .amenities { columns: 1; } }

.spec-strip {
  display: flex;
  flex-wrap: wrap;
  gap: 0;
  border-top: 1px solid var(--sand);
  border-bottom: 1px solid var(--sand);
  margin: 2rem 0;
}
.spec-strip div {
  flex: 1 1 140px;
  padding: 1.1rem 1.25rem;
  border-left: 1px solid var(--sand);
}
.spec-strip div:first-child { border-left: 0; padding-left: 0; }
.spec-strip dt {
  font-size: .68rem;
  letter-spacing: .18em;
  text-transform: uppercase;
  color: var(--clay);
  margin-bottom: .3rem;
}
.spec-strip dd { margin: 0; font-family: var(--font-display); font-size: 1.35rem; }

/* --------------------------------------------------------------- gallery */
.gallery {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(min(100%, 300px), 1fr));
  gap: 10px;
}
.gallery--wide { grid-template-columns: repeat(auto-fill, minmax(min(100%, 340px), 1fr)); }

.gallery__item {
  position: relative;
  display: block;
  margin: 0;
  padding: 0;
  border: 0;
  background: var(--bone-deep);
  cursor: zoom-in;
  overflow: hidden;
  aspect-ratio: 3 / 2;
}
.gallery__item img {
  width: 100%; height: 100%;
  object-fit: cover;
  transition: transform .6s var(--ease), opacity .3s var(--ease);
}
.gallery__item:hover img { transform: scale(1.05); }
.gallery__item::after {
  content: "";
  position: absolute;
  inset: 0;
  background: rgba(34,38,31,0);
  transition: background .3s var(--ease);
}
.gallery__item:hover::after { background: rgba(34,38,31,.12); }
.gallery__item--tall { aspect-ratio: 3 / 4; }

/* lightbox */
.lightbox {
  position: fixed;
  inset: 0;
  z-index: 200;
  display: none;
  align-items: center;
  justify-content: center;
  background: rgba(18,21,16,.94);
  padding: clamp(1rem, 4vw, 3rem);
}
.lightbox[data-open="true"] { display: flex; }
.lightbox__figure { margin: 0; max-width: 100%; max-height: 100%; text-align: center; }
.lightbox__figure img {
  max-width: min(1400px, 92vw);
  max-height: 80vh;
  width: auto;
  margin-inline: auto;
  object-fit: contain;
}
.lightbox__caption {
  color: var(--sand);
  font-size: .85rem;
  letter-spacing: .06em;
  margin-top: 1rem;
}
.lightbox__btn {
  position: absolute;
  background: none;
  border: 1px solid rgba(250,248,244,.35);
  color: var(--bone);
  width: 50px; height: 50px;
  border-radius: 50%;
  font-size: 1.3rem;
  line-height: 1;
  cursor: pointer;
  display: grid;
  place-items: center;
  transition: background .2s var(--ease), border-color .2s var(--ease);
}
.lightbox__btn:hover { background: rgba(250,248,244,.14); border-color: var(--bone); }
.lightbox__close { top: clamp(1rem, 3vw, 2rem); right: clamp(1rem, 3vw, 2rem); }
.lightbox__prev  { left: clamp(.5rem, 2vw, 2rem); top: 50%; transform: translateY(-50%); }
.lightbox__next  { right: clamp(.5rem, 2vw, 2rem); top: 50%; transform: translateY(-50%); }
.lightbox__count {
  position: absolute;
  bottom: clamp(1rem, 3vw, 2rem);
  left: 50%;
  transform: translateX(-50%);
  color: var(--sand);
  font-size: .78rem;
  letter-spacing: .18em;
}

/* --------------------------------------------------------------- awards */
.awards {
  display: grid;
  /* Two-up below the 1000px breakpoint, four across above it. The old
     auto-fit/210px track packed four badges into a single tall column on
     phones, which mattered little when this section sat near the footer but
     pushes the villa and dining features far down now that it runs directly
     under the welcome copy. */
  grid-template-columns: repeat(2, 1fr);
  gap: clamp(1.5rem, 3vw, 2.5rem);
  list-style: none;
  padding: 0;
  margin: 0;
}
@media (min-width: 1000px) {
  .awards { grid-template-columns: repeat(4, 1fr); }
}
.award { text-align: center; margin: 0; }
.award__badge {
  height: 88px;
  display: grid;
  place-items: center;
  margin-bottom: 1.1rem;
}
.award__badge img { max-height: 88px; width: auto; object-fit: contain; }

/* The three award logos are one-colour vectors (moss, #34412f) with very
   different proportions, so a shared max-height leaves them optically
   mismatched. Heights below are tuned so the circular Tripadvisor seal, the
   boxed SLH lockup and the Travel + Leisure wordmark read at the same weight
   across the row. */
.award__badge--seal img     { height: 84px; }
.award__badge--mark img     { height: 64px; }
.award__badge--wordmark img { height: 42px; }
.award h3 {
  font-size: 1.15rem;
  line-height: 1.35;
  margin-bottom: .5rem;
}
.award__year {
  display: block;
  font-family: var(--font-body);
  font-size: .72rem;
  letter-spacing: .2em;
  text-transform: uppercase;
  color: var(--clay);
  margin-bottom: .4rem;
}

/* Homepage variant: no heading, no rule, and the section runs high in the
   page, so everything comes in tighter than the headed version on
   media.html — which keeps the defaults above. */
.awards--strip {
  /* Sits inside the welcome section, under its copy, on the page background —
     no band, no heading. The margin is the only separation, and it is
     deliberately smaller than a section gap so the strip reads as part of
     the block above rather than as its own thing. */
  margin-top: clamp(2.25rem, 5vw, 3.25rem);
  gap: clamp(1.5rem, 3vw, 2rem) 1.25rem;
}
.awards--strip .award__badge { height: 76px; margin-bottom: .7rem; }
.awards--strip .award__badge--seal img     { height: 72px; }
.awards--strip .award__badge--mark img     { height: 56px; }
.awards--strip .award__badge--wordmark img { height: 38px; }
.awards--strip .award__year { font-size: .68rem; margin-bottom: .25rem; }
.awards--strip .award h3 {
  font-size: 1rem;
  line-height: 1.3;
  margin-bottom: .4rem;
}

/* ------------------------------------------------ memberships band */
/* Partner logos on moss. Flex rather than the awards grid: these are four
   marks of wildly different proportions (a boxed lockup, a long thin
   wordmark, a circular badge) with no captions under them, so they want to
   sit on a shared centre line and wrap freely, not sit in equal columns.
   Heights are set per mark for optical balance, the same reasoning as the
   award badges. */
.members__list {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: clamp(2rem, 5vw, 4rem);
  list-style: none;
  padding: 0;
  margin: clamp(1.75rem, 4vw, 2.5rem) 0 0;
}
.members__item { margin: 0; display: flex; align-items: center; }
.members__item--mark img     { height: 72px; width: auto; }
.members__item--wordmark img { width: min(200px, 52vw); height: auto; }
/* Taller than the others on purpose: a portrait oval needs more height than a
   landscape lockup to carry the same optical weight in the row. */
.members__item--seal img     { height: 88px; width: auto; }


/* ------------------------------------------------ certification block */
/* Badge beside its explanation. Flex rather than the .feature grid: the badge
   is a small fixed mark, not a photograph that should hold half the row. It
   stacks on narrow screens once the body can no longer keep its 22rem. */
.cert {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  gap: clamp(1.75rem, 5vw, 3.5rem);
}
.cert__badge { flex: 0 0 auto; }
.cert__badge img { width: clamp(108px, 13vw, 150px); height: auto; }
.cert__body { flex: 1 1 22rem; max-width: var(--wrap-text); }
.cert__body > :last-child { margin-bottom: 0; }

/* press list */
.press {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(min(100%, 320px), 1fr));
  gap: clamp(1.5rem, 3vw, 2.25rem);
  list-style: none;
  padding: 0;
  margin: 0;
}
.press__item {
  margin: 0;
  background: var(--white);
  border: 1px solid var(--sand);
  display: flex;
  flex-direction: column;
}
.press__item img { width: 100%; aspect-ratio: 3 / 2; object-fit: cover; }
.press__body { padding: 1.4rem 1.5rem 1.6rem; display: flex; flex-direction: column; flex: 1; }
.press__source {
  font-size: .7rem;
  letter-spacing: .2em;
  text-transform: uppercase;
  color: var(--clay);
  margin-bottom: .6rem;
}
.press__body h3 { font-size: 1.25rem; margin-bottom: 1rem; }
.press__body .link-arrow { margin-top: auto; align-self: flex-start; font-size: .72rem; }

/* ------------------------------------------------------------ quotes */
.quotes {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 260px), 1fr));
  gap: clamp(1.5rem, 3vw, 2.5rem);
}
.quote { margin: 0; }
.quote blockquote {
  margin: 0 0 .9rem;
  font-family: var(--font-display);
  font-size: clamp(1.15rem, 1.8vw, 1.4rem);
  line-height: 1.5;
  font-style: italic;
  color: var(--ink);
}
.quote figcaption {
  font-size: .74rem;
  letter-spacing: .18em;
  text-transform: uppercase;
  color: var(--clay);
}

/* ------------------------------------------------------------ pillars */
.pillars {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 250px), 1fr));
  gap: clamp(1.5rem, 3vw, 2.5rem);
  list-style: none;
  padding: 0;
  margin: 0;
}
.pillar {
  margin: 0;
  padding: 1.75rem 1.6rem 2rem;
  background: var(--white);
  border-top: 2px solid var(--clay);
}
.section--moss .pillar {
  background: rgba(250,248,244,.06);
  border-top-color: var(--sand);
  color: var(--bone);
}
.pillar h3 { font-size: 1.3rem; margin-bottom: .75rem; }
.pillar ul { margin: 0; padding-left: 1.1em; }
.pillar li { font-size: .93rem; color: var(--ink-soft); }
.section--moss .pillar li { color: rgba(250,248,244,.82); }

.stat-row {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 180px), 1fr));
  gap: clamp(1.5rem, 3vw, 2.5rem);
  text-align: center;
}
.stat__num {
  display: block;
  font-family: var(--font-display);
  font-size: clamp(2.4rem, 5vw, 3.4rem);
  line-height: 1;
  color: var(--clay);
  margin-bottom: .4rem;
}
.stat__label {
  font-size: .74rem;
  letter-spacing: .16em;
  text-transform: uppercase;
  color: var(--ink-soft);
}
.section--moss .stat__num { color: var(--sand); }
.section--moss .stat__label { color: rgba(250,248,244,.75); }

/* ------------------------------------------------------------- contact */
.contact-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 280px), 1fr));
  gap: clamp(2rem, 4vw, 3.5rem);
}
.contact-card {
  background: var(--white);
  border: 1px solid var(--sand);
  padding: clamp(1.75rem, 3vw, 2.5rem);
}
.contact-card h2 { font-size: 1.9rem; margin-bottom: 1.25rem; }
.contact-card dl { margin: 0; }
.contact-card dt {
  font-size: .7rem;
  letter-spacing: .2em;
  text-transform: uppercase;
  color: var(--clay-deep);
  margin-bottom: .25rem;
}
.contact-card dd { margin: 0 0 1.15rem; font-size: 1.02rem; }
.contact-card dd a { text-decoration: none; border-bottom: 1px solid var(--sand); }
.contact-card dd a:hover { border-color: var(--clay); }

.map-embed {
  width: 100%;
  aspect-ratio: 16 / 9;
  border: 1px solid var(--sand);
  filter: grayscale(.25);
}

/* ---------------------------------------------------------------- footer */
.site-footer {
  background: var(--moss-deep);
  color: rgba(250,248,244,.8);
  padding: clamp(3rem, 7vw, 5rem) 0 2rem;
  font-size: .95rem;
}
.site-footer a { color: var(--bone); text-decoration: none; }
.site-footer a:hover { color: var(--sand); }

.footer-grid {
  display: grid;
  grid-template-columns: 1.2fr 1fr 1fr 1fr;
  gap: clamp(2rem, 4vw, 3.5rem);
  align-items: start;
}
@media (max-width: 900px) { .footer-grid { grid-template-columns: 1fr 1fr; } }
@media (max-width: 560px) { .footer-grid { grid-template-columns: 1fr; } }

.footer-logo { width: 168px; margin-bottom: 1.5rem; }

.footer-title {
  font-family: var(--font-body);
  font-size: .7rem;
  letter-spacing: .2em;
  text-transform: uppercase;
  color: var(--sand);
  margin: 0 0 1rem;
}
.footer-list { list-style: none; margin: 0; padding: 0; }
.footer-list li { margin-bottom: .6rem; }

.social {
  display: flex;
  gap: .75rem;
  list-style: none;
  margin: 1.5rem 0 0;
  padding: 0;
}
.social a {
  display: grid;
  place-items: center;
  width: 40px; height: 40px;
  border: 1px solid rgba(250,248,244,.28);
  border-radius: 50%;
  transition: background .2s var(--ease), border-color .2s var(--ease);
}
.social a:hover { background: rgba(250,248,244,.12); border-color: var(--bone); }
.social svg { width: 17px; height: 17px; fill: currentColor; }

.footer-bottom {
  margin-top: clamp(2.5rem, 5vw, 4rem);
  padding-top: 1.5rem;
  border-top: 1px solid rgba(250,248,244,.15);
  display: flex;
  flex-wrap: wrap;
  gap: 1rem;
  justify-content: space-between;
  font-size: .8rem;
  color: rgba(250,248,244,.6);
}

/* ------------------------------------------------------------ reveal fx */
.reveal {
  opacity: 0;
  transform: translateY(18px);
  transition: opacity .7s var(--ease), transform .7s var(--ease);
}
.reveal.is-visible { opacity: 1; transform: none; }

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after {
    animation-duration: .001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .001ms !important;
  }
  .reveal { opacity: 1; transform: none; }
}

/* ------------------------------------------------------------------ 404 */
.error-page {
  min-height: 78vh;
  display: grid;
  place-items: center;
  text-align: center;
  padding: calc(var(--header-h) + 4rem) 0 5rem;
}

@media print {
  .site-header, .site-footer, .lightbox, .promo-modal, .btn { display: none !important; }
  body { background: #fff; }
}

/* ------------------------------------------------------------ promo modal */
/* Optional marketing popup on the homepage, built at runtime by main.js from
   the window.DESAHAY_PROMO block in index.html. Nothing renders unless that
   block exists and is enabled, so these rules are inert the rest of the time.

   z-index 300 sits above .site-header (100) and the lightbox (200) but under
   the skip link (999), which must stay reachable. */
.promo-modal {
  position: fixed;
  inset: 0;
  z-index: 300;
  display: none;
  align-items: center;
  justify-content: center;
  padding: var(--gutter);
}
.promo-modal[data-open="true"] { display: flex; }

.promo-modal__backdrop {
  position: absolute;
  inset: 0;
  background: rgba(20, 24, 18, .6);
}

.promo-modal__card {
  position: relative;
  width: min(30rem, 100%);
  max-height: calc(100dvh - 2 * var(--gutter));
  overflow-y: auto;
  background: var(--bone);
  border: 1px solid var(--sand);
  box-shadow: var(--shadow-md);
  padding: clamp(2rem, 5vw, 2.75rem);
  text-align: center;
  animation: promo-in .45s var(--ease) both;
}

@media (prefers-reduced-motion: reduce) {
  .promo-modal__card { animation: none; }
}

@keyframes promo-in {
  from { opacity: 0; transform: translateY(12px); }
  to   { opacity: 1; transform: none; }
}

.promo-modal__close {
  position: absolute;
  top: .5rem;
  right: .5rem;
  padding: .6rem;
  background: none;
  border: 0;
  color: var(--ink-soft);
  font-size: 1.05rem;
  line-height: 1;
  cursor: pointer;
}
.promo-modal__close:hover { color: var(--ink); }

.promo-modal__title {
  font-family: var(--font-display);
  font-size: clamp(1.7rem, 4vw, 2.2rem);
  font-weight: 400;
  line-height: 1.15;
  margin: .4rem 0 0;
}

.promo-modal__body {
  margin: 1rem 0 0;
  color: var(--ink-soft);
}

.promo-modal__code {
  margin: 1.1rem 0 0;
  font-size: .72rem;
  letter-spacing: .18em;
  text-transform: uppercase;
  color: var(--clay);
}
.promo-modal__code b {
  font-weight: 500;
  letter-spacing: .22em;
  color: var(--ink);
}

.promo-modal__cta { margin-top: 1.6rem; }

/* ------------------------------------------------------ Munduk logo sizing --
   Munduk's lockup is 1.55:1 where Desa Hay's was 2.8:1, and the brand PNG ships
   on a padded canvas — the untrimmed file renders the mark at about 63% of its
   box height, which is why it read far too small. The files in assets/img are
   TRIMMED to the mark, and the logo is sized by height so the ratio change
   cannot overflow the 84px header.

   No colour rule is needed. The header uses the black brand file (#1C1B1A, the
   real brand black, not pure black) and the existing .site-header--transparent
   rule inverts it to white over a hero photo, exactly as it did for Desa Hay.
   The footer uses the white file as-is. */
.brand img { width: auto; height: 64px; }
.footer-logo { width: 150px; }
@media (max-width: 1000px) { .brand img { height: 50px; } }

/* ============================================================================
   DARK THEME  —  scoped to .theme-dark, currently the homepage only.

   Munduk goes dark to contrast with Desa Hay's bone. Scoped rather than set at
   :root so the other nineteen pages stay light while this is settled. To widen
   it, add the class to more <body> tags; to make it the whole site, promote the
   token block to :root and delete the .theme-dark prefixes.

   Palette from the Desa Hay brand guidelines (Nov 2021), which Munduk shares:
   black #1C1B1A, beige #E0D1BC, green #354A44, light brown #BA9262.

   WHY THIS IS NOT A TOKEN SWAP, which MUNDUK-SETUP.md predicted:
   --bone does two jobs in the light sheet. It is the page ground, AND it is the
   light foreground on every dark surface — hero headline over a photo, the
   transparent header's nav, button hovers, the moss band, footer links, the
   lightbox. Inverting the token satisfies the first job and silently breaks the
   second: dark text on dark photography. Every such rule is re-pinned below to
   --on-dark, a literal light that does NOT flip with the theme. The list was
   built by grepping the sheet for `var(--bone)` used as color/background/
   border-color/fill, not by eye — if a rule is ever added that uses --bone as a
   foreground, it must be added here too.
   ========================================================================= */
.theme-dark {
  --bone:      #1c1b1a;   /* page ground — brand black */
  --bone-deep: #232120;   /* .section--sand: now a RAISED dark band */
  --sand:      #3a3733;   /* hairlines and borders */
  --ink:       #e0d1bc;   /* body text — brand beige */
  --ink-soft:  #a79c8c;
  --moss:      #354a44;   /* brand green: contrast band, primary button */
  --moss-deep: #2a3a35;
  --clay:      #ba9262;   /* brand light brown — eyebrows, rules */
  --clay-deep: #c9a574;   /* LIGHTER than --clay here: on a dark ground the
                             relationship inverts or the underline vanishes */
  --white:     #232120;   /* card surface — no longer white */

  --on-dark:   #f5efe6;   /* the light foreground --bone used to provide */

  --shadow-sm: 0 1px 2px rgba(0,0,0,.4), 0 4px 14px rgba(0,0,0,.35);
  --shadow-md: 0 10px 40px rgba(0,0,0,.5);

  background: var(--bone);
  color: var(--ink);
}

/* Hero edge. On a light page the bone ground separates a photograph from the
   body by itself. On the dark theme a dusk or jungle shot bleeds into the
   ground and the hero loses its bottom edge, so it gets a hairline.

   The colour is --sand, which in the dark theme is #3a3733 — a lighter version
   of the #1c1b1a ground, and already this theme's hairline colour, shared with
   .spec-strip and .block-rule. That matters for how it behaves over
   photography: an earlier version used a translucent warm white,
   rgba(245,239,230,.28), which brightens whatever sits behind it and reads as a
   light line drawn ON the photo rather than an edge belonging to the page. An
   opaque tonal value stays consistent whatever the photo is doing.

   On .hero rather than .hero__media on purpose: the media is absolutely
   positioned with inset:0, so it lays out against the padding box and the
   border sits outside it, where it cannot be painted over.

   On the homepage the booking bar overlaps this edge by -26px and paints above
   it, so the line runs out to either side of the search card. */
.theme-dark .hero { border-bottom: 1px solid var(--sand); }

/* --- light foreground on dark surfaces: re-pinned so it cannot flip --------- */
.theme-dark .hero h1,
.theme-dark .hero h2,
.theme-dark .hero p                                   { color: var(--on-dark); }

.theme-dark .site-header--transparent .nav__link,
.theme-dark .site-header--transparent .nav__toggle-label,
.theme-dark .site-header--transparent .burger,
.theme-dark .site-header--transparent .btn--header    { color: var(--on-dark); }
.theme-dark .site-header--transparent .burger span    { background: var(--on-dark); }
.theme-dark .site-header--transparent .btn--header:hover {
  background: var(--on-dark);
  border-color: var(--on-dark);
  color: var(--moss-deep);
}

.theme-dark .section--moss,
.theme-dark .section--moss h1,
.theme-dark .section--moss h2,
.theme-dark .section--moss h3,
.theme-dark .section--moss .pillar                    { color: var(--on-dark); }
.theme-dark .section--moss .pillar li                 { color: rgba(245,239,230,.82); }

.theme-dark .btn:hover,
.theme-dark .btn--ghost:hover                         { color: var(--on-dark); }
.theme-dark .btn { --btn-fg: var(--on-dark); }
.theme-dark .btn--ghost { --btn-fg: var(--ink); --btn-bd: var(--ink); }
.theme-dark .btn--ghost:hover { background: var(--ink); border-color: var(--ink); color: var(--bone); }
.theme-dark .btn--light { --btn-fg: var(--on-dark); --btn-bd: rgba(245,239,230,.7); }
.theme-dark .btn--light:hover {
  background: var(--on-dark);
  border-color: var(--on-dark);
  color: var(--moss-deep);
}

.theme-dark .site-footer a                            { color: var(--on-dark); }
.theme-dark .social a:hover                           { border-color: var(--on-dark); }
.theme-dark .lightbox__btn                            { color: var(--on-dark); }
.theme-dark .lightbox__btn:hover                      { border-color: var(--on-dark); }
.theme-dark .promo-modal__card                        { background: var(--bone-deep); }

/* --- surfaces that were literal light values in the light sheet ------------ */
.theme-dark .site-header--solid {
  background: rgba(28,27,26,.96);
  box-shadow: 0 1px 0 rgba(245,239,230,.10);
}
/* The header logo is the black brand file, so the solid state needs the same
   invert the transparent state already applies. The filter stays on the <img>:
   on .site-header it would make the header a containing block for the fixed
   mobile drawer — the bug recorded in CLAUDE.md. */
.theme-dark .site-header--solid .brand img { filter: brightness(0) invert(1); }

.theme-dark .card { border-color: var(--sand); }

/* The card's hover affordance is a lift plus box-shadow. A dark shadow on a
   dark ground is invisible, so on the cabin pages the lift was doing all the
   work on its own. Warming the border on hover gives the state something to
   read as. Light pages keep the shadow and need nothing. */
.theme-dark a.card:hover { border-color: var(--clay); }

/* --- Cloudbeds picker: deliberately left LIGHT ----------------------------- */
/* The engine ships ~560 of its own tokens and only the unambiguous ones are
   safe to override (see the booking section above). A dark search card would
   need its internal text, placeholders and the calendar popover retuned too,
   and that popover renders in a portal attached to <body>, outside .booking-bar.
   A light card on a dark page is a normal pattern and costs nothing. Revisit
   only with the live widget in front of you, on an allowlisted domain.

   BOTH SURFACES, always. The picker appears twice — the homepage band
   (.booking-bar) and the drawer "Book a room" opens (.booking-panel) — and the
   first version of this block was scoped to .booking-bar alone. The drawer
   therefore kept the light sheet's `background: var(--white)`, which the dark
   theme redefines to #232120: a near-black card, with Cloudbeds' own navy
   calendar icon invisible on it. Exactly the failure the booking section above
   already warns about, one theme later. Every rule here is grouped across both
   containers for that reason. */
.theme-dark .booking-bar .cb-search-card,
.theme-dark .booking-panel .cb-search-card {
  background: #f5efe6 !important;
  border-color: rgba(28,27,26,.18) !important;
}
.theme-dark .booking-bar [data-testid$="-checkin-input"],
.theme-dark .booking-bar [data-testid$="-checkout-input"],
.theme-dark .booking-panel [data-testid$="-checkin-input"],
.theme-dark .booking-panel [data-testid$="-checkout-input"] { color: #1c1b1a !important; }

/* --sand and --white have the SAME dual role as --bone: hairline / card fill in
   the light sheet, but ALSO the muted light label on dark bands (moss eyebrows,
   footer titles, the lightbox chrome) and the white foreground on the skip link
   and .btn--clay. Inverting them turns those dark-on-dark. Found by an actual
   contrast audit of the rendered page, not by reading — three of these shipped
   in the first pass of this theme at ratios of 1.0 to 1.7:1. */
.theme-dark .hero .eyebrow,
.theme-dark .section--moss .eyebrow,
.theme-dark .section--moss .stat__num,
.theme-dark .footer-title,
.theme-dark .site-footer a:hover,
.theme-dark .lightbox__caption,
.theme-dark .lightbox__count            { color: rgba(245,239,230,.78); }

.theme-dark .skip-link,
.theme-dark .skip-link:focus,
.theme-dark .btn--clay:hover            { color: var(--on-dark); }

/* The Elfsight reviews widget ships for a light background: every visible
   string is hardcoded #111, measured at 1.10:1 against our dark ground, i.e.
   invisible except for the stars and the platform logos. A first attempt gave
   it a cream panel to sit on; that worked for contrast but read as a light slab
   dropped onto a dark page, so it is recoloured in place instead.

   ABOUT THESE SELECTORS. `CLAUDE.md` says never to restyle this widget from
   CSS, because its classes are content hashes — `ReviewContainer__Inner-sc-
   4342da43-0` and friends — that change on every release. That warning stands.
   The `es-*` classes used below are a different thing: Elfsight puts them on
   the same elements as semantic, unhashed hooks, the same arrangement as the
   Cloudbeds `data-testid` attributes sitting beside its `d-*` hashes. Key to
   `es-*`, never to the hash.

   It is still a bet on Elfsight keeping those hooks. The more robust fix is to
   recolour the widget in the Elfsight dashboard, where its appearance belongs;
   do that and this whole block can be deleted. Light pages need nothing.

   Verified on the deployed site: every text node in the widget passes AA
   against its actual computed background after these rules. The two containers
   below already carry rgba(17,17,17,.05), a tint that all but vanishes on a
   dark ground — they are re-tinted light so the cards still read as cards. */
.theme-dark .reviews-panel .es-header-heading-text,
.theme-dark .reviews-panel .es-rating-value,
.theme-dark .reviews-panel .es-review-author-name,
.theme-dark .reviews-panel .es-review-content-title,
.theme-dark .reviews-panel .es-text-shortener,
.theme-dark .reviews-panel .es-text-shortener * {
  color: var(--on-dark) !important;
}

/* Muted secondary text. Must come after the rule above: the "Read more"
   control is matched by both at equal specificity, so source order decides. */
.theme-dark .reviews-panel .es-header-rating-reviews-count,
.theme-dark .reviews-panel .es-review-info-date,
.theme-dark .reviews-panel .es-text-shortener-control {
  color: rgba(245,239,230,.6) !important;
}

.theme-dark .reviews-panel .es-header-container,
.theme-dark .reviews-panel .es-review-background-container {
  background: rgba(245,239,230,.06) !important;
}
