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> ) }