/**
 * 438 Main — Motion
 * ---------------------------------------------------------------------------
 * Every animated state in the theme, in one file, so the motion language stays
 * coherent and is trivial to audit or switch off.
 *
 * THREE RULES THIS FILE OBEYS
 *
 * 1. Content is never hidden by motion alone.
 *    Reveal states only apply inside `html.js`, a class app.js sets on itself
 *    at the top of the document. If JavaScript fails, is blocked, or has not
 *    parsed yet, every element renders in its final, visible state. The page is
 *    fully readable with JS disabled.
 *
 * 2. Only compositor-friendly properties animate.
 *    transform and opacity, plus clip-path for masked reveals. No animated
 *    width/height/top/left anywhere, so nothing triggers layout during scroll.
 *
 * 3. prefers-reduced-motion is respected absolutely.
 *    Under that setting everything is immediately visible, parallax is inert,
 *    and the only remaining transitions are instantaneous colour changes.
 */

/* ===========================================================================
 * Reveal on scroll
 * ======================================================================== */

/*
 * Pre-reveal state. Scoped to html.js so no-JS users never see it.
 * translate3d promotes the element to its own layer for the duration of the
 * transition only; will-change is deliberately not set globally, because
 * hundreds of promoted layers cost more than they save.
 */
.js [data-reveal],
.js [data-reveal-item] {
	opacity: 0;
	transform: translate3d(0, 26px, 0);
	transition:
		opacity var(--dur-reveal) var(--ease-out),
		transform var(--dur-reveal) var(--ease-out);
}

/* Revealed state. */
.js [data-reveal].is-revealed,
.js [data-reveal-item].is-revealed {
	opacity: 1;
	transform: none;
}

/*
 * Staggered children. The index comes from --item-index, set inline by PHP, so
 * the cascade needs no nth-child ladder and the delay is correct for any count.
 * Capped at 8 steps so a long list never feels slow.
 */
.js [data-reveal="stagger"].is-revealed > [data-reveal-item],
.js [data-reveal-group].is-revealed [data-reveal-item] {
	transition-delay: calc(min(var(--item-index, 0), 8) * var(--stagger));
}

/* Headline lines reveal in sequence, with a slightly softer rise. */
.js .display--lines .display__line {
	display: block;
	opacity: 0;
	transform: translate3d(0, 0.36em, 0);
	transition:
		opacity var(--dur-reveal) var(--ease-out),
		transform var(--dur-reveal) var(--ease-out);
	transition-delay: calc(var(--line-index, 0) * 90ms);
}

.js .is-revealed .display--lines .display__line,
.js .display--lines.is-revealed .display__line {
	opacity: 1;
	transform: none;
}

/* Reveal direction variants. */
.js [data-reveal="left"]  { transform: translate3d(-24px, 0, 0); }
.js [data-reveal="right"] { transform: translate3d(24px, 0, 0); }
.js [data-reveal="fade"]  { transform: none; }

.js [data-reveal="scale"] {
	transform: scale(1.03);
	transition-duration: 900ms;
}

.js [data-reveal="scale"].is-revealed {
	transform: scale(1);
}

/*
 * Masked reveal.
 * The content sits still while its clip window opens upward — a slower, more
 * architectural entrance than a fade. Used on oversized location typography
 * and the amenity image.
 *
 * IMPORTANT: the clip is applied to the CHILD, not to the observed element.
 *
 * Chromium factors an element's own clip-path into the rectangle it reports to
 * IntersectionObserver. So if the observed element clipped itself to
 * inset(100%), it would report an intersection ratio of zero forever, never
 * reveal, and stay permanently invisible. Clipping the child keeps the observed
 * box intact and the reveal reliable. (Found by rendering the page in Chromium,
 * not by reading the spec — worth remembering.)
 */
.js [data-reveal="mask"] {
	opacity: 1;
	transform: none;
}

.js [data-reveal="mask"] > * {
	clip-path: inset(100% 0 0 0);
	transition: clip-path 1s var(--ease-soft);
}

.js [data-reveal="mask"].is-revealed > * {
	clip-path: inset(0 0 0 0);
}

/* ===========================================================================
 * Parallax
 * ======================================================================== */

/*
 * The hero image drifts while the typography stays anchored.
 * app.js writes a single --parallax value (in pixels) per frame inside a
 * requestAnimationFrame callback; CSS does the compositing. No layout, no
 * scroll-jacking, native scrolling untouched.
 */
.js [data-parallax] {
	transform: translate3d(0, var(--parallax, 0px), 0) scale(var(--parallax-scale, 1.05));
	/* No transition: this must track the scroll position exactly. */
}

/* The closing CTA image settles from 1.04 to 1.0 as it enters the viewport. */
.js [data-settle] {
	transform: scale(1.045);
	transition: transform 1.4s var(--ease-soft);
}

.js [data-settle].is-revealed {
	transform: scale(1);
}

/* ===========================================================================
 * Scroll progress
 * ======================================================================== */

/*
 * A one-pixel reading indicator on long interior pages. Uses the native
 * scroll-driven animation timeline where supported, and is simply absent
 * where it is not — no JS fallback, because it is pure decoration.
 */
.scroll-progress {
	position: fixed;
	inset-block-start: 0;
	inset-inline: 0;
	z-index: calc(var(--z-header) + 1);
	height: 2px;
	background-color: var(--c-forest);
	transform-origin: 0 50%;
	transform: scaleX(0);
	pointer-events: none;
}

@supports (animation-timeline: scroll()) {
	@media (prefers-reduced-motion: no-preference) {
		.scroll-progress {
			animation: m438-progress linear;
			animation-timeline: scroll(root block);
		}
	}
}

@keyframes m438-progress {
	from { transform: scaleX(0); }
	to   { transform: scaleX(1); }
}

/* ===========================================================================
 * Page transitions
 * ======================================================================== */

/*
 * A short cross-fade between same-origin pages, using the native View
 * Transitions API. Progressive enhancement in the purest sense: unsupported
 * browsers simply navigate normally.
 */
@media (prefers-reduced-motion: no-preference) {
	@view-transition {
		navigation: auto;
	}

	::view-transition-old(root) {
		animation: m438-fade-out 140ms var(--ease-in-out) both;
	}

	::view-transition-new(root) {
		animation: m438-fade-in 240ms var(--ease-out) both;
	}
}

@keyframes m438-fade-out { to   { opacity: 0; } }
@keyframes m438-fade-in  { from { opacity: 0; } }

/* ===========================================================================
 * The reduced-motion contract
 * ======================================================================== */

@media (prefers-reduced-motion: reduce) {
	/*
	 * Everything is immediately in its final state. Note this block also
	 * overrides the .js reveal rules above, so no content can be left hidden.
	 */
	.js [data-reveal],
	.js [data-reveal-item],
	.js [data-reveal].is-revealed,
	.js [data-reveal-item].is-revealed,
	.js .display--lines .display__line,
	.js [data-reveal="mask"],
	.js [data-reveal="mask"] > *,
	.js [data-reveal="scale"],
	.js [data-settle] {
		opacity: 1 !important;
		transform: none !important;
		clip-path: none !important;
		transition: none !important;
		animation: none !important;
	}

	/* Parallax becomes a static, correctly-composed image. */
	.js [data-parallax] {
		transform: none !important;
	}

	/*
	 * Deliberately not a blanket `* { animation: none }`: colour and border
	 * transitions on hover/focus carry useful state information and cause no
	 * vestibular discomfort. Only movement is removed.
	 */
	.hero__cue,
	.live__dot,
	.scroll-progress {
		animation: none !important;
	}

	.hero__cue { opacity: 0.6; }

	*,
	*::before,
	*::after {
		scroll-behavior: auto !important;
	}

	/* Transforms on hover are movement; drop them too. */
	.plan-card:hover,
	.btn--light:hover {
		transform: none !important;
	}

	.media__img {
		transition: none !important;
	}
}

/* ===========================================================================
 * Print and low-data
 * ======================================================================== */

@media (prefers-reduced-data: reduce) {
	/* Skip decorative background artwork when the user is paying per byte. */
	.hero__visual--placeholder,
	.location__rings,
	.media__placeholder {
		background-image: none;
	}
}
