/* ==========================================================================
   Homepage hero — V4
   Figma: Landing Page REVAMP → Hero-V4 / {Fitness, Gym, Nutrition, Sport}
   Desktop reference frame: 1440 × 850.

   Layout, type and colour are Tailwind utilities in
   template/template-parts/homepage/hero-section-v4.php. What is left here is
   only what utilities cannot express:

     - the load-in sequence, whose delays are calc() expressions over shared
       tokens so the beats stay welded to each other when one value is retimed
     - the JS-driven .is-active / .is-previous / .is-leaving states
     - one specificity override against main.css

   NOTE: tablet / mobile breakpoints are deliberately not implemented yet.
   Below 1440 the layout keeps its desktop metrics and the reel is clipped by
   the section's overflow.
   ========================================================================== */

.hero-v4 {
	/* Background. The new frame fill is a soft mesh wash, not a flat colour, and
	   Figma exposes no isolated layer for it — so this is a fit, not an export:
	   36 samples taken from content-free regions of the rendered frame all
	   resolve to one base lime under a single pale-blue wash at varying alpha,
	   and these four ellipses were solved against those samples (RMS alpha
	   error 0.042). Close, but swap in the real asset if the designer exports
	   it. Kept here rather than as a utility — four layers of gradient is not
	   something a class name should carry. */
	background-color: #d5f840;
	background-image:
		radial-gradient(46% 43% at 106% 89%, rgba(199, 217, 255, 1) 0%, rgba(199, 217, 255, 0) 100%),
		radial-gradient(26% 101% at 3% 91%, rgba(199, 217, 255, 0.56) 0%, rgba(199, 217, 255, 0) 100%),
		radial-gradient(97% 648% at 142% 30%, rgba(199, 217, 255, 0.54) 0%, rgba(199, 217, 255, 0) 100%),
		radial-gradient(49% 25% at 24% 13%, rgba(199, 217, 255, 0.52) 0%, rgba(199, 217, 255, 0) 100%);

	/* Reel reveal — assets2/js/hero-v4.js reads --hero-v4-reveal-dur so it knows
	   when the outgoing slide is safe to drop. */
	--hero-v4-reveal-dur: 600ms;
	--hero-v4-reveal-ease: cubic-bezier(0.4, 0, 0.2, 1);

	--hero-v4-word-dur: 480ms;
	--hero-v4-word-ease: cubic-bezier(0.22, 1, 0.36, 1);

	/* Load-in sequence. --intro-step is the gap between beats.
	   assets2/js/hero-v4.js derives the sequence length from step + dur so the
	   first audience swap can never land mid-sequence. */
	--hero-v4-intro-step: 300ms;
	--hero-v4-intro-dur: 800ms; /* title, subtitle, form */
	--hero-v4-intro-reel-dur: 500ms; /* the reel opens faster than the copy */
	/* Nothing else starts until the reel has finished, plus one step of breathing
	   room. Derived from the REEL's duration, not the copy's, so retiming either
	   one keeps the copy from starting over the top of the reel. */
	--hero-v4-intro-copy-start: calc(var(--hero-v4-intro-reel-dur) + var(--hero-v4-intro-step));
	--hero-v4-intro-ease: cubic-bezier(0.22, 1, 0.36, 1);
	--hero-v4-intro-rise: 10px;
}

/* --------------------------------------------------------------------------
   Background video

   Held at opacity 0 until it can actually play (hero-v4.js adds .is-ready on
   canplaythrough), so a half-buffered first frame never flashes over the wash.
   If the video never becomes ready the wash simply stays — which is why the
   gradient above is kept rather than replaced.
   -------------------------------------------------------------------------- */

.hero-v4__bg {
	opacity: 0;
	transition: opacity 700ms ease;
}

.hero-v4__bg.is-ready {
	opacity: 1;
}

/* --------------------------------------------------------------------------
   Rotating audience word
   -------------------------------------------------------------------------- */

.hero-v4__word {
	opacity: 0;
	filter: blur(10px);
	transform: translateY(0.34em);
	will-change: opacity, transform, filter;
	transition:
		opacity var(--hero-v4-word-dur) ease,
		filter var(--hero-v4-word-dur) ease,
		transform var(--hero-v4-word-dur) var(--hero-v4-word-ease);
}

.hero-v4__word.is-active {
	opacity: 1;
	filter: blur(0);
	transform: translateY(0);
}

.hero-v4__word.is-leaving {
	opacity: 0;
	filter: blur(10px);
	transform: translateY(-0.34em);
}

/* --------------------------------------------------------------------------
   Email capture form

   main.css ships `.hero-form .input-wrapper:has(input.error)` with a translucent
   red fill meant for the old dark hero; on a white field that reads as pink.
   Same specificity, and hero-v4.css loads after main.css, so this wins.
   -------------------------------------------------------------------------- */

.hero-v4__form .input-wrapper:has(input.error) {
	background-color: #fff;
	box-shadow: inset 0 0 0 1px #f66042;
}

/* Old dark-hero autofill rule forces white text; here the field is white
   with black text, so override it (same specificity, loads after). */
.hero-v4__form input:-webkit-autofill,
.hero-v4__form input:-webkit-autofill:hover,
.hero-v4__form input:-webkit-autofill:focus {
	-webkit-text-fill-color: #000000 !important;
	background-color: transparent !important;
}

/* main.js positions the validation message with a negative `bottom` (-42px for
   the two-line email error, -26px for "required"). At lg only, it sits from the
   top instead — 70px, i.e. 6px below the 64px field, the same for both errors.

   `bottom: auto` is required, not tidiness: an absolutely positioned box with
   both `top` and `bottom` set and `height: auto` stretches to fill between
   them, so it would become a tall box rather than move.

   Scoped to .hero-v4__form and capped at 1279px so it reaches neither xl and
   above, nor the page's other forms. Two classes beat the utilities' one. */
@media (min-width: 1024px) and (max-width: 1279px) {
	.hero-v4__form .custom-error-message {
		top: 70px;
		bottom: auto;
	}
}

/* --------------------------------------------------------------------------
   Image reel

   Slot geometry is Tailwind and switches between the horizontal row and the
   vertical strip at lg; --slot-h comes from the slot's [--slot-h:…] class and
   switches with it, so the reveal always grows to whatever the slot is now.
   The slide is a clipping window, not the photo: growing it wipes the photo
   into view, and the photo never changes size, so nothing squashes. Both are
   anchored to the same edges in the markup (bottom-left) so the window opens
   upward at lg and rightward below it — if the two ever disagree the photo
   drifts inside the window instead of holding still.
   -------------------------------------------------------------------------- */

/* The wipe follows the reel's own axis: below lg the reel is a horizontal row,
   so the window opens left-to-right on width; from lg it is a vertical strip
   and opens bottom-up on height. Whichever axis animates, the photo is pinned
   on the other one in the markup, so it never squashes while the window opens. */
.hero-v4__slide {
	width: 0;
	height: 100%;
	transition: width var(--hero-v4-reveal-dur) var(--hero-v4-reveal-ease);
}

/* The incoming slide wipes in above the outgoing one, which is held at full
   size underneath until the reveal finishes (see .is-previous). Without that
   backdrop the growing window would expose the background instead of the photo
   it is replacing. */
.hero-v4__slide.is-active {
	z-index: 2;
	width: 100%;
}

.hero-v4__slide.is-previous {
	z-index: 1;
	width: 100%;
	transition: none;
}

/* lg — the vertical strip. The slide is full size here and MOVES into place
   rather than growing, so the slot has to clip it: without overflow the
   off-position slides would simply be visible hanging below their slots.
   .is-previous keeps `transition: none` from above on specificity. */
@media (min-width: 1024px) {
	[data-hero-v4-slot] {
		overflow: hidden;
	}

	.hero-v4__slide {
		width: 100%;
		height: var(--slot-h);
		transform: translateY(100%);
		transition: transform var(--hero-v4-reveal-dur) var(--hero-v4-reveal-ease);
	}

	.hero-v4__slide.is-active {
		transform: translateY(0);
	}

	/* Held in place under the incoming slide, which covers it as it arrives. */
	.hero-v4__slide.is-previous {
		transform: translateY(0);
	}
}

/* --------------------------------------------------------------------------
   Load-in sequence

   The reel goes first and finishes alone; the copy waits it out plus one step
   (--hero-v4-intro-copy-start) before starting its own run of beats, each
   --hero-v4-intro-step apart:
     1. reel        opens horizontally from its centre
        (pause)
     2. title       rises --hero-v4-intro-rise and fades in
     3. subtitle    same
     4. form        same

   Every join is written as an expression over --intro-step / --intro-dur, not
   as a literal delay, so retiming one value cannot pull the sequence apart.

   Animations, not transitions on a JS-toggled class, on purpose: the resting
   state here is the VISIBLE one and `both` fill only borrows the hidden state
   while the animation is pending. If anything stops these running — reduced
   motion, a parse error, an old browser — the hero just renders normally.
   Nothing in this block can leave the headline or the form permanently hidden.
   -------------------------------------------------------------------------- */

/* scaleX rather than an animated `width`: from lg the reel is a zero-size anchor
   with its slots positioned around it, so it has no width to animate, and the
   `overflow: hidden` a width-clip would need also clips the strip's vertical
   extent (its slots reach 509px above the anchor). scaleX opens horizontally
   from the origin in both layouts without either problem. */
@keyframes hero-v4-open-x {
	from {
		opacity: 0;
		transform: scaleX(0);
	}

	to {
		opacity: 1;
		transform: scaleX(1);
	}
}

@keyframes hero-v4-rise {
	from {
		opacity: 0;
		transform: translateY(var(--hero-v4-intro-rise));
	}

	to {
		opacity: 1;
		transform: translateY(0);
	}
}

/* 1 — reel. Opens horizontally from its centre, 0 to full width.

   The origin has to follow the layout, and in both cases it lands on the
   horizontal centre of the content: from lg the element is a zero-size anchor
   sitting ON the focal point — the centre of the focused shot — so 0 0 IS that
   centre. Below lg it is a full-width row in flow, where 0 0 would be its
   top-left corner, so it opens from `center` there instead.

   This animates the OUTER element; .hero-v4__reel-inner carries the layout
   scale (0.705 / 1 / 1.434). The two compose rather than overwrite. */
.hero-v4__reel {
	transform-origin: center;
	animation: hero-v4-open-x var(--hero-v4-intro-reel-dur) var(--hero-v4-intro-ease) both;
}

@media (min-width: 1024px) {
	.hero-v4__reel {
		transform-origin: 0 0;
	}
}

/* The layout transform (centring below lg, scaling at lg and up) is kept on a
   separate element from the animation above, because both write `transform`
   and the animated one would otherwise win outright and drop the scale. */
.hero-v4__reel-inner {
	transform-origin: 0 0;
}

/* 2, 3, 4 — copy */
.hero-v4__title,
.hero-v4__sub,
.hero-v4__form {
	animation: hero-v4-rise var(--hero-v4-intro-dur) var(--hero-v4-intro-ease) both;
}

.hero-v4__title {
	animation-delay: var(--hero-v4-intro-copy-start);
}

.hero-v4__sub {
	animation-delay: calc(var(--hero-v4-intro-copy-start) + var(--hero-v4-intro-step));
}

.hero-v4__form {
	animation-delay: calc(var(--hero-v4-intro-copy-start) + var(--hero-v4-intro-step) * 2);
}

/* --------------------------------------------------------------------------
   Reduced motion — the controller also stops auto-rotating (see hero-v4.js).
   -------------------------------------------------------------------------- */

@media (prefers-reduced-motion: reduce) {
	.hero-v4__slide,
	.hero-v4__word {
		transition-duration: 1ms;
	}

	/* An autoplaying loop is exactly what this setting asks us not to do, so the
	   video is dropped entirely and the static wash shows through instead. */
	.hero-v4__bg {
		display: none;
	}

	/* Dropping the animation leaves each element in its resting state, which is
	   the fully visible one — so this shows the hero at once, it does not hide
	   any of it. */
	.hero-v4__reel,
	.hero-v4__title,
	.hero-v4__sub,
	.hero-v4__form {
		animation: none;
	}
}
