{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "magicdock",
  "type": "registry:ui",
  "title": "Lustre Text",
  "description": "A lustrous text component with animated gradient shine.",
  "author": "Ahdeetai <https://aditya.is-cool.dev>",
  "registryDependencies": [],
  "dependencies": ["motion", "lucide-react"],
  "files": [
    {
      "type": "registry:ui",
      "path": "components/ui/magicdock.tsx",
      "content": "'use client';\n\nimport React, { useState, useEffect, useRef } from 'react';\nimport {\n  motion,\n  useMotionValue,\n  useSpring,\n  useTransform,\n  AnimatePresence,\n} from 'motion/react';\n\ntype SpringOptions = {\n  stiffness?: number;\n  damping?: number;\n  mass?: number;\n  velocity?: number;\n  restSpeed?: number;\n  restDelta?: number;\n};\n\nconst cn = (...classes: (string | undefined | false | null)[]) => {\n  return classes.filter(Boolean).join(' ');\n};\n\nexport type DockItemData = {\n  id: number;\n  icon: React.ReactNode;\n  label: string;\n  description?: string;\n  image?: string;\n  onClick: () => void;\n  className?: string;\n};\n\nexport type MagicDockProps = {\n  items: DockItemData[];\n  className?: string;\n  distance?: number;\n  panelHeight?: number;\n  baseItemSize?: number;\n  dockHeight?: number;\n  magnification?: number;\n  spring?: SpringOptions;\n  variant?: 'default' | 'gradient' | 'tooltip';\n};\n\ntype DockItemProps = {\n  item: DockItemData;\n  mouseX: React.RefObject<number>;\n  spring: SpringOptions;\n  distance: number;\n  baseItemSize: number;\n  magnification: number;\n  variant: 'default' | 'gradient' | 'tooltip';\n  setHoveredIndex: React.Dispatch<React.SetStateAction<number | null>>;\n  hoveredIndex: number | null;\n  isTouchDevice: boolean;\n};\n\nfunction DockItem({\n  item,\n  mouseX,\n  spring,\n  distance,\n  magnification,\n  baseItemSize,\n  variant,\n  setHoveredIndex,\n  hoveredIndex,\n  isTouchDevice,\n}: DockItemProps) {\n  const ref = useRef<HTMLDivElement>(null);\n  const mouseXMotion = useMotionValue(0);\n  const isHovered = useMotionValue(0);\n  const x = useMotionValue(0);\n  const tooltipSpringConfig = { stiffness: 100, damping: 5 };\n\n  const rotate = useSpring(\n    useTransform(x, [-100, 100], [-15, 15]),\n    tooltipSpringConfig,\n  );\n  const translateX = useSpring(\n    useTransform(x, [-100, 100], [-20, 20]),\n    tooltipSpringConfig,\n  );\n\n  useEffect(() => {\n    if (hoveredIndex === item.id) {\n      isHovered.set(1);\n    } else {\n      isHovered.set(0);\n    }\n  }, [hoveredIndex, item.id, isHovered]);\n\n  useEffect(() => {\n    if (isTouchDevice) return;\n\n    const handleMouseMove = (e: MouseEvent) => {\n      if (!ref.current) return;\n      const rect = ref.current.getBoundingClientRect();\n      const distance = e.clientX - (rect.x + rect.width / 2);\n      mouseXMotion.set(distance);\n    };\n\n    window.addEventListener('mousemove', handleMouseMove);\n    return () => {\n      window.removeEventListener('mousemove', handleMouseMove);\n    };\n  }, [mouseXMotion, isTouchDevice]);\n\n  const handleItemMouseMove = (\n    event: React.MouseEvent<HTMLDivElement, MouseEvent>,\n  ) => {\n    if (isTouchDevice) return;\n    const halfWidth = event.currentTarget.offsetWidth / 2;\n    x.set(event.nativeEvent.offsetX - halfWidth);\n  };\n\n  const targetSize = useTransform(\n    mouseXMotion,\n    [-distance, 0, distance],\n    [baseItemSize, isTouchDevice ? baseItemSize : magnification, baseItemSize],\n  );\n  const size = useSpring(targetSize, spring);\n\n  const getBorderStyles = () => {\n    switch (variant) {\n      case 'gradient':\n        return 'border-transparent group-hover:border-slate-700 dark:border-white/20';\n      case 'tooltip':\n        return 'border-white/40 group-hover:border-white';\n      default:\n        return 'border-neutral-700';\n    }\n  };\n\n  return (\n    <motion.div\n      ref={ref}\n      className={`group relative ${item.className || ''}`}\n      style={{\n        width: size,\n        height: size,\n      }}\n      onMouseEnter={() => !isTouchDevice && setHoveredIndex(item.id)}\n      onMouseLeave={() => !isTouchDevice && setHoveredIndex(null)}\n      onMouseMove={handleItemMouseMove}\n      onClick={item.onClick}\n      tabIndex={0}\n      role='button'\n      aria-haspopup='true'\n    >\n      <motion.div\n        className={cn(\n          'relative flex h-full w-full items-center justify-center rounded-full bg-black border-2 shadow-md transition-colors duration-300',\n          getBorderStyles(),\n        )}\n        initial={{}}\n      >\n        {item.image ? (\n          <img\n            src={item.image}\n            alt={item.label}\n            className='h-full w-full rounded-full object-cover object-center p-1'\n          />\n        ) : (\n          <div className='flex items-center justify-center'>{item.icon}</div>\n        )}\n      </motion.div>\n\n      {!isTouchDevice && (\n        <AnimatePresence>\n          {hoveredIndex === item.id && (\n            <motion.div\n              initial={{ opacity: 0, y: -10, scale: 0.8 }}\n              animate={{\n                opacity: 1,\n                y: -20,\n                scale: 1,\n                transition: {\n                  type: 'spring',\n                  stiffness: 260,\n                  damping: 10,\n                },\n              }}\n              exit={{ opacity: 0, y: -10, scale: 0.8 }}\n              style={\n                variant === 'tooltip'\n                  ? {\n                      translateX: translateX,\n                      rotate: rotate,\n                      whiteSpace: 'nowrap',\n                    }\n                  : { whiteSpace: 'nowrap' }\n              }\n              className={cn(\n                'absolute z-50 -translate-x-1/2 flex-col items-center justify-center rounded-md bg-black px-4 py-2 text-xs shadow-xl',\n                variant === 'tooltip' ? '-top-16' : '-top-12',\n              )}\n            >\n              {variant === 'tooltip' && (\n                <>\n                  <div className='absolute inset-x-10 -bottom-px z-30 h-px w-[20%] bg-linear-to-r from-transparent via-emerald-500 to-transparent' />\n                  <div className='absolute -bottom-px z-30 h-px w-[40%] bg-linear-to-r from-transparent via-sky-500 to-transparent' />\n                </>\n              )}\n              <div className='relative z-30 text-base font-bold text-white'>\n                {item.label}\n              </div>\n              {item.description && (\n                <div className='text-xs text-white/70'>{item.description}</div>\n              )}\n            </motion.div>\n          )}\n        </AnimatePresence>\n      )}\n    </motion.div>\n  );\n}\n\nexport default function MagicDock({\n  items,\n  className = '',\n  spring = { mass: 0.1, stiffness: 150, damping: 12 },\n  magnification = 70,\n  distance = 150,\n  panelHeight = 64,\n  dockHeight = 256,\n  baseItemSize = 50,\n  variant = 'default',\n}: MagicDockProps) {\n  const [hoveredIndex, setHoveredIndex] = useState<number | null>(null);\n  const [isTouchDevice, setIsTouchDevice] = useState(false);\n  const mouseX = useRef<number>(Infinity as number);\n  const isHovered = useMotionValue(0);\n\n  useEffect(() => {\n    const mediaQuery = window.matchMedia('(pointer: coarse)');\n\n    const handleChange = (e: MediaQueryListEvent) => {\n      setIsTouchDevice(e.matches);\n    };\n\n    // initial value without triggering a render\n    if (mediaQuery.matches !== isTouchDevice) {\n      // we already have the correct value, no need to setState\n    }\n\n    mediaQuery.addEventListener('change', handleChange);\n\n    return () => {\n      mediaQuery.removeEventListener('change', handleChange);\n    };\n    // eslint-disable-next-line react-hooks/exhaustive-deps\n  }, []);\n\n  useEffect(() => {\n    setIsTouchDevice(window.matchMedia('(pointer: coarse)').matches);\n  }, []);\n\n  const maxHeight = Math.max(dockHeight, magnification + magnification / 2 + 4);\n  const heightRow = useTransform(isHovered, [0, 1], [panelHeight, maxHeight]);\n  const height = useSpring(heightRow, spring);\n\n  const getBgStyles = () => {\n    switch (variant) {\n      case 'gradient':\n        return 'bg-black/85 backdrop-blur-md';\n      case 'tooltip':\n        return 'bg-black/70 backdrop-blur-lg';\n      default:\n        return 'bg-black/90';\n    }\n  };\n\n  return (\n    <motion.div\n      style={{ height, scrollbarWidth: 'none' }}\n      className='mx-2 flex max-w-full items-center'\n    >\n      <motion.div\n        onMouseMove={(e: React.MouseEvent<HTMLDivElement, MouseEvent>) => {\n          if (!isTouchDevice) {\n            isHovered.set(1);\n            mouseX.current = e.pageX;\n          }\n        }}\n        onMouseLeave={() => {\n          if (!isTouchDevice) {\n            isHovered.set(0);\n            mouseX.current = Infinity;\n          }\n        }}\n        className={cn(\n          `absolute bottom-2 left-1/2 transform -translate-x-1/2 flex items-end w-fit gap-4 rounded-2xl border-neutral-700/50 border-2 pb-2 px-4 ${getBgStyles()}`,\n          className,\n        )}\n        style={{ height: panelHeight }}\n        role='toolbar'\n        aria-label='Application dock'\n      >\n        {items.map((item) => (\n          <DockItem\n            key={item.id}\n            item={item}\n            mouseX={mouseX}\n            spring={spring}\n            distance={distance}\n            magnification={magnification}\n            baseItemSize={baseItemSize}\n            variant={variant}\n            setHoveredIndex={setHoveredIndex}\n            hoveredIndex={hoveredIndex}\n            isTouchDevice={isTouchDevice}\n          />\n        ))}\n      </motion.div>\n    </motion.div>\n  );\n}\n"
    }
  ]
}
