Instructions

Find easy to follow instructions

GSAP Guide

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.

Custom Code

Lenis Scroll

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>

Homepage Specific Animations

<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>
LET'S CONNECT

Ready to Bring Your Vision to Life?