/* =====================================================================
 * MOTION — interaction motion for the whole site.
 * ---------------------------------------------------------------------
 * Lives here rather than in glass.css on purpose. glass.css is the field
 * system and it is Kimi's file; this is interaction, it ships with the
 * plugin, and account.css / checkout.css / search.css set the precedent.
 *
 * THREE RULES, all of them earned elsewhere in this codebase:
 *
 *   1. Transform and opacity only. Never an animated filter — it forces
 *      re-rasterisation every frame and that is the single biggest scroll
 *      cost on mid-range Android, which is most of this client's traffic.
 *
 *   2. NEVER put transform, filter, backdrop-filter, perspective or
 *      will-change on .zzf-header or any ancestor of it. Each of those
 *      makes the element a containing block for fixed descendants, which
 *      traps the mobile nav overlay inside the 64px header box. That has
 *      broken twice. Everything here targets cards and section children.
 *
 *   3. The resting state is the VISIBLE state. Every reveal below animates
 *      FROM hidden TO visible under @supports + no-preference. If the
 *      timeline is unsupported, or the visitor asks for reduced motion,
 *      nothing runs and the page is simply there. A reveal built the other
 *      way — hidden by default, revealed by script — renders a blank page
 *      the moment anything fails, and it fails silently.
 * ================================================================== */

:root {
	--zzf-reveal-rise: 14px;
	--zzf-lift:        2px;
	--zzf-ease:        cubic-bezier(.22, .61, .36, 1);
}

/* ---------------------------------------------------------------------
 * 1. SCROLL REVEAL
 * Scroll-driven, not time-driven: animation-timeline: view() ties the
 * animation to the element's own progress through the viewport, so there
 * is no IntersectionObserver, no class toggle, and no JavaScript at all.
 *
 * The range ends at entry 55% so an element finishes resolving well
 * before it reaches the middle of the screen. Running it to 100% means
 * copy is still visibly moving while someone is trying to read it.
 * ------------------------------------------------------------------ */
@keyframes zzf-rise {
	from { opacity: 0; transform: translate3d(0, var(--zzf-reveal-rise), 0); }
	to   { opacity: 1; transform: none; }
}

@supports (animation-timeline: view()) {
	@media (prefers-reduced-motion: no-preference) {

		.zzf-trust__card,
		.zzf-stats--bar,
		.zzf-showcase__frame,
		.zzf-coa__panel,
		.zzf-trust__head,
		.zzfcoa-card,
		ul.products > li.product {
			animation: zzf-rise both;
			animation-timeline: view();
			animation-range: entry 0% entry 55%;
		}

		/* Stagger. With a view timeline every element runs on its own
		   progress, so a row of cards at the same height would otherwise
		   fire as one block. Offsetting the START of each one's range
		   walks them in — and because it is still scroll-driven, the
		   stagger holds however fast or slow the page is scrolled, which
		   a fixed animation-delay would not. */
		.zzf-trust__card:nth-child(2),
		.zzfcoa-card:nth-child(3n + 2),
		ul.products > li.product:nth-child(4n + 2) { animation-range: entry 5% entry 60%; }

		.zzf-trust__card:nth-child(3),
		.zzfcoa-card:nth-child(3n + 3),
		ul.products > li.product:nth-child(4n + 3) { animation-range: entry 10% entry 65%; }

		.zzf-trust__card:nth-child(4),
		ul.products > li.product:nth-child(4n + 4) { animation-range: entry 15% entry 70%; }
	}
}

/* ---------------------------------------------------------------------
 * 2. HOVER / FOCUS LIFT
 * A 2px rise and a deeper shadow. Deliberately small: these are product
 * and evidence cards, and a card that jumps reads as a banner ad.
 *
 * :focus-visible carries the same treatment as :hover so the keyboard
 * path gets the same affordance as the mouse, and pointer:fine gates it
 * so a touch device never gets a stuck hover state after a tap.
 * ------------------------------------------------------------------ */
.zzf-trust__card,
.zzf-showcase__frame,
.zzfcoa-card,
ul.products > li.product {
	transition:
		transform   .28s var(--zzf-ease),
		box-shadow  .28s var(--zzf-ease);
}

@media (hover: hover) and (pointer: fine) {
	.zzf-trust__card:hover,
	.zzf-showcase__frame:hover,
	.zzfcoa-card:hover,
	ul.products > li.product:hover {
		transform: translate3d(0, calc(-1 * var(--zzf-lift)), 0);
		box-shadow: 0 18px 44px -14px rgba(11, 27, 54, .22);
	}
}

.zzf-trust__card:focus-within,
.zzfcoa-card:focus-within,
ul.products > li.product:focus-within {
	transform: translate3d(0, calc(-1 * var(--zzf-lift)), 0);
	box-shadow: 0 18px 44px -14px rgba(11, 27, 54, .22);
}

/* ---------------------------------------------------------------------
 * 3. STAT COUNT-UP
 * The only thing here that needs JavaScript. motion.js adds
 * .zzf-count--ready once it has parsed a figure it can animate, so a
 * number it cannot parse is simply left alone and still reads correctly.
 *
 * tabular-nums is not optional. These are proportional-width digits in a
 * mono-ish setting; without it the number changes width on every frame
 * and drags the label beside it back and forth for the whole count.
 * ------------------------------------------------------------------ */
.zzf-stat__n {
	font-variant-numeric: tabular-nums;
	font-feature-settings: "tnum" 1;
}

/* ---------------------------------------------------------------------
 * 4. REDUCED MOTION
 * Transitions collapse to nothing and the reveals never start, because
 * they are inside a no-preference query above. The lift still happens —
 * it is a state change, not motion, and removing the affordance would
 * make the cards read as inert.
 * ------------------------------------------------------------------ */
@media (prefers-reduced-motion: reduce) {
	.zzf-trust__card,
	.zzf-showcase__frame,
	.zzfcoa-card,
	ul.products > li.product {
		transition: box-shadow .01ms;
		transform: none !important;
	}
}
