


.webp)


.webp)





.webp)

.webp)

Come take the floor, everyone!
Book a free trial class, or brief us on your event. Either way, the first conversation costs nothing.
.webp)


.webp)
.webp)


.webp)



.webp)



.webp)
Only the gallery slider, smooth scrolling and stats counter use custom GSAP code. Find their code locations, editing instructions and scripts below. All other animations use Webflow’s GSAP Interactions and can be edited in the Interactions panel.
This slider uses custom GSAP code for automatic slide transitions and the enlarged center card. Location: Home Page Settings → Custom Code → Inside <head> tag. Edit the existing script there, or copy the complete code below when reusing the slider. Keep GSAP loaded and retain the gallery-slide-main, gallery-mask, gallery-track and gallery-card classes. Replace images in the Designer. In getConfig(), change delay for autoplay timing, duration for transition speed and gap for spacing. Save and publish to test.
<script>
window.addEventListener("load", function () {
if (typeof gsap === "undefined") {
console.warn("GSAP is not loaded.");
return;
}
const sliders = gsap.utils.toArray(".gallery-slide-main");
sliders.forEach(function (slider) {
if (slider.dataset.gsapGalleryReady === "true") return;
slider.dataset.gsapGalleryReady = "true";
const mask = slider.querySelector(".gallery-mask");
const track = slider.querySelector(".gallery-track");
const cards = track
? gsap.utils.toArray(track.querySelectorAll(".gallery-card"))
: [];
/* Dot wrapper যদি slider-এর বাইরে থাকে,
parent element থেকেও খুঁজবে */
let dots = gsap.utils.toArray(
slider.querySelectorAll(".active-dot-wrap .dot")
);
if (!dots.length && slider.parentElement) {
dots = gsap.utils.toArray(
slider.parentElement.querySelectorAll(".active-dot-wrap .dot")
);
}
if (!mask || !track || !cards.length) return;
let activeIndex = Math.floor(cards.length / 2);
let autoplay = null;
let resizeCall = null;
let cardWidth = 0;
let cardHeight = 0;
function getConfig() {
const mobile = window.innerWidth <= 767;
const tablet = window.innerWidth <= 991;
return {
gap: mobile ? 28 : tablet ? 30 : 32,
delay: 2.8,
duration: 0.7,
ease: "power3.inOut",
activeScale: mobile ? 1.08 : tablet ? 1.12 : 1.18,
sideScale: mobile ? 0.92 : tablet ? 0.95 : 0.98,
farScale: mobile ? 0.82 : 0.86,
activeY: mobile ? -8 : -16,
sideY: mobile ? -4 : -10,
farY: 0,
badgeY: 40
};
}
let config = getConfig();
function getLoopDistance(index) {
let distance = index - activeIndex;
if (distance > cards.length / 2) {
distance -= cards.length;
}
if (distance < -cards.length / 2) {
distance += cards.length;
}
return distance;
}
function getScale(distance) {
const abs = Math.abs(distance);
if (abs === 0) return config.activeScale;
if (abs === 1) return config.sideScale;
return config.farScale;
}
function getY(distance) {
const abs = Math.abs(distance);
if (abs === 0) return config.activeY;
if (abs === 1) return config.sideY;
return config.farY;
}
function getX(distance) {
if (distance === 0) return 0;
const direction = distance < 0 ? -1 : 1;
const abs = Math.abs(distance);
let x = 0;
for (let i = 1; i <= abs; i++) {
const previousScale = getScale(i - 1);
const currentScale = getScale(i);
x +=
(cardWidth * previousScale) / 2 +
(cardWidth * currentScale) / 2 +
config.gap;
}
return x * direction;
}
function measure() {
cardWidth = 0;
cardHeight = 0;
cards.forEach(function (card) {
cardWidth = Math.max(cardWidth, card.offsetWidth);
cardHeight = Math.max(cardHeight, card.offsetHeight);
});
const height = cardHeight * config.activeScale + 80;
gsap.set(mask, {
position: "relative",
overflow: "hidden",
height: height
});
gsap.set(track, {
position: "relative",
height: height
});
}
function setup() {
gsap.set(cards, {
position: "absolute",
left: "50%",
top: "50%",
xPercent: -50,
yPercent: -50,
transformOrigin: "center center"
});
cards.forEach(function (card) {
const badge = card.querySelector(".gallery-badge");
if (badge) {
gsap.set(badge, {
y: config.badgeY,
autoAlpha: 0
});
}
});
}
function render(duration) {
cards.forEach(function (card, index) {
const distance = getLoopDistance(index);
const abs = Math.abs(distance);
const active = index === activeIndex;
gsap.to(card, {
x: getX(distance),
y: getY(distance),
scale: getScale(distance),
autoAlpha: abs > 3 ? 0 : abs === 3 ? 0.3 : 1,
zIndex: active ? 20 : 10 - abs,
duration: duration,
ease: config.ease,
overwrite: "auto"
});
const badge = card.querySelector(".gallery-badge");
if (badge) {
gsap.to(badge, {
y: active ? 0 : config.badgeY,
autoAlpha: active ? 1 : 0,
duration: duration,
ease: "power2.out",
overwrite: "auto"
});
}
});
dots.forEach(function (dot, index) {
const active = index === activeIndex;
gsap.to(dot, {
scale: active ? 1.7 : 1,
opacity: active ? 1 : 0.35,
duration: duration * 0.6,
ease: "power2.out",
overwrite: "auto"
});
});
}
function nextSlide() {
activeIndex = (activeIndex + 1) % cards.length;
render(config.duration);
}
function stopAutoplay() {
if (autoplay) {
autoplay.kill();
autoplay = null;
}
}
function startAutoplay() {
stopAutoplay();
autoplay = gsap.delayedCall(
config.delay,
function loop() {
nextSlide();
autoplay = gsap.delayedCall(
config.delay,
loop
);
}
);
}
function refresh() {
config = getConfig();
gsap.killTweensOf(cards);
gsap.killTweensOf(dots);
measure();
setup();
render(0);
}
refresh();
startAutoplay();
window.addEventListener("resize", function () {
if (resizeCall) resizeCall.kill();
resizeCall = gsap.delayedCall(
0.2,
refresh
);
});
document.addEventListener(
"visibilitychange",
function () {
if (document.hidden) {
stopAutoplay();
} else {
startAutoplay();
}
}
);
});
});
</script>This GSAP custom code adds smooth mouse-wheel and trackpad scrolling across the website. Code location: Site Settings → Custom Code → Footer Code. Edit the existing smooth scrolling script there, or paste the complete code below if it is missing. Add it only once and keep GSAP loaded before the script runs. Change duration: 0.8 to adjust the smoothing time and ease: "expo.out" to adjust the easing. Touch scrolling stays native. Save your changes and publish to test.
<!-- GSAP Smooth Scrolling -->
<script>
window.addEventListener("load", function () {
if (typeof gsap === "undefined") {
console.warn("GSAP library is not loaded.");
return;
}
const reducedMotion = window.matchMedia(
"(prefers-reduced-motion: reduce)"
);
if (reducedMotion.matches) return;
let targetScroll = window.scrollY;
let currentScroll = window.scrollY;
let scrollTween = null;
const clamp = gsap.utils.clamp;
function getMaxScroll() {
return Math.max(
0,
document.documentElement.scrollHeight - window.innerHeight
);
}
function normalizeDelta(event) {
let delta = event.deltaY;
if (event.deltaMode === 1) {
delta *= 16;
}
if (event.deltaMode === 2) {
delta *= window.innerHeight;
}
return delta;
}
window.addEventListener(
"wheel",
function (event) {
if (event.ctrlKey || event.metaKey) return;
if (
Math.abs(event.deltaX) >
Math.abs(event.deltaY)
) {
return;
}
event.preventDefault();
const delta = normalizeDelta(event);
targetScroll = clamp(
0,
getMaxScroll(),
targetScroll + delta
);
if (scrollTween) {
scrollTween.kill();
}
scrollTween = gsap.to(
{ value: currentScroll },
{
value: targetScroll,
duration: 0.8,
ease: "expo.out",
overwrite: true,
onUpdate: function () {
currentScroll = this.targets()[0].value;
window.scrollTo(
0,
currentScroll
);
},
onComplete: function () {
currentScroll = window.scrollY;
targetScroll = window.scrollY;
scrollTween = null;
}
}
);
},
{
passive: false
}
);
window.addEventListener(
"scroll",
function () {
if (!scrollTween) {
currentScroll = window.scrollY;
targetScroll = window.scrollY;
}
},
{
passive: true
}
);
window.addEventListener("resize", function () {
targetScroll = clamp(
0,
getMaxScroll(),
targetScroll
);
});
});
</script>This GSAP counter animates stats when they scroll into view. Code location: Site Settings → Custom Code → Footer Code, after the smooth scrolling script’s closing </script> tag. Keep GSAP and ScrollTrigger loaded before this code. Replace the existing counter script instead of adding a second copy. Select each number text element and add a custom attribute: Name: data-counter, Value: true. Enter the final number as its text, such as 250+, 98% or 1,500. Change duration: 1.5 to adjust the animation time. Copy the complete script below, save and publish to test.
<!-- GSAP Stats Counter -->
<script>
window.addEventListener("load", function () {
if (!window.gsap || !window.ScrollTrigger) return;
gsap.registerPlugin(ScrollTrigger);
gsap.matchMedia().add(
"(prefers-reduced-motion: no-preference)",
function () {
gsap.utils.toArray('[data-counter="true"]').forEach(function (counter) {
const originalText = counter.textContent.trim();
const match = originalText.match(/^([^0-9-]*)(-?[\d,.]+)(.*)$/);
if (!match) return;
const prefix = match[1];
const numberString = match[2];
const suffix = match[3];
const target = parseFloat(numberString.replace(/,/g, ""));
if (!Number.isFinite(target)) return;
const decimals = numberString.includes(".")
? numberString.split(".")[1].length
: 0;
const count = { value: 0 };
gsap.to(count, {
value: target,
duration: 1.5,
ease: "power2.out",
scrollTrigger: {
trigger: counter,
start: "top 85%",
once: true
},
onUpdate: function () {
const value = count.value.toLocaleString("en-US", {
minimumFractionDigits: decimals,
maximumFractionDigits: decimals,
useGrouping: numberString.includes(",")
});
counter.textContent = prefix + value + suffix;
},
onComplete: function () {
counter.textContent = originalText;
}
});
});
}
);
});
</script>


.webp)


.webp)





.webp)

.webp)

Book a free trial class, or brief us on your event. Either way, the first conversation costs nothing.
.webp)


.webp)
.webp)


.webp)



.webp)



.webp)