{ "$schema": "https://ui.shadcn.com/schema/registry-item.json", "name": "Lightfall-JS-TW", "title": "Lightfall", "description": "Colorful light streaks raining down a glowing tunnel with a cursor light.", "type": "registry:component", "files": [ { "type": "registry:component", "path": "Lightfall/Lightfall.jsx", "content": "import { useEffect, useRef } from 'react';\nimport { Renderer, Program, Mesh, Triangle } from 'ogl';\n\nconst MAX_COLORS = 8;\n\nconst hexToRGB = hex => {\n const c = hex.replace('#', '').padEnd(6, '0');\n const r = parseInt(c.slice(0, 2), 16) / 255;\n const g = parseInt(c.slice(2, 4), 16) / 255;\n const b = parseInt(c.slice(4, 6), 16) / 255;\n return [r, g, b];\n};\n\nconst prepColors = input => {\n const base = (input && input.length ? input : ['#A6C8FF', '#5227FF', '#FF9FFC']).slice(0, MAX_COLORS);\n const count = base.length;\n const arr = [];\n for (let i = 0; i < MAX_COLORS; i++) arr.push(hexToRGB(base[Math.min(i, base.length - 1)]));\n const avg = [0, 0, 0];\n for (let i = 0; i < count; i++) {\n avg[0] += arr[i][0];\n avg[1] += arr[i][1];\n avg[2] += arr[i][2];\n }\n avg[0] /= count;\n avg[1] /= count;\n avg[2] /= count;\n return { arr, count, avg };\n};\n\nconst vertex = `\nattribute vec2 position;\nattribute vec2 uv;\nvarying vec2 vUv;\nvoid main() {\n vUv = uv;\n gl_Position = vec4(position, 0.0, 1.0);\n}\n`;\n\nconst fragment = `\nprecision highp float;\n\nuniform vec3 iResolution;\nuniform vec2 iMouse;\nuniform float iTime;\n\nuniform vec3 uColor0;\nuniform vec3 uColor1;\nuniform vec3 uColor2;\nuniform vec3 uColor3;\nuniform vec3 uColor4;\nuniform vec3 uColor5;\nuniform vec3 uColor6;\nuniform vec3 uColor7;\nuniform int uColorCount;\n\nuniform vec3 uBgColor;\nuniform vec3 uMouseColor;\nuniform float uSpeed;\nuniform int uStreakCount;\nuniform float uStreakWidth;\nuniform float uStreakLength;\nuniform float uGlow;\nuniform float uDensity;\nuniform float uTwinkle;\nuniform float uZoom;\nuniform float uBgGlow;\nuniform float uOpacity;\nuniform float uMouseEnabled;\nuniform float uMouseStrength;\nuniform float uMouseRadius;\n\nvarying vec2 vUv;\n\nvec3 palette(float h) {\n int count = uColorCount;\n if (count < 1) count = 1;\n int idx = int(floor(clamp(h, 0.0, 0.999999) * float(count)));\n if (idx <= 0) return uColor0;\n if (idx == 1) return uColor1;\n if (idx == 2) return uColor2;\n if (idx == 3) return uColor3;\n if (idx == 4) return uColor4;\n if (idx == 5) return uColor5;\n if (idx == 6) return uColor6;\n return uColor7;\n}\n\nvec3 tanhv(vec3 x) {\n vec3 e = exp(-2.0 * x);\n return (1.0 - e) / (1.0 + e);\n}\n\nvec2 sceneC(vec2 frag, vec2 r) {\n vec2 P = (frag + frag - r) / r.x;\n float z = 0.0;\n float d = 1e3;\n vec4 O = vec4(0.0);\n for (int k = 0; k < 39; k++) {\n if (d <= 1e-4) break;\n O = z * normalize(vec4(P, uZoom, 0.0)) - vec4(0.0, 4.0, 1.0, 0.0) / 4.5;\n d = 1.0 - sqrt(length(O * O));\n z += d;\n }\n return vec2(O.x, atan(O.z, O.y));\n}\n\nvoid mainImage(out vec4 o, vec2 C) {\n vec2 r = iResolution.xy;\n vec2 uv0 = (C + C - r) / r.x;\n float T = 0.1 * iTime * uSpeed + 9.0;\n float angRings = max(1.0, floor(6.28318530718 * max(uDensity, 0.05) + 0.5));\n vec2 Y = vec2(5e-3, 6.28318530718 / angRings);\n\n vec2 c0 = sceneC(C, r);\n vec2 cdx = sceneC(C + vec2(1.0, 0.0), r);\n vec2 cdy = sceneC(C + vec2(0.0, 1.0), r);\n vec2 dCx = cdx - c0;\n vec2 dCy = cdy - c0;\n dCx.y -= 6.28318530718 * floor(dCx.y / 6.28318530718 + 0.5);\n dCy.y -= 6.28318530718 * floor(dCy.y / 6.28318530718 + 0.5);\n vec2 fw = abs(dCx) + abs(dCy);\n C = c0;\n\n vec2 P = vec2(2.0, 1.0) * uv0 - (r / r.x) * vec2(0.0, 1.0);\n vec4 O = vec4(uBgColor * 90.0 * uBgGlow / (1e3 * dot(P, P) + 6.0), 0.0);\n\n float mGlow = 0.0;\n if (uMouseEnabled > 0.5) {\n vec2 mN = (iMouse + iMouse - r) / r.x;\n float md = length(uv0 - mN);\n mGlow = exp(-md * md / max(uMouseRadius * uMouseRadius, 1e-4)) * uMouseStrength;\n O.rgb += uMouseColor * mGlow * 0.25;\n }\n\n float zr = 5e-4 * uStreakWidth;\n vec2 rr = vec2(max(length(fw), 1e-5));\n float tail = 19.0 / max(uStreakLength, 0.05);\n\n for (int m = 0; m < 16; m++) {\n if (m >= uStreakCount) break;\n float jf = float(m) + 1.0;\n float ic = fract(sin(dot(vec2(jf, floor(C.x / Y.x + 0.5)), vec2(7.0, 11.0)) * 73.0));\n vec2 Pp = C - (T + T * ic) * vec2(0.0, 1.0);\n Pp -= floor(Pp / Y + 0.5) * Y;\n float h = fract(8663.0 * ic);\n vec3 col = palette(h);\n float weight = mix(1.5, 1.0 + sin(T + 7.0 * h + 4.0), uTwinkle);\n weight *= (1.0 + mGlow * 2.0);\n vec2 inner = vec2(length(max(Pp, vec2(-1.0, 0.0))), length(Pp) - zr) - zr;\n vec2 sm = vec2(1.0) - smoothstep(-rr, rr, inner);\n O.rgb += dot(sm, vec2(exp(tail * Pp.y), 3.0)) * col * weight;\n C.x += Y.x / 8.0;\n }\n\n vec3 colr = sqrt(tanhv(max(O.rgb * uGlow - vec3(0.04, 0.08, 0.02), 0.0)));\n o = vec4(colr, uOpacity);\n}\n\nvoid main() {\n vec4 color;\n mainImage(color, vUv * iResolution.xy);\n gl_FragColor = color;\n}\n`;\n\nconst Lightfall = ({\n className,\n dpr,\n paused = false,\n colors = ['#A6C8FF', '#5227FF', '#FF9FFC'],\n backgroundColor = '#0A29FF',\n speed = 0.5,\n streakCount = 2,\n streakWidth = 1,\n streakLength = 1,\n glow = 1,\n density = 0.6,\n twinkle = 1,\n zoom = 3,\n backgroundGlow = 0.5,\n opacity = 1,\n mouseInteraction = true,\n mouseStrength = 0.5,\n mouseRadius = 1,\n mouseDampening = 0.15,\n mixBlendMode\n}) => {\n const containerRef = useRef(null);\n const rafRef = useRef(null);\n const programRef = useRef(null);\n const meshRef = useRef(null);\n const geometryRef = useRef(null);\n const rendererRef = useRef(null);\n const mouseTargetRef = useRef([0, 0]);\n const lastTimeRef = useRef(0);\n\n useEffect(() => {\n const container = containerRef.current;\n if (!container) return;\n\n const renderer = new Renderer({\n dpr: dpr ?? (typeof window !== 'undefined' ? window.devicePixelRatio || 1 : 1),\n alpha: true,\n antialias: true\n });\n rendererRef.current = renderer;\n const gl = renderer.gl;\n const canvas = gl.canvas;\n\n canvas.style.width = '100%';\n canvas.style.height = '100%';\n canvas.style.display = 'block';\n container.appendChild(canvas);\n\n const { arr, count, avg } = prepColors(colors);\n\n const uniforms = {\n iResolution: { value: [gl.drawingBufferWidth, gl.drawingBufferHeight, 1] },\n iMouse: { value: [0, 0] },\n iTime: { value: 0 },\n uColor0: { value: arr[0] },\n uColor1: { value: arr[1] },\n uColor2: { value: arr[2] },\n uColor3: { value: arr[3] },\n uColor4: { value: arr[4] },\n uColor5: { value: arr[5] },\n uColor6: { value: arr[6] },\n uColor7: { value: arr[7] },\n uColorCount: { value: count },\n uBgColor: { value: hexToRGB(backgroundColor) },\n uMouseColor: { value: avg },\n uSpeed: { value: speed },\n uStreakCount: { value: Math.max(1, Math.min(16, Math.round(streakCount))) },\n uStreakWidth: { value: streakWidth },\n uStreakLength: { value: streakLength },\n uGlow: { value: glow },\n uDensity: { value: density },\n uTwinkle: { value: twinkle },\n uZoom: { value: zoom },\n uBgGlow: { value: backgroundGlow },\n uOpacity: { value: opacity },\n uMouseEnabled: { value: mouseInteraction ? 1 : 0 },\n uMouseStrength: { value: mouseStrength },\n uMouseRadius: { value: mouseRadius }\n };\n\n const program = new Program(gl, { vertex, fragment, uniforms });\n programRef.current = program;\n\n const geometry = new Triangle(gl);\n geometryRef.current = geometry;\n const mesh = new Mesh(gl, { geometry, program });\n meshRef.current = mesh;\n\n const resize = () => {\n const rect = container.getBoundingClientRect();\n renderer.setSize(rect.width, rect.height);\n uniforms.iResolution.value = [gl.drawingBufferWidth, gl.drawingBufferHeight, 1];\n };\n\n resize();\n const ro = new ResizeObserver(resize);\n ro.observe(container);\n\n const onPointerMove = e => {\n const rect = canvas.getBoundingClientRect();\n const scale = renderer.dpr || 1;\n const x = (e.clientX - rect.left) * scale;\n const y = (rect.height - (e.clientY - rect.top)) * scale;\n mouseTargetRef.current = [x, y];\n if (mouseDampening <= 0) {\n uniforms.iMouse.value = [x, y];\n }\n };\n if (mouseInteraction) {\n canvas.addEventListener('pointermove', onPointerMove);\n }\n\n const loop = t => {\n rafRef.current = requestAnimationFrame(loop);\n uniforms.iTime.value = t * 0.001;\n if (mouseDampening > 0) {\n if (!lastTimeRef.current) lastTimeRef.current = t;\n const dt = (t - lastTimeRef.current) / 1000;\n lastTimeRef.current = t;\n const tau = Math.max(1e-4, mouseDampening);\n let factor = 1 - Math.exp(-dt / tau);\n if (factor > 1) factor = 1;\n const target = mouseTargetRef.current;\n const cur = uniforms.iMouse.value;\n cur[0] += (target[0] - cur[0]) * factor;\n cur[1] += (target[1] - cur[1]) * factor;\n } else {\n lastTimeRef.current = t;\n }\n if (!paused && programRef.current && meshRef.current) {\n try {\n renderer.render({ scene: meshRef.current });\n } catch (e) {\n console.error(e);\n }\n }\n };\n rafRef.current = requestAnimationFrame(loop);\n\n return () => {\n if (rafRef.current) cancelAnimationFrame(rafRef.current);\n if (mouseInteraction) canvas.removeEventListener('pointermove', onPointerMove);\n ro.disconnect();\n if (canvas.parentElement === container) {\n container.removeChild(canvas);\n }\n const callIfFn = (obj, key) => {\n if (obj && typeof obj[key] === 'function') {\n obj[key].call(obj);\n }\n };\n callIfFn(programRef.current, 'remove');\n callIfFn(geometryRef.current, 'remove');\n callIfFn(meshRef.current, 'remove');\n callIfFn(rendererRef.current, 'destroy');\n programRef.current = null;\n geometryRef.current = null;\n meshRef.current = null;\n rendererRef.current = null;\n };\n }, [\n dpr,\n paused,\n colors,\n backgroundColor,\n speed,\n streakCount,\n streakWidth,\n streakLength,\n glow,\n density,\n twinkle,\n zoom,\n backgroundGlow,\n opacity,\n mouseInteraction,\n mouseStrength,\n mouseRadius,\n mouseDampening\n ]);\n\n return (\n
\n );\n};\n\nexport default Lightfall;\n" } ], "registryDependencies": [], "dependencies": [ "ogl@^1.0.11" ] }