Playground

My design experiments and prototypes.

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. Component

A 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. Component

Interactive 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 === " " ? "&nbsp;" : 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. Component

Staggered 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. Design

Modern 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. Design

Smooth gradient border effect using background-origin.

CSSTailwindBorder