{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "grid-reveal",
  "title": "Grid Reveal",
  "description": "A grid of blocks that dissolves to reveal content, in random, wave or diagonal patterns.",
  "dependencies": [
    "motion"
  ],
  "registryDependencies": [],
  "files": [
    {
      "path": "registry/clover-ui/grid-reveal.tsx",
      "content": "\"use client\";\n\nimport { motion, useReducedMotion } from \"motion/react\";\nimport * as React from \"react\";\n\nimport { cn } from \"@/lib/utils\";\n\nexport type GridRevealPattern = \"random\" | \"wave\" | \"diagonal\" | \"center\";\n\nexport const GRID_REVEAL_COLORS = {\n  yellow: \"#fde047\",\n  red: \"#f87171\",\n  blue: \"#60a5fa\",\n  green: \"#4ade80\",\n  white: \"#ffffff\",\n  orange: \"#fb923c\",\n  pink: \"#f472b6\",\n  violet: \"#a78bfa\",\n} as const;\n\nexport type GridRevealColor = keyof typeof GRID_REVEAL_COLORS;\n\nexport interface GridRevealProps {\n  ref?: React.Ref<GridRevealRef>;\n  children?: React.ReactNode;\n  rows?: number;\n  cols?: number;\n  color?: GridRevealColor;\n  blockDuration?: number;\n  stagger?: number;\n  duration?: number;\n  pattern?: GridRevealPattern;\n  loop?: boolean;\n  loopDelay?: number;\n  gap?: number;\n  className?: string;\n  gridClassName?: string;\n  blockClassName?: string;\n  autoStart?: boolean;\n  inView?: boolean;\n  once?: boolean;\n\n  onStart?: () => void;\n  onComplete?: () => void;\n}\n\nexport interface GridRevealRef {\n  play: () => void;\n  cover: () => void;\n  reset: () => void;\n}\n\nfunction shuffledIndexes(count: number) {\n  let seed = 0x9e3779b9 ^ count;\n\n  const random = () => {\n    seed |= 0;\n    seed = (seed + 0x6d2b79f5) | 0;\n\n    let t = Math.imul(seed ^ (seed >>> 15), 1 | seed);\n\n    t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t;\n\n    return ((t ^ (t >>> 14)) >>> 0) / 4294967296;\n  };\n\n  const arr = Array.from({ length: count }, (_, index) => index);\n\n  for (let i = arr.length - 1; i > 0; i--) {\n    const j = Math.floor(random() * (i + 1));\n    [arr[i], arr[j]] = [arr[j], arr[i]];\n  }\n\n  return arr;\n}\n\nfunction getDelayFactor(\n  pattern: GridRevealPattern,\n  row: number,\n  col: number,\n  rows: number,\n  cols: number,\n  shuffled: number[],\n  index: number,\n) {\n  switch (pattern) {\n    case \"wave\":\n      return col;\n\n    case \"diagonal\":\n      return row + col;\n\n    case \"center\": {\n      const midRow = (rows - 1) / 2;\n      const midCol = (cols - 1) / 2;\n\n      return Math.hypot(row - midRow, col - midCol);\n    }\n\n    case \"random\":\n    default:\n      return shuffled[index];\n  }\n}\n\nfunction GridReveal({\n  ref,\n  children,\n  rows = 6,\n  cols = 10,\n  color = \"yellow\",\n  blockDuration = 0.45,\n  stagger = 0.03,\n  duration,\n  pattern = \"random\",\n  loop = true,\n  loopDelay = 0.6,\n  gap = 0,\n  className,\n  gridClassName,\n  blockClassName,\n  autoStart = true,\n  inView = false,\n  once = true,\n  onStart,\n  onComplete,\n}: GridRevealProps) {\n  const [revealed, setRevealed] = React.useState(false);\n  const reducedMotion = useReducedMotion();\n\n  const hasPlayedRef = React.useRef(false);\n  const callbacks = React.useRef({ onStart, onComplete });\n  React.useEffect(() => {\n    callbacks.current = { onStart, onComplete };\n  }, [onStart, onComplete]);\n  const timers = React.useRef(new Set<ReturnType<typeof setTimeout>>());\n  const schedule = React.useCallback((callback: () => void, delay: number) => {\n    const timer = setTimeout(() => {\n      timers.current.delete(timer);\n      callback();\n    }, delay);\n    timers.current.add(timer);\n  }, []);\n\n  const count = rows * cols;\n\n  const shuffled = React.useMemo(() => shuffledIndexes(count), [count]);\n\n  const maxFactor = React.useMemo(() => {\n    let max = 0;\n\n    for (let row = 0; row < rows; row++) {\n      for (let col = 0; col < cols; col++) {\n        const index = row * cols + col;\n\n        const factor = getDelayFactor(pattern, row, col, rows, cols, shuffled, index);\n\n        if (factor > max) {\n          max = factor;\n        }\n      }\n    }\n\n    return max || 1;\n  }, [pattern, rows, cols, shuffled]);\n\n  const perStepDelay = React.useMemo(\n    () => (duration ? duration / (maxFactor + 1) : stagger),\n    [duration, maxFactor, stagger],\n  );\n\n  const blocks = React.useMemo(() => {\n    const items: {\n      key: string;\n      factor: number;\n    }[] = [];\n\n    for (let row = 0; row < rows; row++) {\n      for (let col = 0; col < cols; col++) {\n        const index = row * cols + col;\n\n        const factor = getDelayFactor(pattern, row, col, rows, cols, shuffled, index);\n\n        items.push({\n          key: `${row}-${col}`,\n          factor,\n        });\n      }\n    }\n\n    return items;\n  }, [rows, cols, pattern, shuffled]);\n\n  const clearTimers = React.useCallback(() => {\n    timers.current.forEach(clearTimeout);\n    timers.current.clear();\n  }, []);\n\n  const cycleTime = maxFactor * perStepDelay + blockDuration;\n\n  const play = React.useCallback(() => {\n    clearTimers();\n\n    const revealPhase = () => {\n      setRevealed(true);\n      callbacks.current.onStart?.();\n\n      schedule(() => {\n        callbacks.current.onComplete?.();\n\n        if (!loop || reducedMotion) return;\n\n        schedule(() => {\n          setRevealed(false);\n\n          schedule(revealPhase, cycleTime * 1000 + loopDelay * 1000);\n        }, loopDelay * 1000);\n      }, cycleTime * 1000);\n    };\n\n    revealPhase();\n  }, [clearTimers, schedule, loop, reducedMotion, loopDelay, cycleTime]);\n\n  const cover = React.useCallback(() => {\n    clearTimers();\n    setRevealed(false);\n  }, [clearTimers]);\n\n  const reset = React.useCallback(() => {\n    cover();\n    hasPlayedRef.current = false;\n  }, [cover]);\n\n  React.useImperativeHandle(\n    ref,\n    () => ({\n      play,\n      cover,\n      reset,\n    }),\n    [play, cover, reset],\n  );\n\n  React.useEffect(() => {\n    if (autoStart && !inView) {\n      hasPlayedRef.current = true;\n      play();\n    }\n\n    return clearTimers;\n  }, [autoStart, inView, play, clearTimers]);\n\n  const handleViewportEnter = React.useCallback(() => {\n    if (once && hasPlayedRef.current) {\n      return;\n    }\n\n    hasPlayedRef.current = true;\n    play();\n  }, [once, play]);\n\n  return (\n    <motion.div\n      className={cn(\"relative cursor-pointer overflow-hidden\", className)}\n      viewport={{\n        once,\n        margin: \"-10% 0px -10% 0px\",\n      }}\n      onViewportEnter={inView ? handleViewportEnter : undefined}\n    >\n      {children && <div className=\"relative h-full w-full\">{children}</div>}\n\n      <div\n        className={cn(\"absolute inset-0 grid\", children && \"pointer-events-none\", gridClassName)}\n        style={{\n          gridTemplateColumns: `repeat(${cols}, minmax(0, 1fr))`,\n          gridTemplateRows: `repeat(${rows}, minmax(0, 1fr))`,\n          gap,\n        }}\n        aria-hidden=\"true\"\n      >\n        {blocks.map((block) => (\n          <motion.div\n            key={block.key}\n            className={blockClassName}\n            style={{\n              backgroundColor: GRID_REVEAL_COLORS[color] ?? GRID_REVEAL_COLORS.yellow,\n            }}\n            initial={false}\n            animate={\n              revealed\n                ? {\n                    opacity: 0,\n                    scale: 0.85,\n                  }\n                : {\n                    opacity: 1,\n                    scale: 1,\n                  }\n            }\n            transition={{\n              duration: blockDuration,\n              delay: revealed\n                ? block.factor * perStepDelay\n                : (maxFactor - block.factor) * perStepDelay,\n              ease: \"easeInOut\",\n            }}\n          />\n        ))}\n      </div>\n    </motion.div>\n  );\n}\n\nexport { GridReveal };\n",
      "type": "registry:component"
    }
  ],
  "type": "registry:component"
}