/* base/motion.css
   Start and finished states for everything GSAP animates.

   Part of assets/css/. The load order lives in
   inc/setup.php - add new files there, not with @import. */

/* Every animation on this site is GSAP - core for the tweens, ScrollTrigger for deciding
   when they run. The stylesheet has only three jobs here:

     1. hide what is about to animate, so nothing flashes before the script runs
     2. define the finished state, for when the script does not run at all
     3. keep out of GSAP's way while it is tweening

   On (3): the GSAP docs single out CSS transitions on a GSAP-animated element as a cause
   of severe stutter, because both engines then interpolate the same property on every
   frame. .product-card carried a transform transition for its hover lift and was being
   transform-tweened by every product-grid reveal, which is exactly that. Rather than
   hunt for each collision, anything mid-tween is marked and has transitions suspended.

   Only transform and opacity are animated anywhere. They are the only two properties the
   compositor can handle without repainting; clip-path repaints and letter-spacing
   reflows, and both were in here before. */
/* Hiding is scoped to .js-on, which an inline snippet in the head sets before first
   paint. No script, nothing hidden. */
.js-on [data-reveal],
.js-on [data-reveal-group] > * { opacity: 0; }

/* The finished state. GSAP adds .is-in before it drops the inline styles it wrote, so
   every start state declared above needs a matching finished state here - otherwise
   clearing the inline value hands the element straight back to the state it started in.
   That is not theoretical: leaving these out left the hero heading at opacity 0 and the
   masked photograph still parked below its frame. */
.js-on [data-reveal].is-in,
.js-on [data-reveal-group].is-in > * { opacity: 1; }

.js-on [data-reveal="wipe"].is-in > *,
.js-on [data-reveal="wipe"].is-in img { transform: none; }

/* Rule (3). Only the element being tweened, never its descendants: GSAP writes to the
   element itself, so that is the only place a competing transition can matter. The
   descendant form suspended every hover transition inside a revealing section for about
   a second, which made the tab underline and the button fill snap if you happened to
   hover them while the section was arriving. !important because it has to beat whatever
   the component defines, and it is removed as soon as the tween finishes. */
.gsap-busy { transition: none !important; }

/* A single large image animates as a mask: the frame clips its overflow and GSAP slides
   the picture up into it while the photograph travels the other way, so the mask reads as
   moving across a still image. The wrapper stays visible - only its contents move.

   These start positions have to be declared here rather than set by the script, or the
   image is painted in place for a frame before the script moves it. Same reason the
   split heading starts transparent. */
.js-on [data-reveal="wipe"] { opacity: 1; overflow: hidden; }

.js-on [data-reveal="wipe"] > * { transform: translateY(101%); }

.js-on [data-reveal="wipe"] img { transform: translateY(-55%); }

/* Everything the hero entrance animates, hidden until it runs. This has to cover the
   whole block, not just the heading: the entrance waits for the webfont now, so anything
   left visible would sit there for half a second and then blink out when the tween set
   its starting values. */
.js-on .hero-inner > *,
.js-on .page-hero > .wrap > * { opacity: 0; }

.js-on .hero-inner > .is-in,
.js-on .page-hero > .wrap > .is-in { opacity: 1; }

/* Split headings: each rendered line rises out from behind its own edge. The padding
   keeps the clip off the descenders.

   The lines are not unwrapped afterwards - doing that re-ran text-wrap: balance and the
   heading re-flowed to a different width. .vv-done just stops the clipping, so a later
   resize can wrap inside a line without losing anything. */
.vv-line {
	display: block;
	overflow: hidden;
	padding-bottom: 0.09em;
	margin-bottom: -0.09em;
}

.vv-done .vv-line {
	overflow: visible;
	padding-bottom: 0;
	margin-bottom: 0;
}

.vv-line-in { display: block; }

/* The failsafe. The head snippet arms a timer that adds this if motion never reports in,
   so a blocked or broken script can never leave the page invisible. */
.no-motion [data-reveal],
.no-motion [data-reveal] *,
.no-motion [data-reveal-group] > *,
.no-motion .hero-inner > *,
.no-motion .page-hero > .wrap > *,
.no-motion .vv-line-in {
	opacity: 1 !important;
	transform: none !important;
}

@media (prefers-reduced-motion: reduce) {
	.js-on [data-reveal],
		.js-on [data-reveal] *,
		.js-on [data-reveal-group] > * {
			opacity: 1 !important;
			transform: none !important;
		}

	.js-on [data-reveal="wipe"] { overflow: visible; }

	.js-on [data-reveal="wipe"] > *,
		.js-on [data-reveal="wipe"] img,
		.vv-line-in { transform: none !important; }

	.js-on .hero-inner > *,
		.js-on .page-hero > .wrap > * { opacity: 1 !important; }
}

@media (max-width: 980px) {
	/* Those columns are stacked now, so "in from the left/right" no longer means
		   anything - and a sideways offset on a full-width block hangs off the edge of the
		   page until it reveals, which shows up as a horizontal scrollbar. */
	.js-on [data-reveal="left"],
		.js-on [data-reveal="right"] { transform: translateY(24px); }
}
