/* Universe Fact — background, curtain reveal and word animation.
 *
 * Typography (family, size, weight, spacing) is deliberately NOT set here —
 * it belongs to the Breakdance Text element. This file only supplies the
 * generated background, the reveal choreography and the colour variables.
 */

:root {
	--uf-base: #0b0d16;
	--uf-layers: none;
	--uf-text: #f4f1ea;
	--uf-muted: rgba(244, 241, 234, 0.5);
	--uf-soft: rgba(244, 241, 234, 0.82);
	--uf-shadow: rgba(0, 0, 0, 0.35);
	--uf-vignette-color: rgba(0, 0, 0, 0.5);
	--uf-curtain-color: var(--uf-base);
}

html.uf-js,
html.uf-js body {
	background-color: var(--uf-base);
}

/* ---------- the section ---------- */

.uf-section {
	position: relative;
	overflow: hidden;
	isolation: isolate;
	background-color: var(--uf-base);
}

/* Generated background — drifts slowly.
 *
 * Performance notes (the look is unchanged):
 *  - No `filter` here. A filter on an animated, full-bleed layer forces the
 *    compositor to re-run a colour pass over the whole surface every frame.
 *    The 112% saturation it used to apply is now baked into the generated
 *    colours in palette.js instead — same result, zero per-frame cost.
 *  - The layer is 120% of the section rather than 140%: that is ~27% fewer
 *    pixels to rasterise and keep in video memory, and it still covers the
 *    viewport at every point of the drift. palette.js scales the gradient
 *    sizes/positions to match, so the pattern lands identically.
 */
.uf-section::before {
	content: "";
	position: absolute;
	inset: -10%;
	z-index: 0;
	background-color: var(--uf-base);
	background-image: var(--uf-layers);
	transform: scale(1.04);
	animation: uf-drift 48s ease-in-out infinite alternate;
	will-change: transform;
	pointer-events: none;
}

/* vignette */
.uf-section::after {
	content: "";
	position: absolute;
	inset: 0;
	z-index: 1;
	pointer-events: none;
	background: radial-gradient(ellipse at center, transparent 35%, var(--uf-vignette-color) 100%);
}

.uf-section > .section-container {
	position: relative;
	z-index: 2;
}

@keyframes uf-drift {
	0% {
		transform: scale(1.04) translate3d(0, 0, 0) rotate(0deg);
	}
	100% {
		transform: scale(1.16) translate3d(-2.5%, 1.5%, 0) rotate(1.5deg);
	}
}

/* ---------- the fact ---------- */

.uf-fact {
	text-wrap: balance;
}

/* The 40px glow is only painted once the words have settled. While they are
 * still blurring in, a soft shadow under an already-blurred, semi-transparent
 * word is invisible — but it forces every animation frame to re-render the
 * text at 64px with a 40px shadow blur, which is what made the reveal crawl on
 * older machines. Same final appearance, a fraction of the paint work. */
html:not(.uf-js) .uf-fact,
.uf-fact.is-settled,
.uf-fact.is-out {
	text-shadow: 0 1px 40px var(--uf-shadow);
}

.uf-word {
	display: inline-block;
	white-space: pre;
}

html.uf-js .uf-word {
	opacity: 0;
	transform: translate3d(0, 0.45em, 0);
	filter: blur(9px);
	/* Rasterise each word once and let the GPU do the blur/fade, instead of
	 * re-rendering variable-font glyphs on every frame. Dropped again on
	 * .is-settled so the layers are not kept around while idle. */
	will-change: opacity, filter, transform;
}

html.uf-js .uf-fact.is-in .uf-word {
	opacity: 1;
	transform: none;
	filter: blur(0);
	transition:
		opacity 0.9s cubic-bezier(0.22, 1, 0.36, 1),
		transform 1.05s cubic-bezier(0.22, 1, 0.36, 1),
		filter 0.9s cubic-bezier(0.22, 1, 0.36, 1);
	transition-delay: var(--uf-delay, 0ms);
}

html.uf-js .uf-fact.is-out .uf-word {
	opacity: 0;
	transform: translate3d(0, -0.28em, 0);
	filter: blur(7px);
	transition:
		opacity 0.32s ease,
		transform 0.32s ease,
		filter 0.32s ease;
	transition-delay: var(--uf-out-delay, 0ms);
}

/* Reveal finished: drop the filter surface and the layer promotion so an idle
 * page costs nothing. blur(0) and no filter render identically. */
html.uf-js .uf-fact.is-settled .uf-word {
	filter: none;
	transition: none;
	will-change: auto;
}

/* companion elements (eyebrow, source) fade with the fact */
html.uf-js .uf-fade {
	opacity: 0;
	transition: opacity 0.9s ease 0.35s;
}

html.uf-js .uf-fade.is-in {
	opacity: 1;
}

/* ---------- explain toggle ---------- */

/* Without JS the explanation is simply always visible and the button pointless. */
html:not(.uf-js) .uf-explain {
	display: none;
}

html.uf-js .uf-explain {
	opacity: 0;
	transition: opacity 0.6s ease 0.5s;
}

html.uf-js .uf-explain.is-in {
	opacity: 1;
}

html.uf-js .uf-explain[hidden] {
	display: none;
}

html.uf-js .uf-explanation {
	max-height: 0;
	opacity: 0;
	overflow: hidden;
	transform: translate3d(0, -8px, 0);
	transition:
		max-height 0.6s cubic-bezier(0.22, 1, 0.36, 1),
		opacity 0.45s ease,
		transform 0.6s cubic-bezier(0.22, 1, 0.36, 1);
}

html.uf-js .uf-section.is-explained .uf-explanation {
	max-height: 45vh;
	opacity: 1;
	transform: none;
	transition-delay: 0s, 0.12s, 0.12s;
}

/* ---------- reveal curtain (injected by landing.js) ---------- */

.uf-curtain {
	position: absolute;
	inset: 0;
	z-index: 3;
	display: flex;
	pointer-events: none;
}

/* Once the curtain has swept away it is taken out of the render tree entirely,
 * so six full-height composited layers are not held in video memory while the
 * page sits idle. It is off-screen at that point, so nothing changes visually. */
.uf-curtain.is-idle {
	visibility: hidden;
}

.uf-panel {
	flex: 1 1 0;
	background: var(--uf-curtain-color);
	transform: translate3d(0, 0, 0);
}

.uf-curtain:not(.is-idle) .uf-panel {
	will-change: transform;
}

@media (prefers-reduced-motion: reduce) {
	.uf-section::before {
		animation: none;
	}

	html.uf-js .uf-word,
	html.uf-js .uf-fade,
	html.uf-js .uf-explain {
		opacity: 1;
		transform: none;
		filter: none;
		transition: none;
	}

	html.uf-js .uf-explanation {
		transition: none;
	}

	.uf-curtain {
		display: none;
	}
}
