/* ============================================================
   Creativ Razor / Motion

   The first build used ONE identical fade-up-24px on a 70ms lockstep
   stagger for every element on the page. That is what read as robotic:
   a headline and a thumbnail have very different mass, and moving them
   identically tells the eye that nothing was considered.

   Two principles here:
     1. Motion is DIFFERENTIATED by element mass. A display headline is
        cut open over 950ms; a thumbnail settles over 1100ms; body copy
        does not move at all, it only fades.
     2. Motion is CONTINUOUS, not binary. Where the browser supports
        scroll-driven timelines, elements keep drifting with the scroll
        instead of arriving once and freezing.

   Native only: @view-transition, animation-timeline, IntersectionObserver.
   No GSAP, no ScrollTrigger, no Lenis.

   Note on transform: drift keyframes animate `translate` while hover
   states set `scale`. These are independent transform properties, so
   they compose. Using shorthand `transform` for both would let the
   running animation clobber the hover.
   ============================================================ */

/* ============================================================
   1. Cross-document page transitions
   Chrome/Edge honour this; Safari/Firefox just navigate normally.
   The nav and footer are tagged so they stay PUT while the main
   content crossfades: the site reads as one continuous surface
   instead of flashing on every click.
   ============================================================ */
@view-transition { navigation: auto; }

.cr-nav    { view-transition-name: cr-nav; }
.cr-footer { view-transition-name: cr-footer; }
.cr-main   { view-transition-name: cr-main; }

/* Persistent chrome: no animation, it simply stays. */
::view-transition-group(cr-nav),
::view-transition-group(cr-footer) { animation-duration: 0s; }
::view-transition-old(cr-nav), ::view-transition-new(cr-nav),
::view-transition-old(cr-footer), ::view-transition-new(cr-footer) { animation: none; }

/* The outgoing page leaves faster than the new one arrives (exit ~75%
   of enter), which is what keeps a transition from feeling slow. */
::view-transition-old(cr-main) { animation: cr-vt-out 300ms cubic-bezier(0.4, 0, 1, 1) both; }
::view-transition-new(cr-main) { animation: cr-vt-in 460ms var(--cr-ease-expo) both; }
@keyframes cr-vt-out { to   { opacity: 0; translate: 0 -1.25rem; } }
@keyframes cr-vt-in  { from { opacity: 0; translate: 0 1.75rem; } }

/* ============================================================
   2. Scroll-position rule
   Pure CSS scroll-driven animation: no scroll listener, no layout
   read, so it cannot jank.
   ============================================================ */
.cr-progress {
	position: fixed; top: 0; left: 0; z-index: 101;
	height: 2px; width: 100%;
	background: linear-gradient(90deg, var(--cr-ember), var(--cr-amber-hi));
	transform-origin: 0 50%; scale: 0 1;
	pointer-events: none;
}
@supports (animation-timeline: scroll()) {
	.cr-progress { animation: cr-progress linear; animation-timeline: scroll(root block); }
}
@keyframes cr-progress { from { scale: 0 1; } to { scale: 1 1; } }

/* ============================================================
   3. Reveal vocabulary
   `.is-in` is added by the IntersectionObserver in main.js.

   The DEFAULT is the calmest treatment (fade, no movement), so an
   un-typed hook is always safe. Typed variants below override it.
   ============================================================ */
.cr-js [data-cr-reveal] {
	opacity: 0;
	transition: opacity var(--cr-dur-body) var(--cr-ease-quart);
}
.cr-js [data-cr-reveal].is-in { opacity: 1; }

/* --- head: the slowest, furthest move ------------------------------
   Headings are the heaviest thing on the page, so they travel further
   and take longer than anything else. That contrast against body copy
   (which does not move at all) is what reads as considered.

   DO NOT reveal these with `clip-path` on the element itself. An
   IntersectionObserver measures the CLIPPED box, so an element clipped
   to zero height can never register as intersecting, and the reveal
   that would unclip it never fires. That deadlock left every heading
   on the site permanently invisible. If a true wipe is wanted later,
   clip an INNER wrapper and observe the outer element. */
.cr-js [data-cr-reveal='head'] {
	translate: 0 1.5rem;
	transition: opacity var(--cr-dur-head) var(--cr-ease-quart),
	            translate var(--cr-dur-head) var(--cr-ease-expo);
}
.cr-js [data-cr-reveal='head'].is-in { translate: none; }

/* --- label: the rule draws, then the words arrive behind it -------- */
.cr-js [data-cr-reveal='label'] { transition: opacity 520ms var(--cr-ease-quart); }
.cr-js .cr-eyebrow[data-cr-reveal='label']::after {
	scale: 0 1; transform-origin: left;
	transition: scale 760ms var(--cr-ease-expo) 120ms;
}
.cr-js .cr-eyebrow[data-cr-reveal='label'].is-in::after { scale: 1 1; }

/* --- body: fades, never moves -------------------------------------
   Sliding body copy is the biggest single source of that "everything
   marches in" feeling. Text that only fades reads calm. Same as the
   default, named explicitly so templates can be intentional. */
.cr-js [data-cr-reveal='body'] { transition: opacity var(--cr-dur-body) var(--cr-ease-quart); }

/* --- tile: mass. Settles from slightly oversized, slowly. --------- */
.cr-js [data-cr-reveal='tile'] {
	scale: 1.035;
	transition: opacity var(--cr-dur-mass) var(--cr-ease-quart),
	            scale var(--cr-dur-mass) var(--cr-ease-expo);
}
.cr-js [data-cr-reveal='tile'].is-in { scale: 1; }

/* --- row: list items, small rise, overlapping stagger ------------- */
.cr-js [data-cr-reveal='row'] {
	translate: 0 0.9rem;
	transition: opacity 800ms var(--cr-ease-quart),
	            translate 800ms var(--cr-ease-expo);
}
.cr-js [data-cr-reveal='row'].is-in { translate: none; }

/* ============================================================
   4. Hero entrance
   Above the fold, so an observer would fire on the same frame anyway.
   A plain CSS animation is cheaper and avoids a flash of unpositioned
   content. --i sets the place in the sequence.
   ============================================================ */
.cr-js .cr-hero [data-cr-rise] {
	animation: cr-rise 900ms var(--cr-ease-expo) both;
	animation-delay: calc(var(--i, 0) * 110ms + 80ms);
}
/* The h1 is the heaviest element on the page: furthest travel, longest
   duration. Same mechanism as the scroll-triggered heads, so the whole
   site shares one motion vocabulary. */
.cr-js .cr-hero [data-cr-rise='head'] {
	animation-name: cr-rise-head;
	animation-duration: var(--cr-dur-head);
}
@keyframes cr-rise { from { opacity: 0; translate: 0 1.4rem; } }
@keyframes cr-rise-head { from { opacity: 0; translate: 0 2rem; } }

/* ============================================================
   5. Continuity: scroll-linked drift
   This is what stops the page feeling like a series of triggers.
   Where supported, tile images and the hero bloom keep moving across
   their whole pass through the viewport, so there is never a moment
   where motion has "finished".

   Ranges are deliberately small. Felt, not seen.
   ============================================================ */
@supports (animation-timeline: view()) {
	@media (prefers-reduced-motion: no-preference) {
		/* Drift belongs on PHOTOGRAPHS only: case heroes and portraits,
		   which are cropped to fill and so have pixels to spare. The
		   work tiles are contained logos, and translating those inside
		   their frame just looks like a misaligned asset. */
		.cr-case__media img, .cr-team__card img {
			animation: cr-drift linear both;
			animation-timeline: view();
			animation-range: entry 0% exit 100%;
		}
		@keyframes cr-drift {
			from { translate: 0 -1.4%; }
			to   { translate: 0 1.4%; }
		}

		.cr-hero::before {
			animation: cr-bloom linear both;
			animation-timeline: view();
			animation-range: cover 0% cover 100%;
		}
		@keyframes cr-bloom {
			from { translate: 0 -6%; scale: 1;    opacity: .95; }
			to   { translate: -5% 8%; scale: 1.18; opacity: .5; }
		}
	}
}

/* Without scroll timelines the hero bloom still breathes, so the
   section is never completely static. */
@supports not (animation-timeline: view()) {
	@media (prefers-reduced-motion: no-preference) {
		.cr-hero::before { animation: cr-breathe 24s ease-in-out infinite alternate; }
		@keyframes cr-breathe {
			from { translate: 0 0;    scale: 1; }
			to   { translate: -4% 4%; scale: 1.12; }
		}
	}
}

/* ============================================================
   6. Reduced motion: strip all of it
   ============================================================ */
@media (prefers-reduced-motion: reduce) {
	@view-transition { navigation: none; }
	.cr-progress { display: none; }
	.cr-hero [data-cr-rise] { animation: none; }
	[data-cr-reveal] { opacity: 1 !important; translate: none !important; scale: none !important; clip-path: none !important; }
	.cr-eyebrow::after { scale: none !important; }
}

/* Note: there is deliberately NO `.no-js` unhiding rule here.
   Every hiding rule above is gated on `.cr-js`, which main.js adds to
   <html> as its first action. So the default state of the document is
   fully visible, and a JS failure can never leave content hidden. */
