{ "name": "matrix-rain", "type": "registry:ui", "files": [ { "path": "components/ui/matrix-rain.tsx", "content": "\"use client\"\n\nimport { cn } from \"@/lib/utils\"\nimport { useEffect, useRef } from \"react\"\n\ninterface MatrixRainProps {\n className?: string\n variant?: \"default\" | \"cyan\" | \"rainbow\"\n width?: number\n height?: number\n fontSize?: number\n speed?: number\n fixedColor?: string\n}\n\nexport function MatrixRain({\n className,\n variant = \"default\",\n width,\n height,\n fontSize = 16,\n speed = 50,\n fixedColor,\n}: MatrixRainProps) {\n const canvasRef = useRef(null)\n\n useEffect(() => {\n const canvas = canvasRef.current\n if (!canvas) return\n\n const ctx = canvas.getContext(\"2d\")\n if (!ctx) return\n\n // If specific dimensions are provided, use them. Otherwise observe parent.\n const resizeObserver = new ResizeObserver(() => {\n if (!width && !height) {\n canvas.width = canvas.offsetWidth\n canvas.height = canvas.offsetHeight\n }\n })\n resizeObserver.observe(canvas)\n\n // Initial size setup\n if (width) canvas.width = width\n if (height) canvas.height = height\n if (!width && !height) {\n canvas.width = canvas.offsetWidth\n canvas.height = canvas.offsetHeight\n }\n\n const w = canvas.width\n const h = canvas.height\n\n // Columns config\n const columns = Math.floor(w / fontSize)\n const drops = new Array(columns).fill(1)\n\n // Character set: Katakana + Numbers\n const chars = \"ヲァィゥェォャュョッーアイウエオカキクケコサシスセソタチツテトナニヌネノハヒフヘホマミムメモヤユヨラリルレロワン1234567890\"\n\n let isDark = document.documentElement.classList.contains(\"dark\")\n\n // Set default background based on theme immediately to avoid delay\n const bg = isDark ? \"#000000\" : \"#ffffff\"\n\n ctx.fillStyle = bg\n ctx.fillRect(0, 0, w, h)\n\n const draw = () => {\n // Check theme state on every frame\n const currentIsDark = document.documentElement.classList.contains(\"dark\")\n\n // If theme changed, reset the background immediately\n if (currentIsDark !== isDark) {\n isDark = currentIsDark\n ctx.fillStyle = isDark ? \"#000000\" : \"#ffffff\"\n ctx.fillRect(0, 0, canvas.width, canvas.height)\n }\n\n // Semi-transparent color for trail effect\n // Use black for dark mode, white for light mode\n ctx.fillStyle = isDark ? \"rgba(0, 0, 0, 0.05)\" : \"rgba(255, 255, 255, 0.05)\"\n ctx.fillRect(0, 0, canvas.width, canvas.height)\n\n ctx.font = `${fontSize}px monospace`\n\n for (let i = 0; i < drops.length; i++) {\n const text = chars[Math.floor(Math.random() * chars.length)] || \"\"\n\n // Color selection\n if (variant === \"rainbow\" && !fixedColor) {\n const hue = (Date.now() / 20 + i * 10) % 360\n ctx.fillStyle = `hsl(${hue}, 100%, 50%)`\n } else if (fixedColor) {\n ctx.fillStyle = fixedColor\n } else {\n // Adaptive colors based on background\n if (variant === \"cyan\") {\n ctx.fillStyle = isDark ? \"#0FF\" : \"#0e7490\" // Cyan-700\n } else {\n // Default to green\n ctx.fillStyle = isDark ? \"#0F0\" : \"#15803d\" // Green-700\n }\n }\n\n const x = i * fontSize\n const y = drops[i] * fontSize\n\n ctx.fillText(text, x, y)\n\n if (y > canvas.height && Math.random() > 0.975) {\n drops[i] = 0\n }\n\n drops[i]++\n }\n }\n\n const interval = setInterval(draw, speed)\n\n return () => {\n clearInterval(interval)\n resizeObserver.disconnect()\n }\n }, [variant, fontSize, speed, fixedColor, width, height])\n\n return (\n \n )\n}\n", "type": "registry:ui" } ] }