Silhouette of a person with long hair against a warm orange glowing background.
Pair of tall, wrinkled, orange leather boots with square toes on a plain surface.
Close-up of an ear wearing two small silver hoop earrings of different sizes.
Two orange cocktails with ice and lemon slices casting shadows on rippling water.
Silhouette of a man wearing a sleeveless top and necklace, set against a solid reddish background.
Floating beige and white sneaker with red stripes and red sole against a metallic background.
View Project

Instructions

Find easy to follow instructions
GSAP Guide
Animation Code
All GSAP animations used in this template are collected here. On this page, you’ll find guidance on how to locate and edit them. Each code block comes with extra notes to make it easier to understand.
Custom Code
Cursor Move Hero Image
<script>
  document.addEventListener('DOMContentLoaded', function () {
    const triggerWrap = document.querySelector('.wrapper-cursor');
    if (!triggerWrap) return;

    const blockCursor = triggerWrap.querySelector('.block-cursor');
    const lineCursors = triggerWrap.querySelectorAll('.line-cursor');
    const lineCursorX = triggerWrap.querySelectorAll('.line-cursor.v1');
    const lineCursorY = triggerWrap.querySelectorAll('.line-cursor.v2');

    if (blockCursor) gsap.set(blockCursor, { xPercent: -50, yPercent: -50 });

    const blockX = blockCursor ? gsap.quickTo(blockCursor, 'x', { duration: 0.4, ease: 'power3.out' }) : null;
    const blockY = blockCursor ? gsap.quickTo(blockCursor, 'y', { duration: 0.4, ease: 'power3.out' }) : null;
    const lineXTo = lineCursorX ? gsap.quickTo(lineCursorX, 'x', { duration: 0.4, ease: 'power3.out' }) : null;
    const lineYTo = lineCursorY ? gsap.quickTo(lineCursorY, 'y', { duration: 0.4, ease: 'power3.out' }) : null;

    // ===== IMAGE SWAP SETUP =====
    const imageWrap = blockCursor ? blockCursor.querySelector('.wrapper-image-hero') : null;
    const images = imageWrap ? gsap.utils.toArray(imageWrap.querySelectorAll('.block-image-hero')) : [];

    if (images.length) {
      gsap.set(images, { opacity: 0, scale: 0.85 });
    }

    let currentIndex = 0;
    let lastX = 0;
    let lastY = 0;
    let lastTriggerTime = 0;
    const distanceThreshold = 60;
    const minInterval = 120;

    window.addEventListener('mousemove', (e) => {
      if (blockX) blockX(e.clientX);
      if (blockY) blockY(e.clientY);
      if (lineXTo) lineXTo(e.clientX);
      if (lineYTo) lineYTo(e.clientY);

      if (!images.length) return;

      const dx = e.clientX - lastX;
      const dy = e.clientY - lastY;
      const distance = Math.sqrt(dx * dx + dy * dy);
      const now = Date.now();

      if (distance < distanceThreshold || now - lastTriggerTime < minInterval) return;

      lastX = e.clientX;
      lastY = e.clientY;
      lastTriggerTime = now;

      const prevEl = images[currentIndex];
      currentIndex = (currentIndex + 1) % images.length;
      const nextEl = images[currentIndex];

      gsap.to(prevEl, {
        opacity: 0,
        scale: 0.85,
        duration: 0.5,
        ease: 'power2.inOut',
      });

      gsap.killTweensOf(nextEl);
      gsap.fromTo(
        nextEl,
        { opacity: 0, scale: 0.85, zIndex: 10 },
        {
          opacity: 1,
          scale: 1,
          duration: 0.6,
          ease: 'power3.out',
        },
      );
    });
  });
</script>