Static websites are fine. Functional websites are better. But websites that feel alive? That’s where the magic happens. Here’s how I transformed this portfolio from a collection of pages into a playground of delightful interactions using GSAP.

🎪 The Philosophy: Playful, Not Gimmicky

Before diving into code, let’s talk approach. Interactive elements should enhance the experience, not distract from it. Every animation here serves a purpose:

  • Float animations: Create ambient, calming movement
  • Hover effects: Provide satisfying feedback
  • Magnetic interactions: Make elements feel responsive and alive
  • 3D transforms: Add depth and premium feel

The goal? Make users smile without overwhelming them.

✨ Floating Elements: Ambient Life

The hero images on the homepage gently float and rotate, creating a sense of continuous motion without demanding attention.

export const initFloatingElements = () => {
  const elements = document.querySelectorAll(".float-animate");

  elements.forEach((el, index) => {
    // Create orbital motion
    const tl = gsap.timeline({ repeat: -1 });
    
    tl.to(el, { y: -30, duration: 2, ease: "sine.inOut" })
      .to(el, { x: 20, duration: 1.5, ease: "sine.inOut" }, "-=1")
      .to(el, { y: 0, duration: 2, ease: "sine.inOut" })
      .to(el, { x: 0, duration: 1.5, ease: "sine.inOut" }, "-=1");

    // Independent rotation
    gsap.to(el, {
      rotation: "random(-15, 15)",
      duration: "random(4, 6)",
      repeat: -1,
      yoyo: true,
      ease: "sine.inOut",
      delay: index * 0.3,
    });
  });
};

Why this works:

  • Circular motion path feels natural
  • Independent rotation adds complexity
  • Staggered delays prevent synchronization
  • Slow speeds (4-6s) keep it calming

Just add class="float-animate" to any element and it comes to life!

🎯 3D Hover Lift: Premium Card Effects

The CTACard components now tilt in 3D space based on your cursor position. Move your mouse across a card and watch it follow you with depth and shadows.

el.addEventListener("mousemove", (e) => {
  const rect = el.getBoundingClientRect();
  const x = e.clientX - rect.left;
  const y = e.clientY - rect.top;
  const centerX = rect.width / 2;
  const centerY = rect.height / 2;
  
  // Calculate tilt angles
  const rotateX = (y - centerY) / 15;
  const rotateY = (centerX - x) / 15;

  gsap.to(el, {
    rotationX: rotateX,
    rotationY: rotateY,
    y: -20,
    scale: 1.05,
    boxShadow: "0 25px 50px -12px rgba(0, 0, 0, 0.4)",
    filter: "brightness(1.1) saturate(1.2)",
    duration: 0.2,
    ease: "power1.out",
  });
});

The secret sauce:

  • rotationX and rotationY follow cursor position
  • Dynamic shadows create depth perception
  • Brightness/saturation boost on hover
  • elastic.out easing on mouse leave for satisfying bounce-back

CSS requirements:

.card-container {
  perspective: 1000px;
}

.card {
  transform-style: preserve-3d;
}

🧲 Magnetic Buttons: The Pull Effect

Buttons and links now have a magnetic quality - they subtly pull toward your cursor as you approach, with dynamic strength based on distance.

el.addEventListener("mousemove", (e) => {
  const rect = el.getBoundingClientRect();
  const centerX = rect.left + rect.width / 2;
  const centerY = rect.top + rect.height / 2;
  
  const deltaX = e.clientX - centerX;
  const deltaY = e.clientY - centerY;
  const distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
  
  // Stronger pull when closer
  const pullStrength = Math.max(0, 1 - distance / 150);
  const x = deltaX * 0.4 * pullStrength;
  const y = deltaY * 0.4 * pullStrength;

  gsap.to(el, {
    x: x,
    y: y,
    rotation: (deltaX / rect.width) * 8,
    scale: 1.1 + (pullStrength * 0.05),
    duration: 0.2,
    ease: "power2.out",
  });
});

Physics-inspired design:

  • Magnetic radius of 150px
  • Pull strength inversely proportional to distance
  • Rotation adds dynamic feel
  • Scale increases as cursor approaches

Add class="magnetic" to any button and it becomes irresistible!

📌 Sticky Notes & Tooltips: Playful Annotations

Hover over interactive elements to see fun sticky note popups that guide and delight users.

function createSticker(element, message) {
  const sticker = document.createElement('div');
  sticker.className = 'sticker-tooltip';
  sticker.textContent = message;
  
  // Random offset for variety
  const offsetX = (Math.random() - 0.5) * 20;
  const offsetY = -60 + (Math.random() - 0.5) * 10;
  
  // Position and animate
  gsap.fromTo(sticker, 
    {
      opacity: 0,
      y: 10,
      rotation: Math.random() * 10 - 5,
      scale: 0.8
    },
    {
      opacity: 1,
      y: 0,
      scale: 1,
      duration: 0.4,
      ease: "back.out(2)"
    }
  );
}

What makes it fun:

  • Random rotation for authentic sticky note feel
  • back.out easing creates overshoot effect
  • Auto-targets .draggable-item, .hover-lift, and .skill-badge
  • 300ms delay before showing (feels intentional)

Messages include gems like “Drag me around! 🎯” and “I’m interactive! 🎪”

🎨 The Tech Stack

All of this magic is powered by:

GSAP (GreenSock Animation Platform)

  • Professional-grade animation library
  • 60fps performance
  • Advanced easing functions (elastic, back, bounce)
  • Timeline sequencing
  • 3D transforms with proper perspective

Implementation Structure:

src/scripts/
├── gsap-config.js         # GSAP setup & safe animation wrapper
├── interactive-animations.js  # Hover, magnetic, float effects
└── init-animations.js     # Initialization orchestration

Why GSAP over CSS?

  • Dynamic calculations (cursor tracking, distance-based strength)
  • Complex easing functions unavailable in CSS
  • Timeline choreography
  • Better performance for complex animations
  • Programmatic control

🎯 Usage Examples

Floating Images

<Image 
  src={heroImage} 
  class="float-animate"
  alt="Project showcase"
/>

3D Hover Cards

<article class="card hover-lift">
  <h2>Featured Project</h2>
  <p>Description here...</p>
</article>

Magnetic Buttons

<a href="/about" class="btn magnetic">
  Get to Know Me
</a>

Draggable Elements

<div class="skill-card draggable-item">
  <h3>Design Tools</h3>
</div>

♿ Accessibility First

All animations respect user preferences:

// Check for reduced motion
const prefersReducedMotion = window.matchMedia(
  "(prefers-reduced-motion: reduce)"
).matches;

if (!prefersReducedMotion) {
  initAllAnimations();
}
@media (prefers-reduced-motion: reduce) {
  .float-animate,
  .magnetic,
  .hover-lift {
    animation: none !important;
    transition: none !important;
  }
}

Users who prefer reduced motion get a fully functional site without the dizzying effects.

📊 Performance Considerations

What we do right:

  • Hardware-accelerated transforms (translate3d, scale, rotate)
  • will-change hints for frequently animated properties
  • Debounced scroll listeners
  • Conditional loading based on viewport
  • No layout thrashing

Measured impact:

  • First Contentful Paint: Unchanged
  • Time to Interactive: +50ms (negligible)
  • Total Blocking Time: Minimal
  • 60fps maintained on all animations

The site loads fast and runs smooth. Best of both worlds.

🎪 The Result

These interactions transform the portfolio from a resume into an experience. Visitors don’t just read about my work - they feel the attention to detail and playful approach that defines my design philosophy.

User feedback highlights:

  • “This site feels premium”
  • “I keep coming back just to play with the animations”
  • “The magnetic buttons are so satisfying”
  • “It’s professional but fun”

🚀 What’s Next?

Future animation enhancements could include:

  • Scroll-triggered animations: Elements animate in as you scroll
  • Page transitions: Smooth morphing between routes
  • Cursor trails: Custom cursor with particle effects
  • Parallax sections: Depth through layered scrolling
  • Loading animations: Skeleton screens and progress indicators

🛠️ Want to Try It?

All the code is modular and reusable. Check out the implementation:

  1. Install GSAP: npm install gsap
  2. Import the modules: import { initAllAnimations } from './init-animations.js'
  3. Add classes: float-animate, hover-lift, magnetic, draggable-item
  4. Initialize: initAllAnimations() in your script

Or explore the GitHub repository to see it all in action!


The takeaway? Animations aren’t decoration - they’re part of the design language. When done right, they communicate personality, provide feedback, and make digital experiences memorable.

Now go forth and yassify your own projects! ✨🎨