<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: 5,
ease: "power2.out",
scrollTrigger: {
trigger: counter,
start: "top 80%",
once: true
},
onUpdate() {
counter.textContent =
obj.value.toFixed(decimals) + suffix;
}
});
});
</script>
<script>
gsap.registerPlugin(ScrollTrigger);
const mm = gsap.matchMedia();
mm.add({
desktop: "(min-width: 992px)",
tablet: "(min-width: 768px) and (max-width: 991px)",
mobile: "(max-width: 767px)"
}, (context) => {
let { desktop, tablet, mobile } = context.conditions;
let pos;
if (desktop) {
pos = {
first: { x: -240, y: 0, rot: -25, ry: 35, rx: -15, z: 0 },
second: { x: -110, y: -35, rot: -12, ry: 25, rx: -8, z: 20 },
third: { x: 20, y: -35, rot: -2, ry: 15, rx: -2, z: 60 },
fourth: { x: 150, y: -10, rot: 12, ry: 8, rx: 2, z: 120 }
};
}
if (tablet) {
pos = {
first: { x: -170, y: 0, rot: -20, ry: 25, rx: -10, z: 0 },
second: { x: -80, y: -25, rot: -10, ry: 18, rx: -6, z: 20 },
third: { x: 15, y: -25, rot: -2, ry: 10, rx: -2, z: 40 },
fourth: { x: 100, y: -8, rot: 10, ry: 6, rx: 2, z: 70 }
};
}
if (mobile) {
pos = {
first: { x: -95, y: 0, rot: -12, ry: 12, rx: -5, z: 0 },
second: { x: -35, y: -12, rot: -6, ry: 8, rx: -2, z: 10 },
third: { x: 25, y: -12, rot: 2, ry: 5, rx: 0, z: 20 },
fourth: { x: 80, y: 0, rot: 8, ry: 2, rx: 2, z: 30 }
};
}
gsap.set(".studio-image-wrap", {
x: 0,
y: 0,
rotation: 0,
rotationX: 0,
rotationY: 0,
z: 0,
transformOrigin: "center center",
transformStyle: "preserve-3d"
});
const master = gsap.timeline({
repeat: -1,
repeatDelay: 2
});
master.to({}, { duration: 0.4 });
master.to(".studio-image-wrap.ist", {
x: pos.first.x,
y: pos.first.y,
rotation: pos.first.rot,
rotationY: pos.first.ry,
rotationX: pos.first.rx,
z: pos.first.z,
duration: 1.1,
ease: "power3.inOut"
}, 0)
.to(".studio-image-wrap._2nd", {
x: pos.second.x,
y: pos.second.y,
rotation: pos.second.rot,
rotationY: pos.second.ry,
rotationX: pos.second.rx,
z: pos.second.z,
duration: 1.1,
ease: "power3.inOut"
}, 0.08)
.to(".studio-image-wrap._3rd", {
x: pos.third.x,
y: pos.third.y,
rotation: pos.third.rot,
rotationY: pos.third.ry,
rotationX: pos.third.rx,
z: pos.third.z,
duration: 1.1,
ease: "power3.inOut"
}, 0.16)
.to(".studio-image-wrap._4th", {
x: pos.fourth.x,
y: pos.fourth.y,
rotation: pos.fourth.rot,
rotationY: pos.fourth.ry,
rotationX: pos.fourth.rx,
z: pos.fourth.z,
duration: 1.1,
ease: "power3.inOut"
}, 0.24)
.to(".studio-image-wrap", {
y: "+=10",
rotation: "+=2",
duration: 2,
ease: "sine.inOut",
stagger: {
each: 0.12,
yoyo: true,
repeat: 1
}
})
.to(".studio-image-wrap", {
x: 0,
y: 0,
z: 0,
rotation: 0,
rotationX: 0,
rotationY: 0,
duration: 1.2,
ease: "power3.inOut"
});
});
</script>