npm i framer-motion lucide-react @radix-ui/react-slot
npx shadcn-ui@latest add card button
// Create utils/cn.ts
import { clsx, type ClassValue } from "clsx";
import { twMerge } from "tailwind-merge";
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}"use client"
import { useEffect, useState } from "react"
import { motion, AnimatePresence } from "framer-motion"
export function FunnyCursor() {
const [position, setPosition] = useState({ x: 0, y: 0 })
const [isVisible, setIsVisible] = useState(false)
const [isClicked, setIsClicked] = useState(false)
useEffect(() => {
const updatePosition = (e: MouseEvent) => {
setPosition({ x: e.clientX, y: e.clientY })
setIsVisible(true)
}
const handleMouseDown = () => setIsClicked(true)
const handleMouseUp = () => setIsClicked(false)
window.addEventListener("mousemove", updatePosition)
window.addEventListener("mouseleave", () => setIsVisible(false))
window.addEventListener("mouseenter", () => setIsVisible(true))
window.addEventListener("mousedown", handleMouseDown)
window.addEventListener("mouseup", handleMouseUp)
return () => {
window.removeEventListener("mousemove", updatePosition)
window.removeEventListener("mouseleave", () => setIsVisible(false))
window.removeEventListener("mouseenter", () => setIsVisible(true))
window.removeEventListener("mousedown", handleMouseDown)
window.removeEventListener("mouseup", handleMouseUp)
}
}, [])
return (
<AnimatePresence>
{isVisible && (
<motion.div
className="pointer-events-none fixed left-0 top-0 z-50 h-12 w-12 text-white mix-blend-difference"
animate={{
x: position.x - 24,
y: position.y - 24,
scale: isClicked ? 0.8 : 1,
rotate: isClicked ? 45 : 0,
}}
exit={{
opacity: 0,
scale: 0.5,
}}
transition={{
type: "spring",
stiffness: 300,
damping: 20,
mass: 0.5,
}}
>
<motion.svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="currentColor"
animate={{
rotate: isClicked ? 360 : 0,
}}
transition={{
type: "spring",
stiffness: 260,
damping: 20,
}}
>
<path d="M12 2C6.47 2 2 6.47 2 12s4.47 10 10 10 10-4.47 10-10S17.53 2 12 2zm0 18c-4.41 0-8-3.59-8-8s3.59-8 8-8 8 3.59 8 8-3.59 8-8 8zm3.59-13L12 10.59 8.41 7 7 8.41 10.59 12 7 15.59 8.41 17 12 13.41 15.59 17 17 15.59 13.41 12 17 8.41z" />
</motion.svg>
<motion.div
className="absolute left-1/2 top-1/2 h-2 w-2 -translate-x-1/2 -translate-y-1/2 rounded-full bg-white"
animate={{
scale: [1, 1.5, 1],
}}
transition={{
repeat: Infinity,
duration: 1,
ease: "easeInOut",
}}
/>
</motion.div>
)}
</AnimatePresence>
)
}