{ "name": "beams-background", "type": "registry:component", "dependencies": [ "motion" ], "files": [ { "type": "registry:component", "content": "\"use client\";\n\n/**\n * @author: @dorianbaffier\n * @description: Beams Background\n * @version: 1.0.0\n * @date: 2025-06-26\n * @license: MIT\n * @website: https://kokonutui.com\n * @github: https://github.com/kokonut-labs/kokonutui\n */\n\nimport { motion } from \"motion/react\";\nimport { useEffect, useRef } from \"react\";\nimport { cn } from \"@/lib/utils\";\n\ninterface AnimatedGradientBackgroundProps {\n className?: string;\n children?: React.ReactNode;\n intensity?: \"subtle\" | \"medium\" | \"strong\";\n}\n\ninterface Beam {\n x: number;\n y: number;\n width: number;\n length: number;\n angle: number;\n speed: number;\n opacity: number;\n hue: number;\n pulse: number;\n pulseSpeed: number;\n}\n\nfunction createBeam(width: number, height: number, isDarkMode: boolean): Beam {\n const angle = -35 + Math.random() * 10;\n const hueBase = isDarkMode ? 190 : 210;\n const hueRange = isDarkMode ? 70 : 50;\n\n return {\n x: Math.random() * width * 1.5 - width * 0.25,\n y: Math.random() * height * 1.5 - height * 0.25,\n width: 30 + Math.random() * 60,\n length: height * 2.5,\n angle,\n speed: 0.6 + Math.random() * 1.2,\n opacity: 0.12 + Math.random() * 0.16,\n hue: hueBase + Math.random() * hueRange,\n pulse: Math.random() * Math.PI * 2,\n pulseSpeed: 0.02 + Math.random() * 0.03,\n };\n}\n\nexport default function BeamsBackground({\n className,\n intensity = \"strong\",\n}: AnimatedGradientBackgroundProps) {\n const canvasRef = useRef(null);\n const beamsRef = useRef([]);\n const animationFrameRef = useRef(0);\n const MINIMUM_BEAMS = 20;\n const isDarkModeRef = useRef(false);\n\n const opacityMap = {\n subtle: 0.7,\n medium: 0.85,\n strong: 1,\n };\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 // Check for dark mode\n const updateDarkMode = () => {\n isDarkModeRef.current =\n document.documentElement.classList.contains(\"dark\");\n };\n\n const observer = new MutationObserver(updateDarkMode);\n observer.observe(document.documentElement, {\n attributes: true,\n attributeFilter: [\"class\"],\n });\n\n updateDarkMode();\n\n const updateCanvasSize = () => {\n const dpr = window.devicePixelRatio || 1;\n canvas.width = window.innerWidth * dpr;\n canvas.height = window.innerHeight * dpr;\n canvas.style.width = `${window.innerWidth}px`;\n canvas.style.height = `${window.innerHeight}px`;\n ctx.scale(dpr, dpr);\n\n const totalBeams = MINIMUM_BEAMS * 1.5;\n beamsRef.current = Array.from({ length: totalBeams }, () =>\n createBeam(canvas.width, canvas.height, isDarkModeRef.current)\n );\n };\n\n updateCanvasSize();\n window.addEventListener(\"resize\", updateCanvasSize);\n\n function resetBeam(beam: Beam, index: number, totalBeams: number) {\n if (!canvas) return beam;\n\n const column = index % 3;\n const spacing = canvas.width / 3;\n\n const hueBase = isDarkModeRef.current ? 190 : 210;\n const hueRange = isDarkModeRef.current ? 70 : 50;\n\n beam.y = canvas.height + 100;\n beam.x =\n column * spacing + spacing / 2 + (Math.random() - 0.5) * spacing * 0.5;\n beam.width = 100 + Math.random() * 100;\n beam.speed = 0.5 + Math.random() * 0.4;\n beam.hue = hueBase + (index * hueRange) / totalBeams;\n beam.opacity = 0.2 + Math.random() * 0.1;\n return beam;\n }\n\n function drawBeam(ctx: CanvasRenderingContext2D, beam: Beam) {\n ctx.save();\n ctx.translate(beam.x, beam.y);\n ctx.rotate((beam.angle * Math.PI) / 180);\n\n const pulsingOpacity =\n beam.opacity *\n (0.8 + Math.sin(beam.pulse) * 0.2) *\n opacityMap[intensity];\n\n const gradient = ctx.createLinearGradient(0, 0, 0, beam.length);\n\n const saturation = isDarkModeRef.current ? \"85%\" : \"75%\";\n const lightness = isDarkModeRef.current ? \"65%\" : \"45%\";\n\n gradient.addColorStop(\n 0,\n `hsla(${beam.hue}, ${saturation}, ${lightness}, 0)`\n );\n gradient.addColorStop(\n 0.1,\n `hsla(${beam.hue}, ${saturation}, ${lightness}, ${\n pulsingOpacity * 0.5\n })`\n );\n gradient.addColorStop(\n 0.4,\n `hsla(${beam.hue}, ${saturation}, ${lightness}, ${pulsingOpacity})`\n );\n gradient.addColorStop(\n 0.6,\n `hsla(${beam.hue}, ${saturation}, ${lightness}, ${pulsingOpacity})`\n );\n gradient.addColorStop(\n 0.9,\n `hsla(${beam.hue}, ${saturation}, ${lightness}, ${\n pulsingOpacity * 0.5\n })`\n );\n gradient.addColorStop(\n 1,\n `hsla(${beam.hue}, ${saturation}, ${lightness}, 0)`\n );\n\n ctx.fillStyle = gradient;\n ctx.fillRect(-beam.width / 2, 0, beam.width, beam.length);\n ctx.restore();\n }\n\n function animate() {\n if (!(canvas && ctx)) return;\n\n ctx.clearRect(0, 0, canvas.width, canvas.height);\n ctx.filter = \"blur(35px)\";\n\n const totalBeams = beamsRef.current.length;\n beamsRef.current.forEach((beam, index) => {\n beam.y -= beam.speed;\n beam.pulse += beam.pulseSpeed;\n\n // Reset beam when it goes off screen\n if (beam.y + beam.length < -100) {\n resetBeam(beam, index, totalBeams);\n }\n\n drawBeam(ctx, beam);\n });\n\n animationFrameRef.current = requestAnimationFrame(animate);\n }\n\n animate();\n\n return () => {\n window.removeEventListener(\"resize\", updateCanvasSize);\n if (animationFrameRef.current) {\n cancelAnimationFrame(animationFrameRef.current);\n }\n observer.disconnect();\n };\n }, [intensity]);\n\n return (\n \n \n\n \n\n
\n
\n \n Beams\n
\n Background\n \n
\n
\n \n );\n}\n", "path": "/components/kokonutui/beams-background.tsx", "target": "components/kokonutui/beams-background.tsx" } ] }