source.tsx
"use client";
import { useRef, useEffect } from "react";
import gsap from "gsap";
export default function MagneticButton({ children }: { children: React.ReactNode }) {
const buttonRef = useRef<HTMLButtonElement>(null);
const textRef = useRef<HTMLSpanElement>(null);
useEffect(() => {
const button = buttonRef.current;
const text = textRef.current;
if (!button || !text) return;
const xTo = gsap.quickTo(button, "x", { duration: 1, ease: "elastic.out(1, 0.3)" });
const yTo = gsap.quickTo(button, "y", { duration: 1, ease: "elastic.out(1, 0.3)" });
const textXTo = gsap.quickTo(text, "x", { duration: 1, ease: "elastic.out(1, 0.3)" });
const textYTo = gsap.quickTo(text, "y", { duration: 1, ease: "elastic.out(1, 0.3)" });
const handleMouseMove = (e: MouseEvent) => {
const { clientX, clientY } = e;
const { left, top, width, height } = button.getBoundingClientRect();
const x = clientX - (left + width / 2);
const y = clientY - (top + height / 2);
xTo(x * 0.35);
yTo(y * 0.35);
textXTo(x * 0.1);
textYTo(y * 0.1);
};
const handleMouseLeave = () => {
xTo(0);
yTo(0);
textXTo(0);
textYTo(0);
};
button.addEventListener("mousemove", handleMouseMove);
button.addEventListener("mouseleave", handleMouseLeave);
return () => {
button.removeEventListener("mousemove", handleMouseMove);
button.removeEventListener("mouseleave", handleMouseLeave);
};
}, []);
return (
<button
ref={buttonRef}
className="relative px-8 py-4 rounded-full bg-custom-gray-900 dark:bg-white text-white dark:text-custom-gray-900 font-medium overflow-hidden"
>
<span ref={textRef} className="relative z-10 inline-block">
{children}
</span>
</button>
);
}Magnetic Button
01. ComponentA button that magnetically attracts to the cursor movement.
GSAPInteractionMouse
Hover Me
source.tsx
"use client";
import { useEffect, useRef } from "react";
import gsap from "gsap";
export default function CardStack() {
const containerRef = useRef<HTMLDivElement>(null);
useEffect(() => {
const container = containerRef.current;
if (!container) return;
const cards = container.querySelectorAll(".card");
const tl = gsap.timeline({ paused: true });
tl.to(cards[0], { rotate: -10, x: -20, y: -5, duration: 0.3, ease: "power2.out" }, 0)
.to(cards[1], { rotate: 0, x: 0, y: -20, duration: 0.3, ease: "power2.out" }, 0)
.to(cards[2], { rotate: 10, x: 20, y: -5, duration: 0.3, ease: "power2.out" }, 0);
const handleMouseEnter = () => tl.play();
const handleMouseLeave = () => tl.reverse();
container.addEventListener("mouseenter", handleMouseEnter);
container.addEventListener("mouseleave", handleMouseLeave);
return () => {
container.removeEventListener("mouseenter", handleMouseEnter);
container.removeEventListener("mouseleave", handleMouseLeave);
};
}, []);
return (
<div ref={containerRef} className="relative w-40 h-40 flex items-center justify-center cursor-pointer">
<div className="card absolute w-32 h-40 bg-blue-500 rounded-xl border border-white/10" style={{ zIndex: 1 }}></div>
<div className="card absolute w-32 h-40 bg-purple-500 rounded-xl border border-white/10" style={{ zIndex: 2 }}></div>
<div className="card absolute w-32 h-40 bg-pink-500 rounded-xl border border-white/10" style={{ zIndex: 3 }}></div>
<div className="absolute z-10 text-white font-medium mix-blend-overlay">Hover Me</div>
</div>
);
}Card Stack
01. ComponentInteractive card stack that fans out on hover.
GSAPAnimationCards
Hello World
Hover to replay
source.tsx
"use client";
import { useEffect, useRef } from "react";
import gsap from "gsap";
import { Icon } from "@iconify/react";
export default function TextReveal() {
const containerRef = useRef<HTMLDivElement>(null);
const textRef = useRef<HTMLHeadingElement>(null);
useEffect(() => {
const container = containerRef.current;
const text = textRef.current;
if (!container || !text) return;
// Split text logic manually for simplicity since we don't have SplitText plugin
const content = text.textContent || "";
text.innerHTML = content
.split("")
.map((char) => `<span class="inline-block opacity-0 translate-y-4">${char === " " ? " " : char}</span>`)
.join("");
const chars = text.querySelectorAll("span");
const anim = gsap.to(chars, {
opacity: 1,
y: 0,
stagger: 0.05,
duration: 0.5,
ease: "back.out(1.7)",
paused: true,
});
const handleMouseEnter = () => anim.restart();
container.addEventListener("mouseenter", handleMouseEnter);
return () => {
container.removeEventListener("mouseenter", handleMouseEnter);
};
}, []);
return (
<div
ref={containerRef}
className="flex flex-col items-center justify-center gap-4 cursor-pointer p-8"
>
<div className="p-3 rounded-full bg-custom-gray-100 dark:bg-custom-gray-800 text-custom-gray-900 dark:text-white mb-2">
<Icon icon="solar:restart-linear" className="w-6 h-6" />
</div>
<h3 ref={textRef} className="text-2xl font-bold text-custom-gray-900 dark:text-white">
Hello World
</h3>
<p className="text-sm text-custom-gray-500">Hover to replay</p>
</div>
);
}Text Reveal
01. ComponentStaggered text character reveal animation.
GSAPTypographySplitText
Glass Effect
Using backdrop-filter: blur() to create depth and hierarchy.
source.tsx
<div className="w-64 p-6 rounded-2xl bg-white/10 backdrop-blur-md border border-white/20 text-white">
<h4 className="text-lg font-bold mb-2">Glass Effect</h4>
<p className="text-sm text-white/80">
Using backdrop-filter: blur() to create depth and hierarchy.
</p>
</div>
/* CSS */
.glass-card {
background: rgba(255, 255, 255, 0.1);
backdrop-filter: blur(12px);
-webkit-backdrop-filter: blur(12px);
border: 1px solid rgba(255, 255, 255, 0.2);
}Glassmorphism Card
02. DesignModern frosted glass effect using backdrop-filter.
CSSUI DesignGlassmorphism
Gradient Border
source.tsx
<div className="relative p-[1px] rounded-2xl bg-gradient-to-r from-pink-500 via-purple-500 to-blue-500 overflow-hidden">
<div className="bg-custom-gray-900 rounded-2xl p-6 text-white relative z-10">
<h4 className="text-lg font-bold">Gradient Border</h4>
</div>
</div>Gradient Border
02. DesignSmooth gradient border effect using background-origin.
CSSTailwindBorder