Instructions

Learn how to customize Flowtica's Webflow Variables, GSAP animations, and interactive components.

Close-up of the tip of a red pencil on a black background.

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>