:root {
	color-scheme: dark;

	--font-display: 'Outfit', 'Plus Jakarta Sans', ui-sans-serif, system-ui, sans-serif;
	--font-body: 'Inter', ui-sans-serif, system-ui, sans-serif;

	--motion: 1;
	--motion-play: running;

	--ease-orbit: cubic-bezier(0.18, 1.03, 0.32, 1);
	--ease-orbit-out: cubic-bezier(0.55, 0.06, 0.86, 0.3);
	--ease-voyage: cubic-bezier(0.55, 0.06, 0.32, 1);

	/* 1 travels toward the end of the nav, -1 back toward its start. See nav-direction.ts. */
	--nav-dir: 1;
}

:root[data-nav-direction='back'] {
	--nav-dir: -1;
}

@media (prefers-reduced-motion: reduce) {
	:root {
		--motion: 0;
		--motion-play: paused;
	}
}

/* An explicit choice has to outrank the broader OS preference, in both directions. */
:root[data-motion='reduced'] {
	--motion: 0;
	--motion-play: paused;
}

:root[data-motion='full'] {
	--motion: 1;
	--motion-play: running;
}

html {
	/* Keep the persistent scenery live while named content snapshots travel over it. */
	view-transition-name: none;
	font-size: 16px;
	-webkit-text-size-adjust: 100%;
	/* Contains content parked off-screen mid-transition. clip, not hidden, to avoid making
	   this a scroll container. */
	overflow-x: clip;
	/* Pages differ in height; without this the scrollbar appears and disappears across a
	   navigation and shifts every element sideways. */
	scrollbar-gutter: stable;
}

body {
	margin: 0;
	height: 100%;
	background: #04050e;
	font-family: var(--font-body);
	/* Repeated from html: root overflow is propagated to the viewport, and that propagation
	   alone does not contain the offset content. */
	overflow-x: clip;
}

/*
 * Page transitions. See "Application architecture" in README.md.
 *
 * These names have to live on light-DOM elements; view-transition-name has no effect inside a
 * shadow root, so x-page's own header/main/footer cannot be captured.
 */
[view-transition-name='heading'] {
	view-transition-name: page-heading;
}
[view-transition-name='content'] {
	view-transition-name: page-content;
}
[view-transition-name='supplement'] {
	view-transition-name: page-supplement;
}
[view-transition-name='feature'] {
	view-transition-name: page-feature;
}

/* thunderous-csr marks the view .pending while it swaps content, which is when the browser
   captures the outgoing snapshot. Standing the ambient layers down frees that frame budget. */
t-view.pending {
	--motion-play: paused;
}

/* Snapshot overlays must let input reach the live navigation beneath them. */
::view-transition {
	pointer-events: none;
}

/* The arrival delays below are set so no block starts entering until every block has
   finished leaving; the two phases are meant to read as separate, not as a cross-fade. */
::view-transition-old(page-heading),
::view-transition-old(page-content),
::view-transition-old(page-supplement),
::view-transition-old(page-feature) {
	animation: orbit-out calc(var(--motion) * 320ms) var(--ease-orbit-out) both;
}
::view-transition-old(page-content),
::view-transition-old(page-supplement) {
	animation-delay: calc(var(--motion) * 50ms);
}
::view-transition-old(page-feature) {
	animation-delay: calc(var(--motion) * 100ms);
}

/*
 * Must be driven from the pseudo-element. A view transition captures the incoming content
 * rather than showing it live, so an animation declared on the real element is sampled once
 * and never plays.
 */
::view-transition-new(page-heading),
::view-transition-new(page-content),
::view-transition-new(page-supplement),
::view-transition-new(page-feature) {
	animation: orbit-in calc(var(--motion) * 480ms) var(--ease-orbit) both;
	animation-delay: calc(var(--motion) * 470ms);
}
::view-transition-new(page-content),
::view-transition-new(page-supplement) {
	animation-delay: calc(var(--motion) * 535ms);
}
::view-transition-new(page-feature) {
	animation-delay: calc(var(--motion) * 600ms);
}

/*
 * Cold loads have no outgoing page and so no view transition for the rules above to drive.
 * The :not() guard is load-bearing: nav-direction.ts sets that attribute on the first
 * navigation and never clears it, which keeps this from compounding with the pseudo-element
 * animation on every subsequent one.
 */
:root:not([data-nav-direction])
	:is(
		[view-transition-name='heading'],
		[view-transition-name='content'],
		[view-transition-name='supplement'],
		[view-transition-name='feature']
	) {
	animation: orbit-in calc(var(--motion) * 480ms) var(--ease-orbit) both;
}
:root:not([data-nav-direction]) :is([view-transition-name='content'], [view-transition-name='supplement']) {
	animation-delay: calc(var(--motion) * 65ms);
}
:root:not([data-nav-direction]) [view-transition-name='feature'] {
	animation-delay: calc(var(--motion) * 130ms);
}

/*
 * Multiplying by --nav-dir mirrors the gesture for backward navigation; depth and pitch are
 * deliberately left unmirrored, as they read the same either way.
 *
 * Travel is expressed in vw, not a percentage of the element, so a block clears the viewport
 * regardless of its own width. Nothing here touches opacity — these are meant to leave the
 * screen rather than dissolve in place.
 */
@keyframes orbit-in {
	from {
		transform: perspective(1400px) translate3d(calc(105vw * var(--nav-dir)), 0.4rem, -90px)
			rotateY(calc(10deg * var(--nav-dir))) rotateX(1.5deg);
	}
	to {
		transform: perspective(1400px) translate3d(0, 0, 0) rotateY(0deg) rotateX(0deg);
	}
}

@keyframes orbit-out {
	to {
		transform: perspective(1400px) translate3d(calc(-105vw * var(--nav-dir)), -0.3rem, -90px)
			rotateY(calc(-10deg * var(--nav-dir))) rotateX(1.5deg);
	}
}
