/**
 * css/components/motion.css — scroll motion system (design_handoff/scroll-motion-spec.md)
 *
 * Three coordinated on-scroll effects, each firing once per element:
 *   1. per-line title reveal, with a 1px accent line riding the reveal edge
 *   2. rise-in for supporting content, staggered per group
 *   3. stat-number roll (JS-driven; only the tabular-nums hint lives here)
 *
 * Progressive enhancement: every hook below is inert until js/cg-motion.js
 * adds `.anim-mask` / `.anim-rise`, so with no JS the page renders in its
 * final state. The reduced-motion block at the bottom short-circuits all of
 * it — matched on specificity and placed last rather than using
 * `!important`, per the theme's locked no-`!important` rule.
 *
 * Tokens only — no raw hex, no @layer, no !important.
 */

/* ---------- Sitewide fade law ---------- */

/*
 * Every colour shift on the site fades: 250ms in, 500ms out, ease-in-out.
 *
 * `:where()` carries zero specificity, so this only supplies a transition to
 * elements that do not already declare one — components with their own
 * `transition:` shorthand keep it untouched (those were normalised onto the
 * same tokens separately).
 */
:where( a, button, summary, label, input, select, textarea, [ class*='cg-' ], .contact-popup-trigger, .cli-bar-btn, .cli_action_button ) {
	transition-property: color, background-color, border-color, fill, stroke;
	transition-duration: var( --fade-out );
	transition-timing-function: var( --fade-ease );
}

/*
 * The other half of the asymmetry. Real specificity here (not `:where`) so it
 * also shortens components that set their duration through the shorthand —
 * it overrides duration only, leaving each component's own property list and
 * easing alone.
 */
a:hover,
a:focus-visible,
button:hover,
button:focus-visible,
summary:hover,
[ class*='cg-' ]:hover,
[ class*='cg-' ]:focus-visible,
.contact-popup-trigger:hover,
.cli-bar-btn:hover,
.cli_action_button:hover {
	transition-duration: var( --fade-in );
}

@media ( prefers-reduced-motion: reduce ) {

	:where( a, button, summary, label, input, select, textarea, [ class*='cg-' ] ) {
		transition-duration: 0s;
	}
}

/* ---------- Per-line title reveal ---------- */

/*
 * The spec sets `display:inline-block` here to shrink-wrap the title. In this
 * theme that measurably ADDS height — headings are block, and moving them
 * into inline layout introduces baseline descender space below every one
 * (+130px down the Services page). The shrink-wrap is really done by the
 * per-line `width:fit-content` wrappers below, so the element keeps its own
 * display and the layout is untouched.
 */
.anim-mask {
	position: relative;
}

.anim-line {
	position: relative;
	display: block;
	width: fit-content;
	max-width: 100%;
}

/*
 * The reveal wipes horizontally, so only the RIGHT inset animates; the
 * vertical insets are negative and constant, holding the clip open above and
 * below the box.
 *
 * They have to be. Display type here is set at a line-height below 1
 * (`.cg-caps__word` 0.82, `.cg-persona__word` 0.86, `.cg-believe__word` 0.9,
 * `.cg-hero__title` 0.94), which makes the line box SHORTER than the glyphs
 * it carries — normally harmless, since overflow is visible. Clipping to
 * `inset(0)` cropped whatever hung outside: measured against the same word
 * unclipped (`?motion=off`), "CAPABILITIES" on Approach paints 11-12px below
 * its own box, and all 11-12px were being cut off the letter bottoms.
 *
 * 0.25em clears the worst case with room to spare. Insets only move the clip
 * region, never the box, so unlike padding this cannot shift layout.
 */
.anim-linetxt {
	display: block;
	opacity: 0.001;
	clip-path: inset( -0.25em 100% -0.25em 0 );
	transition: clip-path 0.8s cubic-bezier( 0.16, 1, 0.3, 1 ), opacity 0.35s ease;
	will-change: clip-path;
}

.anim-mask.in .anim-linetxt {
	opacity: 1;
	clip-path: inset( -0.25em 0 -0.25em 0 );
}

/*
 * The 1px leading edge. It shares the reveal's duration and easing, so it
 * sits exactly on the clip boundary the whole way across.
 */
.anim-line .anim-edge {
	position: absolute;
	z-index: 2;
	top: 0;
	bottom: 0;
	left: 0;
	width: 1px;
	background: var( --accent );
	opacity: 0;
	pointer-events: none;
}

.anim-mask.in .anim-line .anim-edge {
	animation: cg-edge 0.8s cubic-bezier( 0.16, 1, 0.3, 1 ) forwards;
}

/* Rust bands: a rust edge on rust is invisible — use cream. */
.cg-band--rust .anim-line .anim-edge {
	background: var( --cg-cream );
}

@keyframes cg-edge {

	0% {
		left: 0;
		opacity: 1;
	}

	88% {
		opacity: 1;
	}

	100% {
		left: 100%;
		opacity: 0;
	}
}

/* ---------- Rise-in ---------- */

.anim-rise {
	opacity: 0;
	transform: translateY( 22px );
	transition: opacity 0.7s ease, transform 0.95s cubic-bezier( 0.16, 1, 0.3, 1 );
	will-change: transform, opacity;
}

.anim-rise.in {
	opacity: 1;
	transform: none;
}

/* ---------- Navigation overlay reveal ---------- */

/*
 * Nothing here. The 2A overlay animates whole COLUMNS, not links — three fixed
 * elements on a 60ms stagger, owned entirely by `css/components/header.css`
 * (`.cg-menu.is-open .cg-menu__col`). The per-line wipe the old panel used, and
 * the focus-outruns-the-cascade rule that went with it, are both gone with it.
 *
 * The rule that must never come back: nothing may hide an overlay link from
 * CSS alone. The old panel's hidden state existed only while the script that
 * applied it was about to remove it, which is what made `?motion=off`, reduced
 * motion, a blocked cg-motion.js and a mid-cascade throw all render a fully
 * visible menu. The overlay keeps that property differently — its `opacity:0`
 * is paired with `.is-open`, added by the same JS that unhides the dialog, so
 * no-JS leaves the overlay `hidden` rather than present-but-invisible. A
 * `.cg-js` pre-hide bridge here would be bug #278 in the site's only nav.
 */

/* ─── No-flash bridge for the above-the-fold hero reveal ────────────────────
 * js/cg-motion.js is deferred to the footer, so it adds .anim-mask/.anim-rise
 * (and their hidden initial state) only AFTER first paint — the hero eyebrow +
 * <h1> therefore paint visible for one frame, then hide and animate in (the
 * flash). When JS is present (.cg-js is set synchronously in <head>, before
 * paint) and motion is allowed, start just the hero reveal targets hidden until
 * cg-motion.js marks them. The :not(.anim-*) guards make each rule self-disable
 * the instant the script tags the element, so it never fights the reveal; and
 * scoping to .cg-hero limits this to the one above-the-fold spot that flashes.
 * No .cg-js (no JS) → text renders normally (SEO-safe); reduced-motion excluded.
 */
@media ( prefers-reduced-motion: no-preference ) {

	.cg-js .cg-hero .cg-eyebrow:not( .anim-rise ),
	.cg-js .cg-hero .cg-hero__title:not( .anim-mask ),
	.cg-js .cg-hero .cg-hero__lede:not( .anim-rise ),
	.cg-js .cg-hero .cg-hero__links:not( .anim-rise ) {
		opacity: 0;
	}
}

/* Hold width while the digits churn, so neighbours never shift. */
.cg-stat-num,
.cg-callout-stats__cell,
.cg-quote__num {
	font-variant-numeric: tabular-nums;
}

/* ---------- Reduced motion ---------- */

@media ( prefers-reduced-motion: reduce ) {

	.anim-mask .anim-linetxt,
	.anim-mask.in .anim-linetxt {
		opacity: 1;
		clip-path: none;
		transition: none;
	}

	.anim-line .anim-edge,
	.anim-mask.in .anim-line .anim-edge {
		display: none;
		animation: none;
	}

	.anim-rise,
	.anim-rise.in {
		opacity: 1;
		transform: none;
		transition: none;
	}
}

/* ---------- AJAX listings: auto-load + fade-in ---------- */

/*
 * The "Show More" control is hidden only once JS has taken over paging
 * (`.cg-autoload` is added by the script). Without JS it stays visible and
 * clickable, so pagination never depends on script.
 */
/*
 * The class is doubled to raise specificity: `.js-load-more` also carries the
 * legacy `container-wrap` classes, which style.css sizes from a later sheet
 * in the cascade. The control must keep geometry (an IntersectionObserver
 * ignores `display:none`), so it is clipped rather than removed.
 */
.cg-autoload .js-load-more.js-load-more {
	position: absolute;
	overflow: hidden;
	clip-path: inset( 50% );
	width: 1px;
	min-width: 0;
	height: 1px;
	min-height: 0;
	padding: 0;
	white-space: nowrap;
}

.js-item.cg-appear {
	animation: cg-appear var( --fade-out ) var( --fade-ease ) both;
}

@keyframes cg-appear {

	from {
		opacity: 0;
		transform: translateY( 22px );
	}

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

@media ( prefers-reduced-motion: reduce ) {

	.js-item.cg-appear {
		animation: none;
	}
}
