/*
 * g-kit.io — site CSS.
 *
 * Read apps/g-theme/child-starter/assets/site.css (the starter this file
 * replaced) before adding anything here. The short version: everything below
 * sits inside `@layer cp-base` because this theme declares
 * `add_theme_support('g-box-layered-css')`, so g-box emits its atoms into
 * `@layer cp-plugin` — and an UNLAYERED rule here would outrank every class the
 * style panel writes, no matter how weak its selector. Order is
 * cp-base < cp-core < cp-plugin < unlayered.
 *
 * What lives here, and why none of it could live in the panel:
 *
 *   - `@keyframes`. The panel's Motion section sets `animation-name` but ships
 *     no keyframe authoring at all (packages/ui-b1/src/sections/Motion.tsx).
 *     Blocks reference these by name.
 *   - the card stack's shared scroll-timeline wiring (see THE CARD STACK).
 *   - one `--cp-*` knob the panel does not expose.
 *
 * Everything else about how this site looks is authored in the style panel and
 * lives in the post content, where whoever edits a page next can find it.
 */

@layer cp-base {
	/*
	 * ── How far down the page the header's buttons end ──────────────────
	 *
	 * The bar is `position: fixed`, so nothing in the flow knows it is there
	 * and every page's first section has to clear it by hand. That clearance
	 * was a hand-picked `clamp()` per page, and at 1017px it came out at FOUR
	 * pixels: the hero's eyebrow row sat directly under the MENU pill.
	 *
	 * So the clearance is derived instead of guessed. `gk_hero_top()` in
	 * _seed/lib.php reads this variable; the arithmetic it stands for is:
	 *
	 *     48px   the pill itself — a 34px circle in 7px of padding
	 *   + 10px   g/toggle-header's own vertical padding (g-box ships it; the
	 *            pill is centred in a 68px box, so the visible edge is 10px
	 *            in from the box, not level with it)
	 *   + the bar's fluid top padding, verbatim from _seed/header.php
	 *
	 * Measured against the build it is exact: 78.16px at 1440, 72.23px at
	 * 1017. If the pill or that plugin padding ever changes, this is the one
	 * number to correct — every page follows it.
	 */
	:root {
		--gk-header-h: calc(58px + clamp(12px, 1.4vw, 22px));
	}

	/*
	 * Links default to the body colour, undecorated — the prototype's
	 * `a { color: var(--ink); text-decoration: none }`.
	 *
	 * `:where()` pins this to 0,0,0 deliberately. It is a default, not a
	 * decision: any link the panel colours has to win, and at a bare `a`
	 * (0,0,1) this would tie with a `cp-*` class and beat it on source order.
	 * That is the exact inversion the header comment above describes.
	 */
	:where(a) {
		color: inherit;
		text-decoration: none;
	}

	/* ── Structure ──────────────────────────────────────────────────────
	 * Two rules about stacking and one about repetition. None of them is a
	 * look, and none can be reached from the panel.
	 */

	/*
	 * g-theme's templates wrap post-content in `<main class="wp-block-group">`,
	 * which no block on the page owns, so nothing in the panel can reach it.
	 * The aurora is `position: fixed; z-index: 0`; without a stacking position
	 * of its own the content would render behind the backdrop.
	 */
	main.wp-block-group {
		position: relative;
		z-index: 1;

		/*
		 * The hero's white bloom is `min(124%, 1560px)` wide and centred, so
		 * it reaches about 60px past each edge of a 1440px viewport and gives
		 * the document a horizontal scrollbar. The prototype clips it on the
		 * wrapper around the whole page; this is that wrapper.
		 *
		 * `clip`, never `hidden`. `overflow-x: hidden` makes this a scroll
		 * container, and a scroll container kills `position: sticky` on every
		 * descendant — which here is both card stacks, the single most
		 * expensive thing on the site to get working. `clip` only stops the
		 * painting, and leaves `overflow-y` genuinely visible rather than
		 * forcing it to `auto` the way `hidden` does.
		 */
		overflow-x: clip;
	}

	/*
	 * The footer's "go up" is `<a href="#top">`, which HTML defines as the top
	 * of the document — no anchor element needed.
	 *
	 * This is the fallback path now rather than the mechanism: when site.js is
	 * live and motion is not reduced, Lenis owns anchor clicks (`anchors: true`)
	 * and glides there on its own clock. This keeps the link working with no JS
	 * at all, and under reduced motion, where the browser drops the animation.
	 */
	html {
		scroll-behavior: smooth;
	}

	/* ── Lenis ──────────────────────────────────────────────────────────
	 * Four rules, from the library's own lenis.css plus one of our own. They
	 * only bite while Lenis is running: it puts `lenis` / `lenis-smooth` /
	 * `lenis-stopped` on <html> itself, and under reduced motion site.js never
	 * constructs it, so none of these selectors ever match.
	 */
	html.lenis,
	html.lenis body {
		height: auto;
	}

	/*
	 * Ours, not the library's. Lenis writes each frame with
	 * `scrollTo({behavior:'instant'})`, so the rule above cannot fight it — but
	 * every OTHER scroll still can: focus moving to an off-screen element, a
	 * `scrollIntoView()` with no behavior of its own, the browser restoring a
	 * position on reload. Those animate natively while Lenis's target stays put,
	 * and the two clocks show up as a stutter. `.lenis` (0,1,0) beats the `html`
	 * above (0,0,1) in this same layer, so no `!important` — and deliberately
	 * so: an important declaration inside a layer inverts the layer order and
	 * would start outranking the style panel.
	 */
	html.lenis {
		scroll-behavior: auto;
	}

	.lenis [data-lenis-prevent] {
		overscroll-behavior: contain;
	}

	/* `clip`, not `hidden` — same reason as on main, and this one lands on the
	   root while the overlay menu is open, over both card stacks. */
	.lenis.lenis-stopped {
		overflow: clip;
	}

	/*
	 * The header shows the current page's name beside the wordmark, bound to
	 * `post.title`. On the front page that title IS "g kit", so it would read
	 * "g kit  G KIT". The prototype passes an empty label for the home route;
	 * this is that, expressed against the body class core already prints.
	 */
	body.home .gk-route-label {
		display: none;
	}

	/* ── Reveal on scroll ───────────────────────────────────────────────
	 * g-box's reveal runtime is used as shipped; only the curve is ours. The
	 * panel has no easing control — `--cp-reveal-ease` is reachable from CSS
	 * only, filed as GK-3 — and the prototype's curve leaves the gate more
	 * slowly than the plugin default of cubic-bezier(.16, 1, .3, 1).
	 *
	 * A bare attribute selector (0,1,0) beats the plugin's `:where()` default
	 * (0,0,0). `cp-reveal` is listed in LayerGuard::EXTRA_HANDLES, so it is in
	 * this same layer and there is no layer inversion in play.
	 */
	[data-cp-reveal] {
		--cp-reveal-ease: cubic-bezier(0.16, 0.8, 0.2, 1);
	}

	/* ── Aurora ─────────────────────────────────────────────────────────
	 * Four blurred blobs on a fixed backdrop. Their size, colour, blur and
	 * resting position are panel-authored on four g/container blocks; only
	 * the drift paths are here, because keyframes have nowhere else to go.
	 */
	@keyframes gk-drift1 {
		0%,
		100% {
			transform: translate3d(0, 0, 0) scale(1);
		}
		33% {
			transform: translate3d(16vw, 8vh, 0) scale(1.16);
		}
		66% {
			transform: translate3d(6vw, 20vh, 0) scale(0.92);
		}
	}
	@keyframes gk-drift2 {
		0%,
		100% {
			transform: translate3d(0, 0, 0) scale(1.05);
		}
		40% {
			transform: translate3d(-18vw, 14vh, 0) scale(0.9);
		}
		70% {
			transform: translate3d(-6vw, 30vh, 0) scale(1.2);
		}
	}
	@keyframes gk-drift3 {
		0%,
		100% {
			transform: translate3d(0, 0, 0) scale(1);
		}
		35% {
			transform: translate3d(-12vw, -16vh, 0) scale(1.18);
		}
		68% {
			transform: translate3d(-22vw, -4vh, 0) scale(0.95);
		}
	}
	@keyframes gk-drift4 {
		0%,
		100% {
			transform: translate3d(0, 0, 0) scale(1.08);
		}
		45% {
			transform: translate3d(14vw, -18vh, 0) scale(0.94);
		}
		75% {
			transform: translate3d(26vw, -6vh, 0) scale(1.14);
		}
	}

	/* Hero: two concentric rings pulsing out from under the wordmark. The
	   translate is part of the keyframe because these are centred with
	   left/top 50% + translate(-50%,-50%), which the scale has to preserve. */
	@keyframes gk-ripple {
		0% {
			transform: translate(-50%, -50%) scale(0.9);
			opacity: 0.55;
		}
		100% {
			transform: translate(-50%, -50%) scale(1.35);
			opacity: 0;
		}
	}

	/* The scroll hint's 44px hairline. */
	@keyframes gk-bob {
		0%,
		100% {
			transform: translateY(0);
		}
		50% {
			transform: translateY(8px);
		}
	}

	/* Overlay-menu rows, staggered by a per-row animation-delay. */
	@keyframes gk-in {
		from {
			opacity: 0;
			transform: translateY(26px);
		}
		to {
			opacity: 1;
			transform: none;
		}
	}

	/* ── THE CARD STACK ─────────────────────────────────────────────────
	 *
	 * The prototype drives this from requestAnimationFrame: each card rises
	 * from 78vh below its resting place as its turn comes, and every card
	 * already at rest shrinks by 0.016 for each card still to arrive.
	 *
	 * Here it is a scroll-driven CSS animation — no JS, no rAF loop, no
	 * scroll listener. The tall section names a view timeline; each card
	 * reads that timeline and claims its own slice of it.
	 *
	 * Why `contain` is exactly the right range: for a subject TALLER than the
	 * scrollport, `contain 0%` is the moment the subject's top edge reaches
	 * the scrollport's top, and `contain 100%` the moment its bottom edge
	 * reaches the scrollport's bottom. That is precisely the prototype's
	 *
	 *     p = -rect.top / (rect.height - innerHeight)
	 *
	 * so the two implementations share a clock rather than merely resembling
	 * one another — which is what makes the transforms comparable as numbers
	 * at a matched scroll offset instead of by eye.
	 *
	 * `translate` and `scale` are used as INDEPENDENT properties rather than
	 * one `transform`. That is what lets the rise and the shrink be two
	 * animations on one element without fighting over a single shorthand.
	 * They compose translate → scale, the same order the prototype's
	 * `translate3d(...) scale(...)` composes in.
	 *
	 * Each card supplies three numbers from its Custom CSS field:
	 *   --gk-a / --gk-b   its slice of the timeline, as percentages
	 *   --gk-c            where its shrink starts (it always ends at 100%)
	 *   --gk-shrink       the scale it reaches once every later card is in
	 * The first card never rises (--gk-a equals --gk-b); the last never
	 * shrinks (--gk-shrink is 1).
	 *
	 * Behind @supports so a browser without scroll-driven animation gets a
	 * plain sticky stack — every card at rest, nothing hidden — rather than
	 * four cards frozen 78vh below where they belong.
	 */
	.gk-stack {
		view-timeline-name: --gk-stack;
		view-timeline-axis: block;
	}

	@supports (animation-timeline: view()) {
		.gk-stack-card {
			animation-name: gk-stack-rise, gk-stack-shrink;
			animation-fill-mode: both, both;
			animation-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1), linear;
			animation-timeline: --gk-stack, --gk-stack;
			animation-range:
				contain var(--gk-a, 0%) contain var(--gk-b, 0%),
				contain var(--gk-c, 0%) contain 100%;
		}
	}

	@keyframes gk-stack-rise {
		from {
			translate: 0 78vh;
		}
		to {
			translate: 0 0;
		}
	}
	@keyframes gk-stack-shrink {
		from {
			scale: 1;
		}
		to {
			scale: var(--gk-shrink, 1);
		}
	}
}

/*
 * OUTSIDE THE LAYER, DELIBERATELY.
 *
 * Reduced motion is not a look, it is an accessibility guarantee, so it has to
 * beat whatever the panel says. That is one of the cases the child-starter
 * README names as a legitimate reason to leave `cp-base`.
 *
 * g-box's reveal.css and the g/marquee, g/counter and g/toggle runtimes already
 * honour the preference on their own. This covers the keyframes above, which
 * are ours, and parks the stack at rest instead of 78vh low.
 */
@media (prefers-reduced-motion: reduce) {
	.gk-blob,
	.gk-ripple,
	.gk-bob,
	.gk-menu-row,
	.gk-stack-card {
		animation: none !important;
	}
	.gk-stack-card {
		translate: none !important;
		scale: none !important;
	}
}
