Instruction 1
Lenis Smooth Scrolling
This provides smooth scrolling across the entire template. To turn off just comment out this code or delete the code from site settings.
// Initialize Lenis smooth scroll
const lenis = new Lenis({
duration: 1.2,
easing: (t) => Math.min(1, 1.001 - Math.pow(2, -10 * t)),
smooth: true
});
// Connect Lenis to GSAP ScrollTrigger
lenis.on('scroll', ScrollTrigger.update);
gsap.ticker.add((time) => {
lenis.raf(time * 1000);
});
gsap.ticker.lagSmoothing(0);Instruction 2
Counter Animation with GSAP
What It does
This animates counters to count up from 0 on scroll.
HTML Requirements
Elements like <div class="counter">1234</div> with target number as text. Note: Don't includ the comma in original content in Webflow.
// ============================================
// COUNTER ANIMATION WITH GSAP
// ============================================
function initCounterAnimations() {
const counterElements = document.querySelectorAll(".counter");
counterElements?.forEach(counterItem => {
gsap.from(counterItem, {
textContent: 0,
duration: 3,
ease: "power1.in",
snap: { textContent: 1 },
scrollTrigger: {
trigger: counterItem,
start: "top 95%",
once: true
},
onUpdate: function () {
const counterValue = Math.ceil(parseInt(counterItem.textContent) || 0);
counterItem.textContent = counterValue.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");
}
});
});
}
// ============================================
// INITIALIZE ANIMATIONS ON PAGE LOAD
// ============================================
document.addEventListener('DOMContentLoaded', function () {
initCounterAnimations();
});Instruction 3
Dropdown Open/Close Animation With GSAP
<script>
document.addEventListener("DOMContentLoaded", () => {
const dropdowns = document.querySelectorAll(".nav-dropdown");
if (!dropdowns.length) return;
const mq = window.matchMedia("(min-width: 1024px)");
let activeDropdown = null;
let enabled = false;
const dropdownData = [];
dropdowns.forEach((dropdown) => {
const toggle = dropdown.querySelector(".nav-dropdown-toggle");
const menu = dropdown.querySelector(".nav-dropdown-list");
const arrow = dropdown.querySelector(".nav-dropdown-icon");
if (!toggle || !menu || !arrow) return;
function open() {
if (!enabled) return;
if (activeDropdown && activeDropdown !== dropdown) {
activeDropdown.close();
}
gsap.killTweensOf(menu);
gsap.killTweensOf(arrow);
gsap.set(menu, {
visibility: "visible",
pointerEvents: "auto"
});
gsap.to(menu, {
opacity: 1,
y: 0,
duration: 0.5,
ease: "power3.out"
});
gsap.to(arrow, {
rotate: -180,
duration: 0.45,
ease: "power3.out"
});
activeDropdown = dropdown;
}
function close() {
if (!enabled) return;
gsap.killTweensOf(menu);
gsap.killTweensOf(arrow);
gsap.to(menu, {
opacity: 0,
y: 20,
duration: 0.45,
ease: "power3.out",
onComplete() {
gsap.set(menu, {
visibility: "hidden",
pointerEvents: "none"
});
}
});
gsap.to(arrow, {
rotate: 0,
duration: 0.45,
ease: "power3.out"
});
if (activeDropdown === dropdown) {
activeDropdown = null;
}
}
// Desktop: put GSAP's hidden inline styles in place
function applyDesktopState() {
gsap.killTweensOf(menu);
gsap.killTweensOf(arrow);
gsap.set(menu, {
opacity: 0,
y: 20,
visibility: "hidden",
pointerEvents: "none"
});
gsap.set(arrow, { rotate: 0 });
}
// Tab/Mobile: strip ALL inline styles GSAP added, hand control back to CSS
function clearInlineState() {
gsap.killTweensOf(menu);
gsap.killTweensOf(arrow);
gsap.set(menu, { clearProps: "opacity,visibility,pointerEvents,transform" });
gsap.set(arrow, { clearProps: "rotate,transform" });
}
dropdown.open = open;
dropdown.close = close;
dropdown.applyDesktopState = applyDesktopState;
dropdown.clearInlineState = clearInlineState;
toggle.addEventListener("click", (e) => {
if (!enabled) return; // Tab/Mobile: default Webflow .w--open toggle + your CSS handle it
e.preventDefault();
e.stopPropagation();
if (activeDropdown === dropdown) {
close();
} else {
open();
}
});
dropdownData.push(dropdown);
});
document.addEventListener("click", (e) => {
if (!enabled) return;
if (activeDropdown && !activeDropdown.contains(e.target)) {
activeDropdown.close();
}
});
function enableDesktop() {
enabled = true;
activeDropdown = null;
dropdownData.forEach((d) => d.applyDesktopState());
}
function disableDesktop() {
enabled = false;
activeDropdown = null;
dropdownData.forEach((d) => d.clearInlineState());
}
function handleChange(e) {
if (e.matches) {
enableDesktop();
} else {
disableDesktop();
}
}
handleChange(mq);
mq.addEventListener("change", handleChange);
});
</script>Instruction 4
Looping Cards with GSAP Animation
<script>
function initFeatureLoop() {
const cards = gsap.utils.toArray(".about-loop-item");
if (cards.length < 2) return;
const spacing = 32;
const scaleStep = 0.08;
const opacityStep = 0.18;
const duration = 1.15;
function setInitialState() {
cards.forEach((card, i) => {
gsap.set(card, {
y: i * spacing,
scale: 1 - i * scaleStep,
opacity: i === 0 ? 1 : 1 - i * opacityStep,
zIndex: cards.length - i,
force3D: true
});
});
}
function loopCards() {
const first = cards.shift();
cards.push(first);
cards.forEach((card, i) => gsap.set(card, { zIndex: cards.length - i }));
cards.forEach((card, i) => {
gsap.to(card, {
y: i * spacing,
scale: 1 - i * scaleStep,
opacity: i === 0 ? 1 : 1 - i * opacityStep,
duration: duration,
ease: "power3.out",
overwrite: true,
force3D: true
});
});
}
setInitialState();
gsap.timeline({ repeat: -1, repeatDelay: 1.2 })
.call(loopCards)
.to({}, { duration: duration + 0.4 });
}
if (window.Webflow) {
window.Webflow.push(initFeatureLoop);
} else {
window.addEventListener('load', initFeatureLoop);
}
</script>