{
	"$schema": "https://ui.shadcn.com/schema/registry-item.json",
	"name": "OrbitImages-TS-CSS",
	"title": "OrbitImages",
	"description": "SVG Path customizable orbiting images effect",
	"type": "registry:component",
	"files": [
		{
			"type": "registry:file",
			"path": "OrbitImages.css",
			"target": "@components/OrbitImages.css",
			"content": ".orbit-container {\n  position: relative;\n  margin-left: auto;\n  margin-right: auto;\n}\n\n.orbit-scaling-container {\n  width: 100%;\n  height: 100%;\n  position: relative;\n}\n\n.orbit-scaling-container--responsive {\n  position: absolute;\n  left: 50%;\n  top: 50%;\n  transform-origin: center center;\n}\n\n.orbit-rotation-wrapper {\n  width: 100%;\n  height: 100%;\n  transform-origin: center center;\n  position: relative;\n}\n\n.orbit-path-svg {\n  position: absolute;\n  inset: 0;\n  pointer-events: none;\n}\n\n.orbit-item {\n  position: absolute;\n  will-change: transform;\n  user-select: none;\n}\n\n.orbit-center-content {\n  position: absolute;\n  inset: 0;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  z-index: 10;\n}\n\n.orbit-image {\n  width: 100%;\n  height: 100%;\n  object-fit: contain;\n}\n"
		},
		{
			"type": "registry:component",
			"path": "OrbitImages.tsx",
			"content": "// Component created by Dominik Koch\n// https://x.com/dominikkoch\n\nimport { useMemo, useEffect, useLayoutEffect, useRef, useState, type ReactNode } from 'react';\nimport { motion, useMotionValue, useTransform, animate, MotionValue } from 'motion/react';\nimport './OrbitImages.css';\n\ntype OrbitShape =\n  | 'ellipse'\n  | 'circle'\n  | 'square'\n  | 'rectangle'\n  | 'triangle'\n  | 'star'\n  | 'heart'\n  | 'infinity'\n  | 'wave'\n  | 'custom';\n\ninterface OrbitImagesProps {\n  images?: string[];\n  altPrefix?: string;\n  shape?: OrbitShape;\n  customPath?: string;\n  baseWidth?: number;\n  radiusX?: number;\n  radiusY?: number;\n  radius?: number;\n  starPoints?: number;\n  starInnerRatio?: number;\n  rotation?: number;\n  duration?: number;\n  itemSize?: number;\n  direction?: 'normal' | 'reverse';\n  fill?: boolean;\n  width?: number | '100%';\n  height?: number | 'auto';\n  className?: string;\n  showPath?: boolean;\n  pathColor?: string;\n  pathWidth?: number;\n  easing?: 'linear' | 'easeIn' | 'easeOut' | 'easeInOut';\n  paused?: boolean;\n  centerContent?: ReactNode;\n  responsive?: boolean;\n}\n\ninterface OrbitItemProps {\n  item: ReactNode;\n  index: number;\n  totalItems: number;\n  path: string;\n  itemSize: number;\n  rotation: number;\n  progress: MotionValue<number>;\n  fill: boolean;\n}\n\nfunction generateEllipsePath(cx: number, cy: number, rx: number, ry: number): string {\n  return `M ${cx - rx} ${cy} A ${rx} ${ry} 0 1 0 ${cx + rx} ${cy} A ${rx} ${ry} 0 1 0 ${cx - rx} ${cy}`;\n}\n\nfunction generateCirclePath(cx: number, cy: number, r: number): string {\n  return generateEllipsePath(cx, cy, r, r);\n}\n\nfunction generateSquarePath(cx: number, cy: number, size: number): string {\n  const h = size / 2;\n  return `M ${cx - h} ${cy - h} L ${cx + h} ${cy - h} L ${cx + h} ${cy + h} L ${cx - h} ${cy + h} Z`;\n}\n\nfunction generateRectanglePath(cx: number, cy: number, w: number, h: number): string {\n  const hw = w / 2;\n  const hh = h / 2;\n  return `M ${cx - hw} ${cy - hh} L ${cx + hw} ${cy - hh} L ${cx + hw} ${cy + hh} L ${cx - hw} ${cy + hh} Z`;\n}\n\nfunction generateTrianglePath(cx: number, cy: number, size: number): string {\n  const height = (size * Math.sqrt(3)) / 2;\n  const hs = size / 2;\n  return `M ${cx} ${cy - height / 1.5} L ${cx + hs} ${cy + height / 3} L ${cx - hs} ${cy + height / 3} Z`;\n}\n\nfunction generateStarPath(cx: number, cy: number, outerR: number, innerR: number, points: number): string {\n  const step = Math.PI / points;\n  let path = '';\n  for (let i = 0; i < 2 * points; i++) {\n    const r = i % 2 === 0 ? outerR : innerR;\n    const angle = i * step - Math.PI / 2;\n    const x = cx + r * Math.cos(angle);\n    const y = cy + r * Math.sin(angle);\n    path += i === 0 ? `M ${x} ${y}` : ` L ${x} ${y}`;\n  }\n  return path + ' Z';\n}\n\nfunction generateHeartPath(cx: number, cy: number, size: number): string {\n  const s = size / 30;\n  return `M ${cx} ${cy + 12 * s} C ${cx - 20 * s} ${cy - 5 * s}, ${cx - 12 * s} ${cy - 18 * s}, ${cx} ${cy - 8 * s} C ${cx + 12 * s} ${cy - 18 * s}, ${cx + 20 * s} ${cy - 5 * s}, ${cx} ${cy + 12 * s}`;\n}\n\nfunction generateInfinityPath(cx: number, cy: number, w: number, h: number): string {\n  const hw = w / 2;\n  const hh = h / 2;\n  return `M ${cx} ${cy} C ${cx + hw * 0.5} ${cy - hh}, ${cx + hw} ${cy - hh}, ${cx + hw} ${cy} C ${cx + hw} ${cy + hh}, ${cx + hw * 0.5} ${cy + hh}, ${cx} ${cy} C ${cx - hw * 0.5} ${cy + hh}, ${cx - hw} ${cy + hh}, ${cx - hw} ${cy} C ${cx - hw} ${cy - hh}, ${cx - hw * 0.5} ${cy - hh}, ${cx} ${cy}`;\n}\n\nfunction generateWavePath(cx: number, cy: number, w: number, amplitude: number, waves: number): string {\n  const pts: string[] = [];\n  const segs = waves * 20;\n  const hw = w / 2;\n  for (let i = 0; i <= segs; i++) {\n    const x = cx - hw + (w * i) / segs;\n    const y = cy + Math.sin((i / segs) * waves * 2 * Math.PI) * amplitude;\n    pts.push(i === 0 ? `M ${x} ${y}` : `L ${x} ${y}`);\n  }\n  for (let i = segs; i >= 0; i--) {\n    const x = cx - hw + (w * i) / segs;\n    const y = cy - Math.sin((i / segs) * waves * 2 * Math.PI) * amplitude;\n    pts.push(`L ${x} ${y}`);\n  }\n  return pts.join(' ') + ' Z';\n}\n\nfunction OrbitItem({ item, index, totalItems, path, itemSize, rotation, progress, fill }: OrbitItemProps) {\n  const itemOffset = fill ? (index / totalItems) * 100 : 0;\n\n  const offsetDistance = useTransform(progress, (p: number) => {\n    const offset = (((p + itemOffset) % 100) + 100) % 100;\n    return `${offset}%`;\n  });\n\n  return (\n    <motion.div\n      className=\"orbit-item\"\n      style={{\n        width: itemSize,\n        height: itemSize,\n        offsetPath: `path(\"${path}\")`,\n        offsetRotate: '0deg',\n        offsetAnchor: 'center center',\n        offsetDistance,\n      }}\n    >\n      <div style={{ transform: `rotate(${-rotation}deg)` }}>{item}</div>\n    </motion.div>\n  );\n}\n\nexport default function OrbitImages({\n  images = [],\n  altPrefix = 'Orbiting image',\n  shape = 'ellipse',\n  customPath,\n  baseWidth = 1400,\n  radiusX = 700,\n  radiusY = 170,\n  radius = 300,\n  starPoints = 5,\n  starInnerRatio = 0.5,\n  rotation = -8,\n  duration = 40,\n  itemSize = 64,\n  direction = 'normal',\n  fill = true,\n  width = 100,\n  height = 100,\n  className = '',\n  showPath = false,\n  pathColor = 'rgba(0,0,0,0.1)',\n  pathWidth = 2,\n  easing = 'linear',\n  paused = false,\n  centerContent,\n  responsive = false,\n}: OrbitImagesProps) {\n  const containerRef = useRef<HTMLDivElement>(null);\n  const [scale, setScale] = useState<number | null>(null);\n\n  const designCenterX = baseWidth / 2;\n  const designCenterY = baseWidth / 2;\n\n  const path = useMemo(() => {\n    switch (shape) {\n      case 'circle':\n        return generateCirclePath(designCenterX, designCenterY, radius);\n      case 'ellipse':\n        return generateEllipsePath(designCenterX, designCenterY, radiusX, radiusY);\n      case 'square':\n        return generateSquarePath(designCenterX, designCenterY, radius * 2);\n      case 'rectangle':\n        return generateRectanglePath(designCenterX, designCenterY, radiusX * 2, radiusY * 2);\n      case 'triangle':\n        return generateTrianglePath(designCenterX, designCenterY, radius * 2);\n      case 'star':\n        return generateStarPath(designCenterX, designCenterY, radius, radius * starInnerRatio, starPoints);\n      case 'heart':\n        return generateHeartPath(designCenterX, designCenterY, radius * 2);\n      case 'infinity':\n        return generateInfinityPath(designCenterX, designCenterY, radiusX * 2, radiusY * 2);\n      case 'wave':\n        return generateWavePath(designCenterX, designCenterY, radiusX * 2, radiusY, 3);\n      case 'custom':\n        return customPath || generateCirclePath(designCenterX, designCenterY, radius);\n      default:\n        return generateEllipsePath(designCenterX, designCenterY, radiusX, radiusY);\n    }\n  }, [shape, customPath, designCenterX, designCenterY, radiusX, radiusY, radius, starPoints, starInnerRatio]);\n\n  useLayoutEffect(() => {\n    if (!responsive || !containerRef.current) return;\n    const updateScale = () => {\n      if (!containerRef.current) return;\n      setScale(containerRef.current.clientWidth / baseWidth);\n    };\n    updateScale();\n    const observer = new ResizeObserver(updateScale);\n    observer.observe(containerRef.current);\n    return () => observer.disconnect();\n  }, [responsive, baseWidth]);\n\n  const progress = useMotionValue(0);\n\n  useEffect(() => {\n    if (paused) return;\n    const controls = animate(progress, direction === 'reverse' ? -100 : 100, {\n      duration,\n      ease: easing,\n      repeat: Infinity,\n      repeatType: 'loop',\n    });\n    return () => controls.stop();\n  }, [progress, duration, easing, direction, paused]);\n\n  const containerWidth = responsive ? '100%' : (typeof width === 'number' ? width : '100%');\n  const containerHeight = responsive ? 'auto' : (typeof height === 'number' ? height : (typeof width === 'number' ? width : 'auto'));\n\n  const items = images.map((src, index) => (\n    <img\n      key={src}\n      src={src}\n      alt={`${altPrefix} ${index + 1}`}\n      draggable={false}\n      className=\"orbit-image\"\n    />\n  ));\n\n  return (\n    <div\n      ref={containerRef}\n      className={`orbit-container ${className}`}\n      style={{\n        width: containerWidth,\n        height: containerHeight,\n        aspectRatio: responsive ? '1 / 1' : undefined,\n      }}\n      aria-hidden=\"true\"\n    >\n      <div\n        className={responsive ? 'orbit-scaling-container orbit-scaling-container--responsive' : 'orbit-scaling-container'}\n        style={{\n          width: responsive ? baseWidth : '100%',\n          height: responsive ? baseWidth : '100%',\n          transform: responsive && scale !== null ? `translate(-50%, -50%) scale(${scale})` : undefined,\n          visibility: responsive && scale === null ? 'hidden' : undefined,\n        }}\n      >\n        <div\n          className=\"orbit-rotation-wrapper\"\n          style={{ transform: `rotate(${rotation}deg)` }}\n        >\n          {showPath && (\n            <svg\n              width=\"100%\"\n              height=\"100%\"\n              viewBox={`0 0 ${baseWidth} ${baseWidth}`}\n              className=\"orbit-path-svg\"\n            >\n              <path d={path} fill=\"none\" stroke={pathColor} strokeWidth={pathWidth / (scale ?? 1)} />\n            </svg>\n          )}\n\n          {items.map((item, index) => (\n            <OrbitItem\n              key={index}\n              item={item}\n              index={index}\n              totalItems={items.length}\n              path={path}\n              itemSize={itemSize}\n              rotation={rotation}\n              progress={progress}\n              fill={fill}\n            />\n          ))}\n        </div>\n      </div>\n\n      {centerContent && (\n        <div className=\"orbit-center-content\">\n          {centerContent}\n        </div>\n      )}\n    </div>\n  );\n}\n"
		}
	],
	"registryDependencies": [],
	"dependencies": [
		"motion@^12.23.12"
	]
}