Live Preview
0
1
2
3
4
5
6
7
8
9
10
11
Move mouse to repel blocks
"use client";
import { useRef, useState, useEffect } from "react";
import { motion, useSpring, useMotionValue } from "framer-motion";
const NUM_CARDS = 12;
function GravityCard({ mousePos, index }: { mousePos: { x: number; y: number }, index: number }) {
const ref = useRef<HTMLDivElement>(null);
const x = useMotionValue(0);
const y = useMotionValue(0);
const springX = useSpring(x, { stiffness: 100, damping: 10 });
const springY = useSpring(y, { stiffness: 100, damping: 10 });
useEffect(() => {
if (!ref.current) return;
const rect = ref.current.getBoundingClientRect();
const centerX = rect.left + rect.width / 2;
const centerY = rect.top + rect.height / 2;
const dx = centerX - mousePos.x;
const dy = centerY - mousePos.y;
const dist = Math.sqrt(dx * dx + dy * dy);
if (dist < 150 && mousePos.x !== -9999) {
const force = (150 - dist) / 150;
x.set((dx / dist) * force * 50);
y.set((dy / dist) * force * 50);
} else {
x.set(0);
y.set(0);
}
}, [mousePos.x, mousePos.y, x, y]);
return (
<motion.div
ref={ref}
style={{ x: springX, y: springY }}
className="w-16 h-16 sm:w-20 sm:h-20 bg-zinc-900 border border-zinc-800 rounded-xl shadow-lg flex items-center justify-center text-zinc-500 font-mono text-xs cursor-default select-none hover:bg-zinc-800 transition-colors"
>
{index}
</motion.div>
);
}
export function GravityPlayground() {
const [mousePos, setMousePos] = useState({ x: -9999, y: -9999 });
return (
<div
className="flex flex-col items-center justify-center w-full h-[400px] bg-[#0a0a0a] rounded-xl border border-border relative overflow-hidden"
onMouseMove={(e) => setMousePos({ x: e.clientX, y: e.clientY })}
onMouseLeave={() => setMousePos({ x: -9999, y: -9999 })}
>
<div className="grid grid-cols-4 gap-4">
{Array.from({ length: NUM_CARDS }).map((_, i) => (
<GravityCard key={i} index={i} mousePos={mousePos} />
))}
</div>
<p className="text-sm text-zinc-500 absolute bottom-4 pointer-events-none">Move mouse to repel blocks</p>
</div>
);
}