/*
Theme Name: Gazebo Mansion
Theme URI: https://gazebomansionibiza.com
Author: Gazebo Mansion
Description: A single-page sale brochure theme for a luxury Ibiza villa. Hero, story, gallery, suites, amenities and contact are all edited from a normal WordPress Page using the block editor. Includes a light/dark mode switch (defaults to the visitor's system setting) and WhatsApp enquiry buttons.
Version: 2.7.10
Requires at least: 6.0
Requires PHP: 7.4
License: GNU General Public License v2 or later
License URI: https://www.gnu.org/licenses/gpl-2.0.html
Text Domain: gazebo-mansion
*/

@import url('https://fonts.googleapis.com/css2?family=Fraunces:ital,opsz,wght@0,9..144,340;0,9..144,440;0,9..144,560;1,9..144,440;1,9..144,560&family=Archivo:wght@400;500;600;700;800&display=swap');

/* --gm-accent-raw / --gm-secondary-raw are the two colors set from
   Appearance → Customize → Site Style (color pickers). They're injected
   as a small inline <style> block after this stylesheet, defaulting to
   these same values when nothing's been customized. Everything else
   below derives FROM those two raw values — dark mode auto-brightens
   them a little for contrast against a dark background, and the button
   hover/error shade (--terracotta-ink) is computed from whatever
   --terracotta ends up being — so picking one accent color correctly
   updates every place it's used, in both themes, with no extra pickers. */
:root{
  --gm-accent-raw: #bd5a30;
  --gm-secondary-raw: #9c7b41;
  /* .gm-nav's rendered height: 16px+16px padding-block plus its content —
     not something that changes per breakpoint, but relied on in two
     places (the hero's height calc, and every anchor target's
     scroll-margin-top below) that would otherwise silently drift apart
     if only one of them got updated after a future nav redesign. */
  --gm-nav-height: 71px;

  --sand: #f4ecdd;
  --sand-2: #eadfc7;
  --panel: #fffdf8;
  --ink: #2c2317;
  --ink-soft: #6b5f4d;
  --line: rgba(44,35,23,0.14);
  --terracotta: var(--gm-accent-raw);
  --terracotta-ink: color-mix(in srgb, var(--terracotta) 78%, black);
  --olive: #57603d;
  --brass: var(--gm-secondary-raw);
  --whatsapp: #25a35a;
  --whatsapp-ink: #ffffff;
  --shadow: 0 20px 50px -25px rgba(44,35,23,0.45);
  --radius: 2px;
}

@media (prefers-color-scheme: dark){
  html:not([data-theme="light"]){
    --sand: #1c1811;
    --sand-2: #262019;
    --panel: #211c15;
    --ink: #f1e7d3;
    --ink-soft: #baa98c;
    --line: rgba(241,231,211,0.14);
    --terracotta: color-mix(in srgb, var(--gm-accent-raw) 82%, white 18%);
    --olive: #9caf78;
    --brass: color-mix(in srgb, var(--gm-secondary-raw) 82%, white 18%);
    --whatsapp: #35c078;
    --whatsapp-ink: #10241a;
    --shadow: 0 20px 50px -20px rgba(0,0,0,0.6);
  }
}
html[data-theme="dark"]{
  --sand: #1c1811;
  --sand-2: #262019;
  --panel: #211c15;
  --ink: #f1e7d3;
  --ink-soft: #baa98c;
  --line: rgba(241,231,211,0.14);
  --terracotta: color-mix(in srgb, var(--gm-accent-raw) 82%, white 18%);
  --olive: #9caf78;
  --brass: color-mix(in srgb, var(--gm-secondary-raw) 82%, white 18%);
  --whatsapp: #35c078;
  --whatsapp-ink: #10241a;
  --shadow: 0 20px 50px -20px rgba(0,0,0,0.6);
}

*{ box-sizing: border-box; }
html{ scroll-behavior: smooth; }
body{
  margin:0;
  background: var(--sand);
  color: var(--ink);
  font-family: "Archivo", ui-sans-serif, system-ui, sans-serif;
  -webkit-font-smoothing: antialiased;
  overflow-x: hidden;
}
/* Escapes entry-content's max-width to bleed edge-to-edge regardless of nesting. */
.gm-bleed{ width: 100vw; margin-left: calc(50% - 50vw); margin-right: calc(50% - 50vw); }
body.gm-page{ padding-inline: clamp(18px, 4vw, 56px); }
img{ max-width:100%; height:auto; }
a{ color: inherit; }
h1,h2,h3,
.wp-block-heading{
  font-family: "Fraunces", Georgia, serif;
  font-weight: 500;
  line-height: 1.08;
  text-wrap: balance;
  margin: 0;
}
p{ margin:0; }
.gm-wrap{ max-width: 1180px; margin-inline: auto; }
.entry-content{ max-width: 1180px; margin-inline: auto; }
.entry-content > * + *{ margin-top: clamp(48px, 8vw, 100px); }
.entry-content .wp-block-heading{ font-size: clamp(1.7rem,3vw,2.3rem); }
.entry-content p{ color: var(--ink-soft); line-height:1.7; }

/* One consistent vertical rhythm for eyebrow → heading → body copy,
   applied globally so every section matches regardless of whether it's
   a native block (subject to WordPress core's own block-library
   margins, which don't match each other section to section) or a
   custom HTML block. !important because wp-block-library's own rules
   can otherwise win depending on exact selector/order. */
.entry-content .gm-eyebrow + h1,
.entry-content .gm-eyebrow + h2,
.entry-content .gm-eyebrow + h3,
.entry-content .gm-eyebrow + .wp-block-heading{
  margin-top: 10px !important;
}
.entry-content h1 + p,
.entry-content h2 + p,
.entry-content h3 + p,
.entry-content .wp-block-heading + p{
  margin-top: 16px !important;
}
.entry-content p + p{
  margin-top: 16px !important;
}

.gm-eyebrow{
  font-family:"Archivo", sans-serif !important;
  font-size: 0.72rem !important;
  font-weight: 700;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--brass) !important;
}

/* ---------- buttons ---------- */
.wp-block-button__link{
  padding: 13px 24px !important;
  border-radius: var(--radius) !important;
  font-weight: 700; font-size: 0.88rem;
  font-family:"Archivo", sans-serif;
}
.wp-block-button:not(.is-style-outline):not(.is-style-whatsapp) .wp-block-button__link{
  background: var(--terracotta); color: #fff8f0;
}
.wp-block-button:not(.is-style-outline):not(.is-style-whatsapp) .wp-block-button__link:hover{ background: var(--terracotta-ink); }
.is-style-outline .wp-block-button__link{
  border: 1px solid color-mix(in srgb, var(--ink) 30%, transparent); color: var(--ink); background:transparent;
}
.is-style-outline .wp-block-button__link:hover{ border-color: var(--ink); }
.is-style-whatsapp .wp-block-button__link{ background: var(--whatsapp); color: var(--whatsapp-ink); }
.is-style-whatsapp .wp-block-button__link:hover{ filter: brightness(1.08); }
a:focus-visible, button:focus-visible, input:focus-visible, textarea:focus-visible, [tabindex]:focus-visible{
  outline: 2px solid var(--terracotta); outline-offset: 3px;
}

/* ---------- raw buttons, used inside the hero/contact/film HTML blocks ---------- */
.gm-btn{
  display:inline-flex; align-items:center; gap:10px;
  padding: 13px 24px; border-radius: var(--radius);
  font-weight: 700; font-size: 0.88rem;
  text-decoration:none; cursor:pointer; border: 1px solid transparent;
  font-family:"Archivo", sans-serif;
}
.gm-btn-primary{ background: var(--terracotta); color: #fff8f0; }
.gm-btn-primary:hover{ background: var(--terracotta-ink); }
.gm-btn-ghost{ border-color: color-mix(in srgb, var(--ink) 30%, transparent); color: var(--ink); }
.gm-btn-ghost:hover{ border-color: var(--ink); }
.gm-btn-whatsapp{ background: var(--whatsapp); color: var(--whatsapp-ink); }
.gm-btn-whatsapp:hover{ filter: brightness(1.08); }
.gm-btn-wide{ width:100%; justify-content:center; padding-block:15px; }
.gm-wa-icon{ width:16px; height:16px; flex:none; }

/* ---------- site header / nav ---------- */
.gm-nav{
  position: sticky; top:0; z-index: 40;
  margin-inline: calc(clamp(18px, 4vw, 56px) * -1);
  padding-inline: clamp(18px, 4vw, 56px);
  display:flex; align-items:center; justify-content:space-between;
  padding-block: 16px;
  background: color-mix(in srgb, var(--sand) 88%, transparent);
  backdrop-filter: blur(10px);
  border-bottom: 1px solid transparent;
  transition: border-color .3s ease;
}
.gm-nav.scrolled{ border-bottom-color: var(--line); }
.gm-brand{
  font-family:"Fraunces", serif; font-weight:500; font-size:1.05rem;
  letter-spacing:0.01em; text-decoration:none; white-space:nowrap; color: var(--ink);
}
.gm-brand em{ font-style: italic; color: var(--terracotta); }
.gm-navlinks{ display:flex; gap: 24px; align-items:center; }
.gm-navlinks a{ text-decoration:none; font-size:0.86rem; font-weight:600; color: var(--ink-soft); }
.gm-navlinks a:hover{ color: var(--ink); }
@media (max-width: 760px){ .gm-navlinks a.hide-mobile{ display:none; } }
/* Every section an in-page link (nav or the hero's "View the Villa")
   can jump to. Without this, landing on one of these via an anchor —
   #villa, #gm-intro, #gallery, #suites, #contact — puts the section's
   true top exactly at the very top of the document, which is then the
   first --gm-nav-height of it hidden behind the sticky nav sitting on
   top: the jump reads as having scrolled a touch too far, past the
   start of the section. This holds the browser's own scroll target
   that distance short instead, for both a clicked anchor link and the
   :target that a shared/bookmarked URL like #gm-intro lands on. The
   extra 24px on top of the nav's own height is breathing room, so the
   section starts just clear of the nav rather than touching it exactly
   — applied to all five so this doesn't need re-discovering per
   section later. */
#villa, #gm-intro, #gallery, #suites, #contact{
  scroll-margin-top: calc(var(--gm-nav-height) + 24px);
}

.theme-switch{
  width:38px; height:38px; flex:none; border-radius:50%;
  border:1px solid var(--line); background:transparent; color: var(--ink);
  display:flex; align-items:center; justify-content:center; cursor:pointer; padding:0;
}
.theme-switch:hover{ border-color: var(--brass); }
.theme-switch svg{ width:18px; height:18px; display:none; }
.theme-switch .ts-sun{ display:block; fill:none; stroke:currentColor; stroke-width:2; stroke-linecap:round; stroke-linejoin:round; }
.theme-switch .ts-moon{ fill:currentColor; }
.theme-switch[aria-checked="true"] .ts-sun{ display:none; }
.theme-switch[aria-checked="true"] .ts-moon{ display:block; }

/* ---------- hero (custom HTML block) ---------- */
.gm-hero{
  position: relative;
  color: #fbf3e6;
}
/* Height is a flat 85vh at every breakpoint — replaces an earlier system
   of fixed per-breakpoint pixel heights (650px on desktop, stepping down
   through 900/860/760/720/640/306/278px). That system pinned the box to
   an exact pixel height regardless of viewport width, so on a wide
   desktop monitor object-fit:cover had to crop far more off the top and
   bottom to fill the same 650px — which part of the photo ended up
   visible was effectively random per screen size. 85vh scales with the
   viewport instead, and pairing it with object-position:top on the photo
   (below) means the crop always starts from the same place: the top of
   the photo — sky and sunset — is always what's touching the top edge,
   at every width. flex + align-items:flex-end anchors the text to the
   bottom of the box (over the scrim's darkest region) regardless of
   exactly how tall 85vh comes out to be, so it no longer needs the
   matching per-breakpoint top offsets .gm-hero-inner used to carry —
   those overrides (and the fixed heights above) are removed from the
   CLIENT CUSTOMIZATIONS block below. Bottom padding reuses the same
   clamp() as .entry-content > * + * (the rule that spaces every other
   top-level block from the one before it) so the gap from the hero text
   down to the stat strip reads as the same rhythm as the gap from the
   stat strip down to the video underneath it. A real <img> (not a CSS
   background) so the browser can discover and prioritize fetching it
   early — a background-image is invisible to the browser until the
   stylesheet's been parsed, which costs real time on what's almost
   always the page's biggest visible element.
   Height is 85vh MINUS the sticky nav's own height (71px: 16px+16px
   padding-block plus its content), not a plain 85vh — the nav sits
   in-flow above the hero and is part of what fills the screen on
   first load, so a plain 85vh here plus the nav on top of it added up
   to slightly MORE than the full screen, leaving nothing of the next
   section peeking into view (the original point of using 85vh over
   100vh in the first place). Subtracting the nav's height means
   hero + nav together consistently leave the intended ~15% of the
   screen showing a hint of what's next, regardless of viewport
   height. */
.gm-hero-photo{
  position: relative;
  overflow: hidden;
  background-color: #140f08; /* shows only if both images fail to load */
  height: calc(85vh - var(--gm-nav-height));
  display: flex;
  align-items: flex-end;
  padding-bottom: clamp(48px, 8vw, 100px);
}
/* A ~1-2KB preview (generated from the same photo, resized to ~32px
   wide) shown immediately, blurred and slightly oversized so its edges
   don't reveal any un-blurred/transparent fringe. The full-resolution
   <img> fades in over it once actually loaded — this reads as "coming
   into focus" rather than the glow-wipe shimmer used elsewhere, which
   felt like too much for something this large and this central to the
   page. Kept as a permanent layer (never removed) rather than a
   fade-out overlay: once the sharp photo is opaque on top, this is
   naturally covered regardless. */
/* Background comes from the --gm-hero-blur-* custom properties set inline
   on the element (see functions.php) rather than a plain url() here, so it
   can carry a ?ver= cache-buster tied to GM_VERSION. A static url() here
   would keep pointing visitors at whatever bytes their browser cached under
   this same filename the first time they ever saw the page — this is what
   caused a swapped-in photo to silently keep showing the old one. */
.gm-hero-photo-blur{
  position:absolute; inset:0; z-index:0;
  background: var(--gm-hero-blur-desktop) center top / cover no-repeat;
  filter: blur(24px);
  transform: scale(1.1);
}
@media (max-width: 640px){
  .gm-hero-photo-blur{ background-image: var(--gm-hero-blur-mobile); }
}
.gm-hero-photo-img{
  position:absolute; inset:0; z-index:0;
  display:block; width:100%; height:100%; object-fit:cover;
  object-position: center top;
  opacity: 0;
  transition: opacity .6s ease;
}
/* Not gated behind the two-frame rAF wait the shimmer-based reveals
   need elsewhere: opacity:0 here is the baseline CSS, already painted
   since the very first frame (long before any JS runs) rather than
   something JS itself just set moments earlier — so there's always a
   real "before" state to transition from, however fast the image
   turns out to have loaded. */
.gm-hero-photo-img.gm-hero-loaded{ opacity: 1; }
@media (prefers-reduced-motion: reduce){
  .gm-hero-photo-img{ transition: none; }
}
.gm-hero-scrim{
  position:absolute; inset:0; z-index:0;
  background: linear-gradient(180deg, rgba(20,15,8,0.32) 0%, rgba(20,15,8,0.74) 55%, #140f08 100%);
}
/* content-box (not the page's default border-box) so the padding sits
   OUTSIDE the 1180px budget instead of eating into it — otherwise this
   box centers 56px tighter than every other section on wide screens,
   since they have no padding of their own to double-count. width:100%
   is required now that .gm-hero-photo is a flex container: a flex
   item's width defaults to shrinking to fit its content (here, the
   headline/CTA) rather than filling its container the way a normal
   block does, so without it this box — and the eyebrow/heading inside
   it — quietly shrank to content width and got centered as that
   smaller box, no longer lining up on the left with the 3,000 m² stat
   (and everything else on the page) the way it did before the hero
   became a flex layout. width:100% restores "fill, then cap at
   1180px", matching every other section again. */
.gm-hero-inner{
  position: relative; z-index: 1;
  box-sizing: content-box;
  width: 100%;
  padding-inline: clamp(18px, 4vw, 56px); max-width:1180px; margin-inline:auto;
}
.gm-hero .gm-eyebrow{ color: #e7c99a !important; }
.gm-hero h1{ font-size: clamp(2.4rem, 5.6vw, 4.3rem); color: #fbf3e6; margin-top: 10px; max-width: 16ch; text-shadow: 0 2px 10px rgba(0,0,0,0.35); }
.gm-hero h1 em{ font-style: italic; color: #f0b183; }
.gm-hero-cta{ display:flex; flex-wrap:wrap; gap:14px; margin-top: 30px; }
.gm-hero-cta .is-style-outline .wp-block-button__link{ border-color: rgba(251,243,230,0.5); color:#fbf3e6; }
.gm-hero-cta .is-style-outline .wp-block-button__link:hover{ border-color:#fbf3e6; }
/* The raw .gm-btn-ghost ("View the Villa") uses var(--ink) by default,
   which is theme-aware — right for a button on the page's own light/dark
   background, but wrong here: the hero background is always a dark
   photo/video, so this needs the same fixed light treatment as the rest
   of the hero regardless of site theme. This is what was unreadable in
   light mode (var(--ink) resolved to near-black on a dark image). */
.gm-hero-cta .gm-btn-ghost{ border-color: rgba(251,243,230,0.5); color:#fbf3e6; }
.gm-hero-cta .gm-btn-ghost:hover{ border-color:#fbf3e6; }

/* A solid band, not on top of the photo — keeps the core figures legible
   on every screen instead of fighting the image for contrast. The band
   itself stays full-bleed; the figures inside line up with the template
   width, same content-box reasoning as .gm-hero-inner above. */
.gm-stat-strip{ background: #140f08; }
.gm-stat-strip-inner{
  box-sizing: content-box;
  max-width:1180px; margin-inline:auto;
  padding-inline: clamp(18px, 4vw, 56px);
  display:grid; grid-template-columns: repeat(5,1fr);
}
/* Direct-child combinator only: each stat item is itself a <div>
   wrapping a .gm-stat-num <div> and a .gm-stat-label <div>, and the old
   descendant selector (no ">") matched those nested divs too — giving
   every item a second, short border-right behind the number/label text
   on top of the one intended tall divider spanning the whole item. */
.gm-stat-strip-inner > div{ padding: 18px clamp(10px,2vw,20px); border-right:1px solid rgba(251,243,230,0.14); }
.gm-stat-strip-inner > div:last-child{ border-right:none; }
.gm-stat-num{ font-family:"Fraunces",serif; font-size: clamp(1.2rem,2.4vw,1.7rem); font-variant-numeric: tabular-nums; }
.gm-stat-label{ font-size: clamp(0.68rem, 0.9vw, 0.8rem); letter-spacing:0.1em; text-transform:uppercase; color:#d9c8a9; margin-top:4px; }
@media (max-width: 720px){
  .gm-stat-strip-inner{ grid-template-columns: repeat(2,1fr); }
  .gm-stat-strip-inner > div:nth-child(2n){ border-right:none; }
}

/* ---------- headline features band (custom HTML block) ---------- */
/* Same family as the hero's stat strip — a dark full-bleed band of equal
   items with a divider between them — but icon-led, for the property's
   standout amenities rather than its numbers. */
.gm-features{ background: #140f08; }
.gm-features-inner{
  box-sizing: content-box;
  max-width:1180px; margin-inline:auto;
  padding-inline: clamp(18px, 4vw, 56px);
  padding-block: clamp(40px, 6vw, 60px);
  display:grid; grid-template-columns: repeat(5,1fr);
}
.gm-feature-item{
  display:flex; flex-direction:column; align-items:center; text-align:center; gap:12px;
  padding-inline: clamp(8px,2vw,20px);
  border-right:1px solid rgba(251,243,230,0.14);
}
.gm-feature-item:last-child{ border-right:none; }
.gm-feature-icon{ width:30px; height:30px; flex:none; fill:none; stroke:#e7c99a; stroke-width:1.5; stroke-linecap:round; stroke-linejoin:round; }
.gm-feature-label{ font-size: clamp(0.78rem, 1vw, 0.88rem); font-weight:600; color:#fbf3e6; letter-spacing:0.01em; }
@media (max-width: 720px){
  .gm-features-inner{ grid-template-columns: repeat(2,1fr); row-gap:32px; }
  .gm-feature-item{ border-right:none; }
  .gm-feature-item:nth-child(2n){ border-left:1px solid rgba(251,243,230,0.14); }
}

/* An eyebrow immediately followed by its heading — used wherever those two
   sit as bare entry-content children, so they get this small, deliberate
   gap instead of the big inter-section spacing every top-level block gets.
   (Redundant with the general .gm-eyebrow + heading rule above for most
   cases, kept as a belt-and-braces override for this specific wrapper.) */
.gm-section-head .wp-block-heading{ margin-top: 10px; }

/* ---------- intro video (custom HTML block, replaces the old intro photo) ---------- */
.gm-intro-video{
  position: relative;
  border-radius: var(--radius);
  overflow: hidden;
  box-shadow: var(--shadow);
}
.gm-intro-video-el{ display:block; width:100%; aspect-ratio: 4/3; object-fit:cover; }
/* Sits over the video/poster (not behind, unlike the LiteSpeed images —
   a video poster is fully opaque, not a mostly-transparent placeholder,
   so this needs its own layer on top). A spinner rather than the
   glow-wipe shimmer used for the gallery photos — a full-bleed sweeping
   gradient read as too much for a video already showing its own
   motion. Visible from the very first frame (not toggled on via a
   class — see .gm-lazy-loaded below for how it's faded away), and
   pointer-events:none so it never blocks the mute button underneath. */
.gm-media-loading{
  position:absolute; inset:0; z-index:0;
  display:flex; align-items:center; justify-content:center;
  background: rgba(20,15,8,0.35);
  pointer-events:none;
  opacity: 1;
  transition: opacity .4s ease;
}
.gm-spinner{
  width:38px; height:38px;
  border: 3px solid rgba(255,255,255,0.3);
  border-top-color: #fff;
  border-radius: 50%;
  animation: gm-spin 0.8s linear infinite;
}
@keyframes gm-spin{ to{ transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce){
  .gm-spinner{ animation: none; }
}
.gm-mute-toggle{
  position:absolute; right:14px; bottom:14px; z-index:1;
  width:38px; height:38px; border-radius:50%;
  background: rgba(20,15,8,0.55); border:1px solid rgba(251,243,230,0.35); color:#fbf3e6;
  display:flex; align-items:center; justify-content:center; cursor:pointer; padding:0;
  backdrop-filter: blur(4px);
}
.gm-mute-toggle:hover{ background: rgba(20,15,8,0.75); }
.gm-mute-toggle svg{ width:18px; height:18px; fill:none; stroke:currentColor; stroke-width:2; stroke-linecap:round; stroke-linejoin:round; display:none; }
.gm-mute-toggle .gm-icon-muted{ display:block; }
.gm-mute-toggle[aria-pressed="true"] .gm-icon-muted{ display:none; }
.gm-mute-toggle[aria-pressed="true"] .gm-icon-unmuted{ display:block; }

/* ---------- Gallery/suite photos: glow-wipe shimmer + fade-in ---------- */
/* Applied to every gallery and suite photo (theme.js: initLazyPlaceholders),
   whether or not LiteSpeed ends up lazy-loading that particular image. For
   ones it does rewrite to data-src, the shimmer shows through the
   placeholder SVG because it's ~90% transparent (fill-opacity: 0.1); for
   an eager image with its real src from the start, the same background
   shows through equally well, since a browser paints an <img>'s CSS
   background regardless while the image itself hasn't finished loading —
   no extra wrapper element needed either way. (The hero photo and intro
   video use their own, different loading treatments — a blurred preview
   and a spinner respectively, set up above — rather than this shimmer;
   .gm-lazy-loaded is still shared with the video's spinner overlay below,
   just for the fade itself, not the shimmer look.) */
.gm-lazy-loading{
  background-color: rgba(0,0,0,0.03);
  background-image: linear-gradient(100deg, transparent 30%, rgba(255,255,255,0.45) 50%, transparent 70%);
  background-size: 250% 100%;
  background-repeat: no-repeat;
  background-position: -60% 0;
  animation: gm-glow-wipe 2.2s ease-in-out infinite;
}
@keyframes gm-glow-wipe{
  0%   { background-position: -60% 0; }
  100% { background-position: 160% 0; }
}
/* The moment the real src starts loading, hide it instantly (no
   transition declared here) — masks the brief blank/decode gap that
   swapping an <img>'s own src attribute causes. Not used by the video
   overlay, which fades directly from loading to loaded instead. */
.gm-lazy-swapping{ opacity: 0; }
/* Then fade in once it's actually ready to show. Default direction is
   for the lazy-loaded <img> itself becoming visible (opacity 0 → 1,
   from .gm-lazy-swapping above). */
.gm-lazy-loaded{
  animation: none;
  background-image: none;
  background-color: transparent;
  opacity: 1;
  transition: opacity .5s ease;
}
/* The video's spinner overlay is a covering layer, not content being
   revealed — its own opacity:1 (declared above) never goes through a
   .gm-lazy-swapping step like the LiteSpeed images do, so it needs the
   opposite fade direction from the plain .gm-lazy-loaded rule above:
   this fades the overlay ITSELF away, revealing the video underneath,
   rather than fading some content in. Two classes beat the plain
   .gm-lazy-loaded rule above on specificity. */
.gm-media-loading.gm-lazy-loaded{ opacity: 0; }
@media (prefers-reduced-motion: reduce){
  .gm-lazy-loading{ animation: none; }
  .gm-lazy-swapping{ opacity: 1; transition: none; }
  .gm-lazy-loaded{ opacity: 1; transition: none; }
  .gm-media-loading.gm-lazy-loaded{ opacity: 0; }
}

/* ---------- intro image + gallery images ---------- */
.entry-content .wp-block-image img{ border-radius: var(--radius); box-shadow: var(--shadow); }
.gm-masonry{ column-count: 3; column-gap: 14px; display:block; }
.gm-masonry .wp-block-image{ margin: 0 0 14px; break-inside: avoid; overflow:hidden; border-radius: var(--radius); }
.gm-masonry .wp-block-image img{ cursor: zoom-in; transition: transform .5s cubic-bezier(.16,.84,.44,1); }
.gm-masonry .wp-block-image img:hover{ transform: scale(1.035); }
@media (max-width: 900px){ .gm-masonry{ column-count:2; column-gap:10px; } .gm-masonry .wp-block-image{ margin-bottom:10px; } }

/* ---------- at-a-glance spec grid (custom HTML block) ---------- */
.gm-glance{ background: var(--sand-2); padding: clamp(56px, 9vw, 120px) clamp(18px, 4vw, 56px); }
.gm-glance-inner{ max-width:1180px; margin-inline:auto; }
.gm-glance-head{ display:flex; justify-content:space-between; align-items:flex-start; gap:20px; flex-wrap:wrap; margin-bottom: 36px; }
.gm-glance-head h2{ font-size: clamp(1.7rem,3vw,2.3rem); margin-top:10px; }
.gm-price-pill{
  border:1px solid var(--brass); color: var(--brass);
  font-size:0.72rem; letter-spacing:0.1em; text-transform:uppercase; font-weight:700;
  padding:8px 14px; border-radius:999px; white-space:nowrap;
}
dl.gm-spec-grid{ display:grid; grid-template-columns: repeat(2,1fr); gap: 0; border-top:1px solid var(--line); margin:0; }
.gm-spec-grid > div{ display:grid; grid-template-columns: 34% 1fr; gap:16px; padding: 18px 4px; border-bottom:1px solid var(--line); }
.gm-spec-grid > div:nth-child(odd){ border-right:1px solid var(--line); padding-right:24px; }
.gm-spec-grid > div:nth-child(even){ padding-left:24px; }
.gm-spec-grid dt{ font-weight:700; font-size:0.92rem; }
.gm-spec-grid dd{ margin:0; color: var(--ink-soft); font-size:0.92rem; line-height:1.55; }
@media (max-width: 760px){
  dl.gm-spec-grid{ grid-template-columns:1fr; }
  .gm-spec-grid > div:nth-child(odd){ border-right:none; padding-right:4px; }
  .gm-spec-grid > div:nth-child(even){ padding-left:4px; }
}

/* ---------- suites row (custom HTML block) ---------- */
/* Bottom padding is deliberately smaller than top: the row's own
   padding-bottom (below) already puts space between the cards and the
   scrollbar, so the scrollbar can sit close to the section's own edge
   instead of floating in a second, redundant gap. */
.gm-suites{ background: var(--sand-2); padding-block: clamp(56px, 9vw, 120px) clamp(28px, 5vw, 48px); }
/* Centered at the template width, same as every other section — matches
   the heading above and below it instead of sitting at a fixed inset. */
.gm-suites-inner{
  box-sizing: content-box;
  max-width:1180px; margin-inline:auto;
  padding-inline: clamp(18px, 4vw, 56px);
}
.gm-suites h2{ font-size: clamp(1.7rem,3vw,2.3rem); margin-top:10px; }
.gm-suites > .gm-suites-inner > p{ color: var(--ink-soft); margin-top:16px; max-width:60ch; }
.gm-suites-head-top{ display:flex; justify-content:space-between; align-items:flex-end; gap:18px; flex-wrap:wrap; }
.gm-suite-nav{ display:flex; gap:10px; flex:none; }
.gm-suite-nav-btn{
  width:42px; height:42px; border-radius:50%; flex:none;
  border:1px solid var(--line); background: var(--panel); color: var(--ink);
  display:flex; align-items:center; justify-content:center; cursor:pointer; padding:0;
}
.gm-suite-nav-btn:hover{ border-color: var(--brass); }
.gm-suite-nav-btn:disabled{ opacity:0.35; cursor:default; }
.gm-suite-nav-btn svg{ width:18px; height:18px; fill:none; stroke:currentColor; stroke-width:2; stroke-linecap:round; stroke-linejoin:round; }
/* A direct child of .gm-suites (not .gm-suites-inner), so the scrollable
   area itself stays full-viewport width — full edge-to-edge swipe range —
   but the padding grows to match the template's own margin on wide
   screens, so the first card still lines up with the heading above it
   instead of sitting at a small fixed inset. */
.gm-suite-row{
  display:flex; gap:18px; overflow-x:auto; margin-top:20px;
  padding-block: 4px 24px;
  padding-inline: max(clamp(18px, 4vw, 56px), calc((100% - 1180px) / 2));
  scroll-snap-type: x mandatory;
  scroll-padding-inline: max(clamp(18px, 4vw, 56px), calc((100% - 1180px) / 2));
  -webkit-overflow-scrolling: touch;
  scrollbar-width: thin;
  scrollbar-color: var(--brass) transparent;
}
.gm-suite-row::-webkit-scrollbar{ height: 6px; }
.gm-suite-row::-webkit-scrollbar-track{ background: transparent; }
.gm-suite-row::-webkit-scrollbar-thumb{ background: var(--brass); border-radius: 999px; }
.gm-suite-row::-webkit-scrollbar-thumb:hover{ background: var(--terracotta); }
.gm-suite-card{ flex: 0 0 240px; scroll-snap-align:start; scroll-snap-stop:always; }
.gm-suite-card img{ aspect-ratio: 3/4; object-fit:cover; border-radius:var(--radius); box-shadow: var(--shadow); width:100%; display:block; }
.gm-suite-card .name{ font-family:"Fraunces",serif; font-size:1.15rem; margin-top:12px; }
.gm-suite-card .note{ color: var(--ink-soft); font-size:0.85rem; margin-top:4px; line-height:1.5; }

/* ---------- amenities columns (native Columns block) ---------- */
.gm-amenities .wp-block-columns{ margin-top: 34px !important; gap: 30px; }
.gm-amenities h3{
  font-family:"Archivo",sans-serif !important; font-weight:800 !important; font-size:0.78rem !important;
  letter-spacing:0.08em; text-transform:uppercase; color: var(--olive) !important;
  padding-bottom:10px; border-bottom:1px solid var(--line); margin-bottom:14px !important;
}
.gm-amenities ul{ list-style:none; margin:0; padding:0; display:flex; flex-direction:column; gap:10px; }
.gm-amenities li{ font-size:0.92rem; line-height:1.5; padding-left:16px; position:relative; color: var(--ink); }
.gm-amenities li::before{ content:""; position:absolute; left:0; top:0.6em; width:6px; height:6px; background: var(--terracotta); border-radius:50%; }

/* ---------- film band (custom HTML block) ---------- */
.gm-film{
  position: relative; min-height: 420px; overflow: hidden;
  display:flex; align-items:center; justify-content:center; flex-direction:column; gap:14px;
  color:#fbf3e6; text-align:center; padding: 40px 20px;
}
/* The photo lives on its own layer (not .gm-film's own background) so a
   brightness filter can darken just the image without dimming the text
   drawn above it. */
.gm-film-photo{
  position:absolute; inset:0; z-index:0;
  background-size: cover; background-position:center;
  filter: brightness(0.6);
}
/* Flat black wash on top of the (already-darkened) photo, below the
   text/icon — instead of individual backdrops behind each line of text. */
.gm-film-scrim{ position:absolute; inset:0; z-index:0; background: rgba(0,0,0,0.38); }
/* :not() here matters: .gm-film > * used to also match .gm-film-scrim,
   and at equal specificity this rule (being declared later) silently
   overrode the scrim's position:absolute back to relative — collapsing
   it to a zero-height empty div, so the "darken the photo" fix never
   actually rendered. */
.gm-film > *:not(.gm-film-photo):not(.gm-film-scrim){ position:relative; z-index:1; }
.gm-play-glyph{ width:64px; height:64px; border-radius:50%; border:1.5px solid rgba(251,243,230,0.7); display:flex; align-items:center; justify-content:center; }
.gm-play-glyph svg{ width:20px; height:20px; fill:#fbf3e6; margin-left:3px; }
.gm-film h2{ color:#fbf3e6; font-size: clamp(1.5rem,2.6vw,2rem); text-shadow: 0 2px 10px rgba(0,0,0,0.45); }
.gm-film p{ text-shadow: 0 1px 6px rgba(0,0,0,0.5); }
/* This section is a centered flex column with its own gap:14px between
   the icon/heading/text, not a stacked-copy flow — override the
   general h2 + p rule so its spacing isn't added on top of that gap. */
.gm-film h2 + p{ margin-top:0 !important; }

/* ---------- contact (custom HTML block) ---------- */
.gm-contact{
  background: var(--panel); border:1px solid var(--line); border-radius: var(--radius);
  padding: clamp(24px,4vw,52px); box-shadow: var(--shadow);
  display:grid; grid-template-columns: 1fr 1fr; gap: clamp(30px,5vw,64px);
}
.gm-contact h2{ font-size: clamp(1.6rem,2.8vw,2.1rem); margin-top:10px; }
.gm-contact-intro p{ color: var(--ink-soft); margin-top:16px; line-height:1.7; max-width:44ch; }
.gm-contact-whats{ margin-top:26px; display:flex; flex-direction:column; gap:10px; align-items:flex-start; }
.gm-contact-whats span{ font-size:0.8rem; color: var(--ink-soft); }
.gm-contact form{ display:flex; flex-direction:column; gap:14px; }
.gm-field{ display:flex; flex-direction:column; gap:6px; }
.gm-field label{ font-size:0.76rem; font-weight:700; letter-spacing:0.04em; text-transform:uppercase; color: var(--ink-soft); }
.gm-field input, .gm-field textarea{
  font-family:"Archivo",sans-serif; font-size:0.95rem; padding:12px 14px;
  border:1px solid var(--line); border-radius: var(--radius); background: var(--sand); color: var(--ink);
}
.gm-field textarea{ resize:vertical; min-height:96px; }
.gm-form-note{ font-size:0.78rem; color: var(--ink-soft); min-height: 1.2em; }
.gm-form-note.is-error{ color: var(--terracotta-ink); }
/* Spam honeypot — off-screen, not display:none, so bots that skip hidden
   fields still fill it in and give themselves away. */
.gm-hp{ position:absolute; left:-9999px; width:1px; height:1px; overflow:hidden; }
@media (max-width: 860px){ .gm-contact{ grid-template-columns:1fr; } }

/* ---------- footer ---------- */
.gm-footer{ padding-block: 40px clamp(90px,10vw,50px); max-width:1180px; margin-inline:auto; }
.gm-foot-row{ display:flex; justify-content:space-between; gap:20px; flex-wrap:wrap; align-items:center; border-top:1px solid var(--line); padding-top:24px; }
.gm-foot-row small{ color: var(--ink-soft); font-size:0.78rem; }

/* ---------- floating whatsapp ---------- */
.gm-fab{
  position: fixed; right: clamp(14px,3vw,28px); bottom: clamp(14px,3vw,28px); z-index:50;
  width:56px; height:56px; border-radius:50%; background: var(--whatsapp); color: var(--whatsapp-ink);
  display:flex; align-items:center; justify-content:center; box-shadow: var(--shadow); text-decoration:none;
}
.gm-fab svg{ width:26px; height:26px; fill: var(--whatsapp-ink); }
.gm-fab:hover{ filter: brightness(1.08); }

/* ---------- scroll-in animation ---------- */
/* Rows/photos: slide up from 0 opacity over ~1s. Hidden state only ever
   applies once JS has confirmed it can run the reveal (see theme.js), so
   content stays fully visible with no JS. */
html.gm-armed .gm-reveal,
html.gm-armed .gm-stagger > *{
  opacity: 0;
  transform: translateY(28px);
}
.gm-reveal,
.gm-stagger > *{
  transition: opacity 1s cubic-bezier(.16,.84,.44,1), transform 1s cubic-bezier(.16,.84,.44,1);
}
html.gm-armed .gm-reveal.in-view,
html.gm-armed .gm-stagger.in-view > *{
  opacity: 1;
  transform: translateY(0);
}
.gm-stagger > *:nth-child(1){ transition-delay: 0ms; }
.gm-stagger > *:nth-child(2){ transition-delay: 90ms; }
.gm-stagger > *:nth-child(3){ transition-delay: 180ms; }
.gm-stagger > *:nth-child(4){ transition-delay: 270ms; }
.gm-stagger > *:nth-child(5){ transition-delay: 360ms; }
.gm-stagger > *:nth-child(6){ transition-delay: 450ms; }
.gm-stagger > *:nth-child(7){ transition-delay: 540ms; }
.gm-stagger > *:nth-child(8){ transition-delay: 630ms; }

/* Titles/text: word-by-word or letter-by-letter, split at runtime by JS.
   Spans only ever exist once JS has split them, so no hidden-without-JS risk. */
.gm-word, .gm-letter{
  display: inline-block;
  opacity: 0;
  transform: translateY(0.4em);
  transition: opacity .5s ease, transform .6s cubic-bezier(.16,.84,.44,1);
}
.gm-word{ transition-delay: min(calc(var(--i, 0) * 28ms), 480ms); }
.gm-letter{ transition-delay: min(calc(var(--i, 0) * 14ms), 420ms); }
/* Scoped to the split element's OWN in-view state, not just any ancestor's:
   a paragraph sitting inside a wrapping .gm-reveal/.gm-stagger block (the
   intro columns, the contact card, ...) would otherwise inherit that
   wrapper's in-view as soon as its TOP edge appeared — often well before
   the paragraph itself had scrolled into view — so by the time it was
   actually visible its words had already finished fading in off-screen
   and it just sat there fully formed, looking like it never animated. */
.gm-split-word.in-view .gm-word,
.gm-split-letter.in-view .gm-letter{
  opacity: 1;
  transform: translateY(0);
}

/* ---------- lightbox ---------- */
.gm-lightbox{
  position: fixed; inset: 0; z-index: 200;
  display:flex; align-items:center; justify-content:center;
  background: rgba(10,7,4,0.94);
  opacity: 0; visibility: hidden;
  transition: opacity .3s ease;
  padding: clamp(16px, 4vw, 40px);
}
.gm-lightbox.is-open{ opacity: 1; visibility: visible; }
.gm-lightbox-figure{ margin:0; max-width:min(92vw, 1300px); text-align:center; }
.gm-lightbox-figure img{
  max-width:100%; max-height:78vh; width:auto; display:block; margin-inline:auto;
  object-fit:contain; border-radius: var(--radius); box-shadow: 0 30px 80px rgba(0,0,0,0.55);
}
.gm-lightbox-caption{ color:#ecdfc9; font-size:0.85rem; margin-top:16px; }
.gm-lightbox-counter{
  color:#c9b391; font-size:0.7rem; letter-spacing:0.12em; text-transform:uppercase;
  margin-top:6px; font-variant-numeric: tabular-nums;
}
.gm-lightbox-btn{
  position:fixed; width:44px; height:44px; border-radius:50%;
  background: rgba(255,255,255,0.08); border:1px solid rgba(255,255,255,0.28); color:#fbf3e6;
  display:flex; align-items:center; justify-content:center; cursor:pointer; padding:0;
}
.gm-lightbox-btn:hover{ background: rgba(255,255,255,0.18); }
.gm-lightbox-btn svg{ width:18px; height:18px; fill:none; stroke:currentColor; stroke-width:2; stroke-linecap:round; stroke-linejoin:round; }
.gm-lightbox-close{ top: clamp(14px,3vw,26px); right: clamp(14px,3vw,26px); }
.gm-lightbox-prev{ left: clamp(10px,2.5vw,26px); top:50%; transform:translateY(-50%); }
.gm-lightbox-next{ right: clamp(10px,2.5vw,26px); top:50%; transform:translateY(-50%); }
@media (max-width: 640px){
  .gm-lightbox-prev, .gm-lightbox-next{ width:38px; height:38px; }
  .gm-lightbox-prev{ left:8px; } .gm-lightbox-next{ right:8px; }
}

@media (prefers-reduced-motion: reduce){
  html{ scroll-behavior:auto; }
  html.gm-armed .gm-reveal,
  html.gm-armed .gm-stagger > *,
  .gm-word, .gm-letter{
    opacity: 1 !important;
    transform: none !important;
    transition: none !important;
  }
  .gm-lightbox, .gm-masonry img{ transition: none !important; }
}

/* ====================================================================
   CLIENT CUSTOMIZATIONS — moved in from Appearance → Customize →
   Additional CSS. Values kept exactly as set there; only moved, not
   changed. This is why they loaded late enough to cause visible jumps:
   Additional CSS is a second, separate stylesheet applied after the
   page has already started rendering with this one, and depending on
   LiteSpeed's CSS settings (combine/async) the gap between the two can
   widen further. Being part of this file now, it loads and applies at
   the same time as everything else.
   One rule from the export was dropped: a text-shadow on
   .gm-hero-inner .gm-hero-sub. That element was removed from the hero
   in an earlier round (the body text that used to sit there was moved
   into the first section instead) — the CSS export tool correctly
   flagged it as matching nothing on the page.
   A second set of rules was later removed here (not from the original
   export — added after, straight to Additional CSS, to chase the hero's
   sizing/positioning): a fixed height on #villa .gm-hero-photo per
   breakpoint (650/582/526/495px) and a matching top offset on
   .gm-hero-photo .gm-hero-inner at each one. Replaced by the simpler,
   viewport-relative approach set on .gm-hero-photo itself, above.
   ==================================================================== */

/* Stat strip */
#villa .gm-stat-strip{
	background-color: rgba(72, 65, 50, 0.75);
	-webkit-backdrop-filter: blur(10px);
	backdrop-filter: blur(10px);
}

/* Hero scrim */
.gm-hero-photo .gm-hero-scrim{
	background-image: linear-gradient(180deg, rgba(20, 15, 8, 0.32) 0%, rgba(20, 15, 8, 0.2) 55%, rgba(20, 15, 8, 0.85) 100%);
}

/* Heading */
.gm-hero-inner h1{
	text-shadow: 0px 2px 10px rgba(0, 0, 0, 0.32);
}

/* Paragraph */
.gm-hero-inner .gm-eyebrow{
	text-shadow: 0px 0px 13px rgba(0, 0, 0, 0.38);
}

/* Figure */
.wp-block-group__inner-container .gm-gallery-item:hover{
	transform: scale(1.03) !important;
	transition: all 0.3s ease-in-out !important;
}

/* Box */
body .gm-lightbox{
	-webkit-backdrop-filter: blur(10px);
	backdrop-filter: blur(10px);
	background-color: rgba(10, 7, 4, 0.85);
}

/* Image */
.gm-gallery-item img{
	transition: transform 0.2s cubic-bezier(0.16, 0.84, 0.44, 1) !important;
}

/* Paragraph */
.gm-reveal .gm-split-word.wp-block-paragraph{
	line-height: 2em;
}

/* Paragraph */
.gm-suites-inner > .gm-split-word{
	line-height: 2em;
}

/* List Item */
.wp-block-list li{
	line-height: 1.6em;
}

/* Paragraph */
.gm-contact-intro p.gm-split-word{
	line-height: 2em;
}

/* Stat num */
.gm-stat-strip-inner .gm-stat-num{
	border-right-style: none;
	padding-bottom: 10px;
	padding-top: 14px;
}

/* Stat label */
.gm-stat-strip-inner .gm-stat-label{
	padding-top: 0px;
}

/* Link */
.gm-hero-cta .gm-btn-ghost{
	-webkit-backdrop-filter: blur(6.9px);
	backdrop-filter: blur(6.9px);
}

/* .in-view > div */
.in-view > div{
	padding-bottom: 30px;
}

/* Glance */
.entry-content .gm-glance{
	margin-top: 0px;
}

/* Suites */
.entry-content #suites{
	padding-bottom: 0px;
}

/* Suite row */
#suites #gmSuiteRow{
	padding-bottom: 70px;
}

@media (max-width:900px){
#villa .gm-stat-strip{
	padding-bottom: 0px;
}
}

@media (max-width:640px){
#villa div:nth-child(5){
	border-right-width: 1px !important;
	border-right-color: rgba(251, 243, 230, 0.14) !important;
	border-right-style: solid !important;
}

.gm-stat-strip-inner .gm-stat-num{
	font-size: 22px;
}

.gm-stat-strip-inner .gm-stat-label{
	font-size: 12px;
}

.in-view > div{
	padding-bottom: 24px;
	padding-top: 6px;
	border-bottom-width: 1px !important;
	border-bottom-color: rgba(251, 243, 230, 0.14);
	border-bottom-style: none;
}

.gm-stat-strip div:nth-child(3){
	background-color: transparent !important;
}

.gm-stat-strip div:nth-child(4){
	background-color: transparent !important;
}

#suites #gmSuiteRow{
	padding-bottom: 20px;
}
}

@media (max-width:278px){
.gm-hero-inner h1{
	font-size: 35px;
}
}
