/* ==========================================================================
   Desa Hay Bali — stylesheet
   Palette and type chosen to match the original brand: warm bone paper,
   deep blue-green (shared with Munduk Cabins), hand-carved-wood clay accent.
   ========================================================================== */

:root {
  --bone:      #faf8f4;
  --bone-deep: #f2ede4;
  --sand:      #e3d9c9;
  --ink:       #22261f;
  --ink-soft:  #4d5347;
  --moss:      #354a44;
  --moss-deep: #2a3a35;
  --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; }
.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); }

/* Gallery sits in the Villas panel but is not a villa, so a hairline sets it
   apart from the two room links above it. Drawn as a ::before inset to the
   links' own 1.4rem padding rather than as a border on the <li>: a full-bleed
   line touching both panel edges reads as the end of the menu, an inset one
   reads as a group break. The rule has to be a pseudo-element for that —
   insetting the <li> itself with margin would drag the Gallery link's hover
   background in with it. The .55rem either side keeps the line clear of the
   hover backgrounds above and below. */
.nav__panel-sep {
  position: relative;
  margin-top: .55rem;
  padding-top: .55rem;
}
.nav__panel-sep::before {
  content: "";
  position: absolute;
  top: 0;
  left: 1.4rem;
  right: 1.4rem;
  border-top: 1px solid var(--sand);
}

.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-sep { margin-top: .35rem; padding-top: .35rem; }
  .nav__panel-sep::before { left: 0; right: 0; }
  .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; }
}

/* Scrim ramp over the hero photograph. The waist sits at 34% because that is
   where the headline lands.

   Two strengths. Interior pages carry the heavier ramp: their photographs are
   brighter and busier behind the headline, and .06 through the waist was not
   holding the type. The homepage keeps the lighter original via
   .hero--scrim-soft — its hero is the video, and the heavier ramp muddies the
   footage.

   Interior values started at the Munduk Cabins figures (.60 / .45 / .72) and
   were eased back to .52 / .32 / .66, which reads as a scrim rather than as a
   dimmed photograph. Compared against .55 / .38 / .69 and .48 / .24 / .64 on
   the media page hero, whose rattan lamps are the hardest case on the site;
   below roughly .30 through the waist the lede starts to thin out where the
   brightest lamp sits.

   Marking the homepage as the exception rather than the rule means any new
   page picks up the heavier ramp by default, which is the safer direction. */
.hero__media::after {
  content: "";
  position: absolute;
  inset: 0;
  background:
    linear-gradient(to bottom, rgba(20,24,18,.52) 0%, rgba(20,24,18,.32) 34%, rgba(20,24,18,.66) 100%);
}
.hero--scrim-soft .hero__media::after {
  background:
    linear-gradient(to bottom, rgba(20,24,18,.42) 0%, rgba(20,24,18,.06) 34%, rgba(20,24,18,.62) 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;
}
.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: var(--bone) !important;
  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 #354A44 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; }

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

/* One row at every width, deliberately. The cells used to be flex: 1 1 140px
   with wrap on, which broke below roughly a 420px strip: the cell starting
   the second row kept the border-left and the 1.25rem padding that only the
   very first cell is meant to drop, so it hung under the row above indented
   by 20px with a divider dangling to its left. :first-child cannot express
   "first of each row", and the strip wraps at two unrelated widths — once
   when the feature column narrows on tablets, again on phones — so there is
   no single breakpoint to patch it at either.

   Letting the cells shrink instead of wrap removes the case altogether: three
   short values fit a 280px strip once the padding and the display size scale
   down with it. The first cell still sits flush with the copy above it. */
.spec-strip {
  display: flex;
  flex-wrap: nowrap;
  gap: 0;
  border-top: 1px solid var(--sand);
  border-bottom: 1px solid var(--sand);
  margin: 2rem 0;
}
.spec-strip div {
  flex: 1 1 0;
  min-width: 0;
  padding: 1.1rem clamp(.7rem, 3vw, 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: clamp(1.05rem, 3.4vw, 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 (ink, #22261f) 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__photo { width: 100%; aspect-ratio: 3 / 2; object-fit: cover; }

/* Publication band above the photo. A real vector mark where one exists,
   otherwise the title set in the display face on the same white band — a
   logo only earns the space when a reader recognises it, and an unfamiliar
   mark reads worse than the name in type.

   Fixed height so the bands line up across a row whatever their contents;
   the marks are constrained by max-height, never by width, since their
   aspect ratios differ. */
.press__logo {
  display: flex;
  align-items: center;
  justify-content: center;
  height: 92px;
  padding: 1rem 1.5rem;
  background: var(--white);
  border-bottom: 1px solid var(--sand);
}
.press__logo-mark {
  max-height: 40px;
  max-width: 78%;
  width: auto;
  height: auto;
  object-fit: contain;
}
/* Optical sizing, not mathematical. A wide wordmark and a near-square boxed
   mark capped at the same height do not read as the same size — the square
   one looks half the weight. Same reason the awards row carries --mark,
   --seal and --wordmark modifiers. */
.press__logo-mark--tall { max-height: 58px; }
/* And the reverse: a masthead that is nothing but full-height caps reads
   far heavier than its neighbours at the shared cap. */
.press__logo-mark--sm { max-height: 30px; }
.press__logo-text {
  font-family: var(--font-display);
  font-size: 1.35rem;
  line-height: 1.15;
  letter-spacing: .04em;
  color: var(--ink);
  text-align: center;
}
.press__body { padding: 1.4rem 1.5rem 1.6rem; display: flex; flex-direction: column; flex: 1; }
.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);
  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);
}

/* Menu documents, shown in a modal (dining page). Same overlay conventions
   as .lightbox — fixed inset, data-open toggling display, the same scrim —
   but a panel rather than a bare image, because a PDF needs a title bar and
   an escape hatch to the file itself.

   z-index 200 matches .lightbox. Both sit above .site-header (100) and the
   booking drawer (90); only one can ever be open at a time. */
.doc-modal {
  position: fixed;
  inset: 0;
  z-index: 200;
  display: none;
  align-items: center;
  justify-content: center;
  background: rgba(18,21,16,.94);
  padding: clamp(.75rem, 3vw, 2.5rem);
}
.doc-modal[data-open="true"] { display: flex; }
.doc-modal__panel {
  display: flex;
  flex-direction: column;
  width: min(1000px, 100%);
  height: min(88vh, 1000px);
  background: var(--bone);
  border: 1px solid var(--sand);
}
.doc-modal__bar {
  display: flex;
  align-items: center;
  gap: 1rem;
  padding: .7rem .5rem .7rem 1.1rem;
  border-bottom: 1px solid var(--sand);
  background: var(--bone-deep);
}
.doc-modal__title {
  margin: 0;
  margin-right: auto;
  font-size: .72rem;
  letter-spacing: .18em;
  text-transform: uppercase;
  color: var(--clay);
}
.doc-modal__open {
  font-size: .72rem;
  letter-spacing: .1em;
  color: var(--ink);
  text-decoration: none;
  border-bottom: 1px solid var(--sand);
  white-space: nowrap;
}
.doc-modal__open:hover { border-color: var(--clay); }
.doc-modal__close {
  background: none;
  border: 1px solid var(--sand);
  color: var(--ink);
  width: 38px; height: 38px;
  flex: none;
  font-size: 1rem;
  line-height: 1;
  cursor: pointer;
  display: grid;
  place-items: center;
  transition: background .2s var(--ease), border-color .2s var(--ease);
}
.doc-modal__close:hover { background: var(--ink); color: var(--bone); border-color: var(--ink); }
/* flex:1 with min-height:0 — without the min-height the iframe refuses to
   shrink below its intrinsic size and pushes the panel past the viewport. */
.doc-modal__frame {
  flex: 1;
  min-height: 0;
  width: 100%;
  border: 0;
  display: block;
  background: var(--bone-deep);
}
@media (max-width: 560px) {
  .doc-modal { padding: 0; }
  .doc-modal__panel { width: 100%; height: 100%; border: 0; }
  .doc-modal__open { display: none; }
}

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