Instructions

Find easy to follow instructions

GSAP Guide
Animation Code
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>
Draggable Card Testimonials
Interactive horizontal marquee with drag and inertia support using GSAP Draggable for smooth, user-controlled scrolling animations.
<script>
  gsap.registerPlugin(Draggable, InertiaPlugin);

  const init = () => {
    const marquee = document.querySelector('[wb-data="marquee"]');
    if (!marquee) return;

    const duration = parseInt(marquee.getAttribute('duration'), 20) || 5;
    const marqueeContent = marquee.firstChild;
    if (!marqueeContent) return;

    // Clone for seamless loop
    const marqueeContentClone = marqueeContent.cloneNode(true);
    marquee.append(marqueeContentClone);

    // Make sure marquee can be grabbed
    marquee.style.cursor = 'grab';

    let tween;
    let distanceToTranslate;

    const playMarquee = () => {
      let progress = tween ? tween.progress() : 0;
      tween && tween.progress(0).kill();

      const width = parseInt(getComputedStyle(marqueeContent).getPropertyValue('width'), 10);
      const gap = parseInt(getComputedStyle(marqueeContent).getPropertyValue('column-gap'), 10);
      distanceToTranslate = -1 * (gap + width);

      tween = gsap.fromTo(marquee.children, { x: 0 }, { x: distanceToTranslate, duration, ease: 'none', repeat: -1 });
      tween.progress(progress);
    };

    playMarquee();

    // ---- PAUSE ON HOVER ----
    marquee.addEventListener('mouseenter', () => {
      if (tween) gsap.to(tween, { timeScale: 0, duration: 0.3 });
    });
    marquee.addEventListener('mouseleave', () => {
      if (tween) gsap.to(tween, { timeScale: 1, duration: 0.3 });
    });

    // ---- DRAGGABLE ----
    const proxy = document.createElement('div');
    let startProgress = 0;

    Draggable.create(proxy, {
      type: 'x',
      trigger: marquee,
      inertia: true,
      onPressInit() {
        gsap.killTweensOf(tween);
        tween.timeScale(0);
        marquee.style.cursor = 'grabbing';
        startProgress = tween.progress();
        gsap.set(proxy, { x: 0 });

        gsap.to('.card-testimonials', { scale: 0.95, duration: 0.2 });
      },
      onDrag() {
        const progressDelta = this.x / distanceToTranslate;
        let newProgress = startProgress + progressDelta;
        newProgress = ((newProgress % 1) + 1) % 1;
        tween.progress(newProgress);
      },
      onThrowUpdate() {
        const progressDelta = this.x / distanceToTranslate;
        let newProgress = startProgress + progressDelta;
        newProgress = ((newProgress % 1) + 1) % 1;
        tween.progress(newProgress);
      },
      onRelease() {
        marquee.style.cursor = 'grab';

        gsap.to('.card-testimonials', { scale: 1, duration: 0.3 });
      },
      onThrowComplete() {
        const isHovering = marquee.matches(':hover');
        gsap.to(tween, { timeScale: isHovering ? 0 : 1, duration: 0.3 });
      },
    });

    // ---- DEBOUNCED RESIZE ----
    function debounce(func) {
      var timer;
      return function (event) {
        if (timer) clearTimeout(timer);
        timer = setTimeout(() => func(), 500, event);
      };
    }
    window.addEventListener('resize', debounce(playMarquee));
  };

  document.addEventListener('DOMContentLoaded', init);
</script>
Digital Clock GSAP
Real-time digital clock with animated blinking colon, updating every 10 seconds using GSAP for smooth visual effects.
<script>
  const hourEl = document.querySelector('.hour');
  const minuteEl = document.querySelector('.minute');
  const colonEl = document.querySelector('.colon');

  if (hourEl && minuteEl && colonEl) {
    function updateTime() {
      const now = new Date();
      const hours = now.getHours();
      const minutes = now.getMinutes();
      hourEl.textContent = String(hours).padStart(2, '0');
      minuteEl.textContent = String(minutes).padStart(2, '0');
    }
    updateTime();
    setInterval(updateTime, 1000 * 10);

    if (typeof gsap !== 'undefined') {
      gsap.to(colonEl, {
        opacity: 0,
        duration: 0.6,
        ease: 'power1.inOut',
        repeat: -1,
        yoyo: true,
      });
    }
  }
</script>