Instruction
Webflow Template User Guide
Webflow Template User Guide
<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>
<!--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 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 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--><!-- 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>
<!--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><!-- 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><!--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>
<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>