{
	"$schema": "https://ui.shadcn.com/schema/registry-item.json",
	"name": "Cubes-TS-CSS",
	"title": "Cubes",
	"description": "3D rotating cube cluster. Supports auto-rotation or hover interaction.",
	"type": "registry:component",
	"files": [
		{
			"type": "registry:file",
			"path": "Cubes.css",
			"target": "@components/Cubes.css",
			"content": ":root {\n  --col-gap: 5%;\n  --row-gap: 5%;\n  --cube-perspective: 99999999px;\n  --cube-face-border: 1px solid #fff;\n  --cube-face-bg: #120F17;\n}\n\n.default-animation {\n  position: relative;\n  width: 50%;\n  aspect-ratio: 1 / 1;\n  height: auto;\n}\n\n.default-animation--scene {\n  display: grid;\n  width: 100%;\n  height: 100%;\n  column-gap: var(--col-gap);\n  row-gap: var(--row-gap);\n  perspective: var(--cube-perspective);\n  grid-auto-rows: 1fr;\n}\n\n.cube {\n  position: relative;\n  width: 100%;\n  height: 100%;\n  aspect-ratio: 1 / 1;\n  transform-style: preserve-3d;\n}\n\n.cube::before {\n  content: '';\n  position: absolute;\n  top: -36px;\n  right: -36px;\n  bottom: -36px;\n  left: -36px;\n}\n\n.default-animation .cube-face {\n  position: absolute;\n  width: 100%;\n  height: 100%;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  background: var(--cube-face-bg);\n  border: var(--cube-face-border);\n  opacity: 1;\n}\n\n.default-animation .cube-face--top {\n  transform: translateY(-50%) rotateX(90deg);\n}\n\n.default-animation .cube-face--bottom {\n  transform: translateY(50%) rotateX(-90deg);\n}\n\n.default-animation .cube-face--left {\n  transform: translateX(-50%) rotateY(-90deg);\n}\n\n.default-animation .cube-face--right {\n  transform: translateX(50%) rotateY(90deg);\n}\n\n.default-animation .cube-face--back,\n.default-animation .cube-face--front {\n  transform: rotateY(-90deg) translateX(50%) rotateY(90deg);\n}\n\n@media (max-width: 768px) {\n  .default-animation {\n    width: 90%;\n  }\n}\n"
		},
		{
			"type": "registry:component",
			"path": "Cubes.tsx",
			"content": "import React, { useCallback, useEffect, useRef } from 'react';\nimport gsap from 'gsap';\nimport './Cubes.css';\n\ninterface Gap {\n  row: number;\n  col: number;\n}\ninterface Duration {\n  enter: number;\n  leave: number;\n}\n\nexport interface CubesProps {\n  gridSize?: number;\n  cubeSize?: number;\n  maxAngle?: number;\n  radius?: number;\n  easing?: gsap.EaseString;\n  duration?: Duration;\n  cellGap?: number | Gap;\n  borderStyle?: string;\n  faceColor?: string;\n  shadow?: boolean | string;\n  autoAnimate?: boolean;\n  rippleOnClick?: boolean;\n  rippleColor?: string;\n  rippleSpeed?: number;\n}\n\nconst Cubes: React.FC<CubesProps> = ({\n  gridSize = 10,\n  cubeSize,\n  maxAngle = 45,\n  radius = 3,\n  easing = 'power3.out',\n  duration = { enter: 0.3, leave: 0.6 },\n  cellGap,\n  borderStyle = '1px solid #fff',\n  faceColor = '#120F17',\n  shadow = false,\n  autoAnimate = true,\n  rippleOnClick = true,\n  rippleColor = '#fff',\n  rippleSpeed = 2\n}) => {\n  const sceneRef = useRef<HTMLDivElement | null>(null);\n  const rafRef = useRef<number | null>(null);\n  const idleTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n  const userActiveRef = useRef(false);\n  const simPosRef = useRef<{ x: number; y: number }>({ x: 0, y: 0 });\n  const simTargetRef = useRef<{ x: number; y: number }>({ x: 0, y: 0 });\n  const simRAFRef = useRef<number | null>(null);\n\n  const colGap =\n    typeof cellGap === 'number'\n      ? `${cellGap}px`\n      : (cellGap as Gap)?.col !== undefined\n        ? `${(cellGap as Gap).col}px`\n        : '5%';\n  const rowGap =\n    typeof cellGap === 'number'\n      ? `${cellGap}px`\n      : (cellGap as Gap)?.row !== undefined\n        ? `${(cellGap as Gap).row}px`\n        : '5%';\n\n  const enterDur = duration.enter;\n  const leaveDur = duration.leave;\n\n  const tiltAt = useCallback(\n    (rowCenter: number, colCenter: number) => {\n      if (!sceneRef.current) return;\n      sceneRef.current.querySelectorAll<HTMLDivElement>('.cube').forEach(cube => {\n        const r = +cube.dataset.row!;\n        const c = +cube.dataset.col!;\n        const dist = Math.hypot(r - rowCenter, c - colCenter);\n        if (dist <= radius) {\n          const pct = 1 - dist / radius;\n          const angle = pct * maxAngle;\n          gsap.to(cube, {\n            duration: enterDur,\n            ease: easing,\n            overwrite: true,\n            rotateX: -angle,\n            rotateY: angle\n          });\n        } else {\n          gsap.to(cube, {\n            duration: leaveDur,\n            ease: 'power3.out',\n            overwrite: true,\n            rotateX: 0,\n            rotateY: 0\n          });\n        }\n      });\n    },\n    [radius, maxAngle, enterDur, leaveDur, easing]\n  );\n\n  const onPointerMove = useCallback(\n    (e: PointerEvent) => {\n      userActiveRef.current = true;\n      if (idleTimerRef.current) clearTimeout(idleTimerRef.current);\n\n      const rect = sceneRef.current!.getBoundingClientRect();\n      const cellW = rect.width / gridSize;\n      const cellH = rect.height / gridSize;\n      const colCenter = (e.clientX - rect.left) / cellW;\n      const rowCenter = (e.clientY - rect.top) / cellH;\n\n      if (rafRef.current) cancelAnimationFrame(rafRef.current);\n      rafRef.current = requestAnimationFrame(() => tiltAt(rowCenter, colCenter));\n\n      idleTimerRef.current = setTimeout(() => {\n        userActiveRef.current = false;\n      }, 3000);\n    },\n    [gridSize, tiltAt]\n  );\n\n  const resetAll = useCallback(() => {\n    if (!sceneRef.current) return;\n    sceneRef.current.querySelectorAll<HTMLDivElement>('.cube').forEach(cube =>\n      gsap.to(cube, {\n        duration: leaveDur,\n        rotateX: 0,\n        rotateY: 0,\n        ease: 'power3.out'\n      })\n    );\n  }, [leaveDur]);\n\n  const onTouchMove = useCallback(\n    (e: TouchEvent) => {\n      e.preventDefault();\n      userActiveRef.current = true;\n      if (idleTimerRef.current) clearTimeout(idleTimerRef.current);\n\n      const rect = sceneRef.current!.getBoundingClientRect();\n      const cellW = rect.width / gridSize;\n      const cellH = rect.height / gridSize;\n\n      const touch = e.touches[0];\n      const colCenter = (touch.clientX - rect.left) / cellW;\n      const rowCenter = (touch.clientY - rect.top) / cellH;\n\n      if (rafRef.current) cancelAnimationFrame(rafRef.current);\n      rafRef.current = requestAnimationFrame(() => tiltAt(rowCenter, colCenter));\n\n      idleTimerRef.current = setTimeout(() => {\n        userActiveRef.current = false;\n      }, 3000);\n    },\n    [gridSize, tiltAt]\n  );\n\n  const onTouchStart = useCallback(() => {\n    userActiveRef.current = true;\n  }, []);\n\n  const onTouchEnd = useCallback(() => {\n    if (!sceneRef.current) return;\n    resetAll();\n  }, [resetAll]);\n\n  const onClick = useCallback(\n    (e: MouseEvent | TouchEvent) => {\n      if (!rippleOnClick || !sceneRef.current) return;\n      const rect = sceneRef.current.getBoundingClientRect();\n      const cellW = rect.width / gridSize;\n      const cellH = rect.height / gridSize;\n\n      const clientX = (e as MouseEvent).clientX || ((e as TouchEvent).touches && (e as TouchEvent).touches[0].clientX);\n      const clientY = (e as MouseEvent).clientY || ((e as TouchEvent).touches && (e as TouchEvent).touches[0].clientY);\n\n      const colHit = Math.floor((clientX - rect.left) / cellW);\n      const rowHit = Math.floor((clientY - rect.top) / cellH);\n\n      const baseRingDelay = 0.15;\n      const baseAnimDur = 0.3;\n      const baseHold = 0.6;\n\n      const spreadDelay = baseRingDelay / rippleSpeed;\n      const animDuration = baseAnimDur / rippleSpeed;\n      const holdTime = baseHold / rippleSpeed;\n\n      const rings: Record<number, HTMLDivElement[]> = {};\n      sceneRef.current.querySelectorAll<HTMLDivElement>('.cube').forEach(cube => {\n        const r = +cube.dataset.row!;\n        const c = +cube.dataset.col!;\n        const dist = Math.hypot(r - rowHit, c - colHit);\n        const ring = Math.round(dist);\n        if (!rings[ring]) rings[ring] = [];\n        rings[ring].push(cube);\n      });\n\n      Object.keys(rings)\n        .map(Number)\n        .sort((a, b) => a - b)\n        .forEach(ring => {\n          const delay = ring * spreadDelay;\n          const faces = rings[ring].flatMap(cube => Array.from(cube.querySelectorAll<HTMLElement>('.cube-face')));\n\n          gsap.to(faces, {\n            backgroundColor: rippleColor,\n            duration: animDuration,\n            delay,\n            ease: 'power3.out'\n          });\n          gsap.to(faces, {\n            backgroundColor: faceColor,\n            duration: animDuration,\n            delay: delay + animDuration + holdTime,\n            ease: 'power3.out'\n          });\n        });\n    },\n    [rippleOnClick, gridSize, faceColor, rippleColor, rippleSpeed]\n  );\n\n  useEffect(() => {\n    if (!autoAnimate || !sceneRef.current) return;\n    simPosRef.current = {\n      x: Math.random() * gridSize,\n      y: Math.random() * gridSize\n    };\n    simTargetRef.current = {\n      x: Math.random() * gridSize,\n      y: Math.random() * gridSize\n    };\n    const speed = 0.02;\n    const loop = () => {\n      if (!userActiveRef.current) {\n        const pos = simPosRef.current;\n        const tgt = simTargetRef.current;\n        pos.x += (tgt.x - pos.x) * speed;\n        pos.y += (tgt.y - pos.y) * speed;\n        tiltAt(pos.y, pos.x);\n        if (Math.hypot(pos.x - tgt.x, pos.y - tgt.y) < 0.1) {\n          simTargetRef.current = {\n            x: Math.random() * gridSize,\n            y: Math.random() * gridSize\n          };\n        }\n      }\n      simRAFRef.current = requestAnimationFrame(loop);\n    };\n    simRAFRef.current = requestAnimationFrame(loop);\n    return () => {\n      if (simRAFRef.current != null) {\n        cancelAnimationFrame(simRAFRef.current);\n      }\n    };\n  }, [autoAnimate, gridSize, tiltAt]);\n\n  useEffect(() => {\n    const el = sceneRef.current;\n    if (!el) return;\n    el.addEventListener('pointermove', onPointerMove);\n    el.addEventListener('pointerleave', resetAll);\n    el.addEventListener('click', onClick);\n\n    el.addEventListener('touchmove', onTouchMove, { passive: false });\n    el.addEventListener('touchstart', onTouchStart, { passive: true });\n    el.addEventListener('touchend', onTouchEnd, { passive: true });\n\n    return () => {\n      el.removeEventListener('pointermove', onPointerMove);\n      el.removeEventListener('pointerleave', resetAll);\n      el.removeEventListener('click', onClick);\n\n      el.removeEventListener('touchmove', onTouchMove);\n      el.removeEventListener('touchstart', onTouchStart);\n      el.removeEventListener('touchend', onTouchEnd);\n\n      rafRef.current != null && cancelAnimationFrame(rafRef.current);\n      idleTimerRef.current && clearTimeout(idleTimerRef.current);\n    };\n  }, [onPointerMove, resetAll, onClick, onTouchMove, onTouchStart, onTouchEnd]);\n\n  const cells = Array.from({ length: gridSize });\n  const sceneStyle: React.CSSProperties = {\n    gridTemplateColumns: cubeSize ? `repeat(${gridSize}, ${cubeSize}px)` : `repeat(${gridSize}, 1fr)`,\n    gridTemplateRows: cubeSize ? `repeat(${gridSize}, ${cubeSize}px)` : `repeat(${gridSize}, 1fr)`,\n    columnGap: colGap,\n    rowGap: rowGap\n  };\n  const wrapperStyle = {\n    '--cube-face-border': borderStyle,\n    '--cube-face-bg': faceColor,\n    '--cube-face-shadow': shadow === true ? '0 0 6px rgba(0,0,0,.5)' : shadow || 'none',\n    ...(cubeSize\n      ? {\n          width: `${gridSize * cubeSize}px`,\n          height: `${gridSize * cubeSize}px`\n        }\n      : {})\n  } as React.CSSProperties;\n\n  return (\n    <div className=\"default-animation\" style={wrapperStyle}>\n      <div ref={sceneRef} className=\"default-animation--scene\" style={sceneStyle}>\n        {cells.map((_, r) =>\n          cells.map((__, c) => (\n            <div key={`${r}-${c}`} className=\"cube\" data-row={r} data-col={c}>\n              <div className=\"cube-face cube-face--top\" />\n              <div className=\"cube-face cube-face--bottom\" />\n              <div className=\"cube-face cube-face--left\" />\n              <div className=\"cube-face cube-face--right\" />\n              <div className=\"cube-face cube-face--front\" />\n              <div className=\"cube-face cube-face--back\" />\n            </div>\n          ))\n        )}\n      </div>\n    </div>\n  );\n};\n\nexport default Cubes;\n"
		}
	],
	"registryDependencies": [],
	"dependencies": [
		"gsap@^3.13.0"
	]
}