/* ===== Scroll Area (outer) ===== */
.stack-stage{ position:relative; overflow:visible; }/* ===== Pinned Stage (inner) ===== */
.stack-cards{
position:sticky; top:0; height:100vh; perspective:1400px; overflow:visible;
--gap-x: 28; --gap-y: 14; --gap-z: 110; --tilt: 1.6; --scale-step: 0.045;
--shadow: 0 18px 40px rgba(0,0,0,.2);
--dur: 520ms; --ease: cubic-bezier(.22,.61,.36,1);
}/* Cards: keep your own background styling in Elementor */
.stack-cards .card{
position:absolute; inset:6% 6%;
border-radius:24px;
box-shadow:var(--shadow);
transform-style:preserve-3d; backface-visibility:hidden;
will-change: transform, opacity;
display:flex; align-items:center; justify-content:center; text-align:center;
padding: clamp(20px, 4vw, 56px);
}/* Content fades only on the active card */
.stack-cards .card > .card-content{
max-width: 720px;
opacity: 0; transform: translateY(12px);
transition: opacity var(--dur) var(--ease), transform var(--dur) var(--ease);
}
.stack-cards .card[data-active="true"] > .card-content{
opacity: 1; transform: translateY(0);
}/* Responsive tuning */
@media (max-width:1024px){
.stack-cards{ --gap-x: 24; --gap-y: 12; --gap-z: 90; --scale-step: .05; }
}
@media (max-width:767px){
.stack-cards{ --gap-x: 18; --gap-y: 10; --gap-z: 70; --scale-step: .055; }
.stack-cards .card{ inset:5% 4%; border-radius:18px; padding:24px; }
}@media (prefers-reduced-motion: reduce){
.stack-cards .card, .stack-cards .card > .card-content{ transition:none!important; }
}
Skip to content <
(function(){
function initAll(){
const stages = document.querySelectorAll('.stack-stage');
if(!stages.length) return;
stages.forEach(stage => {
if(stage._stackInit) return; // don't double-init
stage._stackInit = true;
setup(stage);
});
}function setup(stage){
const stack = stage.querySelector('.stack-cards');
if(!stack) return;
const cards = Array.from(stack.querySelectorAll('.card'));
if(cards.length < 2) return;function sizeStage(){
const vh = window.innerHeight;
stage.style.minHeight = (vh * (cards.length + 0.75)) + 'px';
}
sizeStage();
window.addEventListener('resize', sizeStage, {passive:true});let baseIndex = 0;function onScroll(){
const rect = stage.getBoundingClientRect();
const vh = window.innerHeight;
const total = Math.max(stage.offsetHeight - vh, 1);
const inside = Math.min(Math.max(-rect.top, 0), total);
const progress = inside / total; // 0..1
const maxSteps = cards.length - 1;
const cont = progress * maxSteps; // 0..(N-1)
const step = Math.floor(cont);
const frac = cont - step;if(step !== baseIndex) baseIndex = step;
render(frac);
}function render(frac){
const t = 1 - Math.pow(1 - frac, 3); // easeOutCubic
const n = cards.length;
const cs = getComputedStyle(stack);
const gapX = +cs.getPropertyValue('--gap-x') || 34;
const gapY = +cs.getPropertyValue('--gap-y') || 18;
const gapZ = +cs.getPropertyValue('--gap-z') || 80;
const stepS= +cs.getPropertyValue('--scale-step') || 0.055;
const tilt = +cs.getPropertyValue('--tilt') || 2.2;cards.forEach((el, layer)=>{
const v = layer + t;
const x = v * gapX;
const y = -v * gapY;
const z = -v * gapZ;
const s = 1 - v * stepS;
const rot = (layer === 0) ? (-tilt * t) : 0;
el.style.zIndex = (n - layer).toString();
el.style.transform = `translate3d(${x}px,${y}px,${z}px) rotate(${rot}deg) scale(${s})`;
el.style.opacity = 1 - Math.min(v * 0.03, 0.22);
el.dataset.active = (layer===0 && t < 0.999) ? "true" : "false";
});if(t > 0.999){
const first = cards.shift();
cards.push(first);
first.parentNode.appendChild(first);
cards.forEach((el, idx)=> el.dataset.active = (idx===0) ? "true" : "false");
}
}window.addEventListener('scroll', onScroll, {passive:true});
onScroll();
}// Run on normal pages
if(document.readyState === 'loading'){
document.addEventListener('DOMContentLoaded', initAll);
} else {
initAll();
}// Also run inside Elementor editor/preview
if (window.elementorFrontend) {
window.elementorFrontend.hooks.addAction('frontend/element_ready/container.default', function(){
setTimeout(initAll, 50);
});
}// As a fallback, observe DOM changes (useful in editor)
const mo = new MutationObserver(() => initAll());
mo.observe(document.documentElement, {childList:true, subtree:true});
})();