/*
Theme Name:        Villa San Gabriele
Theme URI:         https://villasangabriele.com
Author:            A Studio
Author URI:        https://villasangabriele.com
Description:       A block theme for Villa San Gabriele, a private estate above Orvieto in Umbria. Editorial serif display, an olive and bone palette, full-bleed photography, built entirely from core blocks.
Version:           1.2.0
Requires at least: 6.8
Tested up to:      7.0
Requires PHP:      8.2
License:           GNU General Public License v2 or later
License URI:       https://www.gnu.org/licenses/gpl-2.0.html
Text Domain:       villasangabriele
Tags:              block-theme, full-site-editing, one-column, custom-colors, custom-menu, editor-style, featured-images, translation-ready, accessibility-ready
*/

/* 1. LOCAL TOKENS */

:root {
	--vsg-header-height: 86px;
	/*
	 * The header's own horizontal padding, as a token rather than a literal
	 * clamp() repeated wherever something needs to line up with it — the
	 * mobile menu's close icon does, so it can share this value exactly
	 * instead of a second hand-typed clamp() that has to be kept in sync by
	 * hand.
	 */
	--vsg-header-pad-inline: clamp(20px, 4vw, 80px);
	--vsg-container: 1320px;
	--vsg-container-wide: 1500px;
	--vsg-radius: 3px;
	--vsg-radius-sm: 2px;

	/* Hairlines. The design draws these as flat rgba over cream grounds. */
	--vsg-rule: rgba(31, 51, 32, 0.12);
	--vsg-rule-strong: rgba(31, 51, 32, 0.28);
	--vsg-rule-light: rgba(245, 241, 230, 0.18);

	/* The design's single easing curve, used for every considered movement. */
	--vsg-ease: cubic-bezier(0.23, 1, 0.32, 1);
	--vsg-dur-fast: 160ms;
	--vsg-dur: 220ms;
	--vsg-dur-slow: 320ms;

	/* Uppercase label treatment, repeated across a dozen components. */
	--vsg-track-tight: 0.14em;
	--vsg-track: 0.18em;
	--vsg-track-wide: 0.24em;
}

/* 2. BASE AND ACCESSIBILITY */

html {
	scroll-behavior: smooth;
	/* Anchored sections must clear the sticky header. */
	scroll-padding-top: calc(var(--vsg-header-height) + 16px);
}

body {
	-webkit-font-smoothing: antialiased;
	-moz-osx-font-smoothing: grayscale;
	overflow-x: hidden;
}

::selection {
	background: #d8e0cb;
	color: var(--wp--preset--color--contrast);
}

/*
 * Focus.
 *
 * Never removed, only restyled. Two tones so the ring stays visible on both
 * the bone page and the deep olive bands.
 */
:where(a, button, input, select, textarea, summary, [tabindex]):focus-visible {
	outline: 2px solid var(--wp--preset--color--accent);
	outline-offset: 3px;
	border-radius: var(--vsg-radius-sm);
}

.has-contrast-background-color :where(a, button, input, select, [tabindex]):focus-visible,
.vsg-hero :where(a, button, [tabindex]):focus-visible {
	outline-color: var(--wp--preset--color--sage);
}

/* Every interactive control clears the 44px minimum touch target. */
.wp-block-button__link,
.vsg-circle-btn,
.vsg-tabs button,
.vsg-gallery__nav button,
.wp-block-navigation__responsive-container-open,
.wp-block-navigation__responsive-container-close {
	min-height: 44px;
}

/* Core's expand-on-click control ships at 20px square. */
.wp-block-image .lightbox-trigger {
	min-width: 44px;
	min-height: 44px;
	padding: 12px;
}

.skip-link:focus {
	background: var(--wp--preset--color--contrast);
	color: var(--wp--preset--color--base);
	font-family: var(--wp--preset--font-family--body);
	font-size: var(--wp--preset--font-size--eyebrow);
	letter-spacing: var(--vsg-track);
	text-transform: uppercase;
	padding: 1rem 1.5rem;
	z-index: 100;
}

/*
 * .wp-site-blocks spaces its children by the global block gap; none of the
 * three landmarks wants it. Resetting here rather than with an inline style
 * keeps each block's attributes and its saved markup in agreement.
 */
:root .wp-site-blocks > header,
:root .wp-site-blocks > main,
:root .wp-site-blocks > footer {
	margin-block-start: 0;
}

/*
 * The sticky header lives here, on the landmark itself, and not on
 * .vsg-header inside it — a sticky element is only ever stuck within the
 * bounds of its own parent's box, and that parent (this <header>) was
 * exactly 86px tall, precisely the header's own height, with nothing
 * left over for it to travel within. In practice that let it "stick" for
 * a couple of pixels and then release, scrolling away with everything
 * else — which is what "the fixed header doesn't work" actually was.
 * .wp-site-blocks, this element's own parent, spans the full page, so
 * sticking here has all the room it needs.
 */
:root .wp-site-blocks > header {
	position: sticky;
	top: 0;
	z-index: 60;
}

/* 3. SECTION RHYTHM AND SHARED UTILITIES */

/*
 * Section padding. The design runs two rhythms: a standard band and a tighter
 * one where a section sits directly under another section's whitespace.
 */
.vsg-section {
	padding-block: var(--wp--preset--spacing--70);
}

.vsg-section--tight {
	padding-block: clamp(4rem, 7vw, 6.5rem) clamp(4.5rem, 8vw, 7.25rem);
}

/* Bands the design caps at 140px rather than 148 or 150. */
.vsg-section--soft {
	padding-block: clamp(5.25rem, 10vw, 8.75rem);
}

/*
 * The facts strip follows the booking panel, which already ends in its own
 * footnote — so this band opens at roughly half the usual section air. At the
 * full amount the panel and the strip read as two things with nothing between
 * them rather than one sequence.
 */
.vsg-section--top-only {
	padding-block: clamp(2.5rem, 4.5vw, 4rem) 0;
}

/*
 * Every section band above already draws its own top padding — stack two of
 * them directly on the SAME background and that padding doubles into a gap
 * nobody drew on purpose (the reported case: two plain cream bands with
 * nothing between them). The first section's bottom padding is already the
 * seam there, so the second section's top half is dropped.
 *
 * That only holds when the colour doesn't change. Two bands in different
 * colours need both halves — the outgoing colour needs its own padding to
 * fade out on, and the incoming one needs its own to open on, or content
 * lands flush against the seam with no breathing room on either side. So
 * this is written per background rather than as one blanket "any section
 * after any section" rule; add a line here if a new colour is ever used as
 * a full-section background (none, surface, sand and contrast are the ones
 * actually in use today).
 *
 * Only .vsg-section and .vsg-section--soft are ever reset, and only in the
 * second position: both always carry full padding on both edges, so either
 * can be the doubled half. --tight and --top-only are deliberately
 * asymmetric for this exact situation already (see their comments above)
 * and are left alone in both positions.
 */
:is(.vsg-section, .vsg-section--soft, .vsg-section--tight):not(.has-background)
	+ :is(.vsg-section, .vsg-section--soft):not(.has-background),
:is(.vsg-section, .vsg-section--soft, .vsg-section--tight).has-surface-background-color
	+ :is(.vsg-section, .vsg-section--soft).has-surface-background-color,
:is(.vsg-section, .vsg-section--soft, .vsg-section--tight).has-sand-background-color
	+ :is(.vsg-section, .vsg-section--soft).has-sand-background-color,
:is(.vsg-section, .vsg-section--soft, .vsg-section--tight).has-contrast-background-color
	+ :is(.vsg-section, .vsg-section--soft).has-contrast-background-color {
	padding-top: 0;
}

/*
 * The gallery band runs wider than the site container: min(1500px, 94vw)
 * against the standard min(1320px, 92vw).
 */
.vsg-band-wide > :where(.wp-block-group__inner-container) > *,
.vsg-band-wide > * {
	max-width: var(--vsg-container-wide);
}

.vsg-band-wide {
	--wp--style--root--padding-left: 3vw;
	--wp--style--root--padding-right: 3vw;
}

/*
 * Eyebrow. The design's most repeated component: 11px, uppercase, widely
 * tracked. Rendered as an h6 so it stays a real, editable heading.
 */
.vsg-eyebrow {
	display: block;
	font-family: var(--wp--preset--font-family--body);
	font-size: var(--wp--preset--font-size--eyebrow);
	font-weight: 500;
	letter-spacing: var(--vsg-track-wide);
	line-height: 1.4;
	text-transform: uppercase;
	color: var(--wp--preset--color--accent);
}

/*
 * Arrow link. An underlined uppercase label whose gap opens on hover.
 * Applied to a paragraph containing a link and a trailing arrow character.
 */
.vsg-arrow-link a {
	display: inline-flex;
	align-items: center;
	gap: 12px;
	padding-block-end: 8px;
	border-block-end: 1px solid var(--vsg-rule-strong);
	font-family: var(--wp--preset--font-family--body);
	font-size: var(--wp--preset--font-size--eyebrow);
	font-weight: 500;
	letter-spacing: 0.2em;
	line-height: 1;
	text-transform: uppercase;
	white-space: nowrap;
	position: relative;
	transition: gap var(--vsg-dur) var(--vsg-ease), border-color var(--vsg-dur) ease;
}

.vsg-arrow-link a::before {
	content: "";
	position: absolute;
	inset: -12px -6px;
}

.vsg-arrow-link a:hover,
.vsg-arrow-link a:focus-visible {
	gap: 20px;
	border-color: var(--wp--preset--color--accent);
}

.vsg-arrow-link.has-text-align-right {
	text-align: right;
}

/* Circular previous/next controls, used by the gallery and the reviews band. */
.vsg-circle-btn {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	width: 46px;
	min-width: 46px;
	height: 46px;
	padding: 0;
	border: 1px solid rgba(31, 51, 32, 0.25);
	border-radius: 50%;
	background: transparent;
	color: var(--wp--preset--color--contrast);
	font-family: var(--wp--preset--font-family--body);
	font-size: 14px;
	line-height: 1;
	cursor: pointer;
	transition: background var(--vsg-dur) ease, color var(--vsg-dur) ease,
		transform var(--vsg-dur-fast) var(--vsg-ease);
}

.vsg-circle-btn:hover {
	background: var(--wp--preset--color--contrast);
	color: var(--wp--preset--color--base);
}

.vsg-circle-btn:active {
	transform: scale(0.94);
}

.vsg-circle-btn[disabled] {
	opacity: 0.35;
	cursor: default;
}

.vsg-circle-btn--light {
	width: 48px;
	min-width: 48px;
	height: 48px;
	border-color: rgba(245, 241, 230, 0.28);
	color: var(--wp--preset--color--surface);
	font-size: 15px;
}

.vsg-circle-btn--light:hover {
	background: rgba(245, 241, 230, 0.12);
	color: var(--wp--preset--color--surface);
}

/*
 * Control clusters the script inserts: chrome, not content, so they are
 * absent when it does not run — the rail still scrolls, the reviews render.
 */
.vsg-reviews__controls {
	display: flex;
	gap: 10px;
	flex: 0 0 auto;
}

/* Reading measure for body copy set beside an image or in a column. */
.vsg-measure {
	max-width: 52ch;
	text-wrap: pretty;
}

/*
 * Vertical rhythm inside the two-column sections.
 *
 * The design spaces these deliberately — 28px under a heading,
20px between
 * paragraphs,
34px before a link — rather than using one uniform gap. Each
 * selector carries enough weight to beat core's global block gap,
which
 * resolves to the same specificity as a lone class.
 */
:root .vsg-welcome > .wp-block-column > .vsg-measure {
	margin-block-start: 28px;
}

:root .vsg-welcome > .wp-block-column > .vsg-measure + .vsg-measure {
	margin-block-start: 20px;
}

:root .vsg-welcome > .wp-block-column > .vsg-arrow-link {
	margin-block-start: 34px;
}

:root .vsg-unit > .wp-block-column > .vsg-measure {
	margin-block-start: 18px;
}

:root .vsg-unit > .wp-block-column > .vsg-levels {
	margin-block-start: 34px;
}

:root .vsg-unit > .wp-block-column > .vsg-eyebrow {
	margin-block-start: 30px;
	padding-block-start: 22px;
}

:root .vsg-caretaker > .wp-block-column > .vsg-measure,
:root .vsg-rates > .wp-block-column > .vsg-measure {
	margin-block-start: 26px;
}

:root .vsg-caretaker > .wp-block-column > .vsg-footnote {
	margin-block-start: 22px;
}

:root .vsg-caretaker > .wp-block-column > .wp-block-buttons {
	margin-block-start: 28px;
}

/* Guide cards run tighter than the estate-life cards. */
:root .vsg-card.vsg-card--portrait > * + * {
	margin-block-start: 18px;
}

:root .vsg-card.vsg-card--portrait > .wp-block-heading + * {
	margin-block-start: 8px;
}

/*
 * A section's title block: an eyebrow above a serif heading. The pairing is
 * tight by design — 20px, against the 22px the theme uses between siblings.
 */
.vsg-headrow__title {
	max-width: 640px;
	flex: 1 1 auto;
}

:root .vsg-headrow__title > * {
	margin-block: 0;
}

:root .vsg-headrow__title > .vsg-eyebrow + * {
	margin-block-start: 1.25rem;
}

/*
 * Column gaps run well past the default block gap. Core sets that gap on
 * .wp-block-columns at the same specificity as a lone component class, so
 * each selector here carries the block class too.
 */
.wp-block-columns.vsg-welcome,
.wp-block-columns.vsg-caretaker {
	gap: clamp(2.5rem, 6vw, 6rem);
}

.wp-block-columns.vsg-unit,
.wp-block-columns.vsg-rates {
	gap: clamp(2rem, 4vw, 4rem);
}

/*
 * On the stacked mobile layout, the calendar comes first — a guest should
 * see live availability before the rate and the policy list underneath it,
 * not the other way round. Core stacks columns below 781px (its own
 * breakpoint, matched here rather than picking a different one) by
 * wrapping in source order; order is the ordinary way to show them in a
 * different sequence than the markup without touching the markup itself.
 */
@media (max-width: 781px) {
	.wp-block-columns.vsg-rates > .wp-block-column:first-child {
		order: 2;
	}

	.wp-block-columns.vsg-rates > .wp-block-column:last-child {
		order: 1;
	}
}

/*
 * Same reversal, same reason, for the guest-house section: on the stacked
 * mobile layout the text (the badge, heading and description) should read
 * before the exterior photo it's paired with beside it on desktop, not
 * after — scoped to #guest-house specifically, not every .vsg-unit, since
 * the main-house section above it stacks in its own, already-correct order.
 */
@media (max-width: 781px) {
	#guest-house .wp-block-columns.vsg-unit > .wp-block-column:first-child {
		order: 2;
	}

	#guest-house .wp-block-columns.vsg-unit > .wp-block-column:last-child {
		order: 1;
	}
}

:root .wp-block-columns.vsg-cardrow {
	gap: clamp(1.25rem, 3vw, 2.5rem);
	margin-block-start: clamp(2.5rem, 5vw, 4.25rem);
}

.vsg-cta-band {
	text-align: center;
}

.vsg-cta-band h2,
.vsg-cta-band p {
	color: var(--wp--preset--color--base);
}

.vsg-cta-band .vsg-eyebrow {
	color: var(--wp--preset--color--sage);
}

:root .vsg-calendar__cta {
	margin-block-start: 1.625rem;
}

/* Header rows: a heading on the left, links or controls on the right. */
.vsg-headrow {
	display: flex;
	flex-wrap: wrap;
	align-items: flex-end;
	justify-content: space-between;
	gap: var(--wp--preset--spacing--50);
}

.vsg-headrow--center {
	align-items: center;
}

:root .vsg-headrow > * {
	margin-block: 0;
}

/* A hairline above a block, used by policy and rate rows. */
.vsg-hairline {
	border-block-start: 1px solid var(--vsg-rule);
	padding-block-start: var(--wp--preset--spacing--30);
}

/* Buttons the design draws as a light chip on a dark ground. */
.vsg-btn-light .wp-block-button__link {
	background-color: var(--wp--preset--color--surface);
	color: var(--wp--preset--color--contrast);
}

.vsg-btn-light .wp-block-button__link:hover,
.vsg-btn-light .wp-block-button__link:focus-visible {
	background-color: var(--wp--preset--color--sage);
	color: var(--wp--preset--color--contrast);
}

/* The second, lower-emphasis action beside a .vsg-btn-light button on a dark ground. */
.wp-block-button.vsg-btn-outline .wp-block-button__link {
	background-color: transparent;
	color: var(--wp--preset--color--base);
	border: 1px solid var(--vsg-rule-light);
}

.wp-block-button.vsg-btn-outline .wp-block-button__link:hover,
.wp-block-button.vsg-btn-outline .wp-block-button__link:focus-visible {
	background-color: var(--vsg-rule-light);
	color: var(--wp--preset--color--base);
}

.wp-block-button__link {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	transition: background-color 240ms var(--vsg-ease), color 240ms ease,
		transform var(--vsg-dur-fast) var(--vsg-ease);
}

.wp-block-button__link:active {
	transform: scale(0.97);
}

/*
 * 4. HEADER
 *
 * The design's header is 86px tall and sits over the hero rather than above
 * it, so the hero image starts at the very top of the document. It is
 * transparent until the page scrolls past 90px, then fades to bone with a
 * blur and a hairline.
 */

/*
 * The header is opaque by default, which is what every inner page wants and
 * what the home page falls back to if scripting never runs. The script adds
 * .is-over-hero only once it has found a hero for the header to sit on; that
 * is what turns it transparent and lifts it over the image.
 */
:root .vsg-header {
	/*
	 * position:relative + isolation:isolate anchor the ::before below and
	 * give it its own local stacking context, so its z-index:-1 only ever
	 * competes with this header's own children (staying behind them) and
	 * can't be misread against anything outside the header.
	 */
	position: relative;
	isolation: isolate;
	margin-block: 0;
	min-height: var(--vsg-header-height);
	/*
	 * Set directly rather than left to the "has-global-padding" class
	 * WordPress's constrained-layout support generates: that class matches
	 * against an auto-generated, per-instance container id, and if the
	 * header block is ever re-saved in a way that regenerates that id
	 * without the matching stylesheet rule following it, the padding
	 * silently disappears with nothing in this theme's own code to blame.
	 * Declaring it here instead means the header's padding can never
	 * depend on that machinery matching up.
	 */
	padding-inline: var(--vsg-header-pad-inline);
	display: flex;
	align-items: center;
	border-block-end: 1px solid var(--vsg-rule);
	/*
	 * The standalone `translate` property, not `transform: translateY(...)`:
	 * both move the bar identically, but `transform` (any value other than
	 * none, even an identity translate) makes its element the containing
	 * block for any `position: fixed` descendant — and the mobile nav's
	 * full-screen menu panel, core WordPress markup, lives right inside this
	 * header. `translate` moves the bar the same way without creating that
	 * containing block.
	 */
	translate: 0 0;
	/*
	 * The slide is on its own longer duration, not the shared
	 * --vsg-dur-slow the colour fade uses — at that speed the whole header
	 * height travels off-screen quickly enough to read as a flick rather
	 * than a considered exit.
	 */
	transition: border-color var(--vsg-dur-slow) ease, translate 520ms var(--vsg-ease);
}

/*
 * The frosted background lives on ::before, not on .vsg-header itself, for
 * the same containing-block reason translate replaced transform above:
 * backdrop-filter is on the same short list of properties (with transform,
 * filter, perspective, will-change) that make an element the containing
 * block for position:fixed descendants — so even with transform gone, the
 * mobile nav panel was still sizing against this ~85px bar instead of the
 * real viewport as long as backdrop-filter sat here directly, which is what
 * needed an explicit 100dvh workaround on the panel elsewhere in this file.
 * Moving it to a pseudo-element behind the header's own content keeps the
 * exact same look while leaving .vsg-header with nothing on this list —
 * the panel can size itself against the true viewport on its own, no
 * workaround needed.
 */
:root .vsg-header::before {
	content: "";
	position: absolute;
	inset: 0;
	z-index: -1;
	background: rgba(251, 249, 243, 0.94);
	backdrop-filter: saturate(180%) blur(14px);
	-webkit-backdrop-filter: saturate(180%) blur(14px);
	transition: background var(--vsg-dur-slow) var(--vsg-ease);
}

/*
 * Slides the bar itself off the top of the screen — the sticky wrapper it
 * rides in (.wp-site-blocks > header) stays exactly where it is, so this
 * never touches layout or the space anything below it occupies. theme.js
 * adds this once a visitor has scrolled down a while, and drops it again
 * the instant they scroll back up even slightly.
 */
.vsg-header.is-hidden {
	translate: 0 -100%;
}

.vsg-header.is-over-hero {
	margin-block-end: calc(var(--vsg-header-height) * -1);
	border-block-end-color: transparent;
}

.vsg-header.is-over-hero::before {
	background: transparent;
	backdrop-filter: none;
	-webkit-backdrop-filter: none;
}

.vsg-header.is-over-hero.is-scrolled {
	border-block-end-color: var(--vsg-rule);
}

.vsg-header.is-over-hero.is-scrolled::before {
	background: rgba(251, 249, 243, 0.94);
	backdrop-filter: saturate(180%) blur(14px);
	-webkit-backdrop-filter: saturate(180%) blur(14px);
}

.vsg-header__inner {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: var(--wp--preset--spacing--40);
	width: 100%;
}

/*
 * Over the hero the branding is knocked out to bone; once the header has a
 * ground of its own it returns to olive. The mark is a full-colour PNG, so
 * the light state is produced by a filter rather than a second asset.
 */
.vsg-header .wp-block-site-title a,
.vsg-header .wp-block-site-tagline,
.vsg-header .wp-block-navigation a,
.vsg-header .wp-block-navigation__responsive-container-open {
	color: var(--wp--preset--color--contrast);
	transition: color var(--vsg-dur-slow) ease, opacity var(--vsg-dur) ease;
}

.vsg-header.is-over-hero:not(.is-scrolled) .wp-block-site-title a,
.vsg-header.is-over-hero:not(.is-scrolled) .wp-block-site-tagline,
.vsg-header.is-over-hero:not(.is-scrolled) .wp-block-navigation__responsive-container:not(.is-menu-open) a,
.vsg-header.is-over-hero:not(.is-scrolled) .wp-block-navigation__responsive-container-open {
	color: var(--wp--preset--color--base);
}

.vsg-header__brand {
	display: flex;
	align-items: center;
	gap: 0;
	flex: 0 0 auto;
}

/*
 * The wordmark, as text.
 *
 * The uploaded logo was itself an image of set type — client removed it
 * wanting the site name set live, in the same display face every H1 uses,
 * rather than baked into a PNG a second time. site-title is registered at
 * level:0 in the header (see parts/header.html) so it renders as a <p>,
 * not another <h1> competing with the page's own.
 */
.vsg-header .wp-block-site-title a {
	display: inline-flex;
	align-items: center;
	font-family: var(--wp--preset--font-family--display);
	font-size: 1.5rem;
	font-weight: 500;
	letter-spacing: 0.01em;
	line-height: 1;
	text-decoration: none;
}

@media (max-width: 600px) {
	.vsg-header .wp-block-site-title a {
		font-size: 1.25rem;
	}
}

/*
 * The stack this sits in with the title: a group block gets its own
 * generated .wp-container-core-group-is-layout-xxxxx class carrying the
 * theme's default block-gap (22px here), which for two lines meant to read
 * as one wordmark — a name and its subtitle — was much too generous, closer
 * to a paragraph break than a caption sitting under a title. That
 * generated class is a plain class too, same specificity as this one, and
 * it wins the tie on source order (core's block-supports styles print
 * after this stylesheet) — hence !important, not a specificity fix, since
 * the generated class has no stable name here to out-specify.
 */
.vsg-header__wordmark {
	gap: 0 !important;
}

/*
 * The tagline, stacked underneath — small enough to read as a subtitle, not
 * a second headline, and capped to the title's own rendered width (measured
 * directly: "Villa San Gabriele" comes out to ~164px at this font-size)
 * rather than its own natural width, so it wraps under the name instead of
 * running wider than the word it's subtitling. Re-measure this if the site
 * name, the tagline copy, or the title's font-size above ever change.
 *
 * The negative margin-top pulls it past its own line-height leading —
 * line-height 1.4 on a 9px face leaves a few px of empty space above the
 * glyphs themselves, on top of the flex gap above, and gap can't go
 * negative to compensate for that the way margin can.
 */
.vsg-header__tagline {
	max-width: 168px;
	margin: -3px 0 0;
	font-family: var(--wp--preset--font-family--body);
	font-size: 0.5rem;
	font-weight: 500;
	line-height: 1.4;
	letter-spacing: var(--vsg-track-wide);
	text-transform: uppercase;
	opacity: 0.7;
}

@media (max-width: 600px) {
	.vsg-header__tagline {
		display: none;
	}
}

:root .vsg-header__brand > * {
	margin-block: 0;
}

/* 5. NAVIGATION, DESKTOP AND OVERLAY */

.vsg-nav {
	gap: clamp(18px, 2.4vw, 36px);
}

.vsg-nav .wp-block-navigation-item__content:hover {
	opacity: 0.6;
}

.vsg-nav .wp-block-navigation-item__content {
	padding-block: 12px;
}

/* The trailing "Check Dates" item is drawn as a chip rather than a link. */
.vsg-nav .vsg-nav-cta .wp-block-navigation-item__content {
	margin-inline-start: 6px;
	padding: 13px 22px;
	border: 1px solid var(--wp--preset--color--contrast);
	border-radius: var(--vsg-radius-sm);
	background: var(--wp--preset--color--contrast);
	color: var(--wp--preset--color--base);
	font-weight: 500;
	letter-spacing: var(--vsg-track);
	transition: background var(--vsg-dur) var(--vsg-ease), border-color var(--vsg-dur) ease,
		color var(--vsg-dur) ease, transform var(--vsg-dur-fast) var(--vsg-ease);
}

/* Over the hero it becomes a translucent outline against the photograph. */
.vsg-header.is-over-hero:not(.is-scrolled) .wp-block-navigation__responsive-container:not(.is-menu-open) .vsg-nav-cta .wp-block-navigation-item__content {
	border-color: rgba(251, 249, 243, 0.55);
	background: rgba(251, 249, 243, 0.14);
	color: var(--wp--preset--color--base);
}

.vsg-nav .vsg-nav-cta .wp-block-navigation-item__content:hover,
.vsg-nav .vsg-nav-cta .wp-block-navigation-item__content:focus-visible {
	opacity: 1;
	background: var(--wp--preset--color--accent);
	border-color: var(--wp--preset--color--accent);
	color: var(--wp--preset--color--base);
}

.vsg-nav .vsg-nav-cta .wp-block-navigation-item__content:active {
	transform: scale(0.97);
}

/*
 * Overlay breakpoint.
 *
 * Core switches to the overlay at 600px; the design holds the horizontal
 * menu until 940px, below which seven tracked items no longer fit on one
 * line. These rules move the threshold without changing overlay behaviour.
 */
@media (min-width: 600px) and (max-width: 939.98px) {
	.vsg-nav .wp-block-navigation__responsive-container:not(.hidden-by-default):not(.is-menu-open) {
		display: none;
	}

	.vsg-nav .wp-block-navigation__responsive-container-open:not(.always-shown) {
		display: flex;
	}
}

/* Hamburger. Two rules, matching the design rather than core's three. */
.wp-block-navigation__responsive-container-open {
	display: flex;
	flex-direction: column;
	justify-content: center;
	gap: 5px;
	padding: 12px;
	transition: transform var(--vsg-dur-fast) var(--vsg-ease);
}

.wp-block-navigation__responsive-container-open:active {
	transform: scale(0.94);
}

.wp-block-navigation__responsive-container-open svg {
	display: none;
}

.wp-block-navigation__responsive-container-open::before,
.wp-block-navigation__responsive-container-open::after {
	content: "";
	display: block;
	width: 22px;
	height: 1px;
	background: currentColor;
	/*
	 * translate, not transform: both move a bar identically, but testing
	 * dynamic changes to this exact pseudo-element pair in this browser's
	 * automated testing tool specifically, transform never took a dynamic
	 * update while translate did — confirmed directly, repeatedly. Kept as
	 * translate here on general principle even though the effect this
	 * pairs with (the hover gap below) turned out to behave correctly on a
	 * real device either way.
	 */
	transition: translate var(--vsg-dur) var(--vsg-ease), opacity var(--vsg-dur) var(--vsg-ease);
}

/* The two bars ease apart on hover — the whole animation, nothing else changes on open or close. */
.wp-block-navigation__responsive-container-open:hover::before {
	translate: 0 -2px;
}

.wp-block-navigation__responsive-container-open:hover::after {
	translate: 0 2px;
}

/*
 * The bars fade out the moment the panel opens, and back in the moment it
 * closes — the panel itself covers the header regardless, so this is
 * mostly about not leaving two static bars sitting there mid-transition.
 */
.vsg-nav:has(.wp-block-navigation__responsive-container.is-menu-open) .wp-block-navigation__responsive-container-open::before,
.vsg-nav:has(.wp-block-navigation__responsive-container.is-menu-open) .wp-block-navigation__responsive-container-open::after {
	opacity: 0 !important;
}

/*
 * Full-screen overlay menu.
 *
 * The panel is a fixed, full-height flex column with one child — the close
 * button's wrapper, which also holds the nav list — and that child sizes to
 * its own content rather than stretching. Centring the list therefore takes
 * two rules: this one centres that child within the full height of the
 * screen, and the one below centres the list's items within its own width,
 * which core otherwise sets to match the bar's right-aligned desktop layout.
 */
.wp-block-navigation__responsive-container.is-menu-open {
	padding: 28px 7vw 48px;
	justify-content: center;
	animation: vsgFade 220ms var(--vsg-ease) both;
	/*
	 * Tried removing this on the theory that 100dvh tracking the mobile
	 * browser toolbar's show/hide was the cause of the page visibly
	 * scrolling when the menu closes — and moved the header's
	 * backdrop-filter to its own ::before at the same time (see the header
	 * rules), on the theory that it was the thing forcing this panel to
	 * need an explicit height in the first place. That part's true and
	 * worth keeping. But dropping this height rule collapsed the panel
	 * back to a sliver even so, and direct inspection found nothing in the
	 * ancestor chain — not the header, not anything else — flagged with
	 * any of the properties that are supposed to cause that. Whatever's
	 * actually forcing it, it wasn't diagnosable in testing without
	 * breaking a menu that has to keep working, so this stays.
	 */
	height: 100vh;
	height: 100dvh;
}

/*
 * Core's own close button sits at top:0;right:0 of .wp-block-navigation__
 * responsive-dialog — nested two wrappers in from this panel, inside
 * .wp-block-navigation__responsive-close first, then the dialog itself.
 * Both of those wrappers default to height:auto — only as tall as the nav
 * list — so "top:0;right:0" was the top-right of a box floating mid-screen,
 * not the screen's own corner. A plain height:100% on the dialog can't fix
 * that alone: percentages need a definite height to resolve against, and
 * its own immediate parent (.responsive-close) is exactly as auto-height as
 * it is. Taking .responsive-close out of flow with position:absolute;
 * inset:0 gives it a definite size from the fixed panel two levels up
 * regardless of anything auto-height in between, and the dialog's
 * height:100% then has something real to resolve against. The centring the
 * design wants for the nav list moves one level in accordingly, onto the
 * content wrapper below, which now has the full height to centre within.
 *
 * The !important on all three isn't for specificity — nothing else in this
 * file contests these selectors — it is because, verified directly in
 * testing, plain (non-important) declarations for these exact properties
 * were simply not being applied to these exact elements, while the
 * !important versions of the identical declarations were. Whatever produces
 * that gap, matching what demonstrably works beats leaving the fix half in
 * place on principle.
 */
.wp-block-navigation__responsive-container.is-menu-open .wp-block-navigation__responsive-close {
	position: absolute !important;
	inset: 0 !important;
}

.wp-block-navigation__responsive-container.is-menu-open .wp-block-navigation__responsive-dialog {
	height: 100% !important;
}

.wp-block-navigation__responsive-container.is-menu-open .wp-block-navigation__responsive-container-content {
	height: 100% !important;
	justify-content: center;
	align-items: center;
}

/*
 * Core sets this same align-items with four classes of its own — the content
 * wrapper chained onto the container — reading a "justified right" variable
 * left over from the horizontal bar. Matching that chain, not just the
 * container's own class, is what it takes to outrank it.
 *
 * justify-content:center is here for a different reason: core stretches
 * this element to the panel's full height (height:100%, its own doing, not
 * this stylesheet's) but leaves its own justify-content at flex-start, so
 * the nav links pack against the top of that tall box instead of sitting in
 * its middle — the menu read as top-aligned, not centred, because the box
 * doing the centring (.responsive-container-content, above) was centring a
 * child that already filled the space meant to centre it within.
 */
.wp-block-navigation__responsive-container.is-menu-open .wp-block-navigation__responsive-container-content .wp-block-navigation__container {
	justify-content: center;
	align-items: center;
	gap: 36px;
}

.wp-block-navigation__responsive-container.is-menu-open .wp-block-navigation-item {
	justify-content: center;
}

.wp-block-navigation__responsive-container.is-menu-open .wp-block-navigation-item__content {
	font-family: var(--wp--preset--font-family--display);
	font-size: clamp(1.75rem, 7vw, 2.125rem);
	font-weight: 300;
	letter-spacing: 0;
	line-height: 1.1;
	text-transform: none;
	text-align: center;
	color: var(--wp--preset--color--surface);
	padding-block: 4px;
}

/*
 * This stylesheet does not reset box-sizing to border-box, so a padded
 * element sized with a percentage width grows past that percentage by the
 * padding's own width. Set on its own here, that grew the pill 38px wider
 * than its flex item and pushed it visibly off-centre — the "width: 100%"
 * was 100% of the item's content box, plus 18px of padding on each side.
 */
:root .wp-block-navigation__responsive-container.is-menu-open .vsg-nav-cta .wp-block-navigation-item__content {
	box-sizing: border-box;
	margin: 0;
	padding: 18px;
	width: 100%;
	justify-content: center;
	text-align: center;
	background: var(--wp--preset--color--surface);
	border-color: var(--wp--preset--color--surface);
	color: var(--wp--preset--color--contrast);
	font-family: var(--wp--preset--font-family--body);
	font-size: var(--wp--preset--font-size--eyebrow);
	letter-spacing: 0.2em;
	text-transform: uppercase;
}

/*
 * Core places this at top:0;right:0 of its own positioning context, which
 * — now that .responsive-close and .responsive-dialog are stretched to the
 * panel's full size, see above — is the true top-right of the screen. Left
 * there, it sits flush in the corner while the hamburger it replaces sits
 * inset by the header's own padding-inline and centred within the header's
 * own height, so opening the menu visibly jumped the icon to a different
 * spot.
 *
 * Rather than a hand-typed top offset trying to reproduce where that lands
 * — which has to assume this button is exactly some number of pixels tall,
 * a number nothing enforces and that a future core update could quietly
 * change — this reuses the header's own --vsg-header-pad-inline token for
 * the horizontal side, and its own centring *mechanism* for the vertical
 * side: a box exactly --vsg-header-height tall, flexed and centred, exactly
 * like .vsg-header centres the hamburger. Whatever height this button's
 * padding and icon actually come out to, align-items:center puts it in the
 * middle of that band without needing to know the number.
 */
/*
 * Scoped to .is-menu-open specifically, not the bare class: core already
 * has its own rule hiding this button with display:none whenever the panel
 * is closed (the desktop horizontal bar, .responsive-container:not(.is-menu-
 * -open) at 600px and up) — an earlier, unscoped version of this rule
 * carried display:flex !important with no such condition, which beat that
 * display:none and left the close icon visible over the desktop header
 * even with the panel shut.
 */
.wp-block-navigation__responsive-container.is-menu-open .wp-block-navigation__responsive-container-close {
	position: absolute !important;
	top: 0 !important;
	right: var(--vsg-header-pad-inline) !important;
	height: var(--vsg-header-height) !important;
	display: flex !important;
	align-items: center !important;
	color: var(--wp--preset--color--surface);
	padding: 10px;
}

.wp-block-navigation__responsive-container-close svg {
	transition: transform var(--vsg-dur-fast) var(--vsg-ease);
}

.wp-block-navigation__responsive-container-close:hover svg {
	transform: rotate(90deg);
}

.wp-block-navigation__responsive-container-close:active svg {
	transform: rotate(90deg) scale(0.85);
}

/*
 * A small spin-in as the panel arrives, rather than just popping in with
 * the rest of the panel's vsgFade. Delayed a beat so the panel is visibly
 * under way first.
 */
.wp-block-navigation__responsive-container.is-menu-open .wp-block-navigation__responsive-container-close svg {
	animation: vsgSpinIn var(--vsg-dur-slow) var(--vsg-ease) both;
	animation-delay: 80ms;
}

@keyframes vsgSpinIn {
	from {
		opacity: 0;
		transform: rotate(-90deg) scale(0.6);
	}
	to {
		opacity: 1;
		transform: rotate(0deg) scale(1);
	}
}

/* 6. HERO */

.vsg-hero {
	position: relative;
	/*
	 * A column flex box with the content pushed to the foot. The hero was a
	 * Cover block, which brought its own flex layout; as a Group it has to
	 * declare it, or the headline sits at the top of the frame.
	 */
	display: flex;
	flex-direction: column;
	justify-content: flex-end;
	align-items: flex-start;
	min-height: max(620px, 92vh);
	padding-block: 0 clamp(120px, 15vh, 170px);
	overflow: hidden;
	background-color: #46543c;
}

/*
 * The scrim: a four-stop vertical wash, dark at the top so the knocked-out
 * header stays legible, clearing through the middle, deepening to carry the
 * headline at the foot.
 *
 * Against the supplied photograph the design's middle stops leave the
 * location line at 4.26:1, under the 4.5:1 that 11px text needs. Raising the
 * two middle stops by 0.04, and setting that line at full opacity rather
 * than 0.86, takes the worst case to 5.59:1 and the headline to 3.86:1
 * against a 3:1 requirement, with the sunset intact.
 *
 * Tuned to this photograph: re-check a brighter replacement before shipping.
 */
.vsg-hero::after {
	content: "";
	position: absolute;
	inset: 0;
	z-index: 1;
	pointer-events: none;
	background: linear-gradient(
		180deg,
		rgba(31, 51, 32, 0.5) 0%,
		rgba(31, 51, 32, 0.2) 30%,
		rgba(31, 51, 32, 0.48) 62%,
		rgba(31, 51, 32, 0.84) 100%
	);
}

.vsg-hero > .wp-block-cover__inner-container {
	position: relative;
	z-index: 2;
}

/*
 * The slide layer.
 *
 * Every photograph is stacked in the same box behind the scrim. Swiper's fade
 * effect cross-dissolves them; the Ken Burns drift is a slow scale on
 * whichever slide is active, restarted each time one comes forward.
 */
:root .vsg-hero .vsg-hero__slides,
:root .vsg-hero .vsg-hero__slides.swiper {
	position: absolute;
	inset: 0;
	z-index: 0;
	max-width: none;
	overflow: hidden;
	margin: 0;
	height: auto;
}

:root .vsg-hero .vsg-hero__slides > figure,
:root .vsg-hero .vsg-hero__slides .swiper-slide {
	margin: 0;
	height: 100%;
	overflow: hidden;
}

:root .vsg-hero .vsg-hero__slides img {
	display: block;
	width: 100%;
	height: 100%;
	object-fit: cover;
	border-radius: 0;
	transform: scale(1.005);
}

/* The caption is the slide's title; the meta row displays it instead. */
.vsg-hero__slides figcaption {
	position: absolute;
	width: 1px;
	height: 1px;
	overflow: hidden;
	clip-path: inset(50%);
	white-space: nowrap;
}

/* Without scripting only the first photograph is shown, and it holds still. */
.vsg-hero__slides:not(.swiper-initialized) > figure + figure {
	display: none;
}

:root .vsg-hero .vsg-hero__slides.swiper-initialized .is-panning img {
	animation: vsgKenBurns var(--vsg-kb, 8100ms) linear both;
}

:root .vsg-hero__content > * {
	margin-block: 0;
}

:root .vsg-hero .vsg-hero__content {
	position: relative;
	z-index: 2;
	width: 100%;
	display: flex;
	flex-direction: column;
	align-items: flex-start;
	gap: var(--wp--preset--spacing--40);
	text-align: left;
}

.vsg-hero__kicker {
	font-family: var(--wp--preset--font-family--body);
	font-size: var(--wp--preset--font-size--eyebrow);
	font-weight: 400;
	letter-spacing: 0.32em;
	text-transform: uppercase;
	color: rgba(245, 241, 230, 0.86);
}

.vsg-hero h1 {
	max-width: 17ch;
	color: var(--wp--preset--color--base);
	text-wrap: pretty;
}

.vsg-hero h1 em {
	font-weight: 300;
}

.vsg-hero__lead {
	max-width: 46ch;
	font-size: clamp(0.9375rem, 1.15vw, 1.09375rem);
	line-height: 1.7;
	color: rgba(251, 249, 243, 0.92);
	text-wrap: pretty;
}

/* The scroll cue: a hairline rule and a label,
no icon font. */
:root .vsg-hero__cue {
	display: flex;
	align-items: center;
	gap: 10px;
	margin-block-start: 6px;
	font-family: var(--wp--preset--font-family--body);
	font-size: 0.65625rem;
	letter-spacing: 0.2em;
	text-transform: uppercase;
	color: rgba(251, 249, 243, 0.82);
}

.vsg-hero__cue::before {
	content: "";
	display: block;
	width: 1px;
	height: 34px;
	background: rgba(251, 249, 243, 0.5);
}

/*
 * Load-in stagger, matching the design's four delays.
 *
 * Scoped to .vsg-hero specifically, not just the kicker/h1/lead class names
 * on their own — the static inner-page hero reuses those same classes for
 * its own text, and this animation was never meant for it. Matched only by
 * class name, it still applied there, and being a one-shot CSS animation
 * with no JS driving it, anything that keeps the browser from running it to
 * completion (a backgrounded tab, a slow paint) leaves that text sitting at
 * its from-state indefinitely — opacity 0, translated down 26px. A static
 * hero has no entrance to choreograph, so it is simplest, and safest, for it
 * to have none.
 */
.vsg-js .vsg-hero .vsg-hero__kicker {
	animation: vsgHeroIn 900ms var(--vsg-ease) 120ms both;
}

.vsg-js .vsg-hero h1 {
	animation: vsgHeroIn 1000ms var(--vsg-ease) 220ms both;
}

.vsg-js .vsg-hero .vsg-hero__lead {
	animation: vsgHeroIn 1000ms var(--vsg-ease) 340ms both;
}

.vsg-js .vsg-hero__cue {
	animation: vsgHeroIn 1000ms var(--vsg-ease) 460ms both;
}

/*
 * The hero's progress cluster.
 *
 * The only chrome on the slider: what is on screen, how far through the cycle
 * it is, and where it sits in the sequence. No arrows — the design does not
 * ask for them and the photographs are not navigation.
 */
.vsg-hero__meta {
	position: absolute;
	right: 4vw;
	bottom: clamp(120px, 15vh, 170px);
	z-index: 3;
	display: flex;
	align-items: center;
	gap: 18px;
	pointer-events: none;
}

.vsg-hero__slide-title {
	font-family: var(--wp--preset--font-family--body);
	font-size: 0.65625rem;
	font-weight: 400;
	letter-spacing: var(--vsg-track);
	text-transform: uppercase;
	color: rgba(251, 249, 243, 0.82);
	transition: opacity 600ms var(--vsg-ease);
}

.vsg-hero__progress {
	display: inline-flex;
	align-items: center;
	gap: 10px;
}

.vsg-hero__count {
	font-family: var(--wp--preset--font-family--body);
	font-size: 0.65625rem;
	font-weight: 400;
	letter-spacing: 0.16em;
	color: rgba(251, 249, 243, 0.7);
	font-variant-numeric: tabular-nums;
}

.vsg-hero__gauge {
	display: block;
	width: 22px;
	height: 22px;
}

.vsg-hero__gauge svg {
	display: block;
	width: 22px;
	height: 22px;
	/* Start the sweep at twelve o'clock. */
	transform: rotate(-90deg);
}

.vsg-hero__gauge circle {
	fill: none;
	stroke-width: 1.25;
}

.vsg-hero__gauge-track {
	stroke: rgba(251, 249, 243, 0.28);
}

.vsg-hero__gauge-bar {
	stroke: rgba(251, 249, 243, 0.85);
	stroke-linecap: round;
	/* --vsg-ring and --vsg-cycle are set by the script from the ring's own
	   geometry and the autoplay delay, so the sweep always matches the cycle. */
	animation: vsgGaugeSweep var(--vsg-cycle, 6500ms) linear both;
}

@keyframes vsgGaugeSweep {
	from {
		stroke-dashoffset: var(--vsg-ring, 56.5487);
	}

	to {
		stroke-dashoffset: 0;
	}
}

@media (max-width: 781px) {
	.vsg-hero__meta {
		right: 4vw;
		bottom: clamp(74px, 10vh, 110px);
		gap: 12px;
	}

	.vsg-hero__slide-title {
		display: none;
	}
}

/*
 * 7. BOOKING BAR
 *
 * Floats over the foot of the hero on a negative top margin,
exactly as the
 * design draws it. Every internal gap is set here rather than inherited: the
 * global block gap would otherwise open 22px between the label and its value
 * and turn a 68px cell into a 105px one.
 */

:root .vsg-booking {
	position: relative;
	z-index: 20;
	margin-block-start: -84px;
}

/*
 * On the dedicated booking page there is no hero for this panel to overlap
 * — it sits in its own section band like anything else, so the negative
 * pull above is cancelled rather than dragging it up under the header.
 */
:root .vsg-booking.vsg-booking--page {
	margin-block-start: 0;
}

.vsg-booking__panel {
	background: var(--wp--preset--color--base);
	border: 1px solid var(--vsg-rule);
	border-radius: var(--vsg-radius);
	box-shadow: var(--wp--preset--shadow--card);
	padding: 10px;
}

:root .vsg-booking__panel > * {
	margin-block: 0;
}

.vsg-booking__grid {
	display: grid;
	grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
	gap: 2px;
	align-items: stretch;
}

/*
 * Five cells across once there is room for them: three fields, the total, and
 * the button. The dates carry the widest values, so they take the slack and
 * the total and the button are sized to their content.
 */
@media (min-width: 840px) {
	.vsg-booking__grid {
		grid-template-columns:
			minmax(130px, 1fr) minmax(130px, 1fr) minmax(100px, 0.75fr)
			minmax(180px, auto) minmax(190px, auto);
	}
}

/*
 * Below that the cells pair off — dates, then guests and the total — with the
 * button across the foot. Five in a row would leave each too narrow to read,
 * and letting them wrap strands the button on a row of its own.
 */
@media (min-width: 782px) and (max-width: 839px) {
	.vsg-booking__grid {
		grid-template-columns: 1fr 1fr;
	}

	.vsg-booking__cta {
		grid-column: 1 / -1;
	}
}

:root .vsg-booking__grid > * {
	margin-block: 0;
}

/* The script wraps each field so its popover has a positioning context. */
.vsg-booking__cell {
	position: relative;
	display: flex;
}

.vsg-booking__cell > .vsg-booking__field {
	width: 100%;
}

.vsg-booking__field {
	display: flex;
	flex-direction: column;
	justify-content: center;
	gap: 6px;
	padding: 16px 20px;
	border-inline-start: 1px solid rgba(31, 51, 32, 0.1);
	text-align: left;
	background: none;
	border-block: 0;
	border-inline-end: 0;
	cursor: pointer;
	font: inherit;
	position: relative;
	transition: background var(--vsg-dur) ease;
}

.vsg-booking__field:first-child {
	border-inline-start: 0;
}

:root .vsg-booking__field > *,
:root .vsg-booking__field > * + * {
	margin-block: 0;
}

.vsg-booking__field:hover {
	background: rgba(31, 51, 32, 0.03);
}

.vsg-booking__field[aria-expanded="true"] {
	background: rgba(31, 51, 32, 0.05);
}

.vsg-booking__label {
	font-family: var(--wp--preset--font-family--body);
	font-size: 0.625rem;
	font-weight: 500;
	letter-spacing: 0.2em;
	line-height: 1.2;
	text-transform: uppercase;
	color: var(--wp--preset--color--accent);
	pointer-events: none;
}

.vsg-booking__value {
	font-family: var(--wp--preset--font-family--body);
	font-size: var(--wp--preset--font-size--medium);
	font-weight: 400;
	line-height: 1.2;
	color: var(--wp--preset--color--contrast);
	pointer-events: none;
}

.vsg-booking__value.is-placeholder {
	color: var(--wp--preset--color--muted);
}

/* The call to action is a grid cell, so it fills the row's full height. */
.vsg-booking__cta {
	display: flex;
}

.vsg-booking__cta .wp-block-button,
.vsg-booking__cta .wp-block-button__link {
	width: 100%;
	height: 100%;
}

.vsg-booking__cta .wp-block-button__link {
	padding: 20px 26px;
	letter-spacing: 0.2em;
}

/*
 * Season closure notice. Sits above the fields, because "we are shut until
 * May" is not a footnote — it is the first thing that changes what a visitor
 * should do next.
 */
:root .vsg-booking__closed {
	margin: 0 0 12px;
	padding: 10px 16px;
	border-radius: var(--vsg-radius-sm);
	background: var(--wp--preset--color--sand);
	color: var(--wp--preset--color--contrast);
	font-family: var(--wp--preset--font-family--body);
	font-size: var(--wp--preset--font-size--x-small);
	font-weight: 400;
	letter-spacing: 0.04em;
}

/*
 * The live quote.
 *
 * It is a cell of the field row, sitting immediately before the button that
 * commits the guest, so the number and the commitment are read together. The
 * cell is in the row from the moment the script runs — placeholder first,
 * figure later — so a quote arriving never resizes the panel and nothing on
 * the page below it moves.
 */
.vsg-booking__cell--price {
	display: flex;
	border-inline-start: 1px solid rgba(31, 51, 32, 0.1);
}

/*
 * Tighter than a field cell by exactly the amount the figure is taller than a
 * field value, so the total occupies the same 68px the other cells do and the
 * panel keeps the height the design draws it at.
 */
.vsg-booking__price {
	display: flex;
	flex-direction: column;
	justify-content: center;
	gap: 4px;
	width: 100%;
	padding: 12px 20px;
	background: rgba(87, 114, 74, 0.05);
	text-align: left;
}

/*
 * Carries the night count once there is one — "TOTAL · 4 NIGHTS" — so the
 * length of the stay is stated without spending a line on it.
 */
.vsg-booking__price-label {
	overflow: hidden;
	font-family: var(--wp--preset--font-family--body);
	font-size: 0.625rem;
	font-weight: 500;
	letter-spacing: 0.16em;
	line-height: 1.2;
	text-transform: uppercase;
	white-space: nowrap;
	text-overflow: ellipsis;
	color: var(--wp--preset--color--accent);
}

/*
 * The figure line holds a fixed amount of space whatever is in it — a price,
 * a prompt, or a refusal — so the row this cell sits in is the same height
 * before and after a quote arrives, and the page below never moves.
 */
.vsg-booking__figure {
	display: flex;
	flex-wrap: wrap;
	align-items: baseline;
	gap: 8px;
	min-height: 1.75rem;
}

.vsg-booking__total {
	font-family: var(--wp--preset--font-family--display);
	font-size: 1.625rem;
	font-weight: 400;
	line-height: 1;
	color: var(--wp--preset--color--contrast);
}

/* The rate before a discount, struck through beside the one being charged. */
.vsg-booking__was {
	font-family: var(--wp--preset--font-family--body);
	font-size: var(--wp--preset--font-size--x-small);
	font-weight: 300;
	line-height: 1;
	color: var(--wp--preset--color--muted);
	text-decoration: line-through;
}

.vsg-booking__was:empty {
	display: none;
}

/*
 * Placeholder, pending and error states all stand in for the figure, and none
 * of them is a number — so they drop to the size of a field value rather than
 * setting a sentence in display type.
 */
.vsg-booking__total.is-placeholder {
	font-family: var(--wp--preset--font-family--body);
	font-size: var(--wp--preset--font-size--medium);
	line-height: 1.25;
	color: var(--wp--preset--color--muted);
}

.vsg-booking__cell--price.is-error .vsg-booking__total {
	font-size: var(--wp--preset--font-size--medium);
	color: var(--wp--preset--color--terracotta);
}

/*
 * A stay long enough to be priced is worth marking as settled, so the cell
 * warms once there is a real figure in it.
 */
.vsg-booking__cell--price.is-ready .vsg-booking__price {
	background: var(--wp--preset--color--surface);
}

:root .vsg-booking__note {
	margin: 14px 2px 0;
	font-size: var(--wp--preset--font-size--x-small);
	font-weight: 300;
	letter-spacing: 0.04em;
	color: var(--wp--preset--color--muted);
}

/*
 * Lodgify renders into this mount. Empty, it must take no space at all —
 * otherwise its reserved height pushes the panel open by 76px on every site
 * that has not configured the widget.
 */
.vsg-lodgify:empty {
	display: none;
}

.vsg-lodgify:not(:empty) + .vsg-booking__grid {
	display: none;
}

/*
 * The date picker.
 *
 * A two-month range calendar the script builds over the three static fields
 * above. Without it those fields are plain text in a styled panel, which is
 * why the markup keeps them as ordinary paragraphs.
 */
.vsg-popover {
	position: absolute;
	top: calc(100% + 10px);
	left: 0;
	z-index: 40;
	min-width: 320px;
	padding: 20px;
	background: var(--wp--preset--color--base);
	border: 1px solid var(--vsg-rule);
	border-radius: var(--vsg-radius);
	box-shadow: var(--wp--preset--shadow--card);
	cursor: default;
	animation: vsgPopIn 200ms var(--vsg-ease) both;
}

.vsg-popover[hidden] {
	display: none;
}

@keyframes vsgPopIn {
	from {
		opacity: 0;
		transform: translateY(-6px);
	}

	to {
		opacity: 1;
		transform: none;
	}
}

.vsg-popover--right {
	left: auto;
	right: 0;
}

.vsg-cal {
	display: flex;
	gap: 28px;
}

.vsg-cal__month {
	flex: 0 0 auto;
	width: 266px;
}

.vsg-cal__head {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: 12px;
	margin-block-end: 14px;
	min-height: 32px;
}

.vsg-cal__title {
	font-family: var(--wp--preset--font-family--display);
	font-size: 1.25rem;
	font-weight: 400;
	line-height: 1;
	color: var(--wp--preset--color--contrast);
	text-align: center;
	flex: 1 1 auto;
}

.vsg-cal__nav {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	width: 32px;
	min-width: 32px;
	height: 32px;
	min-height: 32px;
	padding: 0;
	border: 1px solid rgba(31, 51, 32, 0.2);
	border-radius: 50%;
	background: transparent;
	color: var(--wp--preset--color--contrast);
	font-family: var(--wp--preset--font-family--body);
	font-size: 13px;
	line-height: 1;
	cursor: pointer;
	transition: background var(--vsg-dur) ease, color var(--vsg-dur) ease;
}

.vsg-cal__nav:hover:not([disabled]) {
	background: var(--wp--preset--color--contrast);
	color: var(--wp--preset--color--base);
}

.vsg-cal__nav[disabled] {
	opacity: 0.3;
	cursor: default;
}

/* Keeps the month name optically centred when only one arrow is drawn. */
.vsg-cal__nav-spacer {
	display: block;
	width: 32px;
	min-width: 32px;
	height: 32px;
}

/*
 * minmax(0, 1fr), not the bare 1fr shorthand every column is written with
 * elsewhere: bare 1fr means minmax(auto, 1fr), so a column can never shrink
 * past its content's own min-content width — fine at almost every viewport,
 * but the day-of-week header text is exactly wide enough that at certain
 * narrow widths seven columns' worth of "auto" minimums add up to more room
 * than the card actually has, and the grid overflows its own right edge
 * rather than shrink. minmax(0, 1fr) removes that floor, so a column always
 * takes exactly its 1/7th share no matter how little space is available.
 */
.vsg-cal__grid {
	display: grid;
	grid-template-columns: repeat(7, minmax(0, 1fr));
	gap: 2px;
}

.vsg-cal__dow {
	text-align: center;
	padding-block-end: 8px;
	font-family: var(--wp--preset--font-family--body);
	font-size: 0.59375rem;
	font-weight: 500;
	letter-spacing: 0.12em;
	text-transform: uppercase;
	color: var(--wp--preset--color--muted);
}

.vsg-cal__day {
	display: flex;
	align-items: center;
	justify-content: center;
	aspect-ratio: 1 / 1;
	padding: 0;
	border: 0;
	border-radius: var(--vsg-radius-sm);
	background: transparent;
	color: var(--wp--preset--color--contrast);
	font-family: var(--wp--preset--font-family--body);
	font-size: 0.8125rem;
	font-weight: 300;
	line-height: 1;
	cursor: pointer;
	transition: background 140ms ease, color 140ms ease;
}

.vsg-cal__day:hover:not([disabled]) {
	background: var(--wp--preset--color--sand);
}

.vsg-cal__day[disabled] {
	color: var(--wp--preset--color--muted);
	opacity: 0.45;
	cursor: default;
	text-decoration: line-through;
}

/* Reported unavailable by Lodgify, rather than merely in the past. */
.vsg-cal__day.is-blocked {
	background: var(--wp--preset--color--sand);
	opacity: 0.75;
}

.vsg-cal__day.is-empty {
	visibility: hidden;
	cursor: default;
}

/*
 * The nights between the two chosen dates. Sage, not sand: sand is also
 * what a blocked date uses, and having the guest's own selected nights
 * share a colour with "not available" is the opposite of clear. Green reads
 * unambiguously as "this is your stay."
 */
.vsg-cal__day.is-in-range {
	background: var(--wp--preset--color--sage);
	color: var(--wp--preset--color--contrast);
	border-radius: 0;
}

.vsg-cal__day.is-in-range:hover {
	background: var(--wp--preset--color--sage);
	color: var(--wp--preset--color--contrast);
}

/*
 * :not([disabled]) in the plain hover rule above counts as an attribute
 * selector, which makes it more specific than a single .is-selected class —
 * without this, hovering a selected day would flip it to the light hover
 * fill instead of staying the dark selected one.
 */
.vsg-cal__day.is-selected:hover {
	background: var(--wp--preset--color--contrast);
	color: var(--wp--preset--color--base);
}

.vsg-cal__day.is-selected {
	background: var(--wp--preset--color--contrast);
	color: var(--wp--preset--color--base);
	font-weight: 400;
	/*
	 * A day picked as the stay's own departure can still carry is-blocked —
	 * checking out the morning a new booking starts is a valid turnover, not
	 * a conflict — so a selected day must always look fully selected no
	 * matter what other flag also matches it, rather than inheriting a
	 * dimmed, struck-through look from that flag's own rule.
	 */
	opacity: 1;
	text-decoration: none;
}

.vsg-cal__day.is-start {
	border-start-end-radius: 0;
	border-end-end-radius: 0;
}

.vsg-cal__day.is-end {
	border-start-start-radius: 0;
	border-end-start-radius: 0;
}

.vsg-cal__day.is-today:not(.is-selected) {
	box-shadow: inset 0 0 0 1px var(--wp--preset--color--accent);
}

:root .vsg-cal__foot {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: 16px;
	margin-block-start: 16px;
	padding-block-start: 14px;
	border-block-start: 1px solid var(--vsg-rule);
}

/*
 * The running total under the picker.
 *
 * It answers the question a guest is actually asking while they click: how
 * long is this, and what does it cost. The length appears the moment the
 * second date lands; the figure follows a beat later, in the same line.
 */
.vsg-cal__summary {
	display: flex;
	flex-wrap: wrap;
	align-items: baseline;
	gap: 4px 10px;
	font-family: var(--wp--preset--font-family--body);
	font-size: var(--wp--preset--font-size--x-small);
	font-weight: 300;
	color: var(--wp--preset--color--body-text);
}

.vsg-cal__nights {
	font-size: var(--wp--preset--font-size--medium);
	font-weight: 400;
	color: var(--wp--preset--color--contrast);
}

/*
 * The chosen dates, spelled out. Redundant under the picker — the fields
 * above it already read them back — and wanted in the footer, which has no
 * calendar of its own to point at.
 */
.vsg-cal__dates {
	font-size: var(--wp--preset--font-size--medium);
	font-weight: 400;
	color: var(--wp--preset--color--contrast);
}

.vsg-cal__summary .vsg-cal__dates {
	display: none;
}

.vsg-cal__price {
	font-family: var(--wp--preset--font-family--display);
	font-size: 1.375rem;
	font-weight: 400;
	line-height: 1;
	color: var(--wp--preset--color--contrast);
}

.vsg-cal__note {
	color: var(--wp--preset--color--muted);
}

.vsg-cal__summary.is-error .vsg-cal__note {
	color: var(--wp--preset--color--terracotta);
}

.vsg-cal__actions {
	display: flex;
	align-items: center;
	gap: 18px;
	flex: 0 0 auto;
}

/*
 * The calendar no longer dismisses itself once a range is complete, so both
 * ends of the stay can be adjusted in one session. This is the explicit way
 * out; Escape and a click outside also close it.
 */
.vsg-cal__done {
	padding: 10px 18px;
	min-height: 44px;
	border: 0;
	border-radius: var(--vsg-radius-sm);
	background: var(--wp--preset--color--contrast);
	color: var(--wp--preset--color--base);
	font-family: var(--wp--preset--font-family--body);
	font-size: var(--wp--preset--font-size--eyebrow);
	font-weight: 500;
	letter-spacing: var(--vsg-track);
	text-transform: uppercase;
	cursor: pointer;
	transition: background var(--vsg-dur) ease;
}

.vsg-cal__done:hover {
	background: var(--wp--preset--color--accent);
}

.vsg-cal__clear {
	padding: 8px 4px;
	min-height: 44px;
	border: 0;
	background: none;
	color: var(--wp--preset--color--contrast);
	font-family: var(--wp--preset--font-family--body);
	font-size: var(--wp--preset--font-size--eyebrow);
	font-weight: 500;
	letter-spacing: var(--vsg-track);
	text-transform: uppercase;
	text-decoration: underline;
	text-underline-offset: 4px;
	cursor: pointer;
}

/* Guests stepper. */
.vsg-stepper {
	display: flex;
	flex-direction: column;
	gap: 18px;
	min-width: 260px;
}

.vsg-stepper__row {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: 20px;
}

.vsg-stepper__label {
	font-family: var(--wp--preset--font-family--body);
	font-size: var(--wp--preset--font-size--medium);
	font-weight: 400;
	color: var(--wp--preset--color--contrast);
}

.vsg-stepper__hint {
	display: block;
	font-size: var(--wp--preset--font-size--x-small);
	font-weight: 300;
	color: var(--wp--preset--color--muted);
}

.vsg-stepper__controls {
	display: flex;
	align-items: center;
	gap: 6px;
}

.vsg-stepper__count {
	min-width: 40px;
	text-align: center;
	font-family: var(--wp--preset--font-family--body);
	font-size: var(--wp--preset--font-size--medium);
	color: var(--wp--preset--color--contrast);
}

.vsg-stepper__btn {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	width: 36px;
	min-width: 36px;
	height: 36px;
	padding: 0;
	border: 1px solid rgba(31, 51, 32, 0.25);
	border-radius: 50%;
	background: transparent;
	color: var(--wp--preset--color--contrast);
	font-family: var(--wp--preset--font-family--body);
	font-size: 15px;
	line-height: 1;
	cursor: pointer;
	transition: background var(--vsg-dur) ease, color var(--vsg-dur) ease;
}

.vsg-stepper__btn:hover:not([disabled]) {
	background: var(--wp--preset--color--contrast);
	color: var(--wp--preset--color--base);
}

.vsg-stepper__btn[disabled] {
	opacity: 0.3;
	cursor: default;
}

/* Announcements for assistive technology,
visually hidden. */
:root .vsg-sr {
	position: absolute;
	width: 1px;
	height: 1px;
	margin: -1px;
	padding: 0;
	overflow: hidden;
	clip-path: inset(50%);
	white-space: nowrap;
	border: 0;
}

@media (max-width: 781px) {
	:root .vsg-booking {
		margin-block-start: -48px;
	}

	.vsg-booking__grid {
		grid-template-columns: 1fr;
	}

	.vsg-booking__field {
		border-inline-start: 0;
		border-block-start: 1px solid rgba(31, 51, 32, 0.1);
	}

	.vsg-booking__field:first-child {
		border-block-start: 0;
	}

	.vsg-booking__cell--price {
		border-inline-start: 0;
		border-block-start: 1px solid rgba(31, 51, 32, 0.1);
	}

	.vsg-cal {
		flex-direction: column;
		gap: 22px;
	}

	.vsg-cal__month {
		width: 100%;
	}

	.vsg-popover {
		left: 0;
		right: 0;
		min-width: 0;
	}
}


/* 8. FACTS STRIP */

/*
 * The strip is a hairline rule top and bottom with a thin divider before each
 * figure — not a row of boxes. The earlier :not(.is-not-stacked-on-mobile)
 * guard never matched, since this row carries exactly that class, so none of
 * the design's padding or borders were reaching the cells.
 */
.wp-block-columns.vsg-facts {
	border-block: 1px solid var(--vsg-rule);
	gap: 0;
}

.wp-block-columns.vsg-facts > .wp-block-column {
	padding: 26px 22px;
	border-inline-start: 1px solid var(--vsg-rule);
	border-block: 0;
	border-inline-end: 0;
	justify-content: flex-start;
}

/* Closes the strip at the far end, so it reads as one ruled band. */
.wp-block-columns.vsg-facts > .wp-block-column:last-child {
	border-inline-end: 1px solid var(--vsg-rule);
}

:root .vsg-facts > .wp-block-column > * {
	margin-block: 0;
}

:root .wp-block-columns.vsg-facts > .wp-block-column > * + * {
	margin-block-start: 8px;
}

.vsg-fact__value {
	font-family: var(--wp--preset--font-family--display);
	font-size: 2.125rem;
	font-weight: 300;
	line-height: 1;
	color: var(--wp--preset--color--contrast);
}

.vsg-fact__label {
	font-family: var(--wp--preset--font-family--body);
	font-size: 0.65625rem;
	font-weight: 400;
	letter-spacing: var(--vsg-track);
	line-height: 1.4;
	text-transform: uppercase;
	color: var(--wp--preset--color--muted);
}

/* Icon beside the value/label pair — a plain line icon, coloured to match the terracotta accent already used elsewhere. */
.vsg-fact {
	display: flex;
	align-items: center;
	gap: 14px;
}

:root .vsg-fact > * {
	margin-block: 0;
}

.vsg-fact svg {
	flex: 0 0 auto;
	color: var(--wp--preset--color--terracotta);
}

/*
 * Hero pill buttons — "The Main House" / "The Guest House", jumping to
 * their own sections further down this same page.
 */
.vsg-hero__buttons {
	margin-block-start: 6px;
}

.wp-block-button.vsg-btn-pill .wp-block-button__link {
	padding: 13px 22px;
	background: transparent;
	border: 1px solid rgba(251, 249, 243, 0.5);
	border-radius: 999px;
	font-family: var(--wp--preset--font-family--body);
	font-size: 0.6875rem;
	font-weight: 500;
	letter-spacing: 0.16em;
	text-transform: uppercase;
	color: var(--wp--preset--color--base);
}

.wp-block-button.vsg-btn-pill .wp-block-button__link:hover,
.wp-block-button.vsg-btn-pill .wp-block-button__link:focus-visible {
	background: rgba(251, 249, 243, 0.14);
	border-color: rgba(251, 249, 243, 0.85);
}

/*
 * Section badges — "1 The Main House" / "2 The Guest House" — replacing
 * the plainer .vsg-eyebrow--section treatment tried earlier in this same
 * pass, now that there's an actual design to match instead of an
 * improvised fix.
 */
.vsg-section-badge {
	display: inline-flex;
	align-items: center;
	gap: 12px;
	padding: 8px 18px 8px 6px;
	background: var(--wp--preset--color--contrast);
	border-radius: 999px;
	margin-block-end: 22px;
}

.vsg-section-badge__num {
	display: flex;
	align-items: center;
	justify-content: center;
	width: 26px;
	height: 26px;
	flex: 0 0 auto;
	border-radius: 999px;
	background: var(--wp--preset--color--base);
	color: var(--wp--preset--color--contrast);
	font-family: var(--wp--preset--font-family--display);
	font-size: 0.8125rem;
}

.vsg-section-badge__label {
	font-family: var(--wp--preset--font-family--body);
	font-size: 0.75rem;
	font-weight: 600;
	letter-spacing: 0.22em;
	text-transform: uppercase;
	color: var(--wp--preset--color--base);
}

/*
 * 9. INSET IMAGE PAIR
 *
 * A tall portrait with a smaller square overlapping its lower-left corner,
 * matted in the page colour. Used by the welcome section.
 */

.vsg-inset-pair {
	position: relative;
	display: flex;
	justify-content: flex-end;
}

:root .vsg-inset-pair > .wp-block-image:first-child {
	width: min(100%, 520px);
	margin: 0;
}

.vsg-inset-pair > .wp-block-image:first-child img {
	width: 100%;
	aspect-ratio: 4 / 5;
	object-fit: cover;
}

:root .vsg-inset-pair > .wp-block-image:last-child {
	position: absolute;
	left: 0;
	bottom: -8%;
	width: min(46%, 230px);
	margin: 0;
	border: 8px solid var(--wp--preset--color--base);
	border-radius: var(--vsg-radius);
	overflow: hidden;
}

.vsg-inset-pair > .wp-block-image:last-child img {
	display: block;
	width: 100%;
	aspect-ratio: 1 / 1;
	object-fit: cover;
	border-radius: 0;
}

@media (max-width: 781px) {
	.vsg-inset-pair {
		justify-content: flex-start;
	}

	.vsg-inset-pair > .wp-block-image:last-child {
		bottom: -6%;
	}
}

/*
 * 11. ACCOMMODATION
 *
 * Two unit panels with a tab control, each holding room images stepped
 * through by name. Both are progressive enhancements: with scripting off,
 * every panel and every image renders in sequence.
 */

/*
 * Ordinary lists, so an editor can rename or reorder an entry. The script
 * swaps each item's text for a button; these rules dress it either way.
 */
:root .vsg-tabs {
	display: flex;
	flex-wrap: wrap;
	gap: 2px;
	margin: 0;
	padding: 4px;
	list-style: none;
	background: rgba(31, 51, 32, 0.06);
	border-radius: var(--vsg-radius);
	flex: 0 0 auto;
}

:root .vsg-tabs li {
	display: flex;
	margin: 0;
}

.vsg-tabs li::marker {
	content: "";
}

.vsg-tabs:not(:has(button)) li {
	padding: 13px 24px;
	font-family: var(--wp--preset--font-family--body);
	font-size: var(--wp--preset--font-size--eyebrow);
	font-weight: 500;
	letter-spacing: var(--vsg-track-tight);
	text-transform: uppercase;
	color: var(--wp--preset--color--body-text);
}

.vsg-tabs button {
	padding: 13px 24px;
	border: 0;
	border-radius: var(--vsg-radius-sm);
	background: transparent;
	color: var(--wp--preset--color--body-text);
	font-family: var(--wp--preset--font-family--body);
	font-size: var(--wp--preset--font-size--eyebrow);
	font-weight: 500;
	letter-spacing: var(--vsg-track-tight);
	text-transform: uppercase;
	cursor: pointer;
	transition: background var(--vsg-dur) ease, color var(--vsg-dur) ease;
}

.vsg-tabs button:hover {
	color: var(--wp--preset--color--contrast);
}

.vsg-tabs button[aria-selected="true"] {
	background: var(--wp--preset--color--contrast);
	color: var(--wp--preset--color--surface);
}

.vsg-tabpanel[hidden] {
	display: none;
}

/*
 * Gallery slider.
 *
 * A core Gallery block carrying the class "slider-gallery" shows one
 * photograph at a time, with the names of the photographs as the strip of
 * controls beneath it. Editing it is editing an ordinary gallery: add an
 * image, caption it, and it has a button.
 *
 * Core lays a gallery out as a wrapping flex row and sizes each figure to a
 * fraction of it, so the rules that make one image fill the frame have to
 * outrank those — hence the :root prefix and the full selector.
 */
.wp-block-gallery.slider-gallery.is-enhanced {
	position: relative;
	border-radius: var(--vsg-radius);
	overflow: hidden;
	background: var(--wp--preset--color--sand);
}

/*
 * Every rule that reshapes the gallery is gated on is-enhanced, the class the
 * script adds. Sizing the figures to the full width unconditionally would
 * leave a gallery whose script never ran as a column of enormous stacked
 * photographs rather than the ordinary gallery it is.
 */
:root .wp-block-gallery.slider-gallery.is-enhanced figure.wp-block-image {
	flex: 1 1 100%;
	width: 100%;
	max-width: 100%;
	margin: 0;
	border-radius: 0;
}

/*
 * A fixed frame. Photographs arrive at whatever shape the camera gave them,
 * and without this the column changes height every time a guest steps to the
 * next one.
 */
:root .wp-block-gallery.slider-gallery.is-enhanced figure.wp-block-image img {
	display: block;
	width: 100%;
	height: auto;
	aspect-ratio: 5 / 4;
	object-fit: cover;
	border-radius: 0;
}

.wp-block-gallery.slider-gallery.is-enhanced figure.wp-block-image {
	display: none;
	animation: vsgFade 420ms var(--vsg-ease) both;
}

.wp-block-gallery.slider-gallery.is-enhanced figure.wp-block-image.is-active {
	display: block;
}

/*
 * The caption is the button's text. Printing it under the photograph as well
 * would say the same thing twice, so it only shows while the gallery is a
 * plain gallery — which is what a visitor without scripting sees.
 */
.wp-block-gallery.slider-gallery.is-enhanced figcaption {
	display: none;
}

/*
 * The names of the photographs, and the gallery link left-aligned beneath
 * them — no rule between the two, as the design has it.
 */
:root .vsg-gallery__nav {
	display: flex;
	flex-wrap: wrap;
	align-items: baseline;
	gap: 8px 26px;
	margin: 18px 0 0;
	padding: 0;
	list-style: none;
}

:root .vsg-gallery__nav li {
	display: flex;
	margin: 0;
}

.vsg-gallery__nav li::marker {
	content: "";
}

.vsg-gallery__nav button {
	padding: 0 0 5px;
	border: 0;
	border-block-end: 1px solid transparent;
	background: transparent;
	color: var(--wp--preset--color--muted);
	font-family: var(--wp--preset--font-family--body);
	font-size: var(--wp--preset--font-size--eyebrow);
	font-weight: 400;
	letter-spacing: var(--vsg-track-tight);
	text-transform: uppercase;
	cursor: pointer;
	transition: color var(--vsg-dur) ease, border-color var(--vsg-dur) ease;
}

.vsg-gallery__nav button:hover {
	color: var(--wp--preset--color--contrast);
}

.vsg-gallery__nav button[aria-current="true"] {
	color: var(--wp--preset--color--contrast);
	font-weight: 500;
	border-block-end-color: var(--wp--preset--color--contrast);
}

/*
 * The nav strip's thumbnail variant: each photograph in miniature, in place
 * of its name. Compound selectors throughout so this always outranks the
 * plain text-strip rules above, whatever order the two end up in.
 */
.vsg-gallery__nav.vsg-gallery__nav--thumbs {
	display: grid;
	grid-template-columns: repeat(auto-fill, minmax(64px, 1fr));
	gap: 8px;
}

.vsg-gallery__nav.vsg-gallery__nav--thumbs li {
	display: block;
}

/*
 * A square thumbnail via percentage padding rather than aspect-ratio: some
 * Safari versions mis-size an aspect-ratio grid item that contains an
 * object-fit image inside repeat(auto-fill, minmax()), stretching the whole
 * row to the image's own intrinsic height instead of the square the CSS
 * asks for. Padding-bottom against the item's own (grid-resolved) width has
 * no such dependency on any of that machinery.
 */
.vsg-gallery__nav.vsg-gallery__nav--thumbs button {
	position: relative;
	display: block;
	width: 100%;
	height: 0;
	padding: 0 0 100%;
	border: 0;
	border-radius: var(--vsg-radius-sm);
	overflow: hidden;
	background: var(--wp--preset--color--sand);
	cursor: pointer;
	opacity: 0.55;
	outline: 1px solid transparent;
	outline-offset: -1px;
	transition: opacity var(--vsg-dur) ease, outline-color var(--vsg-dur) ease;
}

.vsg-gallery__nav.vsg-gallery__nav--thumbs button:hover {
	opacity: 1;
}

.vsg-gallery__nav.vsg-gallery__nav--thumbs button[aria-current="true"] {
	opacity: 1;
	outline-color: var(--wp--preset--color--contrast);
}

.vsg-gallery__nav.vsg-gallery__nav--thumbs button img {
	position: absolute;
	inset: 0;
	display: block;
	width: 100%;
	height: 100%;
	object-fit: cover;
}

:root .vsg-roomlink {
	margin-block-start: 22px;
	text-align: left;
}

/* The position in the set reads as a pill in the corner of the photograph. */
:root .wp-block-gallery.slider-gallery.is-enhanced .vsg-gallery__count {
	position: absolute;
	right: 12px;
	bottom: 12px;
	z-index: 2;
	margin: 0;
	padding: 6px 12px;
	border-radius: 999px;
	background: rgba(31, 51, 32, 0.72);
	backdrop-filter: blur(6px);
	-webkit-backdrop-filter: blur(6px);
	color: var(--wp--preset--color--base);
	font-family: var(--wp--preset--font-family--body);
	font-size: 0.65625rem;
	font-weight: 400;
	letter-spacing: var(--vsg-track-tight);
	line-height: 1;
	pointer-events: none;
}

/*
 * The static hero every inner page opens with.
 *
 * The darkening behind the text is the Cover block's own overlay — dimRatio
 * and overlayColor, editable per page from the block's own Overlay panel in
 * the editor — rather than a fixed CSS gradient. That trades the previous
 * gradient's lighter top and deeper foot for a flat tint, in exchange for an
 * editor being able to see and adjust it without touching this file.
 *
 * The text-shadow below is unrelated to the overlay and stays regardless: it
 * gives every letter its own edge no matter what is behind it, which a flat
 * tint alone does not guarantee on a busy photograph. If a page's overlay is
 * ever turned down low enough to look pale, check contrast against that
 * page's own photograph before shipping — nothing here enforces a floor.
 */
.vsg-page-hero {
	position: relative;
	display: flex;
	align-items: flex-end;
	min-height: clamp(34rem, 68vh, 52rem);
	/*
	 * Core's own default for any Cover block with no padding configured is
	 * "padding: 1em" — about 16.5px here. Left in place, that stacked with
	 * the inset below and put the heading roughly 17px further right than
	 * the header logo and every section heading beneath it. This is the one
	 * inset that matters; the inner container sets it precisely.
	 */
	padding: 0;
}

/*
 * Core sets this element's own z-index back to 1 through a selector this
 * codebase cannot outrank by specificity alone —
 * "body:not(.editor-styles-wrapper) .wp-block-cover:not(.wp-block-cover:has(
 * .wp-block-cover__background + .wp-block-cover__inner-container)) …" — the
 * :has() argument alone counts two classes, putting the whole selector
 * around six. At equal z-index with the scrim below, paint order (the scrim
 * is generated last, as this element's sibling ::after) put the scrim on
 * top of the text: legible-looking in the inspector, unreadable on screen.
 * The !important is answering an !important-strength selector, not habit.
 */
.vsg-page-hero > .wp-block-cover__inner-container {
	position: relative;
	z-index: 2 !important;
	width: 100%;
	/*
	 * The site's standard content inset, applied by hand — it used to arrive
	 * for free from "constrained" layout's has-global-padding class, lost
	 * when this switched to "default" layout to stop that same layout type
	 * auto-centring the heading (see the deviation note in README.md).
	 *
	 * A flat 4vw put the heading 14px to the right of the header logo and
	 * the section headings below it: those get their edge from centring a
	 * 1320px column, not from a percentage. max() reproduces that — root
	 * padding on a narrow viewport, half the leftover width once the
	 * viewport is wide enough that a 1320px column would sit inside it —
	 * without the auto-margin-on-any-child behaviour that centred the h1.
	 */
	padding-inline: max(var(--wp--style--root--padding-left), calc((100vw - 1320px) / 2))
		max(var(--wp--style--root--padding-right), calc((100vw - 1320px) / 2));
	padding-block-end: clamp(2.5rem, 5vw, 4rem);
}

:root .vsg-page-hero > .wp-block-cover__inner-container > * {
	margin-block: 0;
}

:root .vsg-page-hero > .wp-block-cover__inner-container > * + * {
	margin-block-start: 14px;
}

.vsg-page-hero .vsg-hero__kicker,
.vsg-page-hero h1,
.vsg-page-hero .vsg-hero__lead {
	text-shadow: 0 1px 3px rgba(0, 0, 0, 0.35), 0 1px 16px rgba(0, 0, 0, 0.2);
}

.vsg-page-hero h1 {
	max-width: 16ch;
	color: var(--wp--preset--color--base);
}

/*
 * The full reviews grid on the Guests page. Two columns of ordinary Quote
 * blocks, each closed off with a hairline rather than a card — the same
 * restrained treatment as the policy rows, because six long quotations in
 * boxed cards would compete with each other for attention.
 */
:root .vsg-review-grid .wp-block-quote {
	padding-block-end: 28px;
	margin-block-end: 28px;
	border-block-end: 1px solid rgba(31, 51, 32, 0.14);
}

/*
 * core/quote has no size of its own in this theme — every quote block
 * inherits the "quote" preset (theme.json: clamp(1.75rem, 3.8vw, 3.25rem),
 * 28-52px), sized for the one big pull-quote on the homepage's dark
 * quote-band. Fine at full width; six of them running two-across here
 * inherited that same hero-sized type, which is what actually made the
 * spacing read as wrong — an oversized line-height and the margins below
 * it scale up with the font size they were never meant to reach.
 */
:root .vsg-review-grid .wp-block-quote p {
	font-size: var(--wp--preset--font-size--x-large);
	line-height: 1.5;
}

.vsg-review-grid .wp-block-quote cite {
	display: block;
	margin-block-start: 10px;
	font-style: normal;
	font-family: var(--wp--preset--font-family--body);
	font-size: var(--wp--preset--font-size--x-small);
	font-weight: 500;
	letter-spacing: var(--vsg-track-tight);
	text-transform: uppercase;
	color: var(--wp--preset--color--muted);
}

/* Level rows: a tracked label against a paragraph of detail. */
:root .vsg-levels > * {
	display: grid;
	grid-template-columns: minmax(110px, 0.4fr) 1fr;
	gap: 24px;
	padding-block: 20px;
	border-block-start: 1px solid rgba(31, 51, 32, 0.14);
	margin-block: 0;
}

:root .vsg-levels .vsg-level__name {
	padding-block-start: 3px;
	font-family: var(--wp--preset--font-family--body);
	font-size: 0.65625rem;
	font-weight: 500;
	letter-spacing: var(--vsg-track);
	text-transform: uppercase;
	color: var(--wp--preset--color--accent);
	margin: 0;
}

:root .vsg-levels .vsg-level__detail {
	font-size: 0.96875rem;
	line-height: 1.7;
	color: var(--wp--preset--color--body-text);
	margin: 0;
}

@media (max-width: 599px) {
	.vsg-levels > * {
		grid-template-columns: 1fr;
		gap: 8px;
	}
}

/* At-a-glance tag grid. */
:root .vsg-tags {
	display: grid;
	grid-template-columns: repeat(auto-fit, minmax(min(150px, 100%), 1fr));
	column-gap: 28px;
	row-gap: 10px;
	margin-block-start: 14px;
	padding: 0;
	list-style: none;
}

.vsg-tags li {
	font-size: var(--wp--preset--font-size--small);
	line-height: 1.6;
	color: var(--wp--preset--color--body-text);
}

/*
 * The boxed variant, rebuilt on a completely different mechanic after the
 * per-cell-border version kept coming up short on whichever cell happened
 * to be empty (a trailing row that doesn't fill every column has no cell
 * there to draw a border on, so the line stopped wherever content did).
 *
 * This uses no cell borders at all. The GAP between grid cells is the
 * line: the container's own background is the rule colour, every cell
 * paints over it with the section's actual background, and the 1px gap
 * left uncovered between cells is what reads as a hairline — the same way
 * a grout line works between tiles. That line exists everywhere two cells
 * meet, full stop, because it is a property of the grid itself and not of
 * any individual cell — an empty trailing slot simply has one less tile
 * covering the grout, it does not break the pattern.
 */
:root .vsg-tags.vsg-tags--boxed {
	grid-template-columns: repeat(4, 1fr);
	gap: 1px;
	background: var(--vsg-rule);
	border: 1px solid var(--vsg-rule);
}

.vsg-tags.vsg-tags--boxed li {
	padding: 14px 16px;
	background: var(--wp--preset--color--surface);
}

/*
 * Four columns is too cramped for "Air conditioning" or "Private terrace"
 * below tablet width — two keeps every tag on one line and the grid still
 * reads as boxed, just two rows taller rather than squeezed sideways. The
 * grout-line mechanic above needs nothing extra here: fewer columns is
 * just a different grid, the gap is still the line either way.
 */
@media (max-width: 599px) {
	/*
	 * :root matches the specificity of the rule above it also carries —
	 * without it, this loses that tie on cascade order alone and every
	 * phone keeps the four-column desktop grid regardless of screen width.
	 */
	:root .vsg-tags.vsg-tags--boxed {
		grid-template-columns: repeat(2, 1fr);
	}
}

/* Bed counts: a large serif number against its label, in place of a plain tag. */
.vsg-beds {
	display: grid;
	grid-template-columns: repeat(auto-fit, minmax(min(170px, 100%), 1fr));
	column-gap: 32px;
	margin-block-start: 6px;
}

:root .vsg-beds > * {
	display: flex;
	align-items: baseline;
	gap: 12px;
	padding-block: 13px;
	border-block-end: 1px solid var(--vsg-rule);
	margin-block: 0;
}

.vsg-beds__count {
	font-family: var(--wp--preset--font-family--display);
	font-size: 1.3125rem;
	font-weight: 400;
	line-height: 1;
	color: var(--wp--preset--color--contrast);
	min-width: 16px;
	margin: 0;
}

.vsg-beds__label {
	font-size: var(--wp--preset--font-size--small);
	font-weight: 300;
	line-height: 1.5;
	color: var(--wp--preset--color--body-text);
	margin: 0;
}

/*
 * The connecting line in "A section through the house", between one level's
 * icon and the next. No native block covers a bespoke connector like this,
 * so it is the one custom-purpose class this component needs.
 */
.vsg-connector {
	display: block;
	width: 1px;
	height: 34px;
	margin-block-start: 8px;
	background: rgba(31, 51, 32, 0.2);
}

/* The roman numeral beside a level's name in the same component. */
.vsg-numeral {
	margin: 0;
	font-size: var(--wp--preset--font-size--x-small);
	font-weight: 400;
	letter-spacing: var(--vsg-track-tight);
	color: var(--wp--preset--color--muted);
}

/* A short line under a photograph, naming what it shows. */
.vsg-photo-caption {
	margin-block-start: 14px;
	margin-block-end: 0;
	font-size: var(--wp--preset--font-size--x-small);
	font-weight: 300;
	line-height: 1.6;
	color: var(--wp--preset--color--muted);
}

/* Included-with-the-house grid: a marked row per amenity, no chip background. */
.vsg-amenities {
	display: grid;
	grid-template-columns: repeat(auto-fit, minmax(min(220px, 100%), 1fr));
	column-gap: clamp(24px, 3vw, 48px);
}

:root .vsg-amenities > * {
	display: flex;
	align-items: baseline;
	gap: 12px;
	padding-block: 15px;
	border-block-start: 1px solid var(--vsg-rule);
	margin-block: 0;
}

.vsg-amenity__mark {
	color: var(--wp--preset--color--terracotta);
	font-size: 0.75rem;
	line-height: 1.4;
	margin: 0;
}

.vsg-amenity__label {
	font-size: var(--wp--preset--font-size--small);
	font-weight: 300;
	line-height: 1.6;
	color: var(--wp--preset--color--body-text);
	margin: 0;
}

/*
 * The pill-filterable photo grid. A pill toggles is-filtered-out on the
 * figures that do not match — the flex-wrap gallery reflows around the gap
 * on its own, so no separate masonry logic is needed.
 */
.vsg-gallery-filter__pills {
	display: flex;
	flex-wrap: wrap;
	gap: 10px;
	margin-block-end: clamp(28px, 3vw, 40px);
}

/*
 * Named vsg-filter-pill, not vsg-pill — that name already belongs to the
 * unrelated distance badge on a guide card ("10 min", absolutely
 * positioned over a photo). Reusing it here let that rule's position:
 * absolute leak onto every one of these buttons, stacking them all on top
 * of each other at that badge's fixed coordinates.
 */
.vsg-filter-pill {
	padding: 9px 20px;
	border: 1px solid var(--vsg-rule-strong);
	border-radius: 999px;
	background: transparent;
	color: var(--wp--preset--color--contrast);
	font-family: var(--wp--preset--font-family--body);
	font-size: var(--wp--preset--font-size--eyebrow);
	font-weight: 500;
	letter-spacing: var(--vsg-track-tight);
	text-transform: uppercase;
	cursor: pointer;
	transition: background var(--vsg-dur) ease, color var(--vsg-dur) ease, border-color var(--vsg-dur) ease;
}

.vsg-filter-pill:hover {
	border-color: var(--wp--preset--color--contrast);
}

.vsg-filter-pill.is-active {
	background: var(--wp--preset--color--contrast);
	border-color: var(--wp--preset--color--contrast);
	color: var(--wp--preset--color--base);
}

:root .wp-block-gallery.vsg-gallery-filter__grid figure.wp-block-image {
	margin: 0;
}

/*
 * A figure a pill has ruled out. The flex-wrap grid reflows around the gap
 * on its own. Core's own gallery stylesheet sets
 * ".wp-block-gallery.has-nested-images figure.wp-block-image" to
 * display:flex — one class lighter than it looks (three classes plus the
 * figure tag) but still more specific than a plain two-class rule, so it
 * silently beat a "display: none" here. Matched shape-for-shape (also
 * class+class+tag) so this one wins on specificity rather than luck of
 * source order.
 */
.wp-block-gallery.vsg-gallery-filter__grid figure.wp-block-image.is-filtered-out {
	display: none;
}

/* Shown instead of the grid when a category currently has no photos. */
.vsg-gallery-filter__empty {
	padding: 40px 0;
	font-size: var(--wp--preset--font-size--medium);
	color: var(--wp--preset--color--muted);
	text-align: center;
}

/*
 * A hand-drawn expand glyph in the site's own line-icon style, standing in
 * for the browser's generic zoom-in cursor. Sat on a small cream disc —
 * plain dark-green strokes disappeared entirely over the darker photos in
 * this gallery, so the disc is what actually keeps it visible over every
 * image regardless of what's underneath. The "zoom-in" after the comma is
 * the fallback if a browser cannot render an SVG cursor.
 *
 * Four corner brackets around a centred square, not a magnifying glass —
 * a lens-plus-handle glyph is lopsided by nature (the handle's weight
 * pulls the eye off past the lens no matter how the two are balanced
 * against each other), where four identical brackets are symmetric by
 * construction: the square they trace is centred on the disc exactly
 * because each corner is the same shape, rotated, the same distance out.
 */
.vsg-gallery-filter__grid .wp-block-image img {
	cursor: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='36' height='36' viewBox='0 0 36 36'%3E%3Ccircle cx='18' cy='18' r='17' fill='%23FBF9F3' stroke='%231F3320' stroke-opacity='0.15'/%3E%3Cg fill='none' stroke='%231F3320' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M12 16v-4h4'/%3E%3Cpath d='M20 12h4v4'/%3E%3Cpath d='M24 20v4h-4'/%3E%3Cpath d='M16 24h-4v-4'/%3E%3C/g%3E%3C/svg%3E") 18 18, zoom-in;
}

/*
 * The full-screen lightbox. Same dark-overlay, light-text language as the
 * mobile menu and the reviews slider elsewhere on the site, so opening a
 * photograph does not feel like a different piece of software.
 */
.vsg-lightbox {
	position: fixed;
	inset: 0;
	z-index: 100;
	display: flex;
	flex-direction: column;
	background: rgba(20, 30, 20, 0.94);
	animation: vsgFade 200ms ease both;
}

.vsg-lightbox[hidden] {
	display: none;
}

body.vsg-lightbox-open {
	overflow: hidden;
}

.vsg-lightbox__top {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: 20px;
	padding: 22px clamp(18px, 4vw, 46px);
	flex: 0 0 auto;
}

.vsg-lightbox__counter {
	font-family: var(--wp--preset--font-family--body);
	font-size: var(--wp--preset--font-size--eyebrow);
	font-weight: 400;
	letter-spacing: var(--vsg-track);
	text-transform: uppercase;
	color: var(--vsg-rule-light);
}

.vsg-lightbox__close {
	background: none;
	border: 0;
	padding: 6px 10px;
	color: var(--wp--preset--color--base);
	font-size: 1.875rem;
	font-weight: 300;
	line-height: 1;
	cursor: pointer;
}

.vsg-lightbox__stage {
	flex: 1 1 auto;
	min-height: 0;
	display: flex;
	align-items: center;
	justify-content: center;
	gap: clamp(10px, 2vw, 28px);
	padding: 0 clamp(12px, 3vw, 40px);
}

.vsg-lightbox__nav {
	flex: 0 0 auto;
	width: 50px;
	height: 50px;
	border: 1px solid rgba(245, 241, 230, 0.3);
	border-radius: 999px;
	background: none;
	color: var(--wp--preset--color--base);
	font-size: 1rem;
	cursor: pointer;
	transition: background var(--vsg-dur) ease;
}

.vsg-lightbox__nav:hover {
	background: var(--vsg-rule-light);
}

.vsg-lightbox__figure {
	flex: 1 1 auto;
	height: 100%;
	min-width: 0;
	display: flex;
	align-items: center;
	justify-content: center;
}

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

.vsg-lightbox__caption {
	flex: 0 0 auto;
	margin: 0;
	padding: 22px clamp(18px, 4vw, 46px) 30px;
	text-align: center;
	font-size: var(--wp--preset--font-size--eyebrow);
	font-weight: 500;
	letter-spacing: var(--vsg-track);
	text-transform: uppercase;
	color: rgba(245, 241, 230, 0.82);
}

@media (max-width: 599.98px) {
	.vsg-lightbox__nav {
		width: 40px;
		height: 40px;
	}
}

/* 12. QUOTE BAND */

.vsg-quote-band {
	min-height: min(78vh, 720px);
	align-items: center;
	justify-content: center;
	background-color: var(--wp--preset--color--contrast);
}

.vsg-quote-band::after {
	content: "";
	position: absolute;
	inset: 0;
	z-index: 1;
	pointer-events: none;
	background: linear-gradient(180deg, rgba(31, 51, 32, 0.44), rgba(31, 51, 32, 0.56));
}

.vsg-quote-band > .wp-block-cover__inner-container {
	position: relative;
	z-index: 2;
}

.vsg-quote-band .wp-block-quote {
	max-width: min(880px, 88vw);
	margin-inline: auto;
	text-align: center;
	border: 0;
}

/*
 * Core's own "parallax" is background-attachment: fixed — the photograph
 * pinned to the viewport while the page scrolls over it, which reads as
 * locked in place rather than moving at all, let alone at a different
 * speed than the content. theme.js's initParallax() drives the real thing
 * instead: a transform that tracks scroll position at a fraction of its
 * speed, so the photograph drifts slower than the page around it. This
 * overrides the fixed attachment (transform and background-attachment:
 * fixed fight each other in some browsers) and gives the layer the extra
 * height and headroom that transform needs to move within without
 * exposing bare edges — the actual amount is set inline by the script,
 * matched to that cover's own height, since a fixed guess here would come
 * up short on a very tall or very short one.
 */
/*
 * Every hero-shaped cover gets the same treatment, not only ones with
 * core's has-parallax box ticked: the inner-page hero band
 * (.vsg-page-hero, on The Villa, Estate Life, Orvieto & Umbria, Guests
 * and Contact) is the identical wp:cover shape and reads as the same kind
 * of banner, so it gets the same depth rather than only whichever ones
 * happened to have that checkbox ticked in the editor.
 */
:is(.wp-block-cover.has-parallax, .vsg-page-hero) {
	overflow: hidden;
}

:is(.wp-block-cover.has-parallax, .vsg-page-hero) > .wp-block-cover__image-background {
	/*
	 * !important because core's own has-parallax rule reaches this same
	 * element through the identical selector shape (two classes on the
	 * cover, one on this layer) — at equal specificity the cascade would
	 * decide this on load order alone, which is not a coin flip worth
	 * leaving to chance for the one property this whole change hinges on.
	 */
	background-attachment: scroll !important;
	height: 130%;
	top: -15%;
	will-change: transform;
}

:root .vsg-quote-band .wp-block-quote p {
	color: var(--wp--preset--color--base);
	font-family: var(--wp--preset--font-family--display);
	font-size: var(--wp--preset--font-size--quote);
	font-style: italic;
	font-weight: 300;
	line-height: 1.22;
	text-wrap: balance;
	margin: 0;
}

:root .vsg-quote-band .wp-block-quote cite {
	display: block;
	margin-block-start: 26px;
	font-family: var(--wp--preset--font-family--body);
	font-size: 0.65625rem;
	font-style: normal;
	font-weight: 400;
	letter-spacing: var(--vsg-track-wide);
	text-transform: uppercase;
	color: rgba(251, 249, 243, 0.82);
}

/* 13. CARDS AND EQUAL-HEIGHT ROWS */

.equal-cards > .wp-block-column {
	display: flex;
	flex-direction: column;
	flex-grow: 0;
}

.equal-cards > .wp-block-column > .wp-block-group {
	display: flex;
	flex-direction: column;
	flex-grow: 1;
}

:root .equal-cards .cta-bottom {
	margin-top: auto;
	justify-content: center;
}

/*
 * Card rows step four-up to two-up at 1180 and to one-up at 640 — neither of
 * core's thresholds, so they opt out of its stacking and this file owns the
 * behaviour. Widths arrive as an inline flex-basis, so overriding needs the
 * same !important core uses for its own stacking rule.
 */
@media (max-width: 1179.98px) {
	/* Core forces nowrap above 782px with !important; this has to match it. */
	.wp-block-columns.vsg-cardrow {
		flex-wrap: wrap !important;
	}

	.wp-block-columns.vsg-cardrow > .wp-block-column {
		flex-basis: calc(50% - clamp(0.625rem, 1.5vw, 1.25rem)) !important;
	}
}

@media (max-width: 639.98px) {
	.wp-block-columns.vsg-cardrow > .wp-block-column {
		flex-basis: 100% !important;
	}
}

/*
 * The facts strip stays on one line for as long as five figures fit, then
 * wraps rather than shrinking each cell past legibility.
 */
@media (max-width: 899.98px) {
	.wp-block-columns.vsg-facts {
		flex-wrap: wrap !important;
	}

	.wp-block-columns.vsg-facts > .wp-block-column {
		flex-basis: 33.33% !important;
	}
}

/*
 * Below this width the strip is meant to wrap to two columns (the 899.98px
 * rule above sets flex-wrap; flex-basis:50% below was meant to pair with
 * it) but never actually did: this stylesheet doesn't reset box-sizing to
 * border-box, so flex-basis:50% sizes only the content box and the column's
 * own 22px+22px padding is added on top of that — each cell rendered at
 * roughly 63% of the row, so only one ever fit and every cell wrapped to
 * its own row. box-sizing:border-box here is what actually makes 50% mean
 * half the row.
 *
 * With that fixed, border-inline-start per cell plus border-inline-end on
 * the DOM's last child — built for one unbroken row — puts a stray short
 * line next to the bottom-right cell only, not a real edge of anything.
 * Same fix as the at-a-glance grid elsewhere in this file: no per-cell
 * border, gap + a background colour on the container showing through the
 * gap as grout, with each cell painted over it — draws a full line between
 * every pair of cells, horizontal and vertical alike, with no last-child
 * math.
 */
@media (max-width: 599.98px) {
	.wp-block-columns.vsg-facts {
		gap: 1px;
		background: var(--vsg-rule);
	}

	.wp-block-columns.vsg-facts > .wp-block-column {
		box-sizing: border-box;
		/*
		 * Not a bare 50%: with the container at exactly 345px and a 1px
		 * gap between cells, two cells at a literal 50% each (172.5 + 172.5)
		 * come to 345px on their own — before the 1px gap is added on top,
		 * which is exactly enough overflow for flex-wrap to bump the second
		 * cell onto its own row instead of pairing up. calc(50% - 1px)
		 * leaves the gap room.
		 */
		flex-basis: calc(50% - 1px) !important;
		/*
		 * A flex item's default min-width is auto, not 0 — it won't shrink
		 * past its own content's min-content width no matter what flex-basis
		 * says. min-width:0 is what actually lets flex-basis take effect;
		 * a label too long for the column still wraps onto a second line
		 * rather than forcing the column wider.
		 */
		min-width: 0;
		border: 0 !important;
		background: var(--wp--preset--color--base);
	}
}

/* A card: image on top,
then a kicker,
a title and a paragraph. */
:root .vsg-card > * {
	margin-block: 0;
}

:root .vsg-card > * + * {
	margin-block-start: 12px;
}

:root .vsg-card > .wp-block-image + * {
	margin-block-start: 22px;
}

:root .vsg-card .wp-block-image {
	margin: 0;
	overflow: hidden;
	border-radius: var(--vsg-radius);
}

.vsg-card .wp-block-image img {
	display: block;
	width: 100%;
	object-fit: cover;
	border-radius: 0;
	transition: transform 600ms var(--vsg-ease);
}

.vsg-card--tall .wp-block-image img {
	aspect-ratio: 3 / 4;
}

.vsg-card--portrait .wp-block-image img {
	aspect-ratio: 4 / 5;
}

.vsg-card__kicker {
	font-family: var(--wp--preset--font-family--body);
	font-size: 0.65625rem;
	font-weight: 500;
	letter-spacing: 0.2em;
	text-transform: uppercase;
	color: var(--wp--preset--color--terracotta);
}

.vsg-card p {
	font-size: var(--wp--preset--font-size--medium);
	line-height: 1.75;
	text-wrap: pretty;
}

/* Guide cards are links in the design; the whole card reacts on hover. */
.vsg-card--link {
	position: relative;
	transition: opacity var(--vsg-dur) ease;
}

.vsg-card--link:hover {
	opacity: 0.92;
}

.vsg-card--link:hover .wp-block-image img {
	transform: scale(1.03);
}

/* The distance pill sitting on a guide card image. */
:root .vsg-pill {
	position: absolute;
	top: 34px;
	left: 14px;
	z-index: 2;
	margin: 0;
	padding: 7px 12px;
	border-radius: 999px;
	background: rgba(251, 249, 243, 0.92);
	color: var(--wp--preset--color--contrast);
	font-family: var(--wp--preset--font-family--body);
	font-size: 0.625rem;
	font-weight: 500;
	letter-spacing: var(--vsg-track-tight);
	line-height: 1;
	text-transform: uppercase;
	pointer-events: none;
}

.vsg-card__media {
	position: relative;
}

/*
 * Placeholder wells. Every image slot the client has not supplied yet renders
 * as a flat sand panel at the correct ratio, so nothing shifts when the real
 * photograph arrives. Documented one by one in IMAGES.md.
 */
.vsg-placeholder {
	position: relative;
	display: flex;
	align-items: center;
	justify-content: center;
	background: linear-gradient(150deg, var(--wp--preset--color--sand) 0%, #dcd4bd 100%);
	border-radius: var(--vsg-radius);
	overflow: hidden;
}

:root .vsg-placeholder > * {
	margin: 0;
	padding: 1.5rem;
	font-family: var(--wp--preset--font-family--body);
	font-size: 0.65625rem;
	font-weight: 500;
	letter-spacing: var(--vsg-track-tight);
	text-align: center;
	text-transform: uppercase;
	color: #6b7359;
}

.vsg-placeholder--4x5 {
	aspect-ratio: 4 / 5;
}

.vsg-placeholder--3x4 {
	aspect-ratio: 3 / 4;
}

.vsg-placeholder--5x4 {
	aspect-ratio: 5 / 4;
}

.vsg-placeholder--1x1 {
	aspect-ratio: 1 / 1;
}

/* 14. CARETAKER AND SERVICE LIST */

.vsg-mark-overlap {
	position: relative;
}

:root .vsg-mark-overlap > :first-child {
	width: min(100%, 480px);
	margin: 0;
}

.vsg-mark-overlap > :first-child img {
	width: 100%;
	aspect-ratio: 4 / 5;
	object-fit: cover;
}

/* The olive tree mark,
dropped over the lower corner of the portrait. */
:root .vsg-mark-overlap__mark {
	position: absolute;
	right: 8%;
	bottom: -34px;
	width: auto;
	margin: 0;
	opacity: 0.5;
	pointer-events: none;
}

.vsg-mark-overlap__mark img {
	height: 96px;
	width: auto;
	border-radius: 0;
}

/* Two-column service list,
each row opening with a hairline and a mark. */
:root .vsg-services {
	display: grid;
	grid-template-columns: repeat(auto-fit, minmax(min(240px, 100%), 1fr));
	column-gap: 28px;
	margin-block-start: 32px;
	padding: 0;
	list-style: none;
}

.vsg-services li {
	display: flex;
	align-items: baseline;
	gap: 10px;
	padding-block: 16px;
	border-block-start: 1px solid rgba(31, 51, 32, 0.16);
	font-size: 0.90625rem;
	line-height: 1.6;
	color: var(--wp--preset--color--body-text);
}

.vsg-services li::before {
	content: "\2726";
	flex: 0 0 auto;
	color: var(--wp--preset--color--terracotta);
	font-size: 13px;
	line-height: 1;
}

.vsg-footnote {
	font-size: var(--wp--preset--font-size--x-small);
	letter-spacing: 0.03em;
	color: var(--wp--preset--color--muted);
}

/* 15. REVIEWS */

.vsg-reviews .vsg-eyebrow {
	color: var(--wp--preset--color--sage);
}

.vsg-reviews h2,
.vsg-reviews .wp-block-quote p {
	color: var(--wp--preset--color--base);
}

.vsg-rating {
	display: flex;
	align-items: center;
	gap: 18px;
	padding: 16px 22px;
	border: 1px solid rgba(245, 241, 230, 0.22);
	border-radius: var(--vsg-radius);
	flex: 0 0 auto;
}

:root .vsg-rating > * {
	margin-block: 0;
}

.vsg-rating__score {
	font-family: var(--wp--preset--font-family--display);
	font-size: 2.5rem;
	font-weight: 300;
	line-height: 1;
	color: var(--wp--preset--color--base);
}

.vsg-rating__meta {
	display: flex;
	flex-direction: column;
	gap: 5px;
}

.vsg-rating__stars {
	font-size: 0.8125rem;
	letter-spacing: var(--vsg-track-tight);
	line-height: 1;
	color: var(--wp--preset--color--gold);
}

.vsg-rating__source {
	font-family: var(--wp--preset--font-family--body);
	font-size: 0.65625rem;
	letter-spacing: 0.16em;
	text-transform: uppercase;
	color: rgba(245, 241, 230, 0.72);
}

/*
 * The homepage's reviews band is dark, so .vsg-rating's near-white score,
 * source line and border were never written to work anywhere else — on the
 * Guests page, which sits on the ordinary light ground, that same near-white
 * rendered as near-invisible: cream text on a cream page. This modifier is
 * the same component, recoloured for a light ground rather than a dark one.
 */
.vsg-rating--light {
	border-color: rgba(31, 51, 32, 0.14);
}

.vsg-rating--light .vsg-rating__score {
	color: var(--wp--preset--color--contrast);
}

.vsg-rating--light .vsg-rating__source {
	color: var(--wp--preset--color--muted);
}

/*
 * The Airbnb category breakdown (Cleanliness, Accuracy, etc.) the Guests
 * page shows under its own light-ground .vsg-rating--light, added here
 * too under the homepage's dark one — .vsg-tags's own default text colour
 * is body-text, a dark green meant for the ordinary cream page ground,
 * and would be nearly unreadable against .vsg-reviews's dark olive
 * background. Same fix as vsg-rating--light above, just the other
 * direction: recolour for the dark ground this instance actually sits on.
 */
.vsg-tags--on-dark li {
	color: rgba(245, 241, 230, 0.82);
}

:root .vsg-reviews__track {
	margin-block-start: clamp(44px, 5vw, 72px);
	min-height: 250px;
	display: flex;
	flex-direction: column;
	justify-content: center;
	gap: var(--wp--preset--spacing--60);
}

:root .vsg-reviews__track .wp-block-quote {
	max-width: min(820px, 100%);
	margin: 0;
	border: 0;
	padding: 0;
}

/*
 * Once Swiper takes over.
 *
 * Without these the track stays a column flex box and each quote keeps its
 * 820px reading measure — which becomes the slide width, so a 1320px track
 * shows one quote and half of the next. The slide has to fill the track; the
 * measure belongs to the text inside it.
 *
 * These rules were lost with the gallery section they were grouped under.
 */
:root .vsg-reviews__track.swiper-initialized {
	display: block;
	overflow: hidden;
	gap: 0;
}

:root .vsg-reviews__track.swiper-initialized .swiper-wrapper {
	display: flex;
	align-items: flex-start;
}

:root .vsg-reviews__track.swiper-initialized .wp-block-quote {
	max-width: none;
	width: 100%;
	flex: 0 0 auto;
}

:root .vsg-reviews__track.swiper-initialized .wp-block-quote > * {
	max-width: min(820px, 100%);
}

:root .vsg-reviews__track .wp-block-quote p {
	max-width: 34ch;
	font-size: clamp(1.625rem, 3.2vw, 2.75rem);
	line-height: 1.26;
	text-wrap: pretty;
	margin: 0;
}

:root .vsg-reviews__track .wp-block-quote cite {
	display: flex;
	align-items: center;
	gap: 14px;
	margin-block-start: 30px;
	font-family: var(--wp--preset--font-family--body);
	font-size: var(--wp--preset--font-size--eyebrow);
	font-style: normal;
	letter-spacing: var(--vsg-track);
	text-transform: uppercase;
	color: rgba(245, 241, 230, 0.78);
}

:root .vsg-reviews__foot {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: 20px;
	margin-block-start: 44px;
	padding-block-start: 26px;
	border-block-start: 1px solid var(--vsg-rule-light);
}

:root .vsg-reviews__foot > * {
	margin-block: 0;
}

.vsg-reviews__counter {
	font-family: var(--wp--preset--font-family--body);
	font-size: var(--wp--preset--font-size--eyebrow);
	letter-spacing: 0.16em;
	text-transform: uppercase;
	color: rgba(245, 241, 230, 0.66);
}

/* 16. RATES AND CALENDAR */

:root .vsg-rate-head {
	display: flex;
	flex-wrap: wrap;
	align-items: flex-end;
	justify-content: space-between;
	gap: 24px;
	margin-block-start: 34px;
	padding-block: 26px 22px;
	border-block-start: 1px solid var(--vsg-rule-strong);
	border-block-end: 1px solid rgba(31, 51, 32, 0.14);
}

:root .vsg-rate-head > * {
	margin-block: 0;
}

.vsg-rate-head__price {
	font-family: var(--wp--preset--font-family--display);
	font-size: clamp(2.5rem, 4.4vw, 3.625rem);
	font-weight: 400;
	line-height: 1;
	white-space: nowrap;
	color: var(--wp--preset--color--contrast);
}

.vsg-rate-row {
	display: flex;
	align-items: baseline;
	justify-content: space-between;
	gap: 20px;
	padding-block: 18px;
	border-block-end: 1px solid rgba(31, 51, 32, 0.14);
}

:root .vsg-rate-row > * {
	margin-block: 0;
}

.vsg-rate-row__amount {
	font-family: var(--wp--preset--font-family--display);
	font-size: 1.5rem;
	font-weight: 400;
	white-space: nowrap;
	color: var(--wp--preset--color--contrast);
}

/*
 * Two fixed columns,
so the four policies pair up row by row as the design
 * lays them out — payment beside cancellation,
deposit beside pets. An
 * auto-fit track count would reflow them into three or four across.
 */
:root .vsg-policies {
	display: grid;
	grid-template-columns: repeat(2, minmax(0, 1fr));
	gap: 30px 40px;
	margin-block-start: 38px;
}

@media (max-width: 599.98px) {
	:root .vsg-policies {
		grid-template-columns: 1fr;
	}
}

:root .vsg-policies > * {
	padding-block-start: 16px;
	border-block-start: 1px solid rgba(31, 51, 32, 0.16);
	margin-block: 0;
}

:root .vsg-policies .vsg-policy__label {
	display: block;
	font-family: var(--wp--preset--font-family--body);
	font-size: 0.65625rem;
	font-weight: 500;
	letter-spacing: var(--vsg-track);
	text-transform: uppercase;
	color: var(--wp--preset--color--accent);
	margin: 0;
}

:root .vsg-policies .vsg-policy__body {
	margin-block: 10px 0;
	font-size: 0.875rem;
	line-height: 1.7;
	text-wrap: pretty;
}

.vsg-calendar {
	border: 1px solid rgba(31, 51, 32, 0.14);
	border-radius: var(--vsg-radius);
	padding: clamp(22px, 3vw, 34px);
	background: var(--wp--preset--color--base);
}

.vsg-calendar__head {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: 16px;
}

:root .vsg-calendar__head > * {
	margin-block: 0;
}

.vsg-calendar__month {
	font-family: var(--wp--preset--font-family--display);
	font-size: 1.5rem;
	font-weight: 400;
	color: var(--wp--preset--color--contrast);
}

.vsg-calendar__sync {
	font-family: var(--wp--preset--font-family--body);
	font-size: 0.625rem;
	font-weight: 500;
	letter-spacing: 0.16em;
	text-transform: uppercase;
	color: var(--wp--preset--color--accent);
}

:root .vsg-calendar__legend {
	display: flex;
	flex-wrap: wrap;
	gap: 20px;
	margin-block-start: 24px;
	padding-block-start: 20px;
	border-block-start: 1px solid var(--vsg-rule);
	padding-inline-start: 0;
	list-style: none;
}

.vsg-calendar__legend li {
	display: flex;
	align-items: center;
	gap: 8px;
	font-size: var(--wp--preset--font-size--x-small);
	color: var(--wp--preset--color--body-text);
}

.vsg-calendar__legend li::before {
	content: "";
	width: 11px;
	height: 11px;
	border-radius: var(--vsg-radius-sm);
	flex: 0 0 auto;
}

.vsg-calendar__legend li:nth-child(1)::before {
	background: var(--wp--preset--color--base);
	border: 1px solid rgba(31, 51, 32, 0.2);
}

.vsg-calendar__legend li:nth-child(2)::before {
	background: var(--wp--preset--color--sand);
}

.vsg-calendar__legend li:nth-child(3)::before {
	background: var(--wp--preset--color--contrast);
}

/*
 * The availability calendar in the rates card.
 *
 * Rendered server side, so it is in the HTML without scripting. Display only —
 * choosing dates is the booking bar's job — so the cells are spans, not
 * buttons, and carry their state in a title and a visually hidden label.
 */
.vsg-cal-static__month {
	margin: 0 0 14px;
	font-family: var(--wp--preset--font-family--display);
	font-size: 1.5rem;
	font-weight: 400;
	color: var(--wp--preset--color--contrast);
}

/* Same overflow floor as .vsg-cal__grid above, and the same fix. */
.vsg-cal-static__grid {
	display: grid;
	grid-template-columns: repeat(7, minmax(0, 1fr));
	gap: 4px;
}

.vsg-cal-static__dow {
	text-align: center;
	padding-block-end: 6px;
	font-family: var(--wp--preset--font-family--body);
	font-size: 0.59375rem;
	font-weight: 500;
	letter-spacing: 0.12em;
	text-transform: uppercase;
	color: var(--wp--preset--color--muted);
}

.vsg-cal-static__day {
	display: flex;
	align-items: center;
	justify-content: center;
	aspect-ratio: 1 / 1;
	border-radius: var(--vsg-radius-sm);
	font-family: var(--wp--preset--font-family--body);
	font-size: 0.8125rem;
	font-weight: 300;
	line-height: 1;
	color: var(--wp--preset--color--contrast);
}

.vsg-cal-static__day.is-empty {
	visibility: hidden;
}

.vsg-cal-static__day.is-booked {
	background: var(--wp--preset--color--sand);
	color: var(--wp--preset--color--muted);
	text-decoration: line-through;
}

.vsg-cal-static__day.is-past {
	color: var(--wp--preset--color--muted);
	opacity: 0.4;
}

/* The Lodgify calendar mounts here and replaces the note below it. */
:root .vsg-calendar__mount {
	margin-block-start: 22px;
}

/*
 * Only Lodgify's own widget supersedes the legend. This theme's calendars —
 * the server-rendered month and the live one that replaces it — are drawn in
 * exactly the colours the legend describes, so the key stays.
 */
.vsg-calendar__mount:not(:empty):not(:has(.vsg-cal-static)):not(:has(.vsg-cal)) ~ .vsg-calendar__legend {
	display: none;
}

/*
 * The live calendar in an availability card.
 *
 * It is the picker from the booking bar, one month wide and filling the card
 * rather than sized to a popover, and it writes to the same selection — so a
 * guest who has read this far can change their dates here instead of
 * scrolling back up to the panel.
 */
.vsg-calendar__mount.is-live .vsg-cal {
	display: block;
}

.vsg-calendar__mount.is-live .vsg-cal__month {
	width: 100%;
}

.vsg-calendar__mount.is-live .vsg-cal__title {
	font-size: 1.5rem;
}

/*
 * Room to hit. In a popover the month arrows sit under a pointer that has
 * just been placed there deliberately; in a card they are a target on a phone,
 * so they take the full 44px.
 */
.vsg-calendar__mount.is-live .vsg-cal__grid {
	gap: 4px;
}

.vsg-calendar__mount.is-live .vsg-cal__day {
	font-size: 0.875rem;
}

.vsg-calendar__mount.is-live .vsg-cal__nav {
	width: 44px;
	min-width: 44px;
	height: 44px;
	min-height: 44px;
	font-size: 15px;
}

/*
 * The live total, repeated beside the buttons that leave for the checkout —
 * in the rates card and in the footer. A guest should never have to scroll
 * back to the booking bar to find out what they are about to pay.
 *
 * The line keeps its height whether it holds a prompt or a price, so filling
 * it in moves nothing.
 */
:root .vsg-calendar__quote {
	display: flex;
	flex-wrap: wrap;
	align-items: baseline;
	gap: 2px 10px;
	min-height: 54px;
	margin-block: 20px 0;
	font-family: var(--wp--preset--font-family--body);
	font-size: var(--wp--preset--font-size--x-small);
	font-weight: 300;
	color: var(--wp--preset--color--muted);
}

.vsg-calendar__quote .vsg-cal__nights {
	font-size: var(--wp--preset--font-size--medium);
	color: var(--wp--preset--color--contrast);
}

.vsg-calendar__quote .vsg-cal__price {
	font-size: 1.5rem;
}

.vsg-calendar__quote.is-priced {
	color: var(--wp--preset--color--body-text);
}

/* 17. FOOTER */

.vsg-footer {
	color: rgba(245, 241, 230, 0.82);
	padding-block: clamp(4.5rem, 8vw, 6.875rem) 2.5rem;
}

.wp-block-columns.vsg-footer__cols {
	gap: clamp(2.25rem, 4vw, 4.5rem);
}

:root .vsg-footer__blurb {
	max-width: 30ch;
	margin-block: 1rem 0;
}

:root .vsg-footer__pitch {
	margin-block: 1.25rem 0;
}

:root .vsg-footer__cta {
	margin-block-start: 1.375rem;
}

.vsg-footer .vsg-eyebrow {
	color: var(--wp--preset--color--sage);
}

.vsg-footer p,
.vsg-footer li {
	color: rgba(245, 241, 230, 0.82);
	font-size: 0.90625rem;
	line-height: 1.7;
}

.vsg-footer a {
	color: rgba(245, 241, 230, 0.9);
	transition: color var(--vsg-dur) ease;
}

.vsg-footer a:hover,
.vsg-footer a:focus-visible {
	color: var(--wp--preset--color--base);
}

/*
 * The footer states the total but draws no calendar of its own — its button
 * sends a guest back up to the one in the availability card.
 */
:root .vsg-footer .vsg-calendar__quote {
	display: block;
	min-height: 58px;
	margin-block: 16px 0;
	line-height: 1.5;
	color: rgba(245, 241, 230, 0.6);
}

/*
 * Dates on one line, the total under them. A quarter column cannot hold both
 * on a single line, and the height is held open either way so filling it in
 * moves nothing.
 */
.vsg-footer .vsg-calendar__quote .vsg-cal__dates {
	display: block;
}

.vsg-footer .vsg-calendar__quote .vsg-cal__nights::after {
	content: " \00b7\00a0";
}

.vsg-footer .vsg-calendar__quote .vsg-cal__dates,
.vsg-footer .vsg-calendar__quote .vsg-cal__nights,
.vsg-footer .vsg-calendar__quote .vsg-cal__price {
	color: var(--wp--preset--color--base);
}

/*
 * A quarter-column has room for the length and the total, and not for the
 * breakdown as well — that is what the rates card is for.
 */
.vsg-footer .vsg-calendar__quote .vsg-cal__note {
	display: none;
}

.vsg-footer .vsg-calendar__quote.is-priced {
	color: rgba(245, 241, 230, 0.82);
}

:root .vsg-footer__wordmark {
	margin-block: 1.375rem 0;
	font-family: var(--wp--preset--font-family--display);
	font-size: 1.75rem;
	font-style: italic;
	font-weight: 500;
	line-height: 1.1;
}

/*
 * Color specifically on the link, not just the wrapping <p>: theme.json
 * sets a global colour for every <a> (styles.elements.link), which beats
 * plain inheritance from this wrapper regardless of source order — the
 * same reason the header's own site-title rule targets its <a> directly.
 */
:root .vsg-footer__wordmark a {
	color: var(--wp--preset--color--base);
}

:root .vsg-footer__links {
	display: flex;
	flex-direction: column;
	gap: 10px;
	margin-block-start: 20px;
	padding: 0;
	list-style: none;
}

:root .vsg-footer__legal {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	justify-content: space-between;
	gap: 18px;
	margin-block-start: clamp(48px, 6vw, 82px);
	padding-block-start: 26px;
	border-block-start: 1px solid rgba(245, 241, 230, 0.16);
}

/*
 * A page can end on a section that already carries the footer's own deep
 * olive background — the "book direct" CTA band, most often (see
 * patterns/cta-band.php). With no colour change to mark it, that band and
 * the footer read as one unbroken block with no visible start. Redraw the
 * legal row's own hairline above the logo whenever the block right before
 * the footer is that same colour, so the footer still reads as its own
 * section. :has() is what reaches across the header/main/footer template
 * parts — this file has no other way to see "what came right before me".
 */
main:has(> .entry-content > .has-contrast-background-color:last-child) + .wp-block-template-part .wp-block-columns.vsg-footer__cols {
	padding-block-start: 2.5rem;
	border-block-start: 1px solid rgba(245, 241, 230, 0.16);
}

/*
 * The rule above adds its own top padding to draw the hairline — it was
 * never meant to sit on top of the footer's ordinary ~90px opening padding
 * as well, but .vsg-footer's own padding-block-start doesn't know that
 * rule exists and keeps contributing it regardless, doubling the gap into
 * the same solid-green dead space this was supposed to replace. Dropping
 * it here, in the one case where the hairline is doing that job instead.
 */
main:has(> .entry-content > .has-contrast-background-color:last-child) + .wp-block-template-part .vsg-footer {
	padding-block-start: 0;
}

.vsg-footer__legal,
.vsg-footer__legal p,
.vsg-footer__legal a {
	font-size: var(--wp--preset--font-size--x-small);
	letter-spacing: 0.04em;
	color: rgba(245, 241, 230, 0.72);
}

:root .vsg-footer__legal > * {
	margin-block: 0;
}

.vsg-footer__legal .wp-block-navigation {
	gap: 24px;
}

/*
 * The legal links are the smallest targets on the page; this brings them to
 * a full 44px without moving the text.
 */
.vsg-footer__legal .wp-block-navigation-item__content,
.vsg-footer__links a {
	display: inline-flex;
	align-items: center;
	min-height: 44px;
}

.vsg-footer__legal .wp-block-navigation .wp-block-navigation-item {
	align-items: center;
}

/*
 * 18. EDITORIAL CONTENT DEFAULTS
 *
 * Blog, single post and search results. The design does not specify these,
 * so they inherit the estate's type and rhythm at reading measure.
 */

/* Inner-page title band. Cream, clearing the sticky header above it. */
.vsg-page-head {
	padding-block: clamp(3.5rem, 6vw, 5.5rem) clamp(2.5rem, 4vw, 3.5rem);
}

:root .vsg-page-head > * {
	margin-block: 0;
}

:root .vsg-page-head > * + * {
	margin-block-start: 1.25rem;
}

.vsg-page-head .wp-block-search {
	max-width: 420px;
}

.vsg-404 {
	text-align: center;
}

:root .vsg-404 > * + * {
	margin-block-start: 1.5rem;
}

.vsg-404 .wp-block-search,
.vsg-404 .wp-block-buttons {
	justify-content: center;
	margin-inline: auto;
	max-width: 420px;
}

.vsg-postgrid {
	gap: clamp(2rem, 3vw, 3rem);
}

:root .vsg-prose > * + * {
	margin-block-start: 1.4em;
}

:root .vsg-prose h2,
:root .vsg-prose h3 {
	margin-block-start: 2em;
}

.vsg-postcard {
	display: flex;
	flex-direction: column;
	gap: 14px;
}

.vsg-postcard .wp-block-post-featured-image img {
	aspect-ratio: 4 / 3;
	object-fit: cover;
	border-radius: var(--vsg-radius);
}

:root .vsg-postcard .wp-block-post-title {
	font-size: var(--wp--preset--font-size--xx-large);
	margin: 0;
}

.vsg-meta {
	font-family: var(--wp--preset--font-family--body);
	font-size: 0.65625rem;
	font-weight: 500;
	letter-spacing: var(--vsg-track);
	text-transform: uppercase;
	color: var(--wp--preset--color--muted);
}

.wp-block-search__button {
	border-radius: var(--vsg-radius-sm);
}

.wp-block-search__input {
	border: 1px solid var(--vsg-rule);
	border-radius: var(--vsg-radius-sm);
	padding: 0.875rem 1rem;
	font-family: var(--wp--preset--font-family--body);
	background: var(--wp--preset--color--base);
	color: var(--wp--preset--color--contrast);
}

/*
 * 19. MOTION
 *
 * Reveals are gated behind .vsg-js, set on <html> before first paint. With
 * no scripting, nothing is ever hidden and the page reads as plain content.
 */

@keyframes vsgHeroIn {
	from {
		opacity: 0;
		transform: translateY(26px);
	}

	to {
		opacity: 1;
		transform: none;
	}
}

/*
 * Ken Burns. Deliberately small — a 4% drift over nine seconds reads as the
 * photograph breathing, not as a zoom.
 */
@keyframes vsgKenBurns {
	from {
		transform: scale(1.005);
	}

	to {
		transform: scale(1.045);
	}
}

@keyframes vsgFade {
	from {
		opacity: 0;
	}

	to {
		opacity: 1;
	}
}

.vsg-js .animate-on-scroll {
	opacity: 0;
	transform: translateY(20px);
	transition: opacity 700ms var(--vsg-ease), transform 700ms var(--vsg-ease);
}

.vsg-js .animate-on-scroll.is-visible {
	opacity: 1;
	transform: none;
}

/* Children stagger in sequence, for card rows and stat strips. */
.vsg-js .stagger-children > * {
	opacity: 0;
	transform: translateY(20px);
	transition: opacity 620ms var(--vsg-ease), transform 620ms var(--vsg-ease);
}

.vsg-js .stagger-children.is-visible > * {
	opacity: 1;
	transform: none;
}

.vsg-js .stagger-children.is-visible > *:nth-child(1) {
	transition-delay: 0ms;
}

.vsg-js .stagger-children.is-visible > *:nth-child(2) {
	transition-delay: 90ms;
}

.vsg-js .stagger-children.is-visible > *:nth-child(3) {
	transition-delay: 180ms;
}

.vsg-js .stagger-children.is-visible > *:nth-child(4) {
	transition-delay: 270ms;
}

.vsg-js .stagger-children.is-visible > *:nth-child(6) {
	transition-delay: 450ms;
}

.vsg-js .stagger-children.is-visible > *:nth-child(7) {
	transition-delay: 540ms;
}

.vsg-js .stagger-children.is-visible > *:nth-child(8) {
	transition-delay: 630ms;
}

/*
 * Now that stagger-children also lands on galleries and columns rows
 * automatically (see initReveals() in theme.js), a set can run well past
 * five — extended to eight explicitly (the "at a glance" boxes' own
 * count, padded for the boxed grid) rather than open-ended: past a
 * couple more than a viewport actually shows at once, an even longer
 * stagger just delays the tail of a big gallery for no visible benefit.
 */
.vsg-js .stagger-children.is-visible > *:nth-child(5) {
	transition-delay: 360ms;
}

@media (prefers-reduced-motion: reduce) {
	html {
		scroll-behavior: auto;
	}

	*,
	*::before,
	*::after {
		animation-duration: 0.01ms !important;
		animation-iteration-count: 1 !important;
		transition-duration: 0.01ms !important;
		scroll-behavior: auto !important;
	}

	.vsg-js .animate-on-scroll,
	.vsg-js .stagger-children > * {
		opacity: 1;
		transform: none;
	}
}
