Instructions
Webflow Template User Guide
window._cursorPause() and window._cursorResume() so other scripts can hide it on demand.<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><script>
window.Webflow = window.Webflow || [];
window.Webflow.push(function () {
// ==================================================
// TESTIMONIAL CUSTOM CURSOR + DRAG FIX
// ==================================================
var trigger = document.querySelector(".testimonials-block");
var cursor = document.querySelector(".cursor-testimonials");
if (!trigger || !cursor) return;
// prevent double init
if (cursor.dataset.init === "true") return;
cursor.dataset.init = "true";
// IMPORTANT
// cursor stay outside testimonial
// jangan appendChild lagi
cursor.style.pointerEvents = "none";
cursor.style.zIndex = "9999";
cursor.style.position = "fixed";
cursor.style.top = "0";
cursor.style.left = "0";
// disable browser drag
trigger.querySelectorAll("*").forEach(function(el) {
el.setAttribute("draggable", "false");
el.addEventListener("dragstart", function(e) {
e.preventDefault();
});
});
// state
var mouseX = window.innerWidth / 2;
var mouseY = window.innerHeight / 2;
var x = mouseX;
var y = mouseY;
var active = false;
// initial
gsap.set(cursor, {
xPercent: -50,
yPercent: -50,
opacity: 0,
scale: 0.8,
x: x,
y: y
});
// track mouse
window.addEventListener("mousemove", function(e) {
mouseX = e.clientX;
mouseY = e.clientY;
});
// smooth follow
gsap.ticker.add(function () {
x += (mouseX - x) * 0.14;
y += (mouseY - y) * 0.14;
gsap.set(cursor, {
x: x,
y: y
});
// detect inside
var rect = trigger.getBoundingClientRect();
var inside =
mouseX >= rect.left &&
mouseX <= rect.right &&
mouseY >= rect.top &&
mouseY <= rect.bottom;
// ENTER
if (inside && !active) {
active = true;
// hide default custom cursor
if (window._cursorPause) {
window._cursorPause();
}
gsap.to(cursor, {
opacity: 1,
scale: 1,
duration: 0.35,
ease: "power3.out"
});
}
// LEAVE
else if (!inside && active) {
active = false;
// show back default cursor
if (window._cursorResume) {
window._cursorResume();
}
gsap.to(cursor, {
opacity: 0,
scale: 0.8,
duration: 0.3,
ease: "power3.out"
});
}
});
});
</script><script>
window.Webflow = window.Webflow || [];
window.Webflow.push(function () {
var wrapper = document.querySelector(".testimonials-list-wrapper");
var block = document.querySelector(".testimonials-block");
if (!wrapper || !block) return;
var maxDrag = 0;
function calcBounds() {
var total = wrapper.scrollWidth;
var visible = block.offsetWidth;
maxDrag = Math.max(0, total - visible);
}
calcBounds();
if (maxDrag === 0) return;
gsap.set(wrapper, { x: 0 });
// Manual velocity tracking for momentum on release.
// Used as a fallback if InertiaPlugin is not loaded at runtime.
var lastX = 0;
var lastTime = 0;
var velocity = 0;
var instance = Draggable.create(wrapper, {
type: "x",
bounds: { minX: -maxDrag, maxX: 0 },
inertia: true,
edgeResistance: 0.75,
dragResistance: 0.05,
cursor: "grab",
activeCursor: "grabbing",
dragClickables: true,
onPress: function () {
lastX = this.x;
lastTime = Date.now();
velocity = 0;
},
onDrag: function () {
var now = Date.now();
var dt = now - lastTime;
if (dt > 0) {
velocity = (this.x - lastX) / dt;
}
lastX = this.x;
lastTime = now;
},
onDragEnd: function () {
// If InertiaPlugin already started a tween, let it run.
if (gsap.isTweening(wrapper)) return;
// Otherwise, simulate momentum manually.
var projected = this.x + velocity * 300;
var target = Math.max(-maxDrag, Math.min(0, projected));
gsap.to(wrapper, {
x: target,
duration: 1.4,
ease: "power3.out"
});
}
})[0];
var resizeTimer;
window.addEventListener("resize", function () {
clearTimeout(resizeTimer);
resizeTimer = setTimeout(function () {
calcBounds();
instance.applyBounds({ minX: -maxDrag, maxX: 0 });
}, 200);
});
});
</script><script>
document.addEventListener("DOMContentLoaded", function () {
// ===== helpers (avoid $ clash with jQuery that Webflow ships) =====
const q = (sel, root = document) => root.querySelector(sel);
const qa = (sel, root = document) => Array.from(root.querySelectorAll(sel));
// ============================================================
// 1. HERO
// ============================================================
(function initHero() {
const nav = q(".w-nav");
const counter = q(".counter-wrap");
const testimonial = q(".hero-testimonial-wrap");
const subtitle = q(".hero-subtitle-wrap");
const headline = q(".top-headline");
const subheadline = q(".subheadline");
const cta = q(".hero-cta-wrap");
// bail out entirely if hero isn't on this page
if (!headline && !nav) return;
// initial states
if (nav) gsap.set(nav, { opacity: 0, y: -24, filter: "blur(8px)" });
const floaters = [counter, testimonial].filter(Boolean);
if (floaters.length) {
gsap.set(floaters, { opacity: 0, y: 40, scale: 0.96, filter: "blur(14px)" });
}
if (subtitle) gsap.set(subtitle, { opacity: 0, y: 50, filter: "blur(12px)" });
if (headline) gsap.set(headline, { opacity: 0, y: 120, scale: 0.92, filter: "blur(24px)" });
if (subheadline) gsap.set(subheadline, { opacity: 0, y: 50, filter: "blur(14px)" });
if (cta) gsap.set(cta, { opacity: 0, y: 50, filter: "blur(14px)" });
// timeline
const tl = gsap.timeline({ defaults: { ease: "power3.out" } });
if (nav) {
tl.to(nav, {
opacity: 1, y: 0, filter: "blur(0px)", duration: 1,
onComplete: () => gsap.set(nav, { clearProps: "filter" })
});
}
if (floaters.length) {
tl.to(floaters, {
opacity: 1, y: 0, scale: 1, filter: "blur(0px)",
duration: 1.1, stagger: 0.15, ease: "power4.out",
onComplete: () => gsap.set(floaters, { clearProps: "filter" })
}, "-=0.5");
}
if (subtitle) {
tl.to(subtitle, {
opacity: 1, y: 0, filter: "blur(0px)", duration: 0.9,
onComplete: () => gsap.set(subtitle, { clearProps: "filter" })
}, "-=0.6");
}
if (headline) {
tl.to(headline, {
opacity: 1, y: 0, scale: 1, filter: "blur(0px)",
duration: 1.5, ease: "power4.out",
onComplete: () => gsap.set(headline, { clearProps: "filter" })
}, "-=0.4");
}
if (subheadline) {
tl.to(subheadline, {
opacity: 1, y: 0, filter: "blur(0px)", duration: 1,
onComplete: () => gsap.set(subheadline, { clearProps: "filter" })
}, "-=1");
}
if (cta) {
tl.to(cta, {
opacity: 1, y: 0, filter: "blur(0px)", duration: 1,
onComplete: () => gsap.set(cta, { clearProps: "filter" })
}, "-=0.8");
}
// floating testimonial loop
if (testimonial) {
gsap.to(testimonial, {
y: "-=10", duration: 2.8, repeat: -1, yoyo: true, ease: "sine.inOut"
});
}
})();
// ============================================================
// 2. CTA SECTION (scroll-triggered)
// ============================================================
(function initCta() {
const section = q(".cta-section");
if (!section) return;
const tag = q(".tag-section", section);
const title = q(".title-content-wrap", section);
const sub = q(".subheadline", section);
const buttons = qa(".button-wrap", section);
const fadeGroup = [tag, sub, ...buttons].filter(Boolean);
if (fadeGroup.length) {
gsap.set(fadeGroup, { opacity: 0, y: 40, filter: "blur(10px)" });
}
if (title) {
gsap.set(title, { opacity: 0, y: 80, scale: 0.96, filter: "blur(18px)" });
}
const tl = gsap.timeline({
scrollTrigger: { trigger: section, start: "top 80%", once: true }
});
if (tag) {
tl.to(tag, {
opacity: 1, y: 0, filter: "blur(0px)", duration: 0.8, ease: "power3.out",
onComplete: () => gsap.set(tag, { clearProps: "filter" })
});
}
if (title) {
tl.to(title, {
opacity: 1, y: 0, scale: 1, filter: "blur(0px)",
duration: 1.2, ease: "power4.out",
onComplete: () => gsap.set(title, { clearProps: "filter" })
}, "-=0.45");
}
if (sub) {
tl.to(sub, {
opacity: 1, y: 0, filter: "blur(0px)", duration: 0.8, ease: "power3.out",
onComplete: () => gsap.set(sub, { clearProps: "filter" })
}, "-=0.8");
}
if (buttons.length) {
tl.to(buttons, {
opacity: 1, y: 0, filter: "blur(0px)",
duration: 0.7, stagger: 0.14, ease: "power3.out",
onComplete: () => gsap.set(buttons, { clearProps: "filter" })
}, "-=0.55");
}
})();
// ============================================================
// 3. SECTION REVEALS (scroll-triggered, per-element)
// ============================================================
(function initSectionReveals() {
const headings = qa(".section-heading");
const subtitles = qa(".section-subtitle");
const descs = qa(".section-description");
const allEls = [...headings, ...subtitles, ...descs];
if (!allEls.length) return;
allEls.forEach(el => {
const isHeading = el.classList.contains("section-heading");
gsap.set(el, {
opacity: 0,
y: isHeading ? 50 : 24,
scale: isHeading ? 0.98 : 1,
filter: isHeading ? "blur(16px)" : "blur(10px)",
willChange: "transform, opacity, filter"
});
gsap.to(el, {
opacity: 1, y: 0, scale: 1, filter: "blur(0px)",
duration: isHeading ? 0.9 : 0.6,
ease: isHeading ? "power4.out" : "power3.out",
scrollTrigger: { trigger: el, start: "top 88%", once: true },
onComplete: () => gsap.set(el, { clearProps: "willChange,filter" })
});
});
})();
});
</script><script>
window.Webflow = window.Webflow || [];
window.Webflow.push(function () {
var isTouchDevice = window.matchMedia('(hover: none)').matches;
if (isTouchDevice) return;
if (window.matchMedia('(max-width: 479px)').matches) return;
var wrapper = document.querySelector('.wrapper-image-team');
if (!wrapper) return;
var cards = wrapper.querySelectorAll('.card-team');
if (cards.length === 0) return;
// ===== Tweakable settings =====
var DURATION = 0.75;
var EASE = 'power2.inOut';
var SIDE_OFFSET = -101;
var EXPANDED_VW = 40;
var COLLAPSED_VW = 14;
// ==============================
var activeCard = cards[0];
function vwToPx(vw) {
return (window.innerWidth * vw) / 100;
}
function getExpandedWidth() {
return vwToPx(EXPANDED_VW);
}
function getCollapsedWidth() {
return vwToPx(COLLAPSED_VW);
}
function getSideElement(card) {
return card.querySelector('.img-team-side');
}
// ===== Initial state =====
function setInitialState() {
gsap.set(cards, { width: getCollapsedWidth() });
cards.forEach(function (card) {
var side = getSideElement(card);
if (side) gsap.set(side, { xPercent: 0 });
});
gsap.set(activeCard, { width: getExpandedWidth() });
var activeSide = getSideElement(activeCard);
if (activeSide) gsap.set(activeSide, { xPercent: SIDE_OFFSET });
}
// ===== Animate expand/collapse =====
function expandCard(targetCard) {
if (targetCard === activeCard) return;
var expandedWidth = getExpandedWidth();
var collapsedWidth = getCollapsedWidth();
var prevCard = activeCard;
var prevSide = getSideElement(prevCard);
var targetSide = getSideElement(targetCard);
activeCard = targetCard;
// Collapse previous card
gsap.to(prevCard, {
width: collapsedWidth,
duration: DURATION,
ease: EASE
});
if (prevSide) {
gsap.to(prevSide, {
xPercent: 0,
duration: DURATION,
ease: EASE
});
}
// Expand target card
gsap.to(targetCard, {
width: expandedWidth,
duration: DURATION,
ease: EASE
});
if (targetSide) {
gsap.to(targetSide, {
xPercent: SIDE_OFFSET,
duration: DURATION,
ease: EASE
});
}
}
// ===== Initialize =====
setInitialState();
// ===== Event listeners =====
cards.forEach(function (card) {
card.addEventListener('mouseenter', function () {
expandCard(card);
});
});
// Resize handler — re-set state based on new viewport width.
// Skip on mobile (<480px) where the effect is disabled anyway.
var resizeTimer;
window.addEventListener('resize', function () {
clearTimeout(resizeTimer);
resizeTimer = setTimeout(function () {
if (window.matchMedia('(max-width: 479px)').matches) {
// On mobile, reset all to default so CSS controls the layout
gsap.set(cards, { width: '' });
cards.forEach(function (card) {
var side = getSideElement(card);
if (side) gsap.set(side, { xPercent: 0 });
});
return;
}
setInitialState();
}, 150);
});
});
</script><script>
window.Webflow = window.Webflow || [];
window.Webflow.push(function () {
// Only run on mobile viewport (<= 479px) where cards are stacked.
if (!window.matchMedia('(max-width: 479px)').matches) return;
var wrapper = document.querySelector('.wrapper-image-team');
if (!wrapper) return;
var cards = wrapper.querySelectorAll('.card-team');
if (cards.length < 2) return;
var arrows = document.querySelectorAll('.team-arrow-circle');
if (arrows.length < 2) return;
var prevBtn = arrows[0]; // left arrow = previous
var nextBtn = arrows[1]; // right arrow = next
// ===== Tweakable settings =====
var DURATION = 0.6;
var EASE = 'power3.out';
var SLIDE = 60; // px the card slides in/out horizontally
// ==============================
var current = 0;
var animating = false;
// Initial state: first card visible and centered, the rest hidden.
for (var i = 0; i < cards.length; i++) {
gsap.set(cards[i], {
opacity: i === 0 ? 1 : 0,
x: 0,
zIndex: i === 0 ? 2 : 1
});
}
// dir = 1 means going forward (next), -1 means going back (prev)
function goTo(nextIndex, dir) {
if (animating) return;
if (nextIndex === current) return;
animating = true;
var oldCard = cards[current];
var newCard = cards[nextIndex];
// Incoming card sits above the others.
gsap.set(newCard, { zIndex: 2 });
gsap.set(oldCard, { zIndex: 1 });
// Old card fades out and slides away in the travel direction.
gsap.to(oldCard, {
opacity: 0,
x: -SLIDE * dir,
duration: DURATION,
ease: EASE
});
// New card starts off-screen on the opposite side, then
// fades in and slides to center.
gsap.set(newCard, { x: SLIDE * dir });
gsap.to(newCard, {
opacity: 1,
x: 0,
duration: DURATION,
ease: EASE,
onComplete: function () {
animating = false;
}
});
current = nextIndex;
}
function next() {
var i = (current + 1) % cards.length;
goTo(i, 1);
}
function prev() {
var i = (current - 1 + cards.length) % cards.length;
goTo(i, -1);
}
nextBtn.addEventListener('click', next);
prevBtn.addEventListener('click', prev);
});
</script>