{ "$schema": "https://ui.shadcn.com/schema/registry-item.json", "name": "spotlight-card", "type": "registry:ui", "title": "Spotlight Card", "description": "Interactive cards with cursor-following spotlight effects, animated gradient borders, and 3D tilt animations.", "dependencies": [], "devDependencies": [], "registryDependencies": [], "files": [ { "path": "components/ui/spotlight-card.tsx", "type": "registry:ui", "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { cn } from \"@workspace/ui/lib/utils\"\n\ninterface SpotlightCardProps extends React.HTMLAttributes {\n children: React.ReactNode\n spotlightColor?: string\n borderColor?: string\n borderWidth?: number\n borderRadius?: number\n glowIntensity?: number\n}\n\nfunction SpotlightCard({\n children,\n className,\n spotlightColor = \"rgba(120, 119, 198, 0.3)\",\n borderColor,\n borderWidth = 1,\n borderRadius = 16,\n glowIntensity = 0.15,\n ...props\n}: SpotlightCardProps) {\n const containerRef = React.useRef(null)\n const [position, setPosition] = React.useState({ x: 0, y: 0 })\n const [isHovered, setIsHovered] = React.useState(false)\n const [opacity, setOpacity] = React.useState(0)\n\n const handleMouseMove = React.useCallback(\n (e: React.MouseEvent) => {\n if (!containerRef.current) return\n\n const rect = containerRef.current.getBoundingClientRect()\n setPosition({\n x: e.clientX - rect.left,\n y: e.clientY - rect.top,\n })\n },\n []\n )\n\n const handleMouseEnter = React.useCallback(() => {\n setIsHovered(true)\n setOpacity(1)\n }, [])\n\n const handleMouseLeave = React.useCallback(() => {\n setIsHovered(false)\n setOpacity(0)\n }, [])\n\n return (\n \n {/* Gradient border */}\n \n\n {/* Spotlight effect */}\n \n\n {/* Border glow on hover */}\n \n\n {/* Content */}\n
{children}
\n \n )\n}\n\ninterface SpotlightCardContentProps\n extends React.HTMLAttributes {\n children: React.ReactNode\n}\n\nfunction SpotlightCardContent({\n children,\n className,\n ...props\n}: SpotlightCardContentProps) {\n return (\n
\n {children}\n
\n )\n}\n\ninterface SpotlightCardHeaderProps\n extends React.HTMLAttributes {\n children: React.ReactNode\n}\n\nfunction SpotlightCardHeader({\n children,\n className,\n ...props\n}: SpotlightCardHeaderProps) {\n return (\n
\n {children}\n
\n )\n}\n\ninterface SpotlightCardTitleProps\n extends React.HTMLAttributes {\n children: React.ReactNode\n}\n\nfunction SpotlightCardTitle({\n children,\n className,\n ...props\n}: SpotlightCardTitleProps) {\n return (\n \n {children}\n \n )\n}\n\ninterface SpotlightCardDescriptionProps\n extends React.HTMLAttributes {\n children: React.ReactNode\n}\n\nfunction SpotlightCardDescription({\n children,\n className,\n ...props\n}: SpotlightCardDescriptionProps) {\n return (\n \n {children}\n

\n )\n}\n\n// A more advanced variant with multiple spotlight sources\ninterface MultiSpotlightCardProps extends React.HTMLAttributes {\n children: React.ReactNode\n colors?: string[]\n borderRadius?: number\n}\n\nfunction MultiSpotlightCard({\n children,\n className,\n colors = [\n \"rgba(120, 119, 198, 0.4)\",\n \"rgba(255, 77, 77, 0.3)\",\n \"rgba(77, 255, 174, 0.3)\",\n ],\n borderRadius = 16,\n ...props\n}: MultiSpotlightCardProps) {\n const containerRef = React.useRef(null)\n const [position, setPosition] = React.useState({ x: 0, y: 0 })\n const [isHovered, setIsHovered] = React.useState(false)\n\n const handleMouseMove = React.useCallback(\n (e: React.MouseEvent) => {\n if (!containerRef.current) return\n\n const rect = containerRef.current.getBoundingClientRect()\n setPosition({\n x: e.clientX - rect.left,\n y: e.clientY - rect.top,\n })\n },\n []\n )\n\n return (\n setIsHovered(true)}\n onMouseLeave={() => setIsHovered(false)}\n className={cn(\n \"relative overflow-hidden\",\n \"bg-white dark:bg-neutral-950\",\n \"border border-neutral-200 dark:border-neutral-800\",\n \"transition-all duration-500\",\n className\n )}\n style={{ borderRadius: `${borderRadius}px` }}\n {...props}\n >\n {/* Multiple spotlight layers */}\n {colors.map((color, index) => (\n \n ))}\n\n {/* Content */}\n
{children}
\n \n )\n}\n\n// Beam spotlight effect - creates a beam of light that follows cursor\ninterface BeamSpotlightCardProps extends React.HTMLAttributes {\n children: React.ReactNode\n beamColor?: string\n beamWidth?: number\n borderRadius?: number\n}\n\nfunction BeamSpotlightCard({\n children,\n className,\n beamColor = \"rgba(120, 119, 198, 0.5)\",\n beamWidth = 200,\n borderRadius = 16,\n ...props\n}: BeamSpotlightCardProps) {\n const containerRef = React.useRef(null)\n const [position, setPosition] = React.useState({ x: 0, y: 0 })\n const [isHovered, setIsHovered] = React.useState(false)\n\n const handleMouseMove = React.useCallback(\n (e: React.MouseEvent) => {\n if (!containerRef.current) return\n\n const rect = containerRef.current.getBoundingClientRect()\n setPosition({\n x: e.clientX - rect.left,\n y: e.clientY - rect.top,\n })\n },\n []\n )\n\n return (\n setIsHovered(true)}\n onMouseLeave={() => setIsHovered(false)}\n className={cn(\n \"relative overflow-hidden\",\n \"bg-white dark:bg-neutral-950\",\n \"border border-neutral-200 dark:border-neutral-800\",\n \"transition-all duration-500\",\n className\n )}\n style={{ borderRadius: `${borderRadius}px` }}\n {...props}\n >\n {/* Vertical beam */}\n \n\n {/* Horizontal beam */}\n \n\n {/* Intersection glow */}\n \n\n {/* Content */}\n
{children}
\n \n )\n}\n\n// Gradient follow card - the background gradient follows the cursor\ninterface GradientFollowCardProps extends React.HTMLAttributes {\n children: React.ReactNode\n gradientColors?: [string, string, string]\n borderRadius?: number\n}\n\nfunction GradientFollowCard({\n children,\n className,\n gradientColors = [\"#7877c6\", \"#5eead4\", \"#f472b6\"],\n borderRadius = 16,\n ...props\n}: GradientFollowCardProps) {\n const containerRef = React.useRef(null)\n const [position, setPosition] = React.useState({ x: 50, y: 50 })\n const [isHovered, setIsHovered] = React.useState(false)\n\n const handleMouseMove = React.useCallback(\n (e: React.MouseEvent) => {\n if (!containerRef.current) return\n\n const rect = containerRef.current.getBoundingClientRect()\n const x = ((e.clientX - rect.left) / rect.width) * 100\n const y = ((e.clientY - rect.top) / rect.height) * 100\n setPosition({ x, y })\n },\n []\n )\n\n return (\n setIsHovered(true)}\n onMouseLeave={() => setIsHovered(false)}\n className={cn(\n \"relative overflow-hidden\",\n \"transition-all duration-500\",\n className\n )}\n style={{ borderRadius: `${borderRadius}px` }}\n {...props}\n >\n {/* Animated gradient background */}\n \n\n {/* Base background */}\n \n\n {/* Border */}\n \n\n {/* Content */}\n
{children}
\n \n )\n}\n\n// Tilt card with 3D perspective\ninterface TiltSpotlightCardProps extends React.HTMLAttributes {\n children: React.ReactNode\n maxTilt?: number\n perspective?: number\n scale?: number\n borderRadius?: number\n glareOpacity?: number\n spotlightColor?: string\n}\n\nfunction TiltSpotlightCard({\n children,\n className,\n maxTilt = 10,\n perspective = 1000,\n scale = 1.02,\n borderRadius = 16,\n glareOpacity = 0.2,\n spotlightColor = \"rgba(120, 119, 198, 0.3)\",\n ...props\n}: TiltSpotlightCardProps) {\n const containerRef = React.useRef(null)\n const [transform, setTransform] = React.useState({\n rotateX: 0,\n rotateY: 0,\n scale: 1,\n })\n const [glarePosition, setGlarePosition] = React.useState({ x: 50, y: 50 })\n const [spotlightPosition, setSpotlightPosition] = React.useState({ x: 0, y: 0 })\n const [isHovered, setIsHovered] = React.useState(false)\n\n const handleMouseMove = React.useCallback(\n (e: React.MouseEvent) => {\n if (!containerRef.current) return\n\n const rect = containerRef.current.getBoundingClientRect()\n const centerX = rect.width / 2\n const centerY = rect.height / 2\n const mouseX = e.clientX - rect.left\n const mouseY = e.clientY - rect.top\n\n const rotateY = ((mouseX - centerX) / centerX) * maxTilt\n const rotateX = -((mouseY - centerY) / centerY) * maxTilt\n\n setTransform({ rotateX, rotateY, scale })\n setGlarePosition({\n x: (mouseX / rect.width) * 100,\n y: (mouseY / rect.height) * 100,\n })\n setSpotlightPosition({\n x: mouseX,\n y: mouseY,\n })\n },\n [maxTilt, scale]\n )\n\n const handleMouseLeave = React.useCallback(() => {\n setTransform({ rotateX: 0, rotateY: 0, scale: 1 })\n setIsHovered(false)\n }, [])\n\n return (\n setIsHovered(true)}\n onMouseLeave={handleMouseLeave}\n className={cn(\n \"relative overflow-hidden\",\n \"bg-white dark:bg-neutral-950\",\n \"border border-neutral-200 dark:border-neutral-800\",\n \"transition-[border-color] duration-500\",\n isHovered && \"border-neutral-300 dark:border-neutral-700\",\n className\n )}\n style={{\n borderRadius: `${borderRadius}px`,\n perspective: `${perspective}px`,\n transform: `\n perspective(${perspective}px)\n rotateX(${transform.rotateX}deg)\n rotateY(${transform.rotateY}deg)\n scale(${transform.scale})\n `,\n transition: isHovered\n ? \"transform 0.1s ease-out\"\n : \"transform 0.5s ease-out\",\n }}\n {...props}\n >\n {/* Glare effect */}\n \n\n {/* Spotlight effect */}\n \n\n {/* Content */}\n
{children}
\n \n )\n}\n\nexport {\n SpotlightCard,\n SpotlightCardContent,\n SpotlightCardHeader,\n SpotlightCardTitle,\n SpotlightCardDescription,\n MultiSpotlightCard,\n BeamSpotlightCard,\n GradientFollowCard,\n TiltSpotlightCard,\n}\n" } ], "tailwind": {}, "cssVars": {}, "meta": {} }