{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "radial-intro",
  "title": "Radial Intro",
  "description": "A dynamic radial orbit animation built with Motion and React, featuring upright spinning image nodes.",
  "dependencies": [
    "react",
    "motion"
  ],
  "files": [
    {
      "path": "registry/gammaui/radial-intro.tsx",
      "content": "\"use client\"\n\nimport * as React from \"react\"\nimport {\n  delay,\n  LayoutGroup,\n  motion,\n  useAnimate,\n  type AnimationSequence,\n  type Transition,\n} from \"motion/react\"\n\ninterface RadialIntroProps {\n  orbitItems: OrbitItem[]\n  stageSize?: number\n  imageSize?: number\n}\n\ninterface OrbitItem {\n  id: number\n  name: string\n  src: string\n}\n\nconst transition: Transition = {\n  delay: 0,\n  stiffness: 300,\n  damping: 35,\n  type: \"spring\",\n  restSpeed: 0.01,\n  restDelta: 0.01,\n}\n\nconst spinConfig = {\n  duration: 30,\n  ease: \"linear\" as const,\n  repeat: Infinity,\n}\n\nconst qsa = (root: Element, sel: string) =>\n  Array.from(root.querySelectorAll(sel))\n\nconst angleOf = (el: Element) => Number((el as HTMLElement).dataset.angle || 0)\n\nconst armOfImg = (img: Element) =>\n  (img as HTMLElement).closest(\"[data-arm]\") as HTMLElement | null\n\nexport default function RadialIntro({\n  orbitItems,\n  stageSize = 320,\n  imageSize = 60,\n}: RadialIntroProps) {\n  const step = 360 / orbitItems.length\n  const [scope, animate] = useAnimate()\n\n  React.useEffect(() => {\n    const root = scope.current\n    if (!root) return\n\n    // get arm and image elements\n    const arms = qsa(root, \"[data-arm]\")\n    const imgs = qsa(root, \"[data-arm-image]\")\n    const stops: (() => void)[] = []\n\n    // image lift-in\n    delay(() => animate(imgs, { top: 0 }, transition), 250)\n\n    // build sequence for orbit placement\n    const orbitPlacementSequence: AnimationSequence = [\n      ...arms.map((el): [Element, Record<string, any>, any] => [\n        el,\n        { rotate: angleOf(el) },\n        { ...transition, at: 0 },\n      ]),\n      ...imgs.map((img): [Element, Record<string, any>, any] => [\n        img,\n        { rotate: -angleOf(armOfImg(img)!), opacity: 1 },\n        { ...transition, at: 0 },\n      ]),\n    ]\n\n    // play placement sequence\n    delay(() => animate(orbitPlacementSequence), 700)\n\n    // start continuous spin for arms and images\n    delay(() => {\n      // arms spin clockwise\n      arms.forEach((el) => {\n        const angle = angleOf(el)\n        const ctrl = animate(el, { rotate: [angle, angle + 360] }, spinConfig)\n        stops.push(() => ctrl.cancel())\n      })\n\n      // images counter-spin to stay upright\n      imgs.forEach((img) => {\n        const arm = armOfImg(img)\n        const angle = arm ? angleOf(arm) : 0\n        const ctrl = animate(\n          img,\n          { rotate: [-angle, -angle - 360] },\n          spinConfig\n        )\n        stops.push(() => ctrl.cancel())\n      })\n    }, 1300)\n\n    return () => stops.forEach((stop) => stop())\n  }, [])\n\n  return (\n    <LayoutGroup>\n      <motion.div\n        ref={scope}\n        className=\"relative overflow-visible\"\n        style={{ width: stageSize, height: stageSize }}\n        initial={false}\n      >\n        {orbitItems.map((item, i) => (\n          <motion.div\n            key={item.id}\n            data-arm\n            className=\"absolute inset-0 will-change-transform\"\n            style={{ zIndex: orbitItems.length - i }}\n            data-angle={i * step}\n            layoutId={`arm-${item.id}`}\n          >\n            <motion.img\n              data-arm-image\n              className=\"translate absolute top-1/2 left-1/2 aspect-square -translate-x-1/2 rounded-full bg-black object-fill object-center\"\n              style={{\n                width: imageSize,\n                height: imageSize,\n                opacity: i === 0 ? 1 : 0,\n              }}\n              src={item.src}\n              alt={item.name}\n              draggable={false}\n              layoutId={`arm-img-${item.id}`}\n            />\n          </motion.div>\n        ))}\n      </motion.div>\n    </LayoutGroup>\n  )\n}\n",
      "type": "registry:ui"
    }
  ],
  "type": "registry:ui"
}