{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "floating-elements",
  "title": "Floating Elements",
  "description": "Collection of floating elements that animate infinitely and react to mouse movement.",
  "dependencies": [
    "motion"
  ],
  "files": [
    {
      "path": "registry/blocks/floating-elements/index.tsx",
      "content": "'use client';\n\nimport { cn } from '@/lib/utils';\nimport {\n  type HTMLMotionProps,\n  motion,\n  useMotionValue,\n  useSpring,\n  useAnimationFrame,\n  type MotionValue,\n  useReducedMotion,\n} from 'motion/react';\nimport React from 'react';\n\ninterface FloatingElementsContextValue {\n  pointerX: MotionValue<number> /** Cursor X relative to the container's left edge */;\n  pointerY: MotionValue<number> /** Cursor Y relative to the container's top edge */;\n  containerWidth: React.RefObject<number> /** Container width (updated via ResizeObserver) */;\n  containerHeight: React.RefObject<number> /** Container height (updated via ResizeObserver) */;\n  isInside: React.RefObject<boolean> /** Whether the pointer is currently inside the container */;\n}\n\nconst FloatingElementsContext = React.createContext<\n  FloatingElementsContextValue | undefined\n>(undefined);\n\nfunction useFloatingElements() {\n  const ctx = React.useContext(FloatingElementsContext);\n  if (!ctx) {\n    throw new Error(\n      'useFloatingElements must be used within a <FloatingElements> provider',\n    );\n  }\n  return ctx;\n}\n\nexport function FloatingElements({\n  className,\n  ...props\n}: React.ComponentPropsWithRef<'div'>) {\n  const containerRef = React.useRef<HTMLDivElement>(null);\n\n  // MotionValues — mutated directly, no setState, no re-renders\n  const pointerX = useMotionValue(0);\n  const pointerY = useMotionValue(0);\n\n  const containerWidth = React.useRef(0);\n  const containerHeight = React.useRef(0);\n  const isInside = React.useRef(false);\n\n  // Keep container dimensions up-to-date without re-renders\n  React.useEffect(() => {\n    const el = containerRef.current;\n    if (!el) return;\n\n    const observer = new ResizeObserver(([entry]) => {\n      const { width, height } = entry.contentRect;\n      containerWidth.current = width;\n      containerHeight.current = height;\n    });\n    observer.observe(el);\n    return () => observer.disconnect();\n  }, []);\n\n  const handlePointerMove = React.useCallback(\n    (e: React.PointerEvent<HTMLDivElement>) => {\n      const el = containerRef.current;\n      if (!el) return;\n      const rect = el.getBoundingClientRect();\n      pointerX.set(e.clientX - rect.left);\n      pointerY.set(e.clientY - rect.top);\n    },\n    [pointerX, pointerY],\n  );\n\n  const handlePointerEnter = React.useCallback(() => {\n    isInside.current = true;\n  }, []);\n\n  const handlePointerLeave = React.useCallback(() => {\n    isInside.current = false;\n  }, []);\n\n  const ctxValue = React.useMemo<FloatingElementsContextValue>(\n    () => ({ pointerX, pointerY, containerWidth, containerHeight, isInside }),\n    [pointerX, pointerY],\n  );\n\n  return (\n    <FloatingElementsContext.Provider value={ctxValue}>\n      <div\n        ref={containerRef}\n        onPointerMove={handlePointerMove}\n        onPointerEnter={handlePointerEnter}\n        onPointerLeave={handlePointerLeave}\n        className={cn('relative', className)}\n        {...props}\n      />\n    </FloatingElementsContext.Provider>\n  );\n}\n\ninterface FloatingElementItemProps extends HTMLMotionProps<'div'> {\n  intensity?: number /** Maximum pixel displacement when the cursor is right on top. Default 50 */;\n  stiffness?: number /** Spring stiffness. Default 150 */;\n  damping?: number /** Spring damping. Default 15 */;\n}\n\nexport function FloatingElementItem({\n  style,\n  intensity = 50,\n  stiffness = 150,\n  damping = 15,\n  ...props\n}: FloatingElementItemProps) {\n  const reducedMotion = useReducedMotion();\n  const ref = React.useRef<HTMLDivElement>(null);\n  const { pointerX, pointerY, containerWidth, containerHeight, isInside } =\n    useFloatingElements();\n\n  // Cached center position (relative to container)\n  const centerX = React.useRef(0);\n  const centerY = React.useRef(0);\n\n  // Cache position on mount & resize\n  React.useEffect(() => {\n    const el = ref.current;\n    if (!el) return;\n\n    const cachePosition = () => {\n      const parent = el.offsetParent as HTMLElement | null;\n      if (!parent) return;\n      centerX.current = el.offsetLeft + el.offsetWidth / 2;\n      centerY.current = el.offsetTop + el.offsetHeight / 2;\n    };\n\n    cachePosition();\n\n    const observer = new ResizeObserver(() => cachePosition());\n    observer.observe(el);\n    if (el.offsetParent) observer.observe(el.offsetParent);\n\n    return () => observer.disconnect();\n  }, []);\n\n  const x = useMotionValue(0);\n  const y = useMotionValue(0);\n  const springX = reducedMotion ? x : useSpring(x, { stiffness, damping });\n  const springY = reducedMotion ? y : useSpring(y, { stiffness, damping });\n\n  useAnimationFrame(() => {\n    if (!isInside.current) {\n      // Smoothly return to origin when cursor leaves the container\n      x.set(0);\n      y.set(0);\n      return;\n    }\n\n    const mx = pointerX.get();\n    const my = pointerY.get();\n\n    const dx = mx - centerX.current;\n    const dy = my - centerY.current;\n    const distance = Math.sqrt(dx * dx + dy * dy);\n\n    // Max possible distance is the diagonal of the container\n    const maxDist = Math.sqrt(\n      containerWidth.current ** 2 + containerHeight.current ** 2,\n    );\n    if (maxDist === 0) return;\n\n    // Normalized proximity: 1 when on top, 0 at farthest corner\n    const proximity = 1 - Math.min(distance / maxDist, 1);\n\n    // Ease the proximity for a more natural curve (quadratic ease-in)\n    const easedProximity = proximity * proximity;\n\n    // Direction: repel away from cursor\n    const angle = Math.atan2(dy, dx);\n    const force = easedProximity * intensity;\n\n    x.set(-Math.cos(angle) * force);\n    y.set(-Math.sin(angle) * force);\n  });\n\n  return (\n    <motion.div\n      ref={ref}\n      style={{\n        x: springX,\n        y: springY,\n        ...style,\n      }}\n      {...props}\n    />\n  );\n}\n\ninterface InfiniteFloatingItemProps extends HTMLMotionProps<'div'> {\n  depth?: number /** Parallax depth factor. Higher = more mouse response. Default 0.03 */;\n  amplitude?: number /** Amplitude of the infinite bob in px. Default 12 */;\n  speed?: number /** Speed of the infinite bob (radians/sec). Default 1 */;\n  phase?: number /** Phase offset in radians so items don't move in unison. Default 0 */;\n  stiffness?: number /** Spring stiffness for parallax response. Default 80 */;\n  damping?: number /** Spring damping for parallax response. Default 20 */;\n}\n\nexport function InfiniteFloatingItem({\n  style,\n  depth = 0.03,\n  amplitude = 12,\n  speed = 1,\n  phase = 0,\n  stiffness = 80,\n  damping = 20,\n  ...props\n}: InfiniteFloatingItemProps) {\n  const { pointerX, pointerY, containerWidth, containerHeight, isInside } =\n    useFloatingElements();\n  const reducedMotion = useReducedMotion();\n\n  const x = useMotionValue(0);\n  const y = useMotionValue(0);\n  const springX = reducedMotion ? x : useSpring(x, { stiffness, damping });\n  const springY = reducedMotion ? y : useSpring(y, { stiffness, damping });\n\n  useAnimationFrame((time) => {\n    // time is in ms — convert to seconds\n    const t = time / 1000;\n\n    // Infinite floating bob (Lissajous-like)\n    const bobX = Math.sin(t * speed + phase) * amplitude;\n    const bobY = Math.cos(t * speed * 0.7 + phase + 1) * amplitude * 0.8;\n\n    // Parallax offset based on mouse position relative to container center\n    let parallaxX = 0;\n    let parallaxY = 0;\n\n    if (isInside.current && containerWidth.current > 0) {\n      // Normalize pointer to -1…1 range from container center\n      const normX =\n        (pointerX.get() - containerWidth.current / 2) /\n        (containerWidth.current / 2);\n      const normY =\n        (pointerY.get() - containerHeight.current / 2) /\n        (containerHeight.current / 2);\n\n      parallaxX = normX * depth * containerWidth.current;\n      parallaxY = normY * depth * containerHeight.current;\n    }\n\n    x.set(bobX + parallaxX);\n    y.set(bobY + parallaxY);\n  });\n\n  return (\n    <motion.div\n      style={{\n        x: springX,\n        y: springY,\n        ...style,\n      }}\n      {...props}\n    />\n  );\n}\n",
      "type": "registry:block",
      "target": "components/systaliko-ui/floating-elements.tsx"
    }
  ],
  "type": "registry:block"
}