{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "matrix",
  "files": [
    {
      "path": "components/ui/matrix.tsx",
      "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { useEffect, useMemo, useRef, useState } from \"react\"\n\nimport { cn } from \"@/lib/utils\"\n\nexport type Frame = number[][]\ntype MatrixMode = \"default\" | \"vu\"\n\ninterface CellPosition {\n  x: number\n  y: number\n}\n\ninterface MatrixProps extends React.HTMLAttributes<HTMLDivElement> {\n  rows: number\n  cols: number\n  pattern?: Frame\n  frames?: Frame[]\n  fps?: number\n  autoplay?: boolean\n  loop?: boolean\n  size?: number\n  gap?: number\n  palette?: {\n    on: string\n    off: string\n  }\n  brightness?: number\n  ariaLabel?: string\n  onFrame?: (index: number) => void\n  mode?: MatrixMode\n  levels?: number[]\n}\n\nfunction clamp(value: number): number {\n  return Math.max(0, Math.min(1, value))\n}\n\nfunction ensureFrameSize(frame: Frame, rows: number, cols: number): Frame {\n  const result: Frame = []\n  for (let r = 0; r < rows; r++) {\n    const row = frame[r] || []\n    result.push([])\n    for (let c = 0; c < cols; c++) {\n      result[r][c] = row[c] ?? 0\n    }\n  }\n  return result\n}\n\nfunction useAnimation(\n  frames: Frame[] | undefined,\n  options: {\n    fps: number\n    autoplay: boolean\n    loop: boolean\n    onFrame?: (index: number) => void\n  }\n): { frameIndex: number; isPlaying: boolean } {\n  const [frameIndex, setFrameIndex] = useState(0)\n  const [isPlaying, setIsPlaying] = useState(options.autoplay)\n  const frameIdRef = useRef<number | undefined>(undefined)\n  const lastTimeRef = useRef<number>(0)\n  const accumulatorRef = useRef<number>(0)\n\n  useEffect(() => {\n    if (!frames || frames.length === 0 || !isPlaying) {\n      return\n    }\n\n    const frameInterval = 1000 / options.fps\n\n    const animate = (currentTime: number) => {\n      if (lastTimeRef.current === 0) {\n        lastTimeRef.current = currentTime\n      }\n\n      const deltaTime = currentTime - lastTimeRef.current\n      lastTimeRef.current = currentTime\n      accumulatorRef.current += deltaTime\n\n      if (accumulatorRef.current >= frameInterval) {\n        accumulatorRef.current -= frameInterval\n\n        setFrameIndex((prev) => {\n          const next = prev + 1\n          if (next >= frames.length) {\n            if (options.loop) {\n              options.onFrame?.(0)\n              return 0\n            } else {\n              setIsPlaying(false)\n              return prev\n            }\n          }\n          options.onFrame?.(next)\n          return next\n        })\n      }\n\n      frameIdRef.current = requestAnimationFrame(animate)\n    }\n\n    frameIdRef.current = requestAnimationFrame(animate)\n\n    return () => {\n      if (frameIdRef.current) {\n        cancelAnimationFrame(frameIdRef.current)\n      }\n    }\n  }, [frames, isPlaying, options.fps, options.loop, options.onFrame])\n\n  useEffect(() => {\n    setFrameIndex(0)\n    setIsPlaying(options.autoplay)\n    lastTimeRef.current = 0\n    accumulatorRef.current = 0\n  }, [frames, options.autoplay])\n\n  return { frameIndex, isPlaying }\n}\n\nfunction emptyFrame(rows: number, cols: number): Frame {\n  return Array.from({ length: rows }, () => Array(cols).fill(0))\n}\n\nfunction setPixel(frame: Frame, row: number, col: number, value: number): void {\n  if (row >= 0 && row < frame.length && col >= 0 && col < frame[0].length) {\n    frame[row][col] = value\n  }\n}\n\nexport const digits: Frame[] = [\n  [\n    [0, 1, 1, 1, 0],\n    [1, 0, 0, 0, 1],\n    [1, 0, 0, 0, 1],\n    [1, 0, 0, 0, 1],\n    [1, 0, 0, 0, 1],\n    [1, 0, 0, 0, 1],\n    [0, 1, 1, 1, 0],\n  ],\n  [\n    [0, 0, 1, 0, 0],\n    [0, 1, 1, 0, 0],\n    [0, 0, 1, 0, 0],\n    [0, 0, 1, 0, 0],\n    [0, 0, 1, 0, 0],\n    [0, 0, 1, 0, 0],\n    [0, 1, 1, 1, 0],\n  ],\n  [\n    [0, 1, 1, 1, 0],\n    [1, 0, 0, 0, 1],\n    [0, 0, 0, 0, 1],\n    [0, 0, 0, 1, 0],\n    [0, 0, 1, 0, 0],\n    [0, 1, 0, 0, 0],\n    [1, 1, 1, 1, 1],\n  ],\n  [\n    [0, 1, 1, 1, 0],\n    [1, 0, 0, 0, 1],\n    [0, 0, 0, 0, 1],\n    [0, 0, 1, 1, 0],\n    [0, 0, 0, 0, 1],\n    [1, 0, 0, 0, 1],\n    [0, 1, 1, 1, 0],\n  ],\n  [\n    [0, 0, 0, 1, 0],\n    [0, 0, 1, 1, 0],\n    [0, 1, 0, 1, 0],\n    [1, 0, 0, 1, 0],\n    [1, 1, 1, 1, 1],\n    [0, 0, 0, 1, 0],\n    [0, 0, 0, 1, 0],\n  ],\n  [\n    [1, 1, 1, 1, 1],\n    [1, 0, 0, 0, 0],\n    [1, 1, 1, 1, 0],\n    [0, 0, 0, 0, 1],\n    [0, 0, 0, 0, 1],\n    [1, 0, 0, 0, 1],\n    [0, 1, 1, 1, 0],\n  ],\n  [\n    [0, 1, 1, 1, 0],\n    [1, 0, 0, 0, 0],\n    [1, 0, 0, 0, 0],\n    [1, 1, 1, 1, 0],\n    [1, 0, 0, 0, 1],\n    [1, 0, 0, 0, 1],\n    [0, 1, 1, 1, 0],\n  ],\n  [\n    [1, 1, 1, 1, 1],\n    [0, 0, 0, 0, 1],\n    [0, 0, 0, 1, 0],\n    [0, 0, 1, 0, 0],\n    [0, 1, 0, 0, 0],\n    [0, 1, 0, 0, 0],\n    [0, 1, 0, 0, 0],\n  ],\n  [\n    [0, 1, 1, 1, 0],\n    [1, 0, 0, 0, 1],\n    [1, 0, 0, 0, 1],\n    [0, 1, 1, 1, 0],\n    [1, 0, 0, 0, 1],\n    [1, 0, 0, 0, 1],\n    [0, 1, 1, 1, 0],\n  ],\n  [\n    [0, 1, 1, 1, 0],\n    [1, 0, 0, 0, 1],\n    [1, 0, 0, 0, 1],\n    [0, 1, 1, 1, 1],\n    [0, 0, 0, 0, 1],\n    [0, 0, 0, 0, 1],\n    [0, 1, 1, 1, 0],\n  ],\n]\n\nexport const chevronLeft: Frame = [\n  [0, 0, 0, 1, 0],\n  [0, 0, 1, 0, 0],\n  [0, 1, 0, 0, 0],\n  [0, 0, 1, 0, 0],\n  [0, 0, 0, 1, 0],\n]\n\nexport const chevronRight: Frame = [\n  [0, 1, 0, 0, 0],\n  [0, 0, 1, 0, 0],\n  [0, 0, 0, 1, 0],\n  [0, 0, 1, 0, 0],\n  [0, 1, 0, 0, 0],\n]\n\nexport const loader: Frame[] = (() => {\n  const frames: Frame[] = []\n  const size = 7\n  const center = 3\n  const radius = 2.5\n\n  for (let frame = 0; frame < 12; frame++) {\n    const f = emptyFrame(size, size)\n    for (let i = 0; i < 8; i++) {\n      const angle = (frame / 12) * Math.PI * 2 + (i / 8) * Math.PI * 2\n      const x = Math.round(center + Math.cos(angle) * radius)\n      const y = Math.round(center + Math.sin(angle) * radius)\n      const brightness = 1 - i / 10\n      setPixel(f, y, x, Math.max(0.2, brightness))\n    }\n    frames.push(f)\n  }\n\n  return frames\n})()\n\nexport const pulse: Frame[] = (() => {\n  const frames: Frame[] = []\n  const size = 7\n  const center = 3\n\n  for (let frame = 0; frame < 16; frame++) {\n    const f = emptyFrame(size, size)\n    const phase = (frame / 16) * Math.PI * 2\n    const intensity = (Math.sin(phase) + 1) / 2\n\n    setPixel(f, center, center, 1)\n\n    const radius = Math.floor((1 - intensity) * 3) + 1\n    for (let dy = -radius; dy <= radius; dy++) {\n      for (let dx = -radius; dx <= radius; dx++) {\n        const dist = Math.sqrt(dx * dx + dy * dy)\n        if (Math.abs(dist - radius) < 0.7) {\n          setPixel(f, center + dy, center + dx, intensity * 0.6)\n        }\n      }\n    }\n\n    frames.push(f)\n  }\n\n  return frames\n})()\n\nexport function vu(columns: number, levels: number[]): Frame {\n  const rows = 7\n  const frame = emptyFrame(rows, columns)\n\n  for (let col = 0; col < Math.min(columns, levels.length); col++) {\n    const level = Math.max(0, Math.min(1, levels[col]))\n    const height = Math.floor(level * rows)\n\n    for (let row = 0; row < rows; row++) {\n      const rowFromBottom = rows - 1 - row\n      if (rowFromBottom < height) {\n        let brightness = 1\n        if (row < rows * 0.3) {\n          brightness = 1\n        } else if (row < rows * 0.6) {\n          brightness = 0.8\n        } else {\n          brightness = 0.6\n        }\n        frame[row][col] = brightness\n      }\n    }\n  }\n\n  return frame\n}\n\nexport const wave: Frame[] = (() => {\n  const frames: Frame[] = []\n  const rows = 7\n  const cols = 7\n\n  for (let frame = 0; frame < 24; frame++) {\n    const f = emptyFrame(rows, cols)\n    const phase = (frame / 24) * Math.PI * 2\n\n    for (let col = 0; col < cols; col++) {\n      const colPhase = (col / cols) * Math.PI * 2\n      const height = Math.sin(phase + colPhase) * 2.5 + 3.5\n      const row = Math.floor(height)\n\n      if (row >= 0 && row < rows) {\n        setPixel(f, row, col, 1)\n        const frac = height - row\n        if (row > 0) setPixel(f, row - 1, col, 1 - frac)\n        if (row < rows - 1) setPixel(f, row + 1, col, frac)\n      }\n    }\n\n    frames.push(f)\n  }\n\n  return frames\n})()\n\nexport const snake: Frame[] = (() => {\n  const frames: Frame[] = []\n  const rows = 7\n  const cols = 7\n  const path: Array<[number, number]> = []\n\n  let x = 0\n  let y = 0\n  let dx = 1\n  let dy = 0\n\n  const visited = new Set<string>()\n  while (path.length < rows * cols) {\n    path.push([y, x])\n    visited.add(`${y},${x}`)\n\n    const nextX = x + dx\n    const nextY = y + dy\n\n    if (\n      nextX >= 0 &&\n      nextX < cols &&\n      nextY >= 0 &&\n      nextY < rows &&\n      !visited.has(`${nextY},${nextX}`)\n    ) {\n      x = nextX\n      y = nextY\n    } else {\n      const newDx = -dy\n      const newDy = dx\n      dx = newDx\n      dy = newDy\n\n      const nextX = x + dx\n      const nextY = y + dy\n\n      if (\n        nextX >= 0 &&\n        nextX < cols &&\n        nextY >= 0 &&\n        nextY < rows &&\n        !visited.has(`${nextY},${nextX}`)\n      ) {\n        x = nextX\n        y = nextY\n      } else {\n        break\n      }\n    }\n  }\n\n  const snakeLength = 5\n  for (let frame = 0; frame < path.length; frame++) {\n    const f = emptyFrame(rows, cols)\n\n    for (let i = 0; i < snakeLength; i++) {\n      const idx = frame - i\n      if (idx >= 0 && idx < path.length) {\n        const [y, x] = path[idx]\n        const brightness = 1 - i / snakeLength\n        setPixel(f, y, x, brightness)\n      }\n    }\n\n    frames.push(f)\n  }\n\n  return frames\n})()\n\nexport const Matrix = React.forwardRef<HTMLDivElement, MatrixProps>(\n  (\n    {\n      rows,\n      cols,\n      pattern,\n      frames,\n      fps = 12,\n      autoplay = true,\n      loop = true,\n      size = 10,\n      gap = 2,\n      palette = {\n        on: \"currentColor\",\n        off: \"var(--muted-foreground)\",\n      },\n      brightness = 1,\n      ariaLabel,\n      onFrame,\n      mode = \"default\",\n      levels,\n      className,\n      ...props\n    },\n    ref\n  ) => {\n    const { frameIndex } = useAnimation(frames, {\n      fps,\n      autoplay: autoplay && !pattern,\n      loop,\n      onFrame,\n    })\n\n    const currentFrame = useMemo(() => {\n      if (mode === \"vu\" && levels && levels.length > 0) {\n        return ensureFrameSize(vu(cols, levels), rows, cols)\n      }\n\n      if (pattern) {\n        return ensureFrameSize(pattern, rows, cols)\n      }\n\n      if (frames && frames.length > 0) {\n        return ensureFrameSize(frames[frameIndex] || frames[0], rows, cols)\n      }\n\n      return ensureFrameSize([], rows, cols)\n    }, [pattern, frames, frameIndex, rows, cols, mode, levels])\n\n    const cellPositions = useMemo(() => {\n      const positions: CellPosition[][] = []\n\n      for (let row = 0; row < rows; row++) {\n        positions[row] = []\n        for (let col = 0; col < cols; col++) {\n          positions[row][col] = {\n            x: col * (size + gap),\n            y: row * (size + gap),\n          }\n        }\n      }\n\n      return positions\n    }, [rows, cols, size, gap])\n\n    const svgDimensions = useMemo(() => {\n      return {\n        width: cols * (size + gap) - gap,\n        height: rows * (size + gap) - gap,\n      }\n    }, [rows, cols, size, gap])\n\n    const isAnimating = !pattern && frames && frames.length > 0\n\n    return (\n      <div\n        ref={ref}\n        role=\"img\"\n        aria-label={ariaLabel ?? \"matrix display\"}\n        aria-live={isAnimating ? \"polite\" : undefined}\n        className={cn(\"relative inline-block\", className)}\n        style={\n          {\n            \"--matrix-on\": palette.on,\n            \"--matrix-off\": palette.off,\n            \"--matrix-gap\": `${gap}px`,\n            \"--matrix-size\": `${size}px`,\n          } as React.CSSProperties\n        }\n        {...props}\n      >\n        <svg\n          width={svgDimensions.width}\n          height={svgDimensions.height}\n          viewBox={`0 0 ${svgDimensions.width} ${svgDimensions.height}`}\n          xmlns=\"http://www.w3.org/2000/svg\"\n          className=\"block\"\n          style={{ overflow: \"visible\" }}\n        >\n          <defs>\n            <radialGradient id=\"matrix-pixel-on\" cx=\"50%\" cy=\"50%\" r=\"50%\">\n              <stop offset=\"0%\" stopColor=\"var(--matrix-on)\" stopOpacity=\"1\" />\n              <stop\n                offset=\"70%\"\n                stopColor=\"var(--matrix-on)\"\n                stopOpacity=\"0.85\"\n              />\n              <stop\n                offset=\"100%\"\n                stopColor=\"var(--matrix-on)\"\n                stopOpacity=\"0.6\"\n              />\n            </radialGradient>\n\n            <radialGradient id=\"matrix-pixel-off\" cx=\"50%\" cy=\"50%\" r=\"50%\">\n              <stop\n                offset=\"0%\"\n                stopColor=\"var(--muted-foreground)\"\n                stopOpacity=\"1\"\n              />\n              <stop\n                offset=\"100%\"\n                stopColor=\"var(--muted-foreground)\"\n                stopOpacity=\"0.7\"\n              />\n            </radialGradient>\n\n            <filter\n              id=\"matrix-glow\"\n              x=\"-50%\"\n              y=\"-50%\"\n              width=\"200%\"\n              height=\"200%\"\n            >\n              <feGaussianBlur stdDeviation=\"2\" result=\"blur\" />\n              <feComposite in=\"SourceGraphic\" in2=\"blur\" operator=\"over\" />\n            </filter>\n          </defs>\n\n          <style>\n            {`\n              .matrix-pixel {\n                transition: opacity 300ms ease-out, transform 150ms ease-out;\n                transform-origin: center;\n                transform-box: fill-box;\n              }\n              .matrix-pixel-active {\n                filter: url(#matrix-glow);\n              }\n            `}\n          </style>\n\n          {currentFrame.map((row, rowIndex) =>\n            row.map((value, colIndex) => {\n              const pos = cellPositions[rowIndex]?.[colIndex]\n              if (!pos) return null\n\n              const opacity = clamp(brightness * value)\n              const isActive = opacity > 0.5\n              const isOn = opacity > 0.05\n              const fill = isOn\n                ? \"url(#matrix-pixel-on)\"\n                : \"url(#matrix-pixel-off)\"\n\n              const scale = isActive ? 1.1 : 1\n              const radius = (size / 2) * 0.9\n\n              return (\n                <circle\n                  key={`${rowIndex}-${colIndex}`}\n                  className={cn(\n                    \"matrix-pixel\",\n                    isActive && \"matrix-pixel-active\",\n                    !isOn && \"opacity-20 dark:opacity-[0.1]\"\n                  )}\n                  cx={pos.x + size / 2}\n                  cy={pos.y + size / 2}\n                  r={radius}\n                  fill={fill}\n                  opacity={isOn ? opacity : 0.1}\n                  style={{\n                    transform: `scale(${scale})`,\n                  }}\n                />\n              )\n            })\n          )}\n        </svg>\n      </div>\n    )\n  }\n)\n\nMatrix.displayName = \"Matrix\"\n",
      "type": "registry:ui"
    }
  ],
  "type": "registry:ui"
}