/* ==========================================================================
   Section-library primitives — the parts every section is built from:
   band (section shell), band header, buttons, text link, media frame, meta
   chips, prose, and the motion vocabulary. Typography (text styles and the
   roles sections use) lives in vv-type.css, which loads after this file.

   Rules:
   - Colour comes from role tokens only (vv-tokens.css). Allowed pairings
     are listed in docs/design-system/color.md.
   - Class names deliberately avoid the legacy ones (.vv-section, .vv-btn,
     .vv-eyebrow, .vv-card…). The new system lands without restyling pages
     that haven't migrated yet.
   ========================================================================== */

/* :root raises this above GeneratePress's customizer `body { background-color }`,
   which prints after this file. */
:root body { background-color: var(--vv-color-surface); }

/* ---------- Band (section shell) ---------- */
.vv-band {
  position: relative;
  padding-block: var(--vv-section-y);
  padding-inline: var(--vv-gutter);
  background-color: var(--vv-tone-bg, var(--vv-color-surface));
  color: var(--vv-color-text-default);
  font-family: var(--vv-font-sans);
  font-size: var(--vv-text-body);
  line-height: var(--vv-leading-body);
}
.vv-band--space-compact { padding-block: var(--vv-section-y-compact); }
.vv-band--space-loose { padding-block: var(--vv-section-y-loose); }
/* Stacked: bands that follow one another on one ground (the room pages). Space
   above only, one gap (#46e): every stack variant is the section gap, so the
   old 50 / 60 / 70 steps no longer differ. */
.vv-band--space-stack,
.vv-band--space-stack-50,
.vv-band--space-stack-60,
.vv-band--space-stack-70 { padding-block: var(--vv-section-y) 0; }
/* Nested: a band under the section before it (heading-3) sits 50 below that
   section's content, the head-to-content gap, so it reads as part of it;
   sibling nested bands keep the section gap between them. Its space below
   is still the band's own (user, 2026-10-12). */
.vv-band:has(+ .vv-band--nested) { padding-block-end: 0; }
.vv-band--nested { padding-block-start: var(--vv-head-content-gap); }
.vv-band--nested + .vv-band--nested { padding-block-start: var(--vv-section-y); }

/* The live list bullet: a 10px square turned 45deg in a 20px box, radius
   1px, in the decorative gold (room pages; media-set draws its own). */
.vv-diamond {
  display: grid;
  place-items: center;
  flex: none;
  width: 20px;
  height: 20px;
}
.vv-diamond::before {
  content: "";
  width: 10px;
  height: 10px;
  border-radius: 1px;
  background-color: var(--vv-color-element-highlight);
  transform: rotate(45deg);
}
/* A list of diamond-bulleted lines, 10 apart, the bullet box 10 from the
   text and level with its top (measured: the amenity lists). */
.vv-dlist { display: flex; flex-direction: column; gap: var(--vv-gap-10); margin: 0; padding: 0; list-style: none; }
.vv-dlist > li { display: flex; align-items: flex-start; gap: var(--vv-gap-10); }
.vv-band__inner {
  max-width: var(--vv-container);
  margin-inline: auto;
  container: vv-band / inline-size;
}
.vv-band__inner--wide { max-width: var(--vv-container); }
.vv-band__inner--narrow { max-width: var(--vv-container-narrow); }

/* Resets inside a band. The :where() keeps each rule at one class of
   specificity. That still beats the legacy global element rules
   (homepage-style.css body/p/li/a/h1–h4, GeneratePress margins), but the
   primitive and section classes below win on source order. Headings are
   reset to plain text: their look comes from a type role (vv-type.css). */
.vv-band :where(h1, h2, h3, h4, h5, h6) {
  margin: 0;
  font: inherit;
  letter-spacing: inherit;
  text-transform: none;
  color: var(--vv-color-text-default);
  /* No text-wrap: balance. It came from the parked design system, and on a
     replica it silently rebreaks every heading: our destination header wrapped
     to 552px against the live 769px with identical text and width. Line
     breaking has to match the source. */
}
.vv-band :where(p, li, dd, dt, figcaption, blockquote, figure) { margin: 0; color: inherit; }
.vv-band :where(ul[class], ol[class], dl[class]) { margin: 0; padding: 0; list-style: none; }
.vv-band :where(img) { display: block; max-width: 100%; height: auto; }
.vv-band :where(a:not([class])) {
  color: var(--vv-color-text-cta);
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-decoration-color: color-mix(in srgb, currentColor 40%, transparent);
  text-underline-offset: 0.22em;
  transition: color var(--vv-motion-hover-duration) var(--vv-motion-hover-easing), text-decoration-color var(--vv-motion-hover-duration) var(--vv-motion-hover-easing);
}
.vv-band :where(a:not([class])):hover { color: var(--vv-color-text-cta-hover); text-decoration-color: currentColor; }
.vv-band :where(:focus-visible) { outline: 2px solid var(--vv-color-focus); outline-offset: 3px; }

/* ---------- Band header ---------- */
.vv-band-head {
  display: grid;
  gap: 0;
  justify-items: start;
  max-width: var(--vv-measure-head);
  margin-block-end: var(--vv-section-header-gap);
}
.vv-band-head__eyebrow { margin-block-end: var(--vv-gap-10); color: var(--vv-color-text-cta); }
.vv-band-head__intro { max-width: var(--vv-measure); margin-block-start: var(--vv-head-text-gap); }
.vv-band-head__action { margin-block-start: var(--vv-head-action-gap); }
.vv-band-head--center { margin-inline: auto; text-align: center; justify-items: center; }
.vv-band-head--center .vv-band-head__intro { margin-inline: auto; }
@container vv-band (min-width: 56rem) {
  .vv-band-head--split {
    max-width: none;
    grid-template-columns: minmax(0, var(--vv-measure-head)) auto;
    justify-content: space-between;
    align-items: end;
    column-gap: var(--vv-gap-50);
  }
  .vv-band-head--split > :not(.vv-band-head__action) { grid-column: 1; }
  /* The action sits level with the heading line itself, not the foot of the
     whole header, so it lines up whether or not an eyebrow or intro is there. */
  .vv-band-head--split > .vv-band-head__action { grid-column: 2; grid-row: 1; align-self: center; margin: 0; }
  .vv-band-head--split:has(> .vv-band-head__eyebrow) > .vv-band-head__action { grid-row: 2; }
}

/* ---------- Buttons (Figma Primary / Secondary CTA) ----------
   Type: the Button style in vv-type.css. */
.vv-button {
  --vv-button-bg: var(--vv-cta-primary-bg);
  --vv-button-bg-hover: var(--vv-cta-primary-bg-hover);
  --vv-button-text: var(--vv-cta-primary-text);
  --vv-button-text-hover: var(--vv-cta-primary-text-hover);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--vv-gap-10);
  min-height: 40px;                     /* every primary button is 40 tall (user, 2026-10-07) */
  padding: 0 var(--vv-cta-pad-x);
  border: 0;
  border-radius: var(--vv-cta-radius);
  background-color: var(--vv-button-bg);
  color: var(--vv-button-text);
  text-decoration: none;
  white-space: nowrap;
  cursor: pointer;
  transition:
    background-color var(--vv-motion-hover-duration) var(--vv-motion-hover-easing),
    color var(--vv-motion-hover-duration) var(--vv-motion-hover-easing);
}
/* Secondary: an action on the page — opens a form or dialog, loads more
   (design-principles.md, Buttons). Outlined gold on white, 36 tall, 16 either side
   of the label inside the 1px line, square corners; on hover the line and label
   deepen over the page ground (Figma 923:72 / 926:130). */
.vv-button--secondary {
  --vv-button-bg: var(--vv-cta-secondary-bg);
  --vv-button-bg-hover: var(--vv-cta-secondary-bg-hover);
  --vv-button-text: var(--vv-cta-secondary-text);
  --vv-button-text-hover: var(--vv-cta-secondary-text-hover);
  min-height: 36px;
  padding: 0 var(--vv-cta-pad-x);
  border: 1px solid var(--vv-cta-secondary-border);
  text-align: start;
  transition:
    background-color var(--vv-motion-hover-duration) var(--vv-motion-hover-easing),
    border-color var(--vv-motion-hover-duration) var(--vv-motion-hover-easing),
    color var(--vv-motion-hover-duration) var(--vv-motion-hover-easing);
}
.vv-button--secondary:hover, .vv-button--secondary.is-hover { border-color: var(--vv-cta-secondary-border-hover); }
/* Large: heroes and closing bands (Figma 874:1426): wider, the same 40 tall.
   Its weight comes from the Button large style in vv-type.css. */
.vv-button--lg { padding-inline: var(--vv-cta-pad-x-lg); }
/* Focused or pressed keeps the button's own colours: GeneratePress's inline
   a:focus/a:active and button:focus/button:active rules outrank .vv-button
   and turned the label dark on navy. Hover, below, still wins. */
.vv-button:is(:focus, :active) {
  background-color: var(--vv-button-bg);
  color: var(--vv-button-text);
}
.vv-button:hover, .vv-button.is-hover {
  background-color: var(--vv-button-bg-hover);
  color: var(--vv-button-text-hover);
}
.vv-button:focus-visible, .vv-button.is-focus { outline: 2px solid var(--vv-color-focus); outline-offset: 3px; }

/* Light: the live site's band action — "Discover More" under Signature
   Experiences, "Discover all stays" under the rooms. A white pill, radius 5,
   13px 20px, dark label in the eyebrow style (vv-type.css); navy with a white
   label on hover. Measured 160x40 on the live Overviews. */
.vv-button--light {
  --vv-button-bg: var(--vv-cta-light-bg);
  --vv-button-bg-hover: var(--vv-cta-light-bg-hover);
  --vv-button-text: var(--vv-cta-light-text);
  --vv-button-text-hover: var(--vv-cta-light-text-hover);
  /* A fixed 40px, as live: the 20px label overflows the 13px padding
     rather than growing the pill to 46. */
  height: 40px;
  text-align: start;                    /* shrink-wrapped: draws the same, reports as live */
  padding: 13px 20px;
  border-radius: var(--vv-cta-radius);
  position: relative;
  transition:
    background-color var(--vv-motion-hover-duration) var(--vv-motion-hover-easing),
    color var(--vv-motion-hover-duration) var(--vv-motion-hover-easing);
}
/* The hairline sits on an overlay, as Framer draws it, so it adds nothing to
   the pill's 40px and can go on hover without the label moving. */
.vv-button--light::after {
  content: "";
  position: absolute;
  inset: 0;
  border: 1px solid var(--vv-cta-light-border);
  border-radius: inherit;
  pointer-events: none;
  transition: border-color var(--vv-motion-hover-duration) var(--vv-motion-hover-easing);
}
.vv-button--light:hover::after { border-color: transparent; }

/* Dark: the CTA band's "Send an Enquiry" — the light button's metrics in
   navy, with no hairline. Measured 172x40. */
.vv-button--dark {
  --vv-button-bg: var(--vv-cta-dark-bg);
  --vv-button-bg-hover: var(--vv-cta-dark-bg-hover);
  --vv-button-text: var(--vv-cta-dark-text);
  --vv-button-text-hover: var(--vv-cta-dark-text-hover);
  height: 40px;
  text-align: start;
  padding: 13px 20px;
  border-radius: var(--vv-cta-radius);
  transition:
    background-color var(--vv-motion-hover-duration) var(--vv-motion-hover-easing),
    color var(--vv-motion-hover-duration) var(--vv-motion-hover-easing);
}

/* ---------- Slideshow ----------
   One photograph at a time, arrows either side, a dot per slide, looping
   (vv_slideshow(), js/vv-slides.js). The defaults are the homepage hero's,
   measured 2026-09-24: arrows 32px, 19 in, on a 20% black disc; 4px dots at
   30%, 20 up from the foot; a 1s move. The room pages' galleries
   (.vv-slides--gallery, measured 2026-09-25): 40px discs 20 in, 5px dots
   at 50%, 10 up, a 1.8s move, no autoplay. */
.vv-slides {
  --vv-slides-arrow: 32px;
  --vv-slides-arrow-inset: 19px;
  --vv-slides-dots-bottom: 20px;
  --vv-slides-move: var(--vv-motion-slide-duration);
}
.vv-slides--gallery {
  --vv-slides-arrow: 40px;
  --vv-slides-arrow-inset: 20px;
  --vv-slides-dots-bottom: 10px;
  --vv-slides-move: var(--vv-motion-slide-long-duration);
}
.vv-slides__viewport {
  position: relative;
  height: var(--vv-slides-h, 585px);
  overflow: hidden;
}
.vv-slides__track {
  display: flex;
  height: 100%;
  margin: 0;
  padding: 0;
  list-style: none;
  transform: translateX(calc(var(--vv-slide, 0) * -100%));
}
.vv-slides__track.is-moving { transition: transform var(--vv-slides-move) var(--vv-motion-slide-easing); }
/* Swipe (user, 2026-10-16): the slides follow the finger or the mouse
   (--vv-slide-drag, px) and, let go, move on to the next or settle back.
   Vertical page scrolling stays the browser's. */
.vv-slides__viewport { touch-action: pan-y; }
.vv-slides__track.is-dragging { transform: translateX(calc(var(--vv-slide, 0) * -100% + var(--vv-slide-drag, 0px))); transition: none; cursor: grabbing; user-select: none; }
.vv-slides__track.is-dragging * { pointer-events: none; }
.vv-slides__track.is-moving.is-released { transition-duration: var(--vv-motion-swipe-duration); transition-timing-function: var(--vv-motion-swipe-easing); }
.vv-slides__img { -webkit-user-drag: none; }
.vv-slides__slide {
  position: relative;
  flex: 0 0 100%;
  height: 100%;
  background-color: var(--vv-color-background-photo);
}
.vv-slides__media { position: absolute; inset: 0; }
.vv-slides__img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: center;
}
.vv-slides__controls {
  position: absolute;
  inset: 0;
  margin: 0;
  padding: 0;
  border: 0;
  pointer-events: none;
}
.vv-slides__controls > * { pointer-events: auto; }
.vv-slides__arrow {
  position: absolute;
  top: 50%;
  display: grid;
  place-items: center;
  width: var(--vv-slides-arrow);
  height: var(--vv-slides-arrow);
  padding: 0;
  border: 0;
  border-radius: 50%;
  background-color: var(--vv-color-control-on-image);
  color: var(--vv-color-text-on-image);
  cursor: pointer;
  transform: translateY(-50%);
}
.vv-slides__arrow svg { width: var(--vv-slides-arrow); height: var(--vv-slides-arrow); }
/* Every carousel arrow's chevron is drawn 2.5px at whatever size its disc,
   for prominence (user, 2026-10-17): the slides', the rails' and the gallery
   viewer's. */
:is(.vv-slides__arrow, .vv-rail__arrow) svg path { stroke-width: 2.5px; vector-effect: non-scaling-stroke; }
.vv-slides__arrow--prev { left: var(--vv-slides-arrow-inset); }
.vv-slides__arrow--next { right: var(--vv-slides-arrow-inset); }
/* GeneratePress fills every button on hover and focus. */
.vv-slides__arrow:is(:hover, :focus, :active) {
  background-color: var(--vv-color-control-on-image);
  color: var(--vv-color-text-on-image);
}
/* The dots sit 20 under the photograph, outside it (user, 2026-11-11); a
   hero's, over a full-screen photograph, stay inside it (r-hero.css,
   and the Home Destinations showcase, r-card-grid.css). */
.vv-slides { position: relative; }
.vv-slides__dots { justify-content: center; margin-top: var(--vv-gap-20); }   /* centred under the photograph, as a rail's */
.vv-band--r-hero .vv-slides__dots,
.vv-r-card-grid__show .vv-slides__dots {
  position: absolute;
  bottom: var(--vv-slides-dots-bottom);
  left: 50%;
  z-index: 1;
  margin: 0;
  color: var(--vv-color-control-dot);
  transform: translateX(-50%);
}
.vv-slides .vv-slides__arrow:focus-visible {
  outline: 2px solid var(--vv-color-text-on-image);
  outline-offset: 2px;
}
@media (prefers-reduced-motion: reduce) {
  .vv-slides__track.is-moving { transition: none; }
}

/* ---------- Rail ----------
   A horizontal scroller with the live carousel's controls: a white disc
   with a chevron at each end, optional page dots. Shared by card-grid and
   media-set; items set their own width.

   Measured off the live Overviews: the controls overlay the track with 20px
   of inset, arrows 30x30, vertically centred. An arrow at the end of travel
   fades out and stops taking the pointer. Pages divide the overflow evenly
   (0 / 575 / 1149 / 1724 for four 748px slides in 1300px), so the last
   page is the end of the rail. Dots sit 10px off the bottom, centred; the
   current one is opaque, the rest 50%. No hover states — the live site has
   none. js/vv-rail.js drives them; without it the track still scrolls. */
.vv-rail { position: relative; }

.vv-rail__track {
  display: flex;
  margin: 0;
  padding: 0;
  list-style: none;
  overflow-x: auto;
  scrollbar-width: none;
  /* Every carousel moves as the Destinations showcase does (user,
     2026-10-16): the track follows the finger, and a swipe settles on the
     next item, one at a time. Mouse drag is js/vv-rail.js. */
  scroll-snap-type: x mandatory;
  overscroll-behavior-x: contain;
}
.vv-rail__track::-webkit-scrollbar { display: none; }
.vv-rail__track:focus-visible { outline: 2px solid var(--vv-color-focus); outline-offset: 4px; }
.vv-rail:has(.vv-rail__controls:not([hidden])) .vv-rail__track { cursor: grab; }
.vv-rail__track.is-dragging { scroll-snap-type: none; cursor: grabbing; user-select: none; }
.vv-rail__track.is-dragging * { pointer-events: none; }

.vv-rail__item { flex: none; scroll-snap-align: start; scroll-snap-stop: always; }

.vv-rail__controls {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 20px;
  pointer-events: none;
}
.vv-rail__controls[hidden] { display: none; }

.vv-rail__arrow {
  display: grid;
  place-items: center;
  width: 30px;
  height: 30px;
  padding: 0;
  border: 0;
  border-radius: 40px;
  background-color: var(--vv-color-control-bg);
  color: var(--vv-color-control-icon);
  cursor: pointer;
  pointer-events: auto;
  transition: opacity var(--vv-motion-fade-duration) var(--vv-motion-fade-easing);
}
/* The live chevron is a 16px SVG drawn in a 30x30 <img> with object-fit
   fill, so it is stretched to the whole disc — stroke ~2.5px. A 16px icon
   here reads visibly smaller and thinner. */
.vv-rail__arrow svg { width: 100%; height: 100%; }
/* At either end the arrow fades out and stops taking the pointer, as live.
   It stays in the tab order and, while it has keyboard focus, stays
   visible: an invisible focused control leaves a keyboard user lost. */
.vv-rail__arrow[aria-disabled="true"] { pointer-events: none; }
.vv-rail__arrow[aria-disabled="true"]:not(:focus-visible) { opacity: 0; }
.vv-rail__arrow:focus-visible { outline: 2px solid var(--vv-color-focus); outline-offset: 2px; }

/* Dots: under the rail, outside it (user, 2026-11-11), centred 20 below,
   dark on the band's ground. The room for them is reserved only while the
   controls show; a rail that fits keeps no empty strip. */
.vv-rail--dots-below { --vv-dots-room: calc(var(--vv-gap-20) + var(--vv-gap-25)); }   /* 20 over the dots' 25 row */
.vv-rail--dots-below:has(.vv-rail__controls:not([hidden])) { padding-bottom: var(--vv-dots-room); }
.vv-rail--dots-below .vv-rail__controls { bottom: var(--vv-dots-room); }
.vv-rail__dots {
  position: absolute;
  top: 100%;
  left: 50%;
  margin-top: var(--vv-gap-20);
  transform: translateX(-50%);
}

/* ---------- Dots ----------
   vv_dots() (helpers.php), every carousel's, as Six Senses' (measured on
   sixsenses.com at 375, user 2026-11-11): a 5px solid dot per slide, the
   current one a filled 10px dot (user, 2026-11-11: filled, not a ring), all
   at full strength, close together. An indicator, not a control (aria-hidden; the arrows and a swipe
   move the carousel). Grey on the ground; a hero's, over its photograph,
   white. Each dot is one 10px circle: the others are it filled and scaled
   to half, the current one at full size — transform only. */
.vv-dots {
  display: flex;
  align-items: center;
  gap: var(--vv-gap-5);
  height: 25px;
  color: var(--vv-color-control-dot-on-light);
  pointer-events: none;
}
.vv-dots__dot {
  box-sizing: border-box;
  flex: none;
  width: 10px;
  height: 10px;
  border-radius: 50%;
  background-color: currentColor;
  transform: scale(0.5);
  transition: transform var(--vv-motion-toggle-duration) var(--vv-motion-toggle-easing);
}
.vv-dots__dot[hidden] { display: none; }
.vv-dots__dot[aria-current="true"] { transform: none; }
@media (prefers-reduced-motion: reduce) {
  .vv-dots__dot { transition: none; }
}

/* ---------- Icon link ----------
   vv_icon_link() (inc/chrome.php): the icon and the words are two elements
   in one container, never the icon inside the link (user, 2026-10-06). The
   link's ::after covers the container, so the row hovers, focuses and taps
   as one element; the icon takes the container's colour. Type and colour
   come from the role class on the container; the link inherits them unless
   it carries a shared link style of its own (.vv-textlink). */
.vv-icon-link {
  position: relative;
  display: inline-flex;
  align-items: center;
  gap: var(--vv-gap-10);
}
.vv-icon-link { max-width: 100%; }
.vv-icon-link > .vv-icon { flex: none; }
.vv-icon-link__text { min-width: 0; overflow-wrap: break-word; }   /* a long email breaks after its @ (vv_icon_link()), inside the gutter */
/* An email stays on one line wherever it fits and breaks after its @ only
   where it does not: no width of its own (user, 2026-11-11; from 2026-10-23
   it was min-content on a phone, which broke it even with room to spare). */
.vv-icon-link__text:not(.vv-textlink) { color: inherit; text-decoration: none; }
/* The container carries the hover transition and the words inherit its colour.
   GeneratePress gives every <a> its own 100ms colour transition; on an inheriting
   child that restarts every frame and the words trail the icon. */
.vv-icon-link__text { transition: none; }
a.vv-icon-link__text::after { content: ""; position: absolute; inset: 0; }
a.vv-icon-link__text:focus-visible { outline: none; }
/* A linked contact line is a plain link, not a text CTA: no underline. On hover
   the whole line (icon and words) goes to the lighter dim colour, as the header's
   contact links (user, 2026-10-10); the header and footer keep their own. */
.vv-icon-link:not(.vv-footer-p__contact, .vv-header-contact):has(> a.vv-icon-link__text:not(.vv-textlink)) { transition: color var(--vv-motion-hover-duration) var(--vv-motion-hover-easing); }
.vv-icon-link:not(.vv-footer-p__contact, .vv-header-contact):has(> a.vv-icon-link__text:not(.vv-textlink)):hover { color: var(--vv-color-text-dim); }
.vv-icon-link:has(> a:focus-visible) { outline: 2px solid var(--vv-color-focus); outline-offset: 3px; }

/* ---------- Document rows ----------
   vv_document_list() (blocks/_shared/helpers.php): every PDF to download, the
   Documents band and the split's brochure link alike (user, 2026-11-11). Rows
   between hairlines, a line over the first and under each, as the FAQ; the
   label left, the download icon right, no inline inset so the label keeps the
   column's left edge (§7). The deeper terracotta on hover or focus, over the
   icon link's own dimming above, hence the longer selector. */
.vv-doc-list { margin: 0; padding: 0; list-style: none; }
.vv-doc-list > li { margin: 0; border-block-end: 1px solid var(--vv-color-line); }
.vv-doc-list > li:first-child { border-block-start: 1px solid var(--vv-color-line); }
.vv-doc-row {
  display: flex;
  flex-direction: row-reverse;   /* vv_icon_link() puts the icon first; it sits at the row's end */
  align-items: center;
  justify-content: space-between;
  gap: var(--vv-gap-20);
  padding-block: var(--vv-gap-15);
  width: 100%;
  color: var(--vv-color-text-default);
}
.vv-doc-list.vv-doc-list > li > .vv-doc-row.vv-icon-link:has(> a):is(:hover, :focus-within) { color: var(--vv-color-text-cta-hover); }
.vv-doc-row__icon { flex: none; }

/* ---------- Text link ----------
   The underline link: uppercase medium, 3% tracking, Text-Emphasised, no arrow, on a 2px Element
   Highlight line. On hover neither the text nor the line changes colour: the
   line draws in from both ends to 70% of its width (user, 2026-10-10; the
   same for every one, the Home showcase's included). Transform only. Type:
   the Link style in vv-type.css. */
.vv-textlink {
  position: relative;
  display: inline-flex;
  align-items: center;
  padding-block: var(--vv-gap-5);
  color: var(--vv-color-text-default);
  text-decoration: none;
}
.vv-textlink::after {
  content: "";
  position: absolute;
  inset: auto 0 0 0;
  height: var(--vv-link-underline);
  background-color: var(--vv-color-element-highlight);
  transform-origin: center;
  transition: transform var(--vv-motion-hover-duration) var(--vv-motion-hover-easing);
}
.vv-textlink:hover::after, .vv-textlink:focus-visible::after, .vv-textlink.is-hover::after { transform: scaleX(0.7); }
.vv-textlink.is-focus { outline: 2px solid var(--vv-color-focus); outline-offset: 3px; }
/* As a <button> (Load More, vv_textlink() with 'attrs'): no button chrome,
   over the theme's global button, button:hover and button:focus. */
button.vv-textlink,
button.vv-textlink:hover,
button.vv-textlink:focus {
  padding-inline: 0;
  border: 0;
  border-radius: 0;
  background: none;
  color: var(--vv-color-text-default);
  cursor: pointer;
}
button.vv-textlink:focus-visible { outline: 2px solid var(--vv-color-focus); outline-offset: 3px; }
/* In an icon row (Our Villages' Get Directions) the row's cover must not take
   the underline's place: the line stays under the words. */
a.vv-icon-link__text.vv-textlink::after { inset: auto 0 0 0; }

/* The card link — "Explore Room", "Explore Alleppey", "More about weddings":
   spacing only, its 2px underline directly under the line. No margin of its
   own: the component sets the gap above it (25 under the description), which
   an 8px margin here used to push to 33 (user, 2026-11-11). Its look and
   hover are the text link's: one link component (design-principles.md §7a,
   user 2026-10-07). */
.vv-textlink--card {
  padding-block: 0 2px;
}

/* ---------- Media frame ----------
   A fixed ratio box, so images never shift layout. Reveal settles use
   `transform`; hover zooms use the separate `scale` property so the two
   compose instead of fighting. */
.vv-frame {
  position: relative;
  overflow: hidden;
  isolation: isolate;
  aspect-ratio: var(--vv-frame-ratio, var(--vv-ratio-landscape));
  /* 8px, as the photographs in Figma 874:1419 (user, 2026-09-17). */
  border-radius: var(--vv-radius-md);
  background-color: var(--vv-color-background);
}
.vv-frame--portrait { --vv-frame-ratio: var(--vv-ratio-portrait); }
.vv-frame--landscape { --vv-frame-ratio: var(--vv-ratio-landscape); }
.vv-frame--standard { --vv-frame-ratio: var(--vv-ratio-standard); }
.vv-frame--wide { --vv-frame-ratio: var(--vv-ratio-wide); }
.vv-frame--square { --vv-frame-ratio: var(--vv-ratio-square); }
.vv-frame__img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
}
.vv-frame--scrim::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 1;
  background: var(--vv-scrim);
  pointer-events: none;
}

/* ---------- On image ----------
   Text set over a photograph behind a scrim (page hero, image tiles). Like
   the olive tone, it re-points the text, link and focus roles to their
   on-emphasised values. Unlike the olive tone it leaves the CTA roles alone,
   so a Primary button keeps its rust fill on a photograph (Figma 874:1419).
   Pairings: docs/design-system/color.md §4. */
.vv-on-image {
  --vv-color-text-default: var(--vv-color-text-on-emphasised);
  --vv-color-text-emphasised: var(--vv-color-text-on-emphasised);
  --vv-color-text-cta: var(--vv-color-text-highlight-on-emphasised);
  --vv-color-text-cta-hover: var(--vv-color-text-on-emphasised);
  --vv-color-text-highlight: var(--vv-color-text-highlight-on-emphasised);
  --vv-color-focus: var(--vv-color-text-on-emphasised);
}

/* ---------- Meta chips (label · value) ----------
   Type: Body small (vv-type.css). */
.vv-chips {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--vv-gap-10) var(--vv-gap-15);
}
.vv-chip { display: inline-flex; align-items: baseline; gap: 0.4em; }
.vv-chip + .vv-chip::before {
  content: "";
  align-self: center;
  width: 4px;
  height: 4px;
  margin-inline-end: var(--vv-gap-10);
  border-radius: 50%;
  background-color: var(--vv-color-element-highlight);
}
.vv-chip__label { color: var(--vv-color-text-default); }
.vv-chip__value { color: var(--vv-color-text-default); }

/* ---------- Prose (editor-entered rich text inside a section) ----------
   Headings, quotes and captions inside it map onto the type roles in
   vv-type.css. */
.vv-prose > * + * { margin-block-start: var(--vv-gap-15); }
.vv-prose :where(ul:not([class]), ol:not([class])) { padding-inline-start: 1.15em; }
.vv-prose :where(ul:not([class])) { list-style: disc; }
.vv-prose :where(li)::marker { color: var(--vv-color-element-highlight); }

/* ==========================================================================
   Motion vocabulary. See vv-motion.js and docs/design-system/motion.md.
   Hidden start states apply only under html.vv-motion (set in wp_head), so
   content can never be stranded invisible if JS doesn't run.
   ========================================================================== */
.vv-motion [data-vv-reveal] {
  transition-property: transform, opacity;
  transition-duration: var(--vv-motion-reveal-duration);
  transition-timing-function: var(--vv-motion-reveal-easing);
  transition-delay: calc(var(--vv-i, 0) * var(--vv-motion-stagger) + var(--vv-reveal-delay, 0ms));
}

/* One reveal, quiet (user, 2026-10-10): content fades in while it drifts up
   16px. Every variant name (rise, rise-sm, appear, appear-scale, fade, words,
   lines) is this same reveal; nothing splits into words or lines. */
.vv-motion [data-vv-reveal]:not([data-vv-reveal="mask"]):not(.is-revealed) {
  opacity: 0;
  transform: translate3d(0, var(--vv-shift-sm), 0);
}

/* Photographs only fade in; the frame drops its placeholder fill so no empty
   box shows before the image. */
.vv-motion [data-vv-reveal="mask"] { background-color: transparent; }
.vv-motion [data-vv-reveal="mask"] > .vv-frame__img {
  transition: opacity var(--vv-motion-reveal-duration) var(--vv-motion-reveal-easing);
  transition-delay: calc(var(--vv-i, 0) * var(--vv-motion-stagger) + var(--vv-reveal-delay, 0ms));
}
.vv-motion [data-vv-reveal="mask"]:not(.is-revealed) > .vv-frame__img { opacity: 0; }

/* Used by vvMotion.replay() to snap back to the hidden state instantly. */
.vv-no-transition,
.vv-no-transition * { transition: none !important; }

/* ==========================================================================
   Ratings — kept quiet: each source's name in small sans over its five
   marks, the ratings side by side at every width; the score is read out,
   not shown (user, 2026-11-11; replaces marks-over-score of 2026-10-11).
   vv_ratings() / vv_stars() in helpers.php.
   ========================================================================== */

/* The marks are 14 tall, 12 on a phone. */
:root { --vv-rating-mark-h: 14px; }
@media (max-width: 809.98px) {
  :root { --vv-rating-mark-h: 12px; }
}

.vv-ratings {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--vv-gap-10);
}

.vv-ratings__list {
  display: flex;
  flex-wrap: nowrap;
  justify-content: center;
  gap: var(--vv-gap-30);
  margin: 0;
  padding: 0;
  list-style: none;
}
@media (max-width: 809.98px) {
  .vv-ratings__list { gap: var(--vv-gap-20); }
}

.vv-ratings__item {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--vv-gap-5);
  white-space: nowrap;
}

.vv-ratings__score { margin: 0; color: var(--vv-color-text-subtle); }

/* The marks: an outline row, and over it the same row filled, clipped to the
   score. Gold stars; green circles for Tripadvisor. */
.vv-ratings__stars {
  position: relative;
  display: block;
  line-height: 0;
  color: var(--vv-color-element-highlight);
}
.vv-ratings__stars--bubble { color: var(--vv-color-rating-tripadvisor); }
.vv-ratings__marks { display: block; width: auto; height: var(--vv-rating-mark-h); fill: none; stroke: currentColor; stroke-width: 1; overflow: visible; }
.vv-ratings__fill {
  position: absolute;
  inset: 0 auto 0 0;
  width: var(--vv-rating-fill);
  overflow: hidden;
}
.vv-ratings__fill .vv-ratings__marks { fill: currentColor; }

/* "Read more": one paragraph cut to two lines, the link inline at the
   end of the second. js/vv-more.js builds the teaser; until it
   has, two lines are held so the band does not jump. Without script the whole
   text shows. Opened, the teaser goes and the paragraphs follow in the same
   10 rhythm. */
@media (scripting: enabled) {
  [data-vv-more]:not(.is-ready, .is-open) { max-height: 2lh; overflow: hidden; }
}
[data-vv-more].is-teaser > :not(.vv-more__teaser) { display: none; }
.vv-more__teaser { margin: 0; }
/* Quiet: the paragraph's own type, underlined, no capitals or rule. */
.vv-more__link {
  all: unset;
  cursor: pointer;
  color: var(--vv-color-text-default);
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-underline-offset: 3px;
  text-decoration-color: var(--vv-color-element-highlight);
  transition:
    color var(--vv-motion-hover-duration) var(--vv-motion-hover-easing),
    text-decoration-color var(--vv-motion-hover-duration) var(--vv-motion-hover-easing);
}
.vv-more__link[hidden] { display: none; }
.vv-more__link:hover,
.vv-more__link:focus,
.vv-more__link:focus-visible {
  background: none;   /* over the theme's global button:hover / button:focus */
  color: var(--vv-color-text-cta);
  text-decoration-color: var(--vv-color-text-cta);
}
.vv-more__link:focus-visible { outline: 2px solid var(--vv-color-focus); outline-offset: 3px; }

/* ---------- Closing frame (vv_closing_open()) ----------
   The page's closing: one white card — the contact card, the newsletter
   inside it, 50 below, when it follows — set on the brand pattern,
   which shows 100 above, below and either side of it; 50 above and below and
   25 either side on a phone (user, 2026-10-10). The frame is the container's
   card's width less the frame; the pattern runs the full width of the page
   (user, 2026-10-10), so the card lines up with the gutter on a phone. The section gap of page ground
   above it; none below, the footer follows directly (user, 2026-10-11).
   The pattern is one seamless 223px tile cut from the brand pattern
   (images/closing-pattern-tile.png), drawn at the scale the full image had
   at 1440 (223 × 1440 / 3843) so the motif keeps its size. */
.vv-closing {
  position: relative;
  isolation: isolate;
  margin-block: var(--vv-section-y) 0;
  /* The pattern runs the page's full width; the card keeps the container's
     width less the frame (user, 2026-10-10). */
  padding: var(--vv-closing-frame-y) calc(max(0px, (100% - var(--vv-container)) / 2) + var(--vv-closing-frame-x));
}
.vv-closing::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  background: url("images/closing-pattern-tile.png") center top / 83.56px repeat;
  opacity: var(--vv-color-pattern-opacity);   /* 1; 0.5 in the dark scheme */
}
.vv-closing__card {
  padding: var(--vv-gap-50) var(--vv-closing-card-x);
  background-color: var(--vv-color-background-highlight);
}
/* The bands inside take the card's ground and inset, none of their own. */
.vv-closing .vv-band.vv-band {
  margin-block: 0;
  padding: 0;
  background-color: transparent;
}
.vv-closing .vv-band.vv-band + .vv-band.vv-band {
  margin-block-start: var(--vv-gap-50);    /* 50 between the two sections, no hairline (user, 2026-10-10) */
  padding-block-start: 0;
}

:root {
  --vv-closing-frame-y: var(--vv-gap-100);
  --vv-closing-frame-x: var(--vv-gap-100);
  --vv-closing-card-x: var(--vv-gap-50);
}
@media (max-width: 809.98px) {
  :root {
    --vv-closing-frame-y: var(--vv-gap-50);
    --vv-closing-frame-x: var(--vv-gap-25);
    --vv-closing-card-x: var(--vv-gap-25);
  }
}

/* ---------- Stats (vv_stats()) ----------
   A room's or an experience's stats: spaced out across 480 (400 on a
   tablet), each a live line illustration in a 48 box, the label 10 under it;
   a column 40 apart on a phone. A lone stat (an experience's duration) sits
   at --vv-stats-align (centred by default). The component sets the margin. */
.vv-stats {
  display: flex;
  justify-content: space-between;
  width: 480px;
  max-width: 100%;
  align-self: var(--vv-stats-align, center);
  margin: 0;
  padding: 0;
  list-style: none;
}
.vv-stats:has(> :only-child) { justify-content: var(--vv-stats-align, center); }
.vv-stats__stat {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--vv-gap-10);
}
.vv-stats__icon {
  display: grid;
  place-items: center;
  height: 48px;
}
.vv-stats__icon img { display: block; }
@media (max-width: 1200.98px) and (min-width: 810px) {
  .vv-stats { width: 400px; }
}
@media (max-width: 809.98px) {
  .vv-stats {
    flex-direction: column;
    align-items: center;
    gap: var(--vv-gap-40);
    width: auto;
  }
}
