/**
 * Sticky Stack — frontend styles.
 *
 * The entire stacking engine is CSS-only (position: sticky). No scroll
 * listeners. Everything is driven by CSS custom properties written by the
 * widget controls and by two data attributes on the wrapper:
 *   data-stack-mode = full-overlay | reveal-header | offset-stack | custom-offset
 *   data-anim       = none | scale | fade | scale-fade | opacity | lift
 *
 * Per-card variables set inline in render.php:
 *   --i       : zero-based card index (drives staggered sticky offsets)
 *   z-index   : card number (later cards paint on top — dynamic z-index)
 *
 * Variable defaults (overridden by controls):
 *   --ss-position    : sticky | static   (per-device Enable Stack)
 *   --ss-offset      : base sticky top offset
 *   --ss-offset-step : per-card increment for Offset Stack
 *   --ss-header-h    : visible header height for Reveal Header
 *   --ss-gap         : gap between cards
 *   --ss-min-h       : minimum card height
 *   --ss-radius      : card corner radius
 *   --ss-overflow    : card overflow
 *   --ss-anim-*      : animation duration / easing
 */

/* --------------------------------------------------------------------------
 * Sticky-context fix (the #1 real-world reason sticky "does nothing").
 *
 * Many themes set `overflow-x: hidden` on <html>/<body> to avoid horizontal
 * scrollbars. The browser then FORCES the other axis to `auto`, turning those
 * elements into scroll containers — which silently breaks position: sticky for
 * the entire page (the sticky element's scroll context stops matching where the
 * page actually scrolls).
 *
 * `overflow: clip` prevents horizontal overflow WITHOUT creating a scroll
 * container, so sticky works again. This rule only loads on pages that actually
 * use the Sticky Stack widget. If your theme's rule still wins (specificity or
 * load order), either remove the theme's `overflow-x: hidden` on html/body, or
 * add `!important` here.
 * ----------------------------------------------------------------------- */
html {
	overflow-x: clip;
}

body {
	overflow-x: clip;
	overflow-y: visible;
}

/* --------------------------------------------------------------------------
 * Layout container
 * ----------------------------------------------------------------------- */
.e-sticky-stack {
	position: relative;
	width: 100%;
	/* Never clip the sticky children, and never create a scroll container here
	 * (either would break position: sticky for the cards). */
	overflow: visible;
	/* NOTE: do NOT set --ss-* defaults here. The controls write those variables
	 * on the widget's ROOT element (the parent of .e-sticky-stack). Redefining
	 * them here would shadow the control values via inheritance and force every
	 * card back to 0. Each calc() below already carries a fallback
	 * (e.g. var(--ss-offset, 0px)), which covers the "not set yet" case. */
}

/* IMPORTANT: use normal BLOCK flow, not flexbox.
 * A flex container can interfere with position: sticky on its children in some
 * browsers, and block flow is the canonical, reliable base for stacking cards.
 * The gap is applied as margin between cards instead of `gap`. */
.e-sticky-stack__list {
	display: block;
	overflow: visible;
}

/* --------------------------------------------------------------------------
 * SCROLL RUNWAY — the piece that makes the stack actually HOLD.
 *
 * A sticky element can never be pushed outside its containing block, which here
 * is this list. Without the runway the list is exactly as tall as the cards it
 * contains, so the instant its bottom edge reaches the sticky line the browser
 * rips every pinned card loose and drags them up together: card #1 never stays
 * locked, and the last card never even reaches its pin position. The stack was
 * holding for literally 0px of scroll.
 *
 * This empty trailing box adds scroll length BELOW the last card, inside the
 * containing block — that is the distance over which the finished stack stays
 * pinned before it scrolls away as a unit.
 *
 * A pseudo-element (not a real div) on purpose: the Nested Elements engine
 * injects the child Containers into this list, and an extra DOM node here risks
 * confusing the editor's index-based child injection. Pseudo-elements are
 * invisible to it.
 *
 * Default height is AUTO: cards x the per-card offset step, the same
 * technique Unlimited Elements uses for its own stacking-cards runway
 * (padding-bottom = count x step). A fixed guess breaks as soon as the card
 * count or offset step changes — too short and the last card gets dragged
 * away before it ever finishes locking, too long and a dead empty gap opens
 * up after the stack.
 *
 * The manual override reads --ss-runway-override, NOT --ss-runway: pages
 * built with older versions have `--ss-runway: 0px` baked into their saved
 * Elementor CSS (a control `condition` hides the slider in the panel but
 * does not stop Elementor printing its selectors), which would pin the
 * runway to 0 forever. A fresh property name has no stale saved value, so
 * the auto fallback actually applies until the author explicitly moves the
 * new Hold Distance slider (see controls.php).
 * ----------------------------------------------------------------------- */
.e-sticky-stack__list::after {
	content: "";
	display: block;
	/* × --ss-enabled: collapses to 0 on devices where Enable Stack is Off. */
	height: calc(var(--ss-enabled, 1) * var(--ss-runway-override, calc(var(--ss-count, 1) * var(--ss-header-h, 80px))));
	pointer-events: none;
}

[data-stack-mode="offset-stack"] .e-sticky-stack__list::after {
	height: calc(var(--ss-enabled, 1) * var(--ss-runway-override, calc(var(--ss-count, 1) * var(--ss-offset-step, 40px))));
}

[data-stack-mode="full-overlay"] .e-sticky-stack__list::after,
[data-stack-mode="custom-offset"] .e-sticky-stack__list::after {
	/* Every card pins at the same spot in these modes, so there is no growing
	 * per-card offset to multiply — fall back to a flat viewport-height hold. */
	height: calc(var(--ss-enabled, 1) * var(--ss-runway-override, 100vh));
}

/* Vertical gap between cards (block flow — margin, not flex gap). */
.e-sticky-stack__card:not(:last-child) {
	margin-block-end: var(--ss-gap, 0px);
}

/* --------------------------------------------------------------------------
 * The card. Each card is a positioned context so its child Container can be
 * clipped/rounded and so the Reveal-Header strip (::before) can be absolutely
 * placed. position defaults to sticky but is switched to static per device via
 * the Enable Stack control (--ss-position).
 * ----------------------------------------------------------------------- */
.e-sticky-stack__card {
	position: var(--ss-position, sticky);
	top: var(--ss-offset, 0px);
	min-height: var(--ss-min-h, auto);
	/* z-index is set inline per card (later cards on top). */
}

/* Round + clip the actual Container that lives inside the card. */
.e-sticky-stack__card > .e-con,
.e-sticky-stack__card > .e-con.e-con-full {
	border-radius: var(--ss-radius, 0);
	overflow: var(--ss-overflow, hidden);
	min-height: inherit;
	height: 100%;
}

/* --------------------------------------------------------------------------
 * Stack modes — only the sticky `top` differs between them.
 * `var(--i)` is unitless and multiplied by a length inside calc().
 * ----------------------------------------------------------------------- */

/* 1. Full Overlay: every card pins at the same offset; the newer (higher
 *    z-index) card fully covers the previous one. */
[data-stack-mode="full-overlay"] .e-sticky-stack__card {
	top: var(--ss-offset, 0px);
}

/* 4. Custom Offset: same as full overlay but --ss-offset is the user value. */
[data-stack-mode="custom-offset"] .e-sticky-stack__card {
	top: var(--ss-offset, 0px);
}

/* 3. Offset Stack: each card pins one step lower than the previous, so every
 *    earlier card keeps a growing sliver visible.
 *
 * The step is used EXACTLY as configured — no auto-compression. Squeezing
 * the step to fit the viewport (tried in 1.0.7) produces slivers too short
 * for the cards' own heading text, which reads as clipped garbage. The
 * reference behaviour (WP Mozo scroll-stack-cards) keeps every collapsed
 * strip at a fixed, title-sized height and simply accumulates them, so the
 * author is responsible for choosing a step small enough that
 * offset + (count - 1) x step + one card still fits their viewport. */
[data-stack-mode="offset-stack"] .e-sticky-stack__card {
	top: calc(var(--ss-offset, 0px) + var(--i, 0) * var(--ss-offset-step, 40px));
}

/* 2. Reveal Header: each card pins one header-height lower, leaving the top
 *    strip of the previous card visible. Same no-compression rule as above. */
[data-stack-mode="reveal-header"] .e-sticky-stack__card {
	top: calc(var(--ss-offset, 0px) + var(--i, 0) * var(--ss-header-h, 80px));
}

/* The visible "header" strip is a decorative overlay on the top of each card.
 * Group controls (background / border / shadow) target this ::before. */
[data-stack-mode="reveal-header"] .e-sticky-stack__card::before {
	content: "";
	position: absolute;
	inset-block-start: 0;
	inset-inline: 0;
	height: var(--ss-header-h, 80px);
	z-index: 2;
	pointer-events: none;
	border-radius: var(--ss-radius, 0) var(--ss-radius, 0) 0 0;
}

/* --------------------------------------------------------------------------
 * JS COLLAPSE ENGINE overrides (class added by frontend.js at runtime).
 *
 * Under `.e-sticky-stack--js` the fold is scripted: the LIST is the pinned
 * element and each card's height is animated down to its strip size, with
 * the content clipped from the bottom — the WP Mozo behaviour. The per-card
 * sticky rules above must therefore be neutralised: cards return to normal
 * flow so the cards below rise as the one above them folds.
 *
 * Everything here is inert until the JS adds the class, so no-JS, reduced
 * motion and the editor keep the pure-CSS sticky engine above untouched.
 * This block must stay AFTER the stack-mode rules: several selectors tie on
 * specificity and rely on source order to win.
 * ----------------------------------------------------------------------- */
.e-sticky-stack--js .e-sticky-stack__list {
	position: sticky;
	top: var(--ss-offset, 0px);
}

/* The wrapper's JS-set fixed height provides the scroll runway instead. */
.e-sticky-stack--js .e-sticky-stack__list::after {
	content: none;
}

.e-sticky-stack--js .e-sticky-stack__card,
[data-stack-mode="reveal-header"].e-sticky-stack--js .e-sticky-stack__card,
[data-stack-mode="offset-stack"].e-sticky-stack--js .e-sticky-stack__card {
	position: relative;
	top: auto;
	/* min-height would refuse the fold below the strip size; the natural
	 * (expanded) size lives on the inner container instead — see below. */
	min-height: 0;
	overflow: hidden;
	will-change: height;
}

/* The inner container keeps its natural size while the card folds over it,
 * so content is CLIPPED from the bottom rather than squashed. */
.e-sticky-stack--js .e-sticky-stack__card > .e-con,
.e-sticky-stack--js .e-sticky-stack__card > .e-con.e-con-full {
	height: auto;
	min-height: var(--ss-min-h, auto);
}

/* --------------------------------------------------------------------------
 * Animations.
 *
 * Primary path: CSS scroll-driven animation (no JS) where supported. The card
 * animates as it exits the top of the scrollport (i.e. as it gets covered).
 *
 * Fallback path: frontend.js adds `.is-covered` when the next card intersects,
 * and these transitions run instead. The JS only runs when scroll-driven
 * animations are NOT supported, so the two paths never both fire.
 *
 * All animation is disabled under prefers-reduced-motion.
 * ----------------------------------------------------------------------- */

/* Shared easing/duration for the fallback transition path. */
.e-sticky-stack[data-anim]:not([data-anim="none"]) .e-sticky-stack__card > .e-con {
	transition-property: transform, opacity;
	transition-duration: var(--ss-anim-duration, 400ms);
	transition-timing-function: var(--ss-anim-ease, ease);
	will-change: transform, opacity;
	transform-origin: center top;
}

/* ---- Fallback states (JS toggles .is-covered) ---- */
@media (prefers-reduced-motion: no-preference) {
	[data-anim="scale"]      .e-sticky-stack__card.is-covered > .e-con { transform: var(--ss-k-scale); }
	[data-anim="fade"]       .e-sticky-stack__card.is-covered > .e-con { opacity: var(--ss-k-fade); }
	[data-anim="scale-fade"] .e-sticky-stack__card.is-covered > .e-con { transform: var(--ss-k-scale); opacity: var(--ss-k-fade); }
	[data-anim="opacity"]    .e-sticky-stack__card.is-covered > .e-con { opacity: var(--ss-k-opacity); }
	[data-anim="lift"]       .e-sticky-stack__card.is-covered > .e-con { transform: var(--ss-k-lift); }
}

/* ---- Primary path: scroll-driven (progressive enhancement) ---- */
@supports (animation-timeline: view()) {
	@media (prefers-reduced-motion: no-preference) {
		.e-sticky-stack[data-anim]:not([data-anim="none"]) .e-sticky-stack__card > .e-con {
			/* Transitions are unnecessary here; the timeline drives progress. */
			transition: none;
			animation-duration: 1ms; /* required placeholder; timeline controls playback */
			animation-timing-function: var(--ss-anim-ease, ease);
			animation-fill-mode: both;
			animation-timeline: view();
			/* Animate while the card is exiting the top of the viewport. */
			animation-range: exit 0% exit 100%;
		}

		[data-anim="scale"]      .e-sticky-stack__card > .e-con { animation-name: ss-scale; }
		[data-anim="fade"]       .e-sticky-stack__card > .e-con { animation-name: ss-fade; }
		[data-anim="scale-fade"] .e-sticky-stack__card > .e-con { animation-name: ss-scale-fade; }
		[data-anim="opacity"]    .e-sticky-stack__card > .e-con { animation-name: ss-opacity; }
		[data-anim="lift"]       .e-sticky-stack__card > .e-con { animation-name: ss-lift; }
	}
}

/* Exit-animation end states, scaled by --ss-enabled so they become no-ops
 * (scale 1, opacity 1, no lift) on devices where Enable Stack is Off. */
.e-sticky-stack__card > .e-con {
	--ss-k-scale: scale(calc(1 - 0.08 * var(--ss-enabled, 1)));
	--ss-k-fade: calc(1 - 0.6 * var(--ss-enabled, 1));
	--ss-k-opacity: calc(1 - 0.85 * var(--ss-enabled, 1));
	--ss-k-lift: translateY(calc(-24px * var(--ss-enabled, 1))) scale(calc(1 - 0.03 * var(--ss-enabled, 1)));
}

@keyframes ss-scale      { to { transform: var(--ss-k-scale); } }
@keyframes ss-fade       { to { opacity: var(--ss-k-fade); } }
@keyframes ss-scale-fade { to { transform: var(--ss-k-scale); opacity: var(--ss-k-fade); } }
@keyframes ss-opacity    { to { opacity: var(--ss-k-opacity); } }
@keyframes ss-lift       { to { transform: var(--ss-k-lift); } }

/* Respect reduced motion everywhere: no transforms, no opacity shifts,
 * no scroll-driven animation. Stacking (sticky) still works. */
@media (prefers-reduced-motion: reduce) {
	.e-sticky-stack__card > .e-con {
		transition: none !important;
		animation: none !important;
		transform: none !important;
		opacity: 1 !important;
	}
}
