/* ==========================================================================
   Yazan (Astra Child) — motion layer
   Implements the "Yazan — Motion & Animation Blueprint".
   Animate ONLY transform / opacity / clip-path (+ one backdrop-filter on header).
   ========================================================================== */

/* --------------------------------------------------------------------------
   Part 2 — Motion tokens (change the whole "feel" from a dozen lines)

   THE VOCABULARY. Every duration and curve in this theme comes from here. A literal `300ms` or a
   hand-written `cubic-bezier(0.22, 1, 0.36, 1)` anywhere else is a bug, not a style choice: the
   point of a token is that retuning the brand's motion is one edit, and a component that spells
   the value out is a component that silently stops matching.

   TWO TRANSFORM CHANNELS, KEPT APART — the architectural rule of this system:

       translate: / scale:   →  reveals and hover states. CSS only, owned by this file.
       transform:            →  third-party widgets (FlexSlider, Elementor) and GSAP.

   They are independent CSS properties, so both can act on one element without either clobbering
   the other's matrix. This is not a preference — `.yz-reveal` puts `translateY()` on every product
   card, so a hover lift written as `transform` on the same element cancels the reveal's start
   offset and the card pops in flat. Use the individual properties for anything this file owns.
   -------------------------------------------------------------------------- */
:root {
	--mo-ease-out:   cubic-bezier(0.22, 1, 0.36, 1);  /* signature decelerate */
	--mo-ease-inout: cubic-bezier(0.65, 0, 0.35, 1);  /* drawers, overlays   */

	/* Durations. Four tiers, chosen so that anything needing a fifth is probably wrong. */
	--mo-fast:   250ms;  /* feedback: buttons, hovers      */
	--mo-med:    500ms;  /* drawers, header, small reveals */
	--mo-slow:   800ms;  /* section reveals, text          */
	--mo-hero:  1200ms;  /* hero entrance, image settles   */

	/*
	 * DISTANCES SCALE FROM THE STORE'S OWN INTENSITY SETTING.
	 *
	 * `--yz-motion-intensity` is printed on every page by inc/enqueue.php from the store dashboard
	 * (0 = off, 1 = normal, 2 = emphatic). Until now exactly ONE component read it — the
	 * announcement bar — so a shop owner who set motion to "off" still got every reveal, every
	 * hero animation, every card lift and the sticky add-to-cart bar. The switch was decorative.
	 *
	 * Expressing the DISTANCES in terms of it makes the setting real without a single branch:
	 * at 0 every travel below collapses to zero, so motion becomes a cross-fade rather than a
	 * disappearance — content still arrives, it just stops moving. Durations deliberately do NOT
	 * scale: an instant-but-still-fading element reads as intentional, whereas a 0ms fade reads
	 * as a rendering glitch.
	 */
	--mo-intensity: var(--yz-motion-intensity, 1);
	--mo-rise:      calc(20px * var(--mo-intensity));
	--mo-lift:      calc(6px * var(--mo-intensity));   /* hover elevation */
	--mo-drift:     calc(1.05 * var(--mo-intensity));  /* image hover zoom factor helper */
	--mo-stagger:   calc(90ms * var(--mo-intensity));
}

/* --------------------------------------------------------------------------
   Part 3 — Page load: barely-perceptible body fade (removes paint "pop")
   -------------------------------------------------------------------------- */
body.yazan { animation: yz-page-in var(--mo-med) var(--mo-ease-out) both; }
@keyframes yz-page-in { from { opacity: 0.98; } }

/* --------------------------------------------------------------------------
   Part 7 — Scroll reveals (workhorse). Authoring convention: .yz-reveal
   The observer (motion.js) adds .is-inview once, then unobserves.

   ⚠️ THE REVEAL CONTRACT — three files must agree on this selector list:
        this file (the resting state)
        assets/js/motion.js (what the observer watches)
        assets/css/a11y.css (what reduced motion un-hides)
   They disagreed for a long time and the failure mode is silent: a selector this file hides but
   a11y.css does not un-hide is content that never appears for a reduced-motion visitor.

   Uses `translate:` rather than `transform:` — see the channel rule in Part 2. That change is what
   lets a product card carry BOTH a reveal and a hover lift without the two cancelling.
   -------------------------------------------------------------------------- */
.yz-reveal,
[data-reveal] {
	opacity: 0;
	translate: 0 var(--mo-rise);
	transition: opacity var(--mo-slow) var(--mo-ease-out),
	            translate var(--mo-slow) var(--mo-ease-out);
}
.yz-reveal.is-inview,
[data-reveal].is-inview {
	opacity: 1;
	translate: 0 0;
}

/* Graceful degradation: if JS (and the observer) never runs, nothing stays hidden. The inline
   head script adds `.yz-js` before first paint, so this only applies when JS is off/failed. */
html:not(.yz-js) .yz-reveal,
html:not(.yz-js) [data-reveal] { opacity: 1 !important; transform: none !important; translate: none !important; }
html:not(.yz-js) [data-img-reveal] { clip-path: none !important; }
html:not(.yz-js) [data-img-reveal] img { transform: none !important; }

/* Row stagger — cap at position 4 so deep scroll never feels laggy (Part 10). */
.yz-grid > .yz-reveal:nth-child(2) { transition-delay: calc(var(--mo-stagger) * 1); }
.yz-grid > .yz-reveal:nth-child(3) { transition-delay: calc(var(--mo-stagger) * 2); }
.yz-grid > .yz-reveal:nth-child(4) { transition-delay: calc(var(--mo-stagger) * 3); }

/* Same left-to-right cascade for the WooCommerce shop/archive grid (cards carry .yz-reveal via the
   loop post_class filter; the grid is `ul.products`). Capped at 4 so later rows reveal on scroll-enter
   without accumulating long delays. */
.yazan ul.products > .product.yz-reveal:nth-child(2) { transition-delay: calc(var(--mo-stagger) * 1); }
.yazan ul.products > .product.yz-reveal:nth-child(3) { transition-delay: calc(var(--mo-stagger) * 2); }
.yazan ul.products > .product.yz-reveal:nth-child(4) { transition-delay: calc(var(--mo-stagger) * 3); }

/* --------------------------------------------------------------------------
   Part 4 — Hero entrance (pure CSS; above the fold so no observer needed)
   -------------------------------------------------------------------------- */
.yz-hero__media img {
	animation: yz-hero-img var(--mo-hero) var(--mo-ease-out) both;
}
@keyframes yz-hero-img {
	from { opacity: 0; transform: scale(1.06); }
	to   { opacity: 1; transform: scale(1); }
}

/* Masked line rise for the headline (wrap each line in .line > span). */
.yz-hero .line { display: block; overflow: hidden; }
.yz-hero .line > span { display: block; animation: yz-line-up var(--mo-slow) var(--mo-ease-out) both; }
.yz-hero .line:nth-child(1) > span { animation-delay: 200ms; }
.yz-hero .line:nth-child(2) > span { animation-delay: 320ms; }
@keyframes yz-line-up {
	from { transform: translateY(110%); }
	to   { transform: translateY(0); }
}

.yz-hero__eyebrow,
.yz-hero__cta {
	animation: yz-fade-up var(--mo-slow) var(--mo-ease-out) 600ms both;
}
@keyframes yz-fade-up {
	from { opacity: 0; transform: translateY(var(--mo-rise)); }
	to   { opacity: 1; transform: translateY(0); }
}

/* Below-the-fold headings: same masked rise, driven by the observer. */
[data-reveal] .line > span {
	transform: translateY(110%);
	transition: transform var(--mo-slow) var(--mo-ease-out);
}
[data-reveal].is-inview .line > span { transform: translateY(0); }
[data-reveal].is-inview .line:nth-child(2) > span { transition-delay: 120ms; }

/* --------------------------------------------------------------------------
   Part 6 — Image reveal: "curtain + settle" (large editorial images only)
   -------------------------------------------------------------------------- */
[data-img-reveal] {
	clip-path: inset(0 0 100% 0);
	transition: clip-path var(--mo-hero) var(--mo-ease-inout);
	overflow: hidden;
}
[data-img-reveal] img {
	transform: scale(1.12);
	transition: transform var(--mo-hero) var(--mo-ease-out);
}
[data-img-reveal].is-inview { clip-path: inset(0); }
[data-img-reveal].is-inview img { transform: scale(1); }

/* Part 11 — Header scroll transitions live in assets/css/header.css (+ header.js). */

/* --------------------------------------------------------------------------
   Part 14 — Scroll-DRIVEN motion (CSS scroll timelines)

   The difference from Part 7: a reveal is a one-shot triggered by an observer; this is motion tied
   to scroll POSITION, so it moves with the finger and reverses when the visitor scrolls back. That
   is what makes a page feel cinematic rather than merely animated — and `animation-timeline: view()`
   does it with no JavaScript, no GSAP, and no scroll listener at all. It runs on the compositor.

   ⚠️ TWO RULES, BOTH LEARNED THE HARD WAY:

   1. THE RESTING STATE MUST BE THE VISIBLE STATE. Firefox does not ship this yet (and Safari only
      recently), so every rule here is written as an ENHANCEMENT over a correct static layout: if
      the timeline is unsupported the animation simply never runs and the page looks exactly as it
      does today. Nothing here may be the only thing making content visible.

   2. NEVER PUT `opacity` ON THE LCP ELEMENT. The homepage's `.yz-bigimage__media` is the Largest
      Contentful Paint element (measured). An element that starts at opacity 0 does not count as
      painted, so an entrance fade on it literally postpones the metric it is decorating. Scale and
      translate are free; opacity is not.
   -------------------------------------------------------------------------- */
@supports (animation-timeline: view()) {
	@media (prefers-reduced-motion: no-preference) {

		/*
		 * Depth drift. A full-bleed band's background creeps at a different rate from the page, so
		 * the frame reads as a window onto something behind it rather than a picture glued to the
		 * scroll. Range is deliberately small — ±3% at intensity 1. Large parallax on jewellery
		 * photography reads as a gimmick, which is the note already recorded in parallax.js.
		 */
		.yz-drift {
			animation: yz-drift linear both;
			animation-timeline: view();
			animation-range: entry 0% exit 100%;
		}

		@keyframes yz-drift {
			from { translate: 0 calc(-3% * var(--mo-intensity)); }
			to   { translate: 0 calc(3% * var(--mo-intensity)); }
		}

		/*
		 * Cinematic exit. As a band leaves upward it recedes slightly and dims — the "the camera is
		 * moving on" cue. Applied only to full-bleed image bands, never to text a visitor might
		 * still be reading.
		 */
		.yz-recede {
			animation: yz-recede linear both;
			animation-timeline: view();
			animation-range: exit 0% exit 100%;
		}

		@keyframes yz-recede {
			from { scale: 1; filter: brightness(1); }
			to   { scale: calc(1 - 0.04 * var(--mo-intensity)); filter: brightness(0.72); }
		}
	}
}

/* --------------------------------------------------------------------------
   Part 15 — Page transitions (cross-document View Transitions)

   The whole feature is the one at-rule below. No router, no JS, no dependency, and nothing that
   intercepts a navigation — which is what makes it safe on a WooCommerce store: the cart, the
   checkout, the AJAX fragments and every plugin's own navigation handling are untouched, because
   the browser is still doing an ordinary page load. It simply keeps the old frame on screen while
   the new one arrives, instead of flashing white.

   Browsers without support ignore the at-rule entirely and navigate exactly as they do today, so
   there is no fallback to write and nothing to feature-detect.

   THE MORPH is the part worth having: `view-transition-name` is stamped on the product card's
   image in the loop AND on the main gallery image of that product's page (see inc/woocommerce.php).
   Matching names across two documents tell the browser they are the same object, so clicking a
   ring animates that ring into place on the product page rather than cutting to it. It is the one
   transition on the site that carries meaning rather than decoration.
   -------------------------------------------------------------------------- */
@view-transition {
	navigation: auto;
}

::view-transition-old(root) {
	animation: yz-vt-out var(--mo-fast) var(--mo-ease-out) both;
}

::view-transition-new(root) {
	animation: yz-vt-in var(--mo-med) var(--mo-ease-out) both;
}

@keyframes yz-vt-out { to { opacity: 0; } }
@keyframes yz-vt-in { from { opacity: 0; } }

/* The morphing product image gets the browser's default paired animation, only slower and on the
   brand curve — it is the one moment where the motion IS the information. */
::view-transition-group(.yz-vt-product),
::view-transition-group(*[class*='yz-vt-product']) {
	animation-duration: var(--mo-med);
	animation-timing-function: var(--mo-ease-out);
}

/*
 * Checkout is excluded (inc/perf.php prints no transition name there and this bails on the body
 * class): a payment step must never hold a stale frame on screen while the next one loads, because
 * "did my order go through" is the one question a page transition must never make ambiguous.
 */
body.woocommerce-checkout {
	view-transition-name: none;
}

@media (prefers-reduced-motion: reduce) {
	::view-transition-old(root),
	::view-transition-new(root) {
		animation: none;
	}
}

/* --------------------------------------------------------------------------
   Part 12 — Mobile: shorten motion ~30%; observer triggers sooner via JS
   -------------------------------------------------------------------------- */
@media (max-width: 767px) {
	:root {
		--mo-slow: 550ms;
		--mo-hero: 800ms;
		--mo-rise: 14px;
		--mo-stagger: 60ms;
	}
}

/* --------------------------------------------------------------------------
   Part 2 / Part 13 — Accessibility: honor reduced motion globally (non-negotiable)
   -------------------------------------------------------------------------- */
/*
 * ONE BLANKET, NOT TWO.
 *
 * This file and a11y.css both used to carry a byte-identical `*` reduced-motion blanket, and their
 * reveal selector lists disagreed — so which rules actually won depended on load order, and the
 * duplicate made it look as though both were maintained. a11y.css is the last stylesheet on the
 * page by construction (inc/enqueue.php:306-321) and is therefore the only file that can reliably
 * win this, so the blanket now lives there alone and this is the pointer to it.
 *
 * What stays here is only what belongs to the motion SYSTEM rather than to accessibility: zeroing
 * the intensity, which collapses every travel distance defined in Part 2 to zero at the source.
 * That covers components this file has never heard of, because they all scale from the same token.
 */
@media (prefers-reduced-motion: reduce) {
	:root { --mo-intensity: 0; }
}
