{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "spring-element",
  "type": "registry:ui",
  "title": "Spring Element",
  "description": "A flexible, animated spring component that attaches a draggable element (avatar, text, icon, or any React node) to its origin with a spring line.",
  "dependencies": [
    "motion"
  ],
  "registryDependencies": [],
  "files": [
    {
      "path": "registry/components/spring-element/index.tsx",
      "content": "'use client';\n\nimport * as React from 'react';\nimport {\n  type HTMLMotionProps,\n  motion,\n  useMotionValue,\n  useSpring,\n} from 'motion/react';\nimport { cn } from '@/lib/utils';\n\nconst generateSpringPath = (\n  x1: number,\n  y1: number,\n  x2: number,\n  y2: number,\n  springConfig: {\n    coilCount?: number;\n    amplitudeMin?: number;\n    amplitudeMax?: number;\n    curveRatioMin?: number;\n    curveRatioMax?: number;\n    bezierOffset?: number;\n  } = {},\n) => {\n  const {\n    coilCount = 8,\n    amplitudeMin = 8,\n    amplitudeMax = 20,\n    curveRatioMin = 0.5,\n    curveRatioMax = 1,\n    bezierOffset = 8,\n  } = springConfig;\n\n  const dx = x2 - x1;\n  const dy = y2 - y1;\n  const dist = Math.sqrt(dx * dx + dy * dy);\n  if (dist < 2) return `M${x1},${y1}`;\n  const d = dist / coilCount;\n  const h = Math.max(0.8, 1 - (dist - 40) / 200);\n  const amplitude = Math.max(\n    amplitudeMin,\n    Math.min(amplitudeMax, amplitudeMax * h),\n  );\n  const curveRatio =\n    dist <= 40\n      ? curveRatioMax\n      : dist <= 120\n        ? curveRatioMax - ((dist - 40) / 80) * (curveRatioMax - curveRatioMin)\n        : curveRatioMin;\n  const ux = dx / dist,\n    uy = dy / dist;\n  const perpX = -uy,\n    perpY = ux;\n\n  let path = [];\n  for (let i = 0; i < coilCount; i++) {\n    const sx = x1 + ux * (i * d);\n    const sy = y1 + uy * (i * d);\n    const ex = x1 + ux * ((i + 1) * d);\n    const ey = y1 + uy * ((i + 1) * d);\n\n    const mx = x1 + ux * ((i + 0.5) * d) + perpX * amplitude;\n    const my = y1 + uy * ((i + 0.5) * d) + perpY * amplitude;\n\n    const c1x = sx + d * curveRatio * ux;\n    const c1y = sy + d * curveRatio * uy;\n    const c2x = mx + ux * bezierOffset;\n    const c2y = my + uy * bezierOffset;\n    const c3x = mx - ux * bezierOffset;\n    const c3y = my - uy * bezierOffset;\n    const c4x = ex - d * curveRatio * ux;\n    const c4y = ey - d * curveRatio * uy;\n\n    if (i === 0) path.push(`M${sx},${sy}`);\n    else path.push(`L${sx},${sy}`);\n    path.push(`C${c1x},${c1y} ${c2x},${c2y} ${mx},${my}`);\n    path.push(`C${c3x},${c3y} ${c4x},${c4y} ${ex},${ey}`);\n  }\n  return path.join(' ');\n};\n\nfunction useMotionValueValue(mv: any) {\n  return React.useSyncExternalStore(\n    (callback) => {\n      const unsub = mv.on('change', callback);\n      return unsub;\n    },\n    () => mv.get(),\n    () => mv.get(),\n  );\n}\n\ntype SpringAvatarProps = {\n  children: React.ReactElement;\n  className?: string;\n  springClassName?: string;\n  dragElastic?: number;\n  springConfig?: { stiffness?: number; damping?: number };\n  springPathConfig?: {\n    coilCount?: number;\n    amplitudeMin?: number;\n    amplitudeMax?: number;\n    curveRatioMin?: number;\n    curveRatioMax?: number;\n    bezierOffset?: number;\n  };\n} & HTMLMotionProps<'div'>;\n\nfunction SpringElement({\n  ref,\n  children,\n  className,\n  springClassName,\n  dragElastic = 0.2,\n  springConfig = { stiffness: 200, damping: 16 },\n  springPathConfig = {},\n  ...props\n}: SpringAvatarProps) {\n  const x = useMotionValue(0);\n  const y = useMotionValue(0);\n\n  const springX = useSpring(x, {\n    stiffness: springConfig.stiffness,\n    damping: springConfig.damping,\n  });\n  const springY = useSpring(y, {\n    stiffness: springConfig.stiffness,\n    damping: springConfig.damping,\n  });\n\n  const sx = useMotionValueValue(springX);\n  const sy = useMotionValueValue(springY);\n\n  const childRef = React.useRef<HTMLDivElement>(null);\n  React.useImperativeHandle(ref, () => childRef.current as HTMLDivElement);\n  const [center, setCenter] = React.useState({ x: 0, y: 0 });\n  const [isDragging, setIsDragging] = React.useState(false);\n\n  React.useLayoutEffect(() => {\n    function update() {\n      if (childRef.current) {\n        const rect = childRef.current.getBoundingClientRect();\n        setCenter({\n          x: rect.left + rect.width / 2,\n          y: rect.top + rect.height / 2,\n        });\n      }\n    }\n    update();\n    window.addEventListener('resize', update);\n    window.addEventListener('scroll', update, true);\n    return () => {\n      window.removeEventListener('resize', update);\n      window.removeEventListener('scroll', update, true);\n    };\n  }, []);\n\n  React.useEffect(() => {\n    if (isDragging) {\n      document.body.style.cursor = 'grabbing';\n    } else {\n      document.body.style.cursor = 'default';\n    }\n  }, [isDragging]);\n\n  const path = generateSpringPath(\n    center.x,\n    center.y,\n    center.x + sx,\n    center.y + sy,\n    springPathConfig,\n  );\n\n  return (\n    <>\n      <svg\n        width=\"100vw\"\n        height=\"100vh\"\n        className=\"fixed inset-0 w-screen h-screen pointer-events-none z-40 inset-0\"\n      >\n        <path\n          d={path}\n          strokeLinecap=\"round\"\n          strokeLinejoin=\"round\"\n          className={cn(\n            'stroke-2 stroke-neutral-900 dark:stroke-neutral-100 fill-none',\n            springClassName,\n          )}\n        />\n      </svg>\n      <motion.div\n        ref={childRef}\n        className={cn(\n          'z-50',\n          isDragging ? 'cursor-grabbing' : 'cursor-grab',\n          className,\n        )}\n        style={{\n          x: springX,\n          y: springY,\n        }}\n        drag\n        dragElastic={dragElastic}\n        dragMomentum={false}\n        onDragStart={() => {\n          setIsDragging(true);\n        }}\n        onDrag={(_, info) => {\n          x.set(info.offset.x);\n          y.set(info.offset.y);\n        }}\n        onDragEnd={() => {\n          x.set(0);\n          y.set(0);\n          setIsDragging(false);\n        }}\n        {...props}\n      >\n        {children}\n      </motion.div>\n    </>\n  );\n}\n\nexport { SpringElement };\n",
      "type": "registry:ui",
      "target": "components/animate-ui/components/spring-element.tsx"
    }
  ]
}