This provides smooth scrolling across the entire template. To turn off just comment out this code.
<link rel="stylesheet" href="https://unpkg.com/lenis@1.3.20/dist/lenis.css" />
<script src="https://unpkg.com/lenis@1.3.20/dist/lenis.min.js"></script>
<script>
gsap.registerPlugin(ScrollTrigger);
const lenis = new Lenis({
duration: 1.5,
easing: (t) => 1 - Math.pow(1 - t, 3),
smooth: true,
smoothTouch: false, // ✅ important
});
// sync
lenis.on('scroll', ScrollTrigger.update);
// raf loop
function raf(time) {
lenis.raf(time);
requestAnimationFrame(raf);
}
requestAnimationFrame(raf);
// ✅ correct scroller
ScrollTrigger.scrollerProxy(document.documentElement, {
scrollTop(value) {
return arguments.length ? lenis.scrollTo(value, { immediate: true }) : lenis.scroll;
},
getBoundingClientRect() {
return {
top: 0,
left: 0,
width: window.innerWidth,
height: window.innerHeight,
};
},
});
// ✅ important
ScrollTrigger.defaults({
scroller: document.documentElement,
});
// refresh fix
ScrollTrigger.addEventListener('refresh', () => lenis.resize());
ScrollTrigger.refresh();
</script><script>
gsap.registerPlugin(ScrollTrigger);
gsap.utils.toArray(".counter").forEach(counter => {
const finalText = counter.textContent.trim();
const endValue = parseFloat(finalText.replace(/[^0-9.-]/g, ""));
const suffix = finalText.replace(/[0-9.-]/g, "");
const decimals = finalText.includes(".") ? 1 : 0;
// Reset to zero
counter.textContent = "0" + suffix;
const obj = { value: 0 };
gsap.to(obj, {
value: endValue,
duration: 2,
ease: "power2.out",
scrollTrigger: {
trigger: counter,
start: "top 80%",
once: true
},
onUpdate() {
counter.textContent =
obj.value.toFixed(decimals) + suffix;
}
});
});
</script>
<script>
const container = document.querySelector(".stack-container");
const cards = [...container.children];
// Positions for LEFT, MIDDLE, RIGHT
const positions = [
{ x: -50, rot: -7.56, scale: 0.95, z: 1 }, // LEFT
{ x: 0, rot: 0, scale: 1, z: 3 }, // MIDDLE
{ x: 50, rot: 7.56, scale: 0.95, z: 2 } // RIGHT
];
// ----------------------------
// 1️⃣ Layout stack using GSAP
// ----------------------------
function layoutStack(){
cards.forEach((card, i)=>{
const pos = positions[i];
gsap.to(card, {
x: pos.x,
y: 0,
rotation: pos.rot, // Z-axis
rotateX: 0, // reset 3D
rotateY: 0,
scale: pos.scale,
zIndex: pos.z,
duration: 0.4,
ease: "power2.out"
});
});
}
// ----------------------------
// 2️⃣ Cycle stack using GSAP
// ----------------------------
function rotateStack(){
// Move first card to end
const first = cards.shift();
container.appendChild(first);
cards.push(first);
// Animate new positions
layoutStack();
}
// ----------------------------
// 3️⃣ Drag with full GSAP control
// ----------------------------
function drag(card){
let startX=0, startY=0, x=0, y=0, dragging=false;
const sensitivity = 120;
card.onpointerdown = e => {
dragging = true;
card.setPointerCapture(e.pointerId);
startX = e.clientX - x;
startY = e.clientY - y;
}
card.onpointermove = e => {
if(!dragging) return;
x = e.clientX - startX;
y = e.clientY - startY;
// Drag movement using GSAP
gsap.set(card, {
x: x,
y: y,
rotateX: -y*0.3,
rotateY: x*0.3
});
}
card.onpointerup = () => {
dragging = false;
const middleCard = cards[1];
if(card === middleCard && (Math.abs(x)>sensitivity || Math.abs(y)>sensitivity)){
rotateStack();
} else {
// Snap back using GSAP
gsap.to(card, {
x: 0,
y: 0,
rotateX: 0,
rotateY: 0,
duration: 0.3,
ease: "power2.out"
});
}
x = 0;
y = 0;
}
}
// ----------------------------
// Initialize
// ----------------------------
cards.forEach(drag);
layoutStack();
</script>
<link rel="stylesheet" href="https://unpkg.com/lenis@1.3.20/dist/lenis.css" />
<script src="https://unpkg.com/lenis@1.3.20/dist/lenis.min.js"></script>
<script>
gsap.registerPlugin(ScrollTrigger);
const lenis = new Lenis({
duration: 1.5,
easing: (t) => 1 - Math.pow(1 - t, 3),
smooth: true,
smoothTouch: false, // ✅ important
});
// sync
lenis.on('scroll', ScrollTrigger.update);
// raf loop
function raf(time) {
lenis.raf(time);
requestAnimationFrame(raf);
}
requestAnimationFrame(raf);
// ✅ correct scroller
ScrollTrigger.scrollerProxy(document.documentElement, {
scrollTop(value) {
return arguments.length ? lenis.scrollTo(value, { immediate: true }) : lenis.scroll;
},
getBoundingClientRect() {
return {
top: 0,
left: 0,
width: window.innerWidth,
height: window.innerHeight,
};
},
});
// ✅ important
ScrollTrigger.defaults({
scroller: document.documentElement,
});
// refresh fix
ScrollTrigger.addEventListener('refresh', () => lenis.resize());
ScrollTrigger.refresh();
</script>