/*
 * Hero card stack — the "at a glance" panel rides up over the media panel as
 * the page scrolls. Beacon (HERO 1) 6596:8073 -> (HERO 2) 6596:8145.
 *
 * WHICH ELEMENT MOVES. The artboard anchors the media at its TOP edge: it is at
 * y 535 in both states and simply shrinks 1197x510 -> 960x409 (0.802) in place,
 * while the panel rises 241px to overlap it by 125. An earlier build inverted
 * that — it held the panel still and sank the media instead, which produces the
 * same overlap with no layout change but detaches the media from the button
 * above it, opening a 241px hole there part-way down the scroll. The media must
 * not move.
 *
 * So the panel rises, and the 241px it vacates has to come back or the page
 * grows a hole below it instead. The panel and the curve divider under it (the
 * section's 2nd and 3rd children) take the same translate, which keeps their
 * 120px gap, and the SECTION takes the distance back as margin-block-end so the
 * rest of the page follows. That margin sits outside the section's border box,
 * so the section's height — and therefore this timeline's own range — does not
 * change as the animation runs. Putting the compensation inside the section
 * instead makes progress a fixed point of its own output; it converges, but
 * there is no reason to introduce the loop.
 *
 * --hero-travel is 241/1197 of the media's width, and the media is the page's
 * 1200 measure, so one expression holds at every width.
 *
 * TIMING. The range has to be empty at scroll 0 at EVERY viewport height, or the
 * page loads already part-overlapped — which is what an `entry` range on the
 * panel did above a ~1234px viewport. Three things that do not work here:
 *
 *   - `scroll(root block)` resolves but its currentTime is null, so nothing
 *     drives. A JS `new ScrollTimeline({source: documentElement})` on the same
 *     page reports 6.66%, so the root IS scrollable; the CSS function just does
 *     not bind, most likely because <body> carries `overflow-x: clip` and the
 *     viewport's overflow is propagated from it.
 *   - any `entry`/`cover` percentage on an element, since progress at load is
 *     (viewportHeight - subjectTop) / subjectHeight and no fixed percentage is
 *     safe for an arbitrary height.
 *   - a LENGTH offset — `exit 0px exit 450px` and `contain 0px contain 450px`
 *     both render an empty range and the animation never moves. Chrome appears
 *     to resolve the length against the whole timeline (7504px of document
 *     scroll) rather than against the named range, so the end lands before the
 *     start. Percentages behave; lengths do not.
 *
 * `exit` is not scroll 0 either: the hero section is TALLER than the viewport,
 * so `contain` means "covers the scrollport" and runs from its top reaching the
 * viewport top (scroll 0) to its bottom leaving it; `exit` only begins at scroll
 * 758. `contain 0%` is the edge that means scroll 0, and it means that at any
 * viewport shorter than the section.
 *
 * The range starts at 12%, not 0, so the hero scrolls normally for ~90px before
 * anything moves; 12% -> 62% is then ~380px of travel. Those percentages are one
 * value for every width because the contain range barely moves: 758 / 715 / 783
 * at 1440 / 900 / 420. `:has()` keeps all of this on Beacon rather than on the
 * five pages that share `hero-band`.
 */

.elementor .hero-band:has( .hero-stack-media ) {
	--hero-travel: calc( min( 1200px, 100vw - 48px ) * 0.2013 );
	view-timeline-name: --facteus-hero;
	view-timeline-axis: block;
}

/* The media shrinks from its top edge, as the artboard draws it. Declared here
 * rather than in the keyframes: transform-origin inside @keyframes did not apply
 * (it computed to centre), which puts the end state 50px low. */
.elementor .hero-stack-media.hero-stack-media {
	transform-origin: top center;
}

@supports ( animation-timeline: view() ) {
	@media ( prefers-reduced-motion: no-preference ) {
		/* Every animation property goes in ONE rule per element. Splitting them —
		 * animation-timeline/-range on a shared selector list and animation-name on
		 * the individual rules — computes identically and silently does not run:
		 * the animation object exists with the right name, timeline and range, and
		 * the timeline reports currentTime null. Setting the same values inline on
		 * the same element drives it correctly, so it is the split that breaks it. */

		/* The media shrinks in place — it never leaves the button above it. */
		.elementor .hero-stack-media {
			animation: facteus-hero-recede ease-in-out both;
			animation-timeline: --facteus-hero;
			animation-range: contain 12% contain 62%;
		}

		/* The panel and the curve below it rise together. */
		.elementor .hero-band:has( .hero-stack-media ) > :nth-child( n + 2 ) {
			animation: facteus-hero-rise ease-in-out both;
			animation-timeline: --facteus-hero;
			animation-range: contain 12% contain 62%;
		}

		/* and the page takes the same distance back. */
		.elementor .hero-band:has( .hero-stack-media ) {
			animation: facteus-hero-close ease-in-out both;
			animation-timeline: --facteus-hero;
			animation-range: contain 12% contain 62%;
		}

		/* The shadow belongs to HERO 2 only, so it arrives with the overlap. */
		.elementor .hero-stack-front.hero-stack-front > * {
			animation: facteus-hero-lift ease-in-out both;
			animation-timeline: --facteus-hero;
			animation-range: contain 12% contain 62%;
		}
	}
}

@keyframes facteus-hero-recede {
	from { scale: 1;     border-radius: 36px; }
	to   { scale: 0.802; border-radius: 44.9px; }
}

@keyframes facteus-hero-rise {
	from { translate: 0 0; }
	to   { translate: 0 calc( -1 * var( --hero-travel ) ); }
}

/* The margin alone is not enough. It sits outside the border box, so the
 * section keeps painting its full onyx height while the next section is pulled
 * up underneath it — and `hero-band` is `position: relative`, so that onyx paints
 * OVER it and the page shows a 241px black band under the curve. The clip takes
 * the same distance off what the section paints; its children have already moved
 * up by it, so nothing of the hero is lost.
 *
 * The extra 1px trims the curve divider's own bottom row, which is white painted
 * through a mask and therefore lands at alpha < 1 over the onyx behind it. It is
 * constant rather than scaled with progress so that row is never half-covered
 * mid-scroll.
 *
 * What the 1px does NOT fix is the clip edge itself — see the cover rule below.
 */
@keyframes facteus-hero-close {
	from {
		margin-block-end: 0;
		clip-path: inset( 0 0 1px 0 );
	}
	to {
		margin-block-end: calc( -1 * var( --hero-travel ) );
		clip-path: inset( 0 0 calc( var( --hero-travel ) + 1px ) 0 );
	}
}

@keyframes facteus-hero-lift {
	from { box-shadow: 0 -26px 54px rgba( 0, 0, 0, 0 ); }
	to   { box-shadow: 0 -26px 54px rgba( 0, 0, 0, 0.56 ); }
}

/* THE CLIP EDGE BLEEDS, and no amount of trimming moves it.
 *
 * The clip lands on a fractional y (the section's border box bottom is not a
 * whole pixel at any real width), so Chrome rasterises the last row of the
 * clipped layer at partial alpha — and what shows through is the SECTION'S OWN
 * BACKGROUND. Against the page white that composites to a pale grey hairline
 * across the full width, right where the curve meets the section below.
 *
 * An earlier note here claimed the 1px trim above removed it. It does not: the
 * line follows the clip edge wherever it is put. Proved by moving the clip 20px
 * and 40px up — the hairline moved with it both times — and by flooding the
 * section's background magenta, which turned the hairline PINK. It is the
 * background, not the mask, and not the 1px gap between the clip and the next
 * section (closing that gap changes nothing).
 *
 * It cannot be fixed from above, so it is covered from below: the section after
 * the hero paints 2px of white up over the join. `box-shadow` is used rather
 * than a margin — an adjoining negative margin would COLLAPSE with the hero's
 * own -241px and be swallowed — and rather than a pseudo-element, which Elementor
 * already uses on `.e-con` for background overlays. Nothing moves; the shadow is
 * pure paint.
 *
 * The section needs its own stacking context to land above `hero-band`, which is
 * `position: relative`. The class is doubled so this beats a per-widget rule:
 * this sheet loads around index 7 against Elementor's 25-34.
 */
.elementor .hero-band.hero-band:has( .hero-stack-media ) + * {
	position: relative;
	z-index: 1;
	box-shadow: 0 -2px 0 0 var( --white, #fff );
}

/* On Beacon the section after the hero is the photo band. Switching the cover
 * OFF here was wrong and the hairline came straight back: measured on the live
 * page it is rgb(159,156,161), a bright grey row across the full width — the
 * clip edge showing the PAGE WHITE, not the hero's own onyx as assumed above.
 * A bleed has to be covered, not reasoned away.
 *
 * Onyx is the colour to cover it with. The band's first child is an onyx curve
 * carving the hero's colour down into it, so the band's own top row is already
 * onyx across the left half; on the right half 2px of onyx against #261A33 is a
 * difference of thirteen levels, under a photograph, and reads as the hero
 * being two pixels taller. White read as a 142-level line.
 *
 * One extra class takes this to (0,4,0) against the rule above's (0,3,0), so it
 * wins regardless of source order; the other four product pages are untouched
 * because none of them carries `band-photo-section`.
 */
.elementor .hero-band.hero-band:has( .hero-stack-media ) + .band-photo-section {
	box-shadow: 0 -2px 0 0 var( --onyx );
}
