{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "trippy",
  "type": "registry:block",
  "title": "Trippy",
  "description": "Trippy",
  "files": [
    {
      "path": "components/usages/trippyusage.tsx",
      "content": "import Trippy from \"@/registry/open-source/trippy\";\n\nexport default function Usage() {\n\treturn (\n\t\t<div className=\"h-screen w-screen relative\">\n\t\t\t<Trippy />\n\t\t\t<div\n\t\t\t\tstyle={{\n\t\t\t\t\tposition: \"absolute\",\n\t\t\t\t\ttop: 8,\n\t\t\t\t\tleft: 8,\n\t\t\t\t\tfontSize: \"0.6em\",\n\t\t\t\t\tcolor: \"white\",\n\t\t\t\t}}\n\t\t\t>\n\t\t\t\t{\"Move mouse & Hold mouse down\"}\n\t\t\t</div>\n\t\t</div>\n\t);\n}\n",
      "type": "registry:block",
      "target": "~/example.tsx"
    },
    {
      "path": "components/usages/trippyusage.tsx",
      "content": "import Trippy from \"@/registry/open-source/trippy\";\n\nexport default function Usage() {\n\treturn (\n\t\t<div className=\"h-screen w-screen relative\">\n\t\t\t<Trippy />\n\t\t\t<div\n\t\t\t\tstyle={{\n\t\t\t\t\tposition: \"absolute\",\n\t\t\t\t\ttop: 8,\n\t\t\t\t\tleft: 8,\n\t\t\t\t\tfontSize: \"0.6em\",\n\t\t\t\t\tcolor: \"white\",\n\t\t\t\t}}\n\t\t\t>\n\t\t\t\t{\"Move mouse & Hold mouse down\"}\n\t\t\t</div>\n\t\t</div>\n\t);\n}\n",
      "type": "registry:ui"
    },
    {
      "path": "registry/open-source/trippy.tsx",
      "content": "import React, { useCallback, useMemo, useRef, useState } from \"react\";\n\nimport { motion } from \"motion/react\";\n\n// Credit:\n// https://github.com/tkh44/data-driven-motion/blob/master/demo/src/demos/Trippy.js\n\ntype TrippyProps = {\n\tdata?: unknown[];\n\t// Diameter in pixels for each circle\n\tcircleSize?: number;\n};\n\nconst DEFAULT_DATA = Array.from({ length: 8 }, (_, i) => ({\n\tkey: `item-${i}`,\n}));\n\nexport default function Trippy({\n\tdata = DEFAULT_DATA,\n\tcircleSize = 48,\n}: TrippyProps) {\n\tconst containerRef = useRef<HTMLDivElement | null>(null);\n\tconst [isMouseDown, setIsMouseDown] = useState(false);\n\tconst [mouseX, setMouseX] = useState(0);\n\tconst [mouseY, setMouseY] = useState(0);\n\n\tconst handleMouseMove = useCallback(\n\t\t(e: React.MouseEvent<HTMLDivElement>) => {\n\t\t\tconst rect = containerRef.current?.getBoundingClientRect();\n\t\t\tif (!rect) return;\n\t\t\tsetMouseX(e.clientX - rect.left);\n\t\t\tsetMouseY(e.clientY - rect.top);\n\t\t},\n\t\t[]\n\t);\n\n\tconst handleMouseDown = useCallback(() => {\n\t\tsetIsMouseDown(true);\n\t}, []);\n\n\tconst handleMouseUpOrLeave = useCallback(() => {\n\t\tsetIsMouseDown(false);\n\t}, []);\n\n\tconst satellites = useMemo(() => {\n\t\ttype HasKey = { key?: string | number };\n\t\tconst hasKey = (o: unknown): o is HasKey =>\n\t\t\ttypeof o === \"object\" &&\n\t\t\to !== null &&\n\t\t\t\"key\" in (o as Record<string, unknown>);\n\t\tconst num = data.length;\n\t\tif (num === 0)\n\t\t\treturn [] as Array<{\n\t\t\t\tkey: string | number;\n\t\t\t\tx: number;\n\t\t\t\ty: number;\n\t\t\t\thue: number;\n\t\t\t}>;\n\t\treturn data.map((item, index) => {\n\t\t\t// Stack directly under the cursor; no polar offset when stacking\n\t\t\tconst x = mouseX;\n\t\t\tconst y = mouseY;\n\t\t\tconst hue = (((mouseX + mouseY + index * 45) % 360) + 360) % 360;\n\t\t\tconst key = hasKey(item) && item.key !== undefined ? item.key : index;\n\t\t\treturn { key, x, y, hue };\n\t\t});\n\t}, [data, mouseX, mouseY]);\n\n\tconst diameter = circleSize;\n\tconst radius = diameter / 2;\n\n\treturn (\n\t\t<div\n\t\t\tref={containerRef}\n\t\t\tstyle={{\n\t\t\t\tposition: \"relative\",\n\t\t\t\twidth: \"100%\",\n\t\t\t\theight: \"100%\",\n\t\t\t\tbackground: \"#0f1115\",\n\t\t\t\toverflow: \"hidden\",\n\t\t\t\tcursor: \"none\",\n\t\t\t}}\n\t\t\tonMouseMove={handleMouseMove}\n\t\t\tonMouseDown={handleMouseDown}\n\t\t\tonMouseUp={handleMouseUpOrLeave}\n\t\t\tonMouseLeave={handleMouseUpOrLeave}\n\t\t>\n\t\t\t{/* Primary follower circle */}\n\t\t\t<div\n\t\t\t\tstyle={{\n\t\t\t\t\tposition: \"absolute\",\n\t\t\t\t\ttop: 0,\n\t\t\t\t\tleft: 0,\n\t\t\t\t\twidth: `${diameter}px`,\n\t\t\t\t\theight: `${diameter}px`,\n\t\t\t\t\tborderRadius: \"50%\",\n\t\t\t\t\ttransform: `translate(${mouseX - radius}px, ${mouseY - radius}px)`,\n\t\t\t\t\tbackground: \"transparent\",\n\t\t\t\t\tborder: `3px solid hsl(${(((mouseX + mouseY) % 360) + 360) % 360}, 70%, 55%)`,\n\t\t\t\t\tboxShadow: \"0 0 12px rgba(255,255,255,0.15)\",\n\t\t\t\t\ttransition:\n\t\t\t\t\t\t\"transform 120ms ease-out, border-color 200ms linear\",\n\t\t\t\t\tpointerEvents: \"none\",\n\t\t\t\t}}\n\t\t\t/>\n\n\t\t\t{/* Satellite circles when mouse is down */}\n\t\t\t{satellites.map(({ key, x, y, hue }, idx) => (\n\t\t\t\t<motion.div\n\t\t\t\t\tkey={key}\n\t\t\t\t\tstyle={{\n\t\t\t\t\t\tposition: \"absolute\",\n\t\t\t\t\t\ttop: 0,\n\t\t\t\t\t\tleft: 0,\n\t\t\t\t\t\twidth: `${diameter + idx * 15}px`,\n\t\t\t\t\t\theight: `${diameter + idx * 15}px`,\n\t\t\t\t\t\tborderRadius: \"50%\",\n\t\t\t\t\t\ttransform: `translate3d(calc(${x}px - 5vw), calc(${y}px - 5vh), ${idx * 30}px)`,\n\t\t\t\t\t\tbackground: \"transparent\",\n\t\t\t\t\t\tborder: `2px solid hsl(${hue + idx * 20}, 65%, 50%)`,\n\t\t\t\t\t\topacity: isMouseDown ? 1 : 0,\n\t\t\t\t\t\tzIndex: isMouseDown ? 1000 + idx : 0,\n\t\t\t\t\t\ttransition:\n\t\t\t\t\t\t\t\"transform 180ms ease-out, width 180ms ease-out, height 180ms ease-out, opacity 120ms ease-out\",\n\t\t\t\t\t\tpointerEvents: \"none\",\n\t\t\t\t\t}}\n\t\t\t\t/>\n\t\t\t))}\n\t\t</div>\n\t);\n}\n",
      "type": "registry:ui"
    }
  ]
}