Instructions
Everything you need to customize, manage, and launch the Quire template with confidence.
This template includes custom GSAP-powered animations designed to create smooth, engaging interactions across the entire website. From page transitions and scroll-based effects to dynamic content reveals, every animation has been carefully optimized for performance and responsiveness on modern devices.
Each animation module is organized with scalability and customization in mind. Whether you're adjusting scroll triggers, refining timelines, or creating entirely new motion sequences, the provided code structure makes implementation simple and efficient.
Lenis code for smooth scrolling
The script below enables smooth scrolling functionality using Lenis. Please review this code carefully before modifying or removing the smooth scrolling behavior, as it may affect the overall user experience and site performance.
<script>
const lenis = new Lenis({ duration: 1.6 });
lenis.on('scroll', ScrollTrigger.update);
gsap.ticker.add((time) => {
lenis.raf(time * 1000);
});
gsap.ticker.lagSmoothing(0);
</script>Script code for looping image Gallery Section.
The script below controls the continuous looping animation of the gallery. Simply replace the selector with the class name of your image wrapper to make the animation work correctly.
<script>
// Animation for Gallery Home-V2
gsap.registerPlugin(ScrollTrigger);
const items = document.querySelectorAll('.img-wrapp-gallery');
items.forEach((item, index) => {
gsap.set(item, {
transformStyle: 'preserve-3d',
});
gsap.fromTo(
item,
{
z: -2000,
opacity: 0,
},
{
z: 600,
opacity: 0,
duration: 4.5,
ease: 'none',
repeat: -1,
delay: index * 0.8,
scrollTrigger: {
trigger: '.wrapper-highlight',
start: 'top 120%',
markers: false,
},
modifiers: {
opacity: function (value, target) {
const progress = gsap.getProperty(target, 'z');
if (progress < -1200) {
return 0;
}
if (progress < -400) {
return gsap.utils.mapRange(-1200, -400, 0, 1, progress);
}
if (progress < 200) {
return 1;
}
return gsap.utils.mapRange(200, 600, 1, 0, progress);
},
},
},
);
});
</script>
Interactive Why Choose Us Section Script
This script adds mouse-follow parallax motion and hover-based image switching (v1–v5). Replace the class names with your own structure to activate the interaction.
<script>
document.addEventListener('DOMContentLoaded', () => {
const whyWrap = document.querySelector('.content-why-about');
const whyImage = document.querySelector('.block-image-why');
const innerImage = document.querySelector('.image-why-about');
const items = document.querySelectorAll('.content-why.v1, .content-why.v2, .content-why.v3, .content-why.v4, .content-why.v5');
const images = document.querySelectorAll('.image-why-about');
if (!whyWrap || !whyImage || !innerImage) return;
gsap.set(images, {
opacity: 0,
});
whyWrap.addEventListener('mousemove', (e) => {
const rect = whyWrap.getBoundingClientRect();
const xPos = e.clientX - rect.left;
const yPos = e.clientY - rect.top;
gsap.to(whyImage, {
x: xPos,
y: yPos,
duration: 0.6,
ease: 'power2.out',
overwrite: 'auto',
});
gsap.to(innerImage, {
x: xPos * 0.05,
y: yPos * 0.05,
duration: 1,
ease: 'sine.out',
overwrite: 'auto',
});
});
// HOVER LOGIC v1-v5
items.forEach((item, index) => {
item.addEventListener('mouseenter', () => {
gsap.to(images, {
opacity: 0,
duration: 0.3,
});
const target = document.querySelector('.image-why-about.v' + (index + 1));
if (target) {
gsap.to(target, {
opacity: 1,
duration: 0.4,
ease: 'power2.out',
});
}
});
});
});
</script>Founder Stack Card Animation Script
Controls a clickable stacked card system where each card smoothly animates to the back of the stack with GSAP timeline sequencing and dynamic z-index management.
<script>
const cards = gsap.utils.toArray('.card-founder');
let current = 0;
let activeTl = null;
function initStack() {
const total = cards.length;
cards.forEach((card, i) => {
gsap.set(card, {
zIndex: total - i,
x: i * 60,
y: 0,
scale: 1 - i * 0.07,
rotation: 0,
opacity: 1,
});
});
}
function advanceStack() {
// If an animation is running, snap it to completion before starting the next
if (activeTl && activeTl.isActive()) {
activeTl.progress(1);
}
const total = cards.length;
const topCard = cards[current];
activeTl = gsap.timeline({
onComplete: () => {
current = (current + 1) % total;
},
});
// 1. Pull back
activeTl.to(topCard, {
x: '-=30',
scale: 0.92,
duration: 0.2,
ease: 'power2.in',
});
// 2. Throw to the left — smooth with momentum
activeTl.to(
topCard,
{
x: -500,
autoAlpha: 0,
rotation: -10,
zIndex: total + 1,
duration: 0.5,
ease: 'expo.inOut',
},
'-=0.05',
);
// 3. Cards behind move forward
cards.forEach((card, i) => {
if (card === topCard) return;
const pos = (i - current - 1 + total) % total;
activeTl.to(
card,
{
zIndex: total - pos,
x: pos * 60,
y: 0,
scale: 1 - pos * 0.07,
opacity: 1,
rotation: 0,
duration: 0.45,
ease: 'power3.out',
},
'-=0.3',
);
});
// 4. Teleport to back position — invisible, slightly below
activeTl.call(() => {
gsap.set(topCard, {
x: (total - 1) * 60,
y: 40,
rotation: -6,
scale: 1 - (total - 1) * 0.07,
zIndex: 1,
autoAlpha: 0,
});
});
// 5. Animate into back stack position — smooth, no flicker
activeTl.to(
topCard,
{
y: 0,
rotation: 0,
autoAlpha: 1,
duration: 0.35,
ease: 'power2.out',
},
'+=0.05',
);
}
initStack();
document.querySelectorAll('.card-founder').forEach((c) => c.addEventListener('click', advanceStack));
</script>Founder Cursor Follow Script
Creates a smooth custom cursor that follows mouse movement inside the founder section using GSAP easing for a fluid interaction experience.
<script>
//testimonial cursor founder
document.addEventListener('DOMContentLoaded', function () {
const founderWrap = document.querySelector('.wrapper-content-founder');
const founderCursor = document.querySelector('.cursor-founder');
if (!founderWrap || !founderCursor) return;
founderWrap.addEventListener('mousemove', (e) => {
const rect = founderWrap.getBoundingClientRect();
const xPos = e.clientX - rect.left;
const yPos = e.clientY - rect.top;
gsap.to(founderCursor, {
x: xPos,
y: yPos,
duration: 0.6,
ease: 'power2.out',
overwrite: 'auto',
});
});
});
</script>Draggable Elements Script
This script enables interactive drag-and-drop cards that automatically snap back to their original positions upon release. Ensure your section wrapper matches the bounding class to constrain the dragging area properly.
<script>
document.addEventListener('DOMContentLoaded', function () {
gsap.registerPlugin(Draggable);
const cardsAbout = document.querySelectorAll('.about-v3-image-wrapper');
if (!cardsAbout) return;
cardsAbout.forEach((card) => {
const originalZIndex = window.getComputedStyle(card).zIndex || 1;
// 1. Hover Logic
card.addEventListener('mouseenter', () => {
if (!Draggable.get(card)?.isDragging) {
gsap.set(card, { zIndex: 10 });
}
});
card.addEventListener('mouseleave', () => {
if (!Draggable.get(card)?.isDragging) {
gsap.set(card, { zIndex: originalZIndex });
}
});
// 2. Drag & Snap Back Logic
Draggable.create(card, {
bounds: '.about-v3-headline-card',
edgeResistance: 0.65,
type: 'x,y',
onDragStart: function () {
gsap.set(this.target, { zIndex: 10 });
},
onRelease: function () {
gsap.set(this.target, { zIndex: originalZIndex });
gsap.to(this.target, {
x: 0,
y: 0,
duration: 0.6,
ease: 'back.out(1.5)',
});
},
});
});
});
</script>Mouse Follow Tag Script About (Home V3)
This script triggers a smooth, elastic hover badge (.button-hover) that dynamically follows the user's mouse movement across the card item. It also applies a subtle, fluid scaling effect to the preview image (.work-v3-image) when entering the card area, enhancing the section's visual depth and interactivity.
<script>
document.addEventListener('DOMContentLoaded', () => {
const cards = document.querySelectorAll('.work-v3-card');
cards.forEach((card) => {
const imageWrap = card.querySelector('.work-v3-image-wrapper.v3');
const img = card.querySelector('.work-v3-image');
const tag = card.querySelector('.button-hover');
if (!imageWrap || !tag) return;
gsap.set(tag, {
opacity: 0,
scale: 0,
xPercent: -50,
yPercent: -50,
position: 'absolute',
});
imageWrap.addEventListener('mousemove', (e) => {
const rect = imageWrap.getBoundingClientRect();
const xPos = e.clientX - rect.left;
const yPos = e.clientY - rect.top;
gsap.to(tag, {
x: xPos,
y: yPos,
duration: 0.35,
ease: 'power3.out',
overwrite: 'auto',
});
});
imageWrap.addEventListener('mouseenter', () => {
if (img) {
gsap.to(img, {
scale: 1.08,
duration: 0.5,
ease: 'power2.out',
});
}
gsap.to(tag, {
opacity: 1,
scale: 1,
duration: 0.3,
ease: 'back.out(1.5)',
});
});
imageWrap.addEventListener('mouseleave', () => {
if (img) {
gsap.to(img, {
scale: 1,
duration: 0.5,
ease: 'power2.out',
});
}
gsap.to(tag, {
opacity: 0,
scale: 0,
duration: 0.25,
ease: 'power2.in',
});
});
});
});
</script>Mouse Follow Tag Script (Portfolio V3)
This script triggers a smooth, elastic hover badge (.button-hover) that dynamically follows the user's mouse movement across the card item. It also applies a subtle, fluid scaling effect to the preview image (.wrapper-image-v3) when entering the card area, enhancing the section's visual depth and interactivity.
<script>
document.addEventListener('DOMContentLoaded', () => {
const cards = document.querySelectorAll('.link-work-v3');
cards.forEach((card) => {
const imageWrap = card.querySelector('.wrapper-image-v3');
const tag = card.querySelector('.button-hover');
if (!imageWrap || !tag) return;
gsap.set(tag, {
opacity: 0,
scale: 0,
xPercent: -50,
yPercent: -50,
position: 'absolute',
});
imageWrap.addEventListener('mousemove', (e) => {
const rect = imageWrap.getBoundingClientRect();
const xPos = e.clientX - rect.left;
const yPos = e.clientY - rect.top;
gsap.to(tag, {
x: xPos,
y: yPos,
duration: 0.35,
ease: 'power3.out',
overwrite: 'auto',
});
});
imageWrap.addEventListener('mouseenter', () => {
gsap.to(tag, {
opacity: 1,
scale: 1,
duration: 0.3,
ease: 'back.out(1.5)',
});
});
imageWrap.addEventListener('mouseleave', () => {
gsap.to(tag, {
opacity: 0,
scale: 0,
duration: 0.25,
ease: 'power2.in',
});
});
});
});
</script>Infinite Draggable Marquee Slider
Controls the endless looping marquee animation for the hero slider section, integrated with fluid drag-to-scrub functionality via GSAP Draggable. It features automatic viewport resize tracking, smooth deceleration logic when hovered or dragged, and a subtle scaling feedback effect on the slides.
<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 ----
// A proxy element that Draggable controls; we read its x and apply it to the tween
const proxy = document.createElement('div');
let startProgress = 0;
Draggable.create(proxy, {
type: 'x',
trigger: marquee,
inertia: true,
onPressInit() {
// Pause the tween while user is interacting
gsap.killTweensOf(tween);
tween.timeScale(0);
marquee.style.cursor = 'grabbing';
startProgress = tween.progress();
gsap.set(proxy, { x: 0 });
// Scale down hero-slide during drag start
gsap.to('.hero-slide', { scale: 0.95, duration: 0.2 }); // Adjust scale to your preference
},
onDrag() {
// Map drag distance to tween progress
// Negative distanceToTranslate means dragging right should move progress backwards
const progressDelta = this.x / distanceToTranslate;
let newProgress = startProgress + progressDelta;
// Wrap progress between 0 and 1 so it loops infinitely
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('.hero-slide', { scale: 1, duration: 0.3 }); // Adjust scale reset to original
},
onThrowComplete() {
// Resume autoplay (respect hover state)
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>Card Showcase Hover Interaction Script
Creates a smooth hover interaction for about cards with cursor-following tags and image zoom effects using GSAP animations.
<script>
document.addEventListener('DOMContentLoaded', () => {
// Ambil semua card about-card-item
const cards = document.querySelectorAll('.about-card-item');
cards.forEach((card) => {
// Cari wrapper thumbnail, elemen gambar, dan tag hover di dalam card ini
const thumbnailWrap = card.querySelector('.about-thumbnail');
const img = card.querySelector('.image-preview');
const tag = card.querySelector('.button-hover');
if (!thumbnailWrap || !tag) return;
// Set posisi awal tag via GSAP agar pas di tengah kursor dan tersembunyi
gsap.set(tag, {
opacity: 0,
scale: 0,
xPercent: -50,
yPercent: -50,
position: 'absolute',
});
// Tracking pergerakan mouse di dalam area about-thumbnail
thumbnailWrap.addEventListener('mousemove', (e) => {
// Menghitung posisi kursor relatif terhadap .about-thumbnail
const rect = thumbnailWrap.getBoundingClientRect();
const xPos = e.clientX - rect.left;
const yPos = e.clientY - rect.top;
// Gerakkan tag .button-hover mengikuti kursor
gsap.to(tag, {
x: xPos,
y: yPos,
duration: 0.35,
ease: 'power3.out',
overwrite: 'auto',
});
});
// Efek saat kursor masuk ke area about-thumbnail
thumbnailWrap.addEventListener('mouseenter', () => {
// 1. Zoom In gambar (.image-preview)
if (img) {
gsap.to(img, {
scale: 1.08,
duration: 0.5,
ease: 'power2.out',
});
}
// 2. Munculkan tag "Discover / Buy Template" (.button-hover)
gsap.to(tag, {
opacity: 1,
scale: 1,
duration: 0.3,
ease: 'back.out(1.5)',
});
});
// Efek saat kursor keluar dari area about-thumbnail
thumbnailWrap.addEventListener('mouseleave', () => {
// 1. Kembalikan ukuran gambar semula
if (img) {
gsap.to(img, {
scale: 1,
duration: 0.5,
ease: 'power2.out',
});
}
// 2. Sembunyikan kembali tag (.button-hover)
gsap.to(tag, {
opacity: 0,
scale: 0,
duration: 0.25,
ease: 'power2.in',
});
});
});
});
</script>
