/*
 * Motion Contract (master plan §7.1). Elements are marked on the server with data-v-anim.
 * Nothing is hidden unless html.v-motion is present (set only when JS runs and the visitor
 * has not asked for reduced motion). html.v-motion-failsafe reveals everything if the
 * script never initialises. Animate only transform, opacity and clip-path.
 */

/* ---------------------------------------------------------------- Initial (hidden) states */
.v-motion:not(.v-motion-failsafe) [data-v-anim="lines"]:not(.is-split) { opacity: 0; }
.v-motion:not(.v-motion-failsafe) [data-v-anim="fade"]:not(.is-in),
.v-motion:not(.v-motion-failsafe) [data-v-anim="stagger"] > :not(.is-in) {
	opacity: 0;
	transform: translateY(var(--v-lift));
}
/* Clip reveal: the mask opens from the bottom edge upwards; the image settles inside it.
   The mask sits on the element's CHILDREN: IntersectionObserver counts a target's own clip-path,
   so a fully clipped element would never report itself as visible. Put data-v-anim="clip" on
   the image's wrapper (figure, Elementor widget), never on the image element itself. */
.v-motion:not(.v-motion-failsafe) [data-v-anim="clip"]:not(.is-in) > * { clip-path: inset(100% 0 0 0); }
.v-motion:not(.v-motion-failsafe) [data-v-anim="clip"]:not(.is-in) img { transform: scale(var(--v-clip-settle)); }
/* The Stitch draws: a clip uncovers it from the left, so the dashes never stretch. It starts
   from a 1 px sliver (an invisible 1 × 1.5 px dash end) so the observer can see it. */
.v-motion:not(.v-motion-failsafe) [data-v-anim="stitch"]:not(.is-in) { clip-path: inset(0 calc(100% - 1px) 0 0); }

/* ---------------------------------------------------------------- Transitions to the final state
   Only until the reveal has finished (.is-done, set by the engine), so an element's own
   transitions (button press, card zoom, tile hover) are never slowed or delayed afterwards. */
[data-v-anim="fade"]:not(.is-done),
[data-v-anim="stagger"] > :not(.is-done) {
	transition:
		opacity var(--v-dur-reveal) var(--v-ease-out) var(--v-delay, 0ms),
		transform var(--v-dur-reveal) var(--v-ease-out) var(--v-delay, 0ms);
}
/* Explicit end states: clip-path only interpolates between two inset() shapes, never to `none`. */
[data-v-anim="clip"]:not(.is-done) > *,
[data-v-anim="stitch"]:not(.is-done) {
	clip-path: inset(0 0 0 0);
	transition: clip-path var(--v-dur-image) var(--v-ease-in-out) var(--v-delay, 0ms);
}
/* Lists both properties: the image is often the mask's direct child, and a lone `transform`
   transition here would silently drop the clip-path transition. */
[data-v-anim="clip"]:not(.is-done) img {
	transition:
		clip-path var(--v-dur-image) var(--v-ease-in-out) var(--v-delay, 0ms),
		transform var(--v-dur-settle) var(--v-ease-in-out) var(--v-delay, 0ms);   /* tracks the mask, then settles just after it */
}

/* Line-mask headings: each line slides up from its own mask. Extra bottom room keeps
   descenders (g, y, p) from being clipped at tight display line-heights. */
.v-line {
	display: block;
	overflow: clip;
	padding-bottom: 0.14em;
	margin-bottom: -0.14em;
}
.v-line__in {
	display: block;
	transform: translateY(110%);
	transition: transform var(--v-dur-reveal) var(--v-ease-out);
	transition-delay: calc(var(--v-line-i, 0) * var(--v-stagger-lines));
}
.is-in > .v-line > .v-line__in { transform: none; }

/* Replay (style guide): snap back to the hidden state without playing the transition in reverse. */
.v-anim-reset,
.v-anim-reset *,
.v-anim-reset > * { transition: none !important; }

/* ---------------------------------------------------------------- Hero image settle (never hidden: it is the LCP) */
@keyframes v-settle {
	from { transform: scale(1.02); }
	to { transform: none; }
}
.v-motion [data-v-anim="settle"] { animation: v-settle var(--v-dur-settle) var(--v-ease-out) both; }

/* ---------------------------------------------------------------- Parallax: CSS scroll-driven only (no JS), max 1 per page
   The image is 2 × --v-parallax taller than its frame and drifts ±--v-parallax.
   The timeline is set on the FRAME (the parent), not the image: the frame must clip the image
   (overflow: hidden), and an overflow: hidden parent is itself a scroll container, which would
   freeze a plain view() timeline on the image. */
[data-v-anim="parallax"] {
	height: calc(100% + 2 * var(--v-parallax));
	margin-block: calc(var(--v-parallax) * -1);
}
@supports (animation-timeline: view()) {
	@keyframes v-parallax {
		from { transform: translateY(calc(var(--v-parallax) * -1)); }
		to { transform: translateY(var(--v-parallax)); }
	}
	:has(> [data-v-anim="parallax"]) { view-timeline: --v-parallax block; }
	.v-motion [data-v-anim="parallax"] {
		animation: v-parallax linear both;
		animation-timeline: --v-parallax;
		animation-range: cover;
	}
}

/* ---------------------------------------------------------------- Sticky story: Stitch progress (scroll-linked)
   Where scroll-driven animations exist, the vertical Stitch beside the steps draws with scroll:
   empty when the first step reaches the middle of the screen, full when the last step leaves it.
   Elsewhere (and with reduced motion) it advances one step at a time from --v-story-p (set by the engine). */
.v-story__rail::after {
	clip-path: inset(0 0 calc((1 - var(--v-story-p, 0)) * 100%) 0);
	transition: clip-path var(--v-dur-ui) var(--v-ease-out);
}
@supports (animation-timeline: view()) {
	@keyframes v-story-draw {
		from { clip-path: inset(0 0 100% 0); }
		to { clip-path: inset(0 0 0 0); }
	}
	.v-story__track {
		view-timeline: --v-story block;
		view-timeline-inset: 50% 50%;
	}
	.v-motion .v-story__rail::after {
		transition: none;
		animation: v-story-draw linear both;
		animation-timeline: --v-story;
		animation-range: cover 0% cover 100%;
	}
	/* The scissors ride the drawing edge and snip as you scroll: 8 cuts across the story (§7.1 v1.25). */
	@keyframes v-story-cut {
		from { translate: 0 0; }
		to { translate: 0 100cqh; }
	}
	.v-motion .v-story__scissors {
		transition: none;
		animation: v-story-cut linear both;
		animation-timeline: --v-story;
		animation-range: cover 0% cover 100%;
	}
	.v-motion .v-story__scissors :is(.v-scissors__blade--a, .v-scissors__blade--b) {
		animation: v-snip-a linear 8 both;
		animation-timeline: --v-story;
		animation-range: cover 0% cover 100%;
	}
	.v-motion .v-story__scissors .v-scissors__blade--b { animation-name: v-snip-b; }
}

/* ---------------------------------------------------------------- Reduced motion and print: always final state */
@media (prefers-reduced-motion: reduce), print {
	[data-v-anim],
	[data-v-anim="stagger"] > *,
	[data-v-anim="clip"] > *,
	[data-v-anim="clip"] img { opacity: 1 !important; transform: none !important; clip-path: none !important; animation: none !important; }
	.v-line__in { transform: none !important; }
	.v-story__rail::after,
	.v-story__scissors { transition: none; }
}
