"use client" import React, { useCallback, useEffect, useMemo, useRef, useState } from "react" import { motion, useMotionTemplate, useMotionValue, useSpring, } from "motion/react" import { useTheme } from "next-themes" import { cn } from "@/lib/utils" interface MagicCardBaseProps { children?: React.ReactNode className?: string gradientSize?: number gradientFrom?: string gradientTo?: string } interface MagicCardGradientProps extends MagicCardBaseProps { mode?: "gradient" gradientColor?: string gradientOpacity?: number glowFrom?: never glowTo?: never glowAngle?: never glowSize?: never glowBlur?: never glowOpacity?: never } interface MagicCardOrbProps extends MagicCardBaseProps { mode: "orb" glowFrom?: string glowTo?: string glowAngle?: number glowSize?: number glowBlur?: number glowOpacity?: number gradientColor?: never gradientOpacity?: never } type MagicCardProps = MagicCardGradientProps | MagicCardOrbProps type ResetReason = "enter" | "leave" | "global" | "init" function isOrbMode(props: MagicCardProps): props is MagicCardOrbProps { return props.mode === "orb" } export function MagicCard(props: MagicCardProps) { const { children, className, gradientSize = 200, gradientColor = "#262626", gradientOpacity = 0.8, gradientFrom = "#9E7AFF", gradientTo = "#FE8BBB", mode = "gradient", } = props const glowFrom = isOrbMode(props) ? (props.glowFrom ?? "#ee4f27") : "#ee4f27" const glowTo = isOrbMode(props) ? (props.glowTo ?? "#6b21ef") : "#6b21ef" const glowAngle = isOrbMode(props) ? (props.glowAngle ?? 90) : 90 const glowSize = isOrbMode(props) ? (props.glowSize ?? 420) : 420 const glowBlur = isOrbMode(props) ? (props.glowBlur ?? 60) : 60 const glowOpacity = isOrbMode(props) ? (props.glowOpacity ?? 0.9) : 0.9 const { theme, systemTheme } = useTheme() const [mounted, setMounted] = useState(false) useEffect(() => setMounted(true), []) const isDarkTheme = useMemo(() => { if (!mounted) return true const currentTheme = theme === "system" ? systemTheme : theme return currentTheme === "dark" }, [theme, systemTheme, mounted]) const mouseX = useMotionValue(-gradientSize) const mouseY = useMotionValue(-gradientSize) const orbX = useSpring(mouseX, { stiffness: 250, damping: 30, mass: 0.6 }) const orbY = useSpring(mouseY, { stiffness: 250, damping: 30, mass: 0.6 }) const orbVisible = useSpring(0, { stiffness: 300, damping: 35 }) const modeRef = useRef(mode) const glowOpacityRef = useRef(glowOpacity) const gradientSizeRef = useRef(gradientSize) useEffect(() => { modeRef.current = mode }, [mode]) useEffect(() => { glowOpacityRef.current = glowOpacity }, [glowOpacity]) useEffect(() => { gradientSizeRef.current = gradientSize }, [gradientSize]) const reset = useCallback( (reason: ResetReason = "leave") => { const currentMode = modeRef.current if (currentMode === "orb") { if (reason === "enter") orbVisible.set(glowOpacityRef.current) else orbVisible.set(0) return } const off = -gradientSizeRef.current mouseX.set(off) mouseY.set(off) }, [mouseX, mouseY, orbVisible] ) const handlePointerMove = useCallback( (e: React.PointerEvent) => { const rect = e.currentTarget.getBoundingClientRect() mouseX.set(e.clientX - rect.left) mouseY.set(e.clientY - rect.top) }, [mouseX, mouseY] ) useEffect(() => { reset("init") }, [reset]) useEffect(() => { const handleGlobalPointerOut = (e: PointerEvent) => { if (!e.relatedTarget) reset("global") } const handleBlur = () => reset("global") const handleVisibility = () => { if (document.visibilityState !== "visible") reset("global") } window.addEventListener("pointerout", handleGlobalPointerOut) window.addEventListener("blur", handleBlur) document.addEventListener("visibilitychange", handleVisibility) return () => { window.removeEventListener("pointerout", handleGlobalPointerOut) window.removeEventListener("blur", handleBlur) document.removeEventListener("visibilitychange", handleVisibility) } }, [reset]) return ( reset("leave")} onPointerEnter={() => reset("enter")} style={{ background: useMotionTemplate` linear-gradient(var(--color-background) 0 0) padding-box, radial-gradient(${gradientSize}px circle at ${mouseX}px ${mouseY}px, ${gradientFrom}, ${gradientTo}, var(--color-border) 100% ) border-box `, }} >
{mode === "gradient" && ( )} {mode === "orb" && ( ) }