Instruction

Webflow Template User Guide

GSAP Guide
Every GSAP code used on this template is here. How to edit them and find them is explain on this page. In every code block on this page, we added additional explanation to help you understand everything.

You can find the code in (Site settings) Footer Code.
Lenis Smooth Scroll
<script src="https://unpkg.com/lenis@1.3.1/dist/lenis.min.js"></script> 

 

<script>

// lenis smooth scroll
  
{
  let lenis;

  const initScroll = () => {
    lenis = new Lenis({});
    lenis.on("scroll", ScrollTrigger.update);
    gsap.ticker.add((time) => lenis.raf(time * 1000));
    gsap.ticker.lagSmoothing(0);
  };

  function initGsapGlobal() {
    
    // Do everything that needs to happen
    //  before triggering all
    // the gsap animations 

    initScroll();

    // match reduced motion media
    // const media = gsap.matchMedia();

    // Send a custom
    //  event to all your
    // gsap animations
    // to start them 

    const sendGsapEvent = () => {
      window.dispatchEvent(
        new CustomEvent("GSAPReady", {
          detail: {
            lenis,
          },
        })
      );
    };

    // Check if fonts are already loaded
    
    if (document.fonts.status === "loaded") {
      sendGsapEvent();
    } else {
      document.fonts.ready.then(() => {
        sendGsapEvent();
      });
    }

    // We need specific handling because the
    // grid/list changes the scroll height of the whole container
    //

    let resizeTimeout;
    const onResize = () => {
      clearTimeout(resizeTimeout);
      resizeTimeout = setTimeout(() => {
        ScrollTrigger.refresh();
      }, 50);
    };

    window.addEventListener("resize", () => onResize());
    const resizeObserver = new ResizeObserver((entries) => onResize());
    resizeObserver.observe(document.body);

    queueMicrotask(() => {
      gsap.to("[data-start='hidden']", {
        autoAlpha: 1,
        duration: 0.1,
        delay: 0.2,
      });
    });
  }

  // this only for dev
  
  const documentReady =
    document.readyState === "complete" || document.readyState === "interactive";

  if (documentReady) {
    initGsapGlobal();
  } else {
    addEventListener("DOMContentLoaded", (event) => initGsapGlobal());
  }
}

</script>
Lenis Smooth Scroll is a lightweight JavaScript library that enables buttery-smooth, hardware-accelerated scrolling for websites. It works by intercepting the browser’s native scroll behavior and applying eased, customizable motion, creating a more fluid and refined browsing experience. Lenis supports vertical and horizontal scroll, inertia effects, and syncs seamlessly with animations from libraries like GSAP or ScrollTrigger.
Cursor Lens Distortion Effect
GSAP + SVG Effect for cursor distortion effect.
<!--LENS DISTORTION EFFECT-->
<svg xmlns="http://www.w3.org/2000/svg" style="position: absolute; width: 0; height: 0; overflow: hidden;">
  <defs>
    <filter id="lensDistort">
      <!-- Subtle fractal noise field -->
      <feTurbulence
        id="turbulence"
        type="fractalNoise"
        baseFrequency="0.015"
        numOctaves="2"
        result="turb"
        seed="2"
      />

      <!-- Radial gradient mask defined properly (no layout impact) -->
      <radialGradient id="fade" cx="50%" cy="50%" r="50%">
        <stop offset="0%" stop-color="white"/>
        <stop offset="85%" stop-color="rgba(255,255,255,0)"/>
        <stop offset="100%" stop-color="rgba(255,255,255,0)"/>
      </radialGradient>

      <!-- Soft mask for distortion falloff -->
      <rect id="maskShape" width="200" height="200" fill="url(#fade)" result="mask" />

      <!-- Blend turbulence with soft falloff -->
      <feBlend in="turb" in2="mask" mode="multiply" result="softNoise" />

      <!-- Subtle displacement for natural refraction -->
      <feDisplacementMap
        id="displacement"
        in="SourceGraphic"
        in2="softNoise"
        scale="25"
        xChannelSelector="R"
        yChannelSelector="G"
      />
    </filter>
  </defs>
</svg>
<!--END LENS DISTORTION EFFECT-->


<!--LENS DISTORTION EFFECT GSAP ANIMATION-->
<script>
  const turb = document.querySelector("#turbulence");
  const disp = document.querySelector("#displacement");

  let mouse = { x: 0, y: 0 };
  let last = { x: 0, y: 0 };
  let speed = 0;

  // Animate turbulence over time for subtle “breathing” motion
  gsap.to(turb, {
    attr: { baseFrequency: 0.02 },
    duration: 3,
    repeat: -1,
    yoyo: true,
    ease: "sine.inOut"
  });

  // Track mouse movement
  document.addEventListener("mousemove", (e) => {
    mouse.x = e.clientX;
    mouse.y = e.clientY;
  });

  // Calculate motion speed and animate distortion intensity
  gsap.ticker.add(() => {
    const dx = mouse.x - last.x;
    const dy = mouse.y - last.y;
    const distance = Math.sqrt(dx * dx + dy * dy);

    // Smooth damping for realistic effect
    speed += (distance - speed) * 0.15;
    const intensity = gsap.utils.clamp(0, 40, speed * 1.5);

    // Animate scale of distortion
    gsap.to(disp, {
      attr: { scale: intensity },
      duration: 0.3,
      ease: "power2.out"
    });

    last.x = mouse.x;
    last.y = mouse.y;
  });
</script>
<!--END LENS DISTORTION EFFECT GSAP ANIMATION-->
Hero GSAP Clock
Featuring digital clock using gsap.
<!--HERO DIGITAL CLOCK GSAP-->
<script>
const hourEl = document.querySelector('.hour');
const minuteEl = document.querySelector('.minute');
const colonEl = document.querySelector('.colon');

// 🕐 Update the time
function updateTime() {
  const now = new Date();
  let hours = now.getHours();
  let minutes = now.getMinutes();

  // Convert to 12-hour format (optional)
  // hours = hours % 12 || 12;

  hourEl.textContent = String(hours).padStart(2, "0");
  minuteEl.textContent = String(minutes).padStart(2, "0");
}

updateTime();
setInterval(updateTime, 1000 * 10); // refresh every 10s

// 💡 GSAP blinking colon
gsap.to(colonEl, {
  opacity: 0,
  duration: 0.6,
  ease: "power1.inOut",
  repeat: -1,
  yoyo: true
});
</script>
<!--END HERO DIGITAL CLOCK GSAP-->
Hero Typing Effect
Featuring typing effect using GSAP for hero heading.
<!--HERO TITLE GSAP EFFECT-->
<script>
document.addEventListener("DOMContentLoaded", () => {
  const h1_1 = document.querySelector(".hero-h1-1");
  const h1_2 = document.querySelector(".hero-h1-2");

  // Get text directly from elements
  const texts = [h1_1.textContent.trim(), h1_2.textContent.trim()];
  const letters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";

  // Save original texts and clear them initially
  h1_1.textContent = "";
  h1_2.textContent = "";
  h1_2.style.display = "none"; // completely hidden

  // Typewriter effect with random characters
  function typewriterEffect(element, text, delay = 0) {
    return new Promise(resolve => {
      let displayText = "";
      let i = 0;
      const total = text.length;

      gsap.delayedCall(delay, () => {
        const interval = setInterval(() => {
          if (i < total) {
            displayText = text.substring(0, i) + letters[Math.floor(Math.random() * letters.length)];
            element.textContent = displayText;
            i++;
          } else {
            element.textContent = text;
            clearInterval(interval);
            setTimeout(() => resolve(), 2000); // wait 2s before delete
          }
        }, 100);
      });
    });
  }

  // Delete text gradually
  function deleteEffect(element) {
    return new Promise(resolve => {
      const text = element.textContent;
      let i = text.length;
      const interval = setInterval(() => {
        i--;
        element.textContent = text.substring(0, i);
        if (i <= 0) {
          clearInterval(interval);
          resolve();
        }
      }, 80);
    });
  }

  async function loop() {
    while (true) {
      // --- HERO H1-1 ---
      gsap.set(h1_1, { opacity: 1, display: "flex" });
      await typewriterEffect(h1_1, texts[0]);
      await deleteEffect(h1_1);
      gsap.set(h1_1, { opacity: 0, display: "none" });

      // --- HERO H1-2 ---
      gsap.set(h1_2, { opacity: 1, display: "flex" });
      await typewriterEffect(h1_2, texts[1]);
      await deleteEffect(h1_2);
      gsap.set(h1_2, { opacity: 0, display: "none" });
    }
  }

  loop();
});
</script>
<!--END HERO TITLE GSAP EFFECT-->
Hero Distrotion Transition
Transitioning effect using SVG + GSAP.
<!-- SVG FILTER -->
<svg xmlns="http://www.w3.org/2000/svg" style="display:none">
  <filter id="lensDistortScroll" x="-50%" y="-50%" width="200%" height="200%">
    <!-- Lower numOctaves for speed -->
    <feTurbulence
      id="turbulenceScroll"
      type="fractalNoise"
      baseFrequency="0.008"
      numOctaves="1"
      seed="2"
      result="turb"
    />
    <!-- Slight blur smooths out harsh noise -->
    <feGaussianBlur in="turb" stdDeviation="0.6" result="softNoise" />
    <feDisplacementMap
      id="displaceScroll"
      in="SourceGraphic"
      in2="softNoise"
      scale="0"
      xChannelSelector="R"
      yChannelSelector="G"
    />
  </filter>
</svg>



<!-- GSAP + SCROLLTRIGGER ANIMATION -->
<script>
document.addEventListener("DOMContentLoaded", () => {
  // ✅ Run only on desktop (width > 1024px)
  if (window.innerWidth <= 1024) return;

  gsap.registerPlugin(ScrollTrigger);

  const turb = document.querySelector("#turbulenceScroll");
  const disp = document.querySelector("#displaceScroll");

  if (!turb || !disp) return; // safety check

  // ensure clean start (no distortion)
  gsap.set(turb, { attr: { baseFrequency: 0.008 } });
  gsap.set(disp, { attr: { scale: 0 } });

  // animate distortion smoothly with scroll
  gsap.fromTo(
    turb,
    { attr: { baseFrequency: 0.008 } }, // start clean
    {
      attr: { baseFrequency: 0.02 },    // end distorted
      scrollTrigger: {
        trigger: ".hero-sticky",
        start: "top 80%",
        end: "bottom top",
        scrub: 0.6,
      },
      ease: "power1.out"
    }
  );

  gsap.fromTo(
    disp,
    { attr: { scale: 0 } },             // start clean
    {
      attr: { scale: 30 },              // end distorted
      scrollTrigger: {
        trigger: ".hero-sticky",
        start: "30% 80%",
        end: "bottom 80%",
        scrub: 0.6,
      },
      ease: "power1.out"
    }
  );
});
</script>
3D Box Draggable GSAP
3D Box Draggable GSAP in studio section.
<!--STUDIO BOX DRAGGABLE -->
<script>
  // BOX DRAGGABLE
  gsap.registerPlugin(Draggable, InertiaPlugin);

  const cubeWrapper = document.querySelector(".cube-work-in-wrap-wrapper");

  if (cubeWrapper) {
    const original = { x: 0, y: 0, rotation: 0 };
    let returnTween = null; // store reference for the return animation

    Draggable.create(cubeWrapper, {
      type: "x,y",
      inertia: true,
      edgeResistance: 0.85,
      bounds: ".cube-work-wrap",

      onPress() {
        // 💡 Kill any ongoing return animation before starting new drag
        if (returnTween) {
          returnTween.kill();
          returnTween = null;
        }

        gsap.to(this.target.querySelector(".cube-work-in-wrap"), {
          scale: 0.27, // slight grow while dragging
          duration: 0.2,
        });
      },

      onRelease() {
        gsap.to(this.target.querySelector(".cube-work-in-wrap"), {
          scale: 0.25,
          duration: 0.6,
          ease: "elastic.out(1, 0.5)",
        });
      },

      onThrowComplete() {
        // ✅ Create a single tween reference for return animation
        returnTween = gsap.to(this.target, {
          x: original.x,
          y: original.y,
          rotation: original.rotation,
          duration: 2,
          ease: "power2.out",
          delay: 0.5,
        });
      },
    });
  }
</script>
CEO Line Animation
Line animation when cursor moving using GSAP.
<!-- TEAM-LINE GSAP ANIMATION -->
<script>
  document.addEventListener("DOMContentLoaded", () => {
    const teamLine = document.querySelector(".team-line");
    if (!teamLine) return;

    let isAnimating = false;

    window.addEventListener("mousemove", () => {
      if (isAnimating) return;
      isAnimating = true;

      // move left by its own width
      gsap.to(teamLine, {
        xPercent: -100,
        duration: 0.3,
        ease: "power2.in",
        onComplete: () => {
          // jump to right instantly
          gsap.set(teamLine, { xPercent: 100 });
          // move back to center
          gsap.to(teamLine, {
            xPercent: 0,
            duration: 0.4,
            ease: "power2.out",
            onComplete: () => {
              isAnimating = false;
            }
          });
        }
      });
    });
  });
</script>
3D Box Blending Effect
Box draggable blending effect when CEO is clicked.
<!--BOX DRAGGABLE BLENDING HOVER EFFECT -->
<script>
  // BLENDING HOVER EFFECT
  document.addEventListener("DOMContentLoaded", () => {
    const ceoWrap = document.querySelector(".ceo-wrap");
    const cubeWorkWrap = document.querySelector(".cube-work-wrap");

    if (!ceoWrap || !cubeWorkWrap) return;

    // Initial state
    cubeWorkWrap.style.mixBlendMode = "darken";
    gsap.set(cubeWorkWrap, { opacity: 1 });

    // On hover: fade in + change blend mode
    ceoWrap.addEventListener("mouseenter", () => {
      gsap.to(cubeWorkWrap, {
        
        opacity: 1,
        duration: 0.4,
        ease: "power2.out",
        onStart: () => {
          cubeWorkWrap.style.mixBlendMode = "normal";
        }
      });
    });

    // On hover out: fade out + revert blend mode
    ceoWrap.addEventListener("mouseleave", () => {
      gsap.to(cubeWorkWrap, {
        
        opacity: 1,
        duration: 0.4,
        ease: "power2.in",
        onComplete: () => {
          cubeWorkWrap.style.mixBlendMode = "darken";
        }
      });
    });
  });
</script>
Counter Animation
Project counter animation.
<script>
document.addEventListener("DOMContentLoaded", () => {
  gsap.registerPlugin(ScrollTrigger);

  const counters = document.querySelectorAll(".counter-number");

  counters.forEach(counter => {
    const target = parseInt(counter.textContent, 10);
    counter.textContent = "0";

    gsap.to(counter, {
      innerText: target,
      duration: 2.8,
      ease: "expo.out",
      scrollTrigger: {
        trigger: counter,
        start: "top 85%",
        once: true,
      },
      onUpdate() {
        counter.textContent = Math.floor(counter.innerText);
      }
    });
  });
});
</script>