All GSAP animations used in this template are collected here. On this page, you’ll find guidance on how to locate and edit them. Each code block comes with extra notes to make it easier to understand.
Smooth scrolling effect for the template using Lenis with GSAP integration for precise scroll updates and animations.
<link rel="stylesheet" href="https://unpkg.com/lenis@1.3.15/dist/lenis.css" />
<script src="https://unpkg.com/lenis@1.3.15/dist/lenis.min.js"></script>
<script>
const lenis = new Lenis({ duration: 1.4 });
lenis.on('scroll', ScrollTrigger.update);
gsap.ticker.add((time) => {
lenis.raf(time * 1000);
});
gsap.ticker.lagSmoothing(0);
</script><script>
/**
* Tormuz - Global Script*/
var Webflow = Webflow || [];
Webflow.push(function() {
// --- 1. NAVIGATION LOGIC ---
const burgerBtn = document.querySelector('.burger-btn');
const line1 = document.querySelector('.line-1');
const line2 = document.querySelector('.line-2');
const navWrapper = document.querySelector('.nav_wrapper');
const menuOverlay = document.querySelector('.menu_overlay');
const bgOverlay = document.querySelector('.menu_bg_overlay');
const topBar = document.querySelector('.navbar-content-wrap');
if (burgerBtn && navWrapper && line1 && line2 && menuOverlay && bgOverlay && topBar) {
const body = document.body;
const getTargetWidth = () => {
if (window.innerWidth <= 991) {
return window.innerWidth <= 500 ? '100%' : '500px';
}
return '660px';
};
// Initial state
gsap.set(navWrapper, { width: getTargetWidth(), height: '57px', overflow: 'hidden' });
gsap.set(menuOverlay, { opacity: 0, display: 'none' });
gsap.set(bgOverlay, { display: 'none', opacity: 0 });
const menuTimeline = gsap.timeline({
paused: true,
reversed: true,
defaults: { ease: "expo.inOut", duration: 0.8 },
onStart: () => {
gsap.set([bgOverlay, menuOverlay], { display: 'block' });
body.style.overflow = 'hidden';
},
onReverseComplete: () => {
gsap.set([bgOverlay, menuOverlay], { display: 'none' });
gsap.set(navWrapper, { width: getTargetWidth(), height: '57px' });
body.style.overflow = 'auto';
gsap.to(line2, { width: "30px", duration: 0.4 });
gsap.to(burgerBtn, { backgroundColor: "transparent", duration: 0.4 });
}
});
menuTimeline
.to(bgOverlay, { opacity: 1, duration: 0.3 }, 0)
.to(navWrapper, { width: '100%' }, 0)
.to(burgerBtn, { backgroundColor: "#363636", duration: 0.4 }, 0)
.to(line1, { y: 5.25, duration: 0.7 }, 0)
.to(line2, { y: -5.25, width: "42px", duration: 0.7 }, 0)
.to(menuOverlay, { opacity: 1, duration: 0.5 }, 0.8);
function toggleMenu() {
if (!menuTimeline.isActive()) {
if (menuTimeline.reversed()) {
// Calculate dynamic height for opening
gsap.set(menuOverlay, { display: 'block', visibility: 'hidden', opacity: 1 });
const finalH = topBar.offsetHeight + menuOverlay.offsetHeight;
gsap.set(menuOverlay, { display: 'none', visibility: 'visible', opacity: 0 });
menuTimeline.to(navWrapper, { height: finalH + "px" }, 0.7);
menuTimeline.play();
} else {
menuTimeline.reverse();
}
burgerBtn.classList.toggle('is-active');
}
}
burgerBtn.addEventListener('click', toggleMenu);
bgOverlay.addEventListener('click', toggleMenu);
// Burger hover effect
burgerBtn.addEventListener('mouseenter', () => {
if (menuTimeline.reversed()) {
gsap.to(line2, { width: "42px", duration: 0.4 });
gsap.to(burgerBtn, { backgroundColor: "#363636", duration: 0.4 });
}
});
burgerBtn.addEventListener('mouseleave', () => {
if (menuTimeline.reversed()) {
gsap.to(line2, { width: "30px", duration: 0.4 });
gsap.to(burgerBtn, { backgroundColor: "transparent", duration: 0.4 });
}
});
window.addEventListener('keydown', (e) => {
if ((e.key === "Escape") && !menuTimeline.reversed()) toggleMenu();
});
}
// --- 2. BUTTONS LOGIC (Manual Webflow Structure) ---
const buttons = document.querySelectorAll('[gsap-button]');
buttons.forEach((btn) => {
const orig = btn.querySelector('.text-orig');
const clone = btn.querySelector('.text-clone');
const arrow = btn.querySelector('.arrow-btn');
const arrowWrapp = btn.querySelector('.btn-arrow-wrap');
// Handle arrow clone (if not present in DOM)
if (arrowWrapp && arrow && !arrowWrapp.querySelector('.arrow-clone')) {
const arrowClone = arrow.cloneNode(true);
arrowClone.classList.add('arrow-clone');
gsap.set(arrowClone, { position: "absolute", top: "50%", left: "50%", xPercent: -200, yPercent: -50, opacity: 0 });
arrowWrapp.appendChild(arrowClone);
}
btn.addEventListener('mouseenter', () => {
const aClone = btn.querySelector('.arrow-clone');
gsap.to([orig, clone], { yPercent: -100, duration: 0.4, ease: "power2.inOut", overwrite: true });
if(arrow && aClone) {
gsap.to(arrow, { xPercent: 200, opacity: 0, duration: 0.4, ease: "power2.inOut" });
gsap.to(aClone, { xPercent: -50, opacity: 1, duration: 0.4, ease: "power2.inOut" });
}
});
btn.addEventListener('mouseleave', () => {
const aClone = btn.querySelector('.arrow-clone');
gsap.to([orig, clone], { yPercent: 0, duration: 0.4, ease: "power2.inOut", overwrite: true });
if(arrow && aClone) {
gsap.to(arrow, { xPercent: 0, opacity: 1, duration: 0.4, ease: "power2.inOut" });
gsap.to(aClone, { xPercent: -200, opacity: 0, duration: 0.4, ease: "power2.inOut" });
}
});
});
// --- 3. NUMBER ROLL ANIMATION (Global - usable on all pages) ---
const numberElements = document.querySelectorAll('[number-roll]');
numberElements.forEach((el) => {
const rawText = el.innerText.trim();
// Extract only the number
const targetValue = parseFloat(rawText.replace(/[^0-9.]/g, '')) || 0;
// Extract the suffix (M, %, etc.)
const suffix = rawText.replace(/[0-9.]/g, '');
let countObj = { value: 0 };
// Dynamic duration between 1.5s and 3.5s depending on number magnitude
const dynamicDuration = Math.min(Math.max(targetValue / 100, 1.5), 3.5);
gsap.to(countObj, {
value: targetValue,
duration: dynamicDuration,
ease: "expo.out",
scrollTrigger: {
trigger: el,
start: "top 90%",
toggleActions: "play none none none"
},
onUpdate: function() {
// Display raw number rounded down to nearest integer
el.innerText = Math.floor(countObj.value) + suffix;
},
onComplete: function() {
// Ensure final value is exact
el.innerText = targetValue + suffix;
}
});
});
});
</script>