{
	"$schema": "https://ui.shadcn.com/schema/registry-item.json",
	"name": "LetterGlitch-JS-TW",
	"title": "LetterGlitch",
	"description": "Matrix style letter animation.",
	"type": "registry:component",
	"files": [
		{
			"type": "registry:component",
			"path": "LetterGlitch/LetterGlitch.jsx",
			"content": "import { useRef, useEffect } from 'react';\n\nconst LetterGlitch = ({\n  glitchColors = ['#2b4539', '#61dca3', '#61b3dc'],\n  glitchSpeed = 50,\n  centerVignette = false,\n  outerVignette = true,\n  smooth = true,\n  lightMode = false,\n  backgroundColor,\n  className = '',\n  characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ!@#$&*()-_+=/[]{};:<>.,0123456789'\n}) => {\n  const canvasRef = useRef(null);\n  const animationRef = useRef(null);\n  const letters = useRef([]);\n  const grid = useRef({ columns: 0, rows: 0 });\n  const context = useRef(null);\n  const lastGlitchTime = useRef(Date.now());\n\n  const lettersAndSymbols = Array.from(characters);\n\n  const fontSize = 16;\n  const charWidth = 10;\n  const charHeight = 20;\n\n  const getRandomChar = () => {\n    return lettersAndSymbols[Math.floor(Math.random() * lettersAndSymbols.length)];\n  };\n\n  const getRandomColor = () => {\n    return glitchColors[Math.floor(Math.random() * glitchColors.length)];\n  };\n\n  const hexToRgb = hex => {\n    const shorthandRegex = /^#?([a-f\\d])([a-f\\d])([a-f\\d])$/i;\n    hex = hex.replace(shorthandRegex, (m, r, g, b) => {\n      return r + r + g + g + b + b;\n    });\n\n    const result = /^#?([a-f\\d]{2})([a-f\\d]{2})([a-f\\d]{2})$/i.exec(hex);\n    return result\n      ? {\n          r: parseInt(result[1], 16),\n          g: parseInt(result[2], 16),\n          b: parseInt(result[3], 16)\n        }\n      : null;\n  };\n\n  const interpolateColor = (start, end, factor) => {\n    const result = {\n      r: Math.round(start.r + (end.r - start.r) * factor),\n      g: Math.round(start.g + (end.g - start.g) * factor),\n      b: Math.round(start.b + (end.b - start.b) * factor)\n    };\n    return `rgb(${result.r}, ${result.g}, ${result.b})`;\n  };\n\n  const calculateGrid = (width, height) => {\n    const columns = Math.ceil(width / charWidth);\n    const rows = Math.ceil(height / charHeight);\n    return { columns, rows };\n  };\n\n  const initializeLetters = (columns, rows) => {\n    grid.current = { columns, rows };\n    const totalLetters = columns * rows;\n    letters.current = Array.from({ length: totalLetters }, () => ({\n      char: getRandomChar(),\n      color: getRandomColor(),\n      targetColor: getRandomColor(),\n      colorProgress: 1\n    }));\n  };\n\n  const resizeCanvas = () => {\n    const canvas = canvasRef.current;\n    if (!canvas) return;\n    const parent = canvas.parentElement;\n    if (!parent) return;\n\n    const dpr = window.devicePixelRatio || 1;\n    const rect = parent.getBoundingClientRect();\n\n    canvas.width = rect.width * dpr;\n    canvas.height = rect.height * dpr;\n\n    canvas.style.width = `${rect.width}px`;\n    canvas.style.height = `${rect.height}px`;\n\n    if (context.current) {\n      context.current.setTransform(dpr, 0, 0, dpr, 0, 0);\n    }\n\n    const { columns, rows } = calculateGrid(rect.width, rect.height);\n    initializeLetters(columns, rows);\n\n    drawLetters();\n  };\n\n  const drawLetters = () => {\n    if (!context.current || letters.current.length === 0) return;\n    const ctx = context.current;\n    const { width, height } = canvasRef.current.getBoundingClientRect();\n    ctx.clearRect(0, 0, width, height);\n    ctx.font = `${fontSize}px monospace`;\n    ctx.textBaseline = 'top';\n\n    letters.current.forEach((letter, index) => {\n      const x = (index % grid.current.columns) * charWidth;\n      const y = Math.floor(index / grid.current.columns) * charHeight;\n      ctx.fillStyle = letter.color;\n      ctx.fillText(letter.char, x, y);\n    });\n  };\n\n  const updateLetters = () => {\n    if (!letters.current || letters.current.length === 0) return;\n\n    const updateCount = Math.max(1, Math.floor(letters.current.length * 0.05));\n\n    for (let i = 0; i < updateCount; i++) {\n      const index = Math.floor(Math.random() * letters.current.length);\n      if (!letters.current[index]) continue;\n\n      letters.current[index].char = getRandomChar();\n      letters.current[index].targetColor = getRandomColor();\n\n      if (!smooth) {\n        letters.current[index].color = letters.current[index].targetColor;\n        letters.current[index].colorProgress = 1;\n      } else {\n        letters.current[index].colorProgress = 0;\n      }\n    }\n  };\n\n  const handleSmoothTransitions = () => {\n    let needsRedraw = false;\n    letters.current.forEach(letter => {\n      if (letter.colorProgress < 1) {\n        letter.colorProgress += 0.05;\n        if (letter.colorProgress > 1) letter.colorProgress = 1;\n\n        const startRgb = hexToRgb(letter.color);\n        const endRgb = hexToRgb(letter.targetColor);\n        if (startRgb && endRgb) {\n          letter.color = interpolateColor(startRgb, endRgb, letter.colorProgress);\n          needsRedraw = true;\n        }\n      }\n    });\n\n    if (needsRedraw) {\n      drawLetters();\n    }\n  };\n\n  const animate = () => {\n    const now = Date.now();\n    if (now - lastGlitchTime.current >= glitchSpeed) {\n      updateLetters();\n      drawLetters();\n      lastGlitchTime.current = now;\n    }\n\n    if (smooth) {\n      handleSmoothTransitions();\n    }\n\n    animationRef.current = requestAnimationFrame(animate);\n  };\n\n  useEffect(() => {\n    const canvas = canvasRef.current;\n    if (!canvas) return;\n\n    context.current = canvas.getContext('2d');\n    resizeCanvas();\n    animate();\n\n    let resizeTimeout;\n\n    const handleResize = () => {\n      clearTimeout(resizeTimeout);\n      resizeTimeout = setTimeout(() => {\n        cancelAnimationFrame(animationRef.current);\n        resizeCanvas();\n        animate();\n      }, 100);\n    };\n\n    window.addEventListener('resize', handleResize);\n\n    return () => {\n      cancelAnimationFrame(animationRef.current);\n      window.removeEventListener('resize', handleResize);\n    };\n    // eslint-disable-next-line react-hooks/exhaustive-deps\n  }, [glitchSpeed, smooth]);\n\n  return (\n    <div\n      className={`relative w-full h-full overflow-hidden ${className}`}\n      style={{ backgroundColor: backgroundColor || (lightMode ? '#ffffff' : '#000000') }}\n    >\n      <canvas ref={canvasRef} className=\"block w-full h-full\" />\n      {outerVignette && (\n        <div\n          className=\"absolute top-0 left-0 w-full h-full pointer-events-none\"\n          style={{\n            background: lightMode\n              ? 'radial-gradient(circle, rgba(255,255,255,0) 58%, rgba(255,255,255,0.96) 100%)'\n              : 'radial-gradient(circle, rgba(0,0,0,0) 60%, rgba(0,0,0,1) 100%)'\n          }}\n        ></div>\n      )}\n      {centerVignette && (\n        <div\n          className=\"absolute top-0 left-0 w-full h-full pointer-events-none\"\n          style={{\n            background: lightMode\n              ? 'radial-gradient(circle, rgba(255,255,255,0.9) 0%, rgba(255,255,255,0) 60%)'\n              : 'radial-gradient(circle, rgba(0,0,0,0.8) 0%, rgba(0,0,0,0) 60%)'\n          }}\n        ></div>\n      )}\n    </div>\n  );\n};\n\nexport default LetterGlitch;\n"
		}
	],
	"registryDependencies": [],
	"dependencies": []
}