/**
 * Motion system.
 *
 * No animation library. IntersectionObserver in app.js adds .is-in; everything
 * else is CSS. Only transform and opacity are animated — anything touching
 * layout (width, top, margin) causes jank and is off-limits here.
 *
 * Every effect must survive JS never running: the .no-js fallback below shows
 * all content. Never hide content behind a state JS is required to remove.
 */

/* ---------------------------------------------------------------
   Reveal — the general-purpose scroll-in
   --------------------------------------------------------------- */

[data-reveal] {
	opacity: 0;
	transform: translateY(14px);
	transition:
		opacity 0.5s ease-out,
		transform 0.6s var(--wp--custom--ease--out, cubic-bezier(0.22, 1, 0.36, 1));
	will-change: opacity, transform;
}

[data-reveal].is-in {
	opacity: 1;
	transform: none;
}

/* Stagger children. --i is set per child by app.js. */
[data-stagger] > * {
	opacity: 0;
	transform: translateY(14px);
	transition:
		opacity 0.5s ease-out,
		transform 0.6s var(--wp--custom--ease--out, cubic-bezier(0.22, 1, 0.36, 1));
	transition-delay: calc(var(--i, 0) * 80ms);
	will-change: opacity, transform;
}

[data-stagger].is-in > * {
	opacity: 1;
	transform: none;
}

/* ---------------------------------------------------------------
   Hero entrance — CSS only, deliberately NOT the IO reveal above
   ---------------------------------------------------------------

   The hero is above the fold and its <h1> is the LCP element on every page.
   [data-reveal] starts at opacity:0 and waits for deferred app.js, which
   would push the largest text paint back behind a script load for no visual
   gain. A keyframe with `both` fill runs off the stylesheet alone: no JS, no
   .no-js special case, nothing to go wrong if app.js never arrives.

   Same rule as the in-house animation guide — CSS keyframes above the fold,
   observers below it. */

@keyframes jl-rise {
	from {
		opacity: 0;
		transform: translateY(16px);
	}

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

.jl-hero__inner > * {
	animation: jl-rise 0.7s var(--wp--custom--ease--out, cubic-bezier(0.22, 1, 0.36, 1)) both;
}

.jl-hero__inner > :nth-child(1) { animation-delay: 0.06s; }
.jl-hero__inner > :nth-child(2) { animation-delay: 0.16s; }
.jl-hero__inner > :nth-child(3) { animation-delay: 0.26s; }
.jl-hero__inner > :nth-child(4) { animation-delay: 0.36s; }

/* A slow push-in on the photo. Transform only — the image is already
   composited, so this costs nothing per frame. */
@keyframes jl-hero-settle {
	from {
		transform: scale(1.06);
	}

	to {
		transform: scale(1);
	}
}

.jl-hero__img {
	animation: jl-hero-settle 1.6s var(--wp--custom--ease--out, cubic-bezier(0.22, 1, 0.36, 1)) both;
}

/* ---------------------------------------------------------------
   Terrace — the logo's houses, popping up
   --------------------------------------------------------------- */

.jl-terrace {
	display: block;
	width: 100%;
	max-width: 620px;
	height: auto;
	margin-inline: auto;
	/* Houses overshoot their box on the way up; don't clip them. */
	overflow: visible;
}

.jl-house {
	/* fill-box + bottom origin = they grow up off the ground line,
	   rather than scaling about their middle. */
	transform-box: fill-box;
	transform-origin: 50% 100%;
	opacity: 0;
	transform: translateY(20%) scaleY(0.84);
	transition:
		transform 0.6s var(--wp--custom--ease--pop, cubic-bezier(0.34, 1.56, 0.64, 1)),
		opacity 0.36s ease-out;
	/* --i is authored into the SVG left-to-right. It deliberately does NOT
	   match document order: the houses overlap and their paint order is what
	   keeps the blue one in front, so the markup can't be reordered. */
	transition-delay: calc(var(--i, 0) * 110ms);
	will-change: transform, opacity;
}

.is-in .jl-house {
	opacity: 1;
	transform: translateY(0) scaleY(1);
}

.jl-terrace-ground {
	max-width: 620px;
	height: 3px;
	margin-inline: auto;
	background: var(--wp--preset--color--navy);
	opacity: 0.12;
}

/* ---------------------------------------------------------------
   Header — transparent over a photo hero, solid once past it
   --------------------------------------------------------------- */

.jl-header {
	position: fixed;
	inset-block-start: 0;
	inset-inline: 0;
	z-index: 100;
	transition:
		background-color 0.3s ease,
		box-shadow 0.3s ease;
}

/* Default (no hero): solid from the start. A transparent header only works
   over something dark enough to carry white nav text. */
.jl-header {
	background: var(--wp--preset--color--surface);
	box-shadow: 0 1px 0 var(--wp--preset--color--line);
}

/* Over a hero: transparent until scrolled. */
.has-hero .jl-header:not(.is-stuck) {
	background: transparent;
	box-shadow: none;
}

.has-hero .jl-header:not(.is-stuck) .jl-header__links a {
	color: #fff;
}

/* ---------------------------------------------------------------
   Reduced motion — hard stop
   --------------------------------------------------------------- */

@media (prefers-reduced-motion: reduce) {
	[data-reveal],
	[data-stagger] > *,
	.jl-house {
		opacity: 1;
		transform: none;
		transition: none;
	}

	.jl-header {
		transition: none;
	}

	/* The hero runs on keyframes rather than the observer, so the rules above
	   don't reach it — it needs killing explicitly. animation:none alone
	   would leave the `both` fill applying the from-state, i.e. an invisible
	   headline. Reset the properties too. */
	.jl-hero__inner > *,
	.jl-hero__img {
		animation: none;
		opacity: 1;
		transform: none;
	}

	.jl-numcard,
	.jl-numcard:hover {
		transition: none;
		transform: none;
	}
}

/* ---------------------------------------------------------------
   No JS — content must still be visible
   --------------------------------------------------------------- */

.no-js [data-reveal],
.no-js [data-stagger] > *,
.no-js .jl-house {
	opacity: 1;
	transform: none;
}
