instructions

Instructions

Webflow  Template User  Guide

GSAP Setup & Main Functions
All scripts go in Webflow Page Settings → Before </body> tag (or in Site Settings → Custom Code → Footer Code if global).

Webflow Site Settings → GSAP must have Draggable enabled. Inertia optional (script has fallback).
1. Main Cursor
What it does: Custom cursor that smoothly follows the mouse. Exposes window._cursorPause() and window._cursorResume() so other scripts can hide it on demand.
Required markup:
.cursor-core — outer cursor element (animated container)
.cursor-pointer — inner visual (the actual circle, can be hidden via pause/resume)
<script>
window.Webflow = window.Webflow || [];
window.Webflow.push(function () {

  // ==================================================
  // MAIN CURSOR
  // ==================================================

  if (window._cursorCoreInit) return;
  window._cursorCoreInit = true;

  var cursor = document.querySelector(".cursor-core");
  var pointer = document.querySelector(".cursor-pointer");

  if (!cursor) return;

  var mouseX = -100;
  var mouseY = -100;

  var x = -100;
  var y = -100;

  var paused = false;

  // initial
  gsap.set(cursor, {
    xPercent: -50,
    yPercent: -50,
    x: x,
    y: y
  });

  // smooth follow
  gsap.ticker.add(function () {

    x += (mouseX - x) * 0.15;
    y += (mouseY - y) * 0.15;

    gsap.set(cursor, {
      x: x,
      y: y
    });

  });

  // track mouse
  window.addEventListener("mousemove", function (e) {

    mouseX = e.clientX;
    mouseY = e.clientY;

  });

  // =========================================
  // HIDE POINTER ONLY
  // =========================================

  window._cursorPause = function () {

    paused = true;

    if (!pointer) return;

    gsap.to(pointer, {
      opacity: 0,
      duration: 0.2,
      overwrite: true
    });

  };

  // =========================================
  // SHOW POINTER AGAIN
  // =========================================

  window._cursorResume = function () {

    paused = false;

    if (!pointer) return;

    gsap.to(pointer, {
      opacity: 1,
      duration: 0.2,
      overwrite: true
    });

  };

});
</script>
2. Testimonial Custom Cursor
What it does: Creates a subtle, organic floating animation for background blur elements inside the CTA section. Each blur blob moves independently with different speeds and directions, producing a smooth ambient motion effect. The animation also includes a gentle scale pulse to make the background feel more dynamic and alive.
Required markup:
.cta-content-wrapper —  the parent container that contains all blur elements
.bg-blur — the individual blur blobs that will be animated
<script>
document.addEventListener("DOMContentLoaded", () => {
  if (window.matchMedia("(prefers-reduced-motion: reduce)").matches) return;

  const blurs = document.querySelectorAll(".cta-content-wrapper .bg-blur");
  if (!blurs.length) return;

  // Konfigurasi gerak berbeda per blob (amplitudo dalam %, durasi dalam detik)
  const configs = [
    { ax: 40, ay: 30, dx: 6, dy: 4,  rot: 1 },   // blob 1
    { ax: 35, ay: 45, dx: 5,  dy: 7, rot: -1 },  // blob 2
    { ax: 50, ay: 25, dx: 7, dy: 5, rot: 1 },   // blob 3
  ];

  blurs.forEach((el, i) => {
    const c = configs[i % configs.length];

    // Gerak horizontal — melengkung karena ease sinus
    gsap.to(el, {
      xPercent: c.ax,
      duration: c.dx,
      ease: "sine.inOut",
      repeat: -1,
      yoyo: true,
    });

    // Gerak vertikal — durasi beda → lintasan jadi kurva, bukan garis lurus
    gsap.to(el, {
      yPercent: c.ay * c.rot,
      duration: c.dy,
      ease: "sine.inOut",
      repeat: -1,
      yoyo: true,
    });

    // Sedikit "napas" skala biar terasa hidup (opsional)
    gsap.to(el, {
      scale: 1.15,
      duration: c.dx + 2,
      ease: "sine.inOut",
      repeat: -1,
      yoyo: true,
    });
  });
});
</script>
3. Counter Number Animation (GSAP + ScrollTrigger)
What it does: Creates a smooth counting animation that starts when the counter enters the viewport. Each counter animates from 0 to its final value only once, providing an engaging way to highlight statistics and key metrics.
Required markup:
.counter-number-wrapper — the parent container used as the ScrollTrigger target
.counter-number-up — the container that holds the number to animate
<script>
window.Webflow = window.Webflow || [];
window.Webflow.push(function () {

  gsap.registerPlugin(ScrollTrigger);

  document.querySelectorAll(".counter-number-wrapper").forEach((counter)=>{

    const number = counter.querySelector(".counter-number-up > *");
    if (!number) return;

    const finalValue = parseInt(number.textContent);

    let obj = { value: 0 };

    gsap.to(obj,{
      value: finalValue,
      duration: 2,
      ease: "expo.out",

      scrollTrigger:{
        trigger: counter,
        start: "top 85%",
        once: true
      },

      onUpdate(){
        number.textContent = Math.round(obj.value);
      }
    });

  });

});
</script>