From d8ff27c03547b6074014f7be33ebddb3fb4a4f2e Mon Sep 17 00:00:00 2001 From: Elias Gulam Date: Tue, 21 Jul 2026 20:06:04 +0200 Subject: [PATCH 1/4] Initial commit; Updated all Plasma background files, including the demo and bg studio. --- public/r/Plasma-JS-CSS.json | 2 +- public/r/Plasma-JS-TW.json | 2 +- public/r/Plasma-TS-CSS.json | 2 +- public/r/Plasma-TS-TW.json | 2 +- src/content/Backgrounds/Plasma/Plasma.jsx | 103 +++++++-- src/demo/Backgrounds/PlasmaDemo.jsx | 88 ++++++- src/tailwind/Backgrounds/Plasma/Plasma.jsx | 103 +++++++-- .../background-studio/backgrounds/index.js | 8 +- src/ts-default/Backgrounds/Plasma/Plasma.tsx | 111 +++++++-- src/ts-tailwind/Backgrounds/Plasma/Plasma.tsx | 217 +++++++++++++----- 10 files changed, 515 insertions(+), 123 deletions(-) diff --git a/public/r/Plasma-JS-CSS.json b/public/r/Plasma-JS-CSS.json index 84fa3ccb7..024519fc8 100644 --- a/public/r/Plasma-JS-CSS.json +++ b/public/r/Plasma-JS-CSS.json @@ -13,7 +13,7 @@ { "type": "registry:component", "path": "Plasma/Plasma.jsx", - "content": "import { useEffect, useRef } from 'react';\nimport { Renderer, Program, Mesh, Triangle } from 'ogl';\nimport './Plasma.css';\n\nconst hexToRgb = hex => {\n const result = /^#?([a-f\\d]{2})([a-f\\d]{2})([a-f\\d]{2})$/i.exec(hex);\n if (!result) return [1, 0.5, 0.2];\n return [parseInt(result[1], 16) / 255, parseInt(result[2], 16) / 255, parseInt(result[3], 16) / 255];\n};\n\nconst vertex = `#version 300 es\nprecision highp float;\nin vec2 position;\nin vec2 uv;\nout vec2 vUv;\nvoid main() {\n vUv = uv;\n gl_Position = vec4(position, 0.0, 1.0);\n}\n`;\n\nconst fragment = `#version 300 es\nprecision highp float;\nuniform vec2 iResolution;\nuniform float iTime;\nuniform vec3 uCustomColor;\nuniform float uUseCustomColor;\nuniform float uSpeed;\nuniform float uDirection;\nuniform float uScale;\nuniform float uOpacity;\nuniform vec2 uMouse;\nuniform float uMouseInteractive;\nout vec4 fragColor;\n\nvoid mainImage(out vec4 o, vec2 C) {\n vec2 center = iResolution.xy * 0.5;\n C = (C - center) / uScale + center;\n \n vec2 mouseOffset = (uMouse - center) * 0.0002;\n C += mouseOffset * length(C - center) * step(0.5, uMouseInteractive);\n \n float i, d, z, T = iTime * uSpeed * uDirection;\n vec3 O, p, S;\n\n for (vec2 r = iResolution.xy, Q; ++i < 60.; O += o.w/d*o.xyz) {\n p = z*normalize(vec3(C-.5*r,r.y)); \n p.z -= 4.; \n S = p;\n d = p.y-T;\n \n p.x += .4*(1.+p.y)*sin(d + p.x*0.1)*cos(.34*d + p.x*0.05); \n Q = p.xz *= mat2(cos(p.y+vec4(0,11,33,0)-T)); \n z+= d = abs(sqrt(length(Q*Q)) - .25*(5.+S.y))/3.+8e-4; \n o = 1.+sin(S.y+p.z*.5+S.z-length(S-p)+vec4(2,1,0,8));\n }\n \n o.xyz = tanh(O/1e4);\n}\n\nbool finite1(float x){ return !(isnan(x) || isinf(x)); }\nvec3 sanitize(vec3 c){\n return vec3(\n finite1(c.r) ? c.r : 0.0,\n finite1(c.g) ? c.g : 0.0,\n finite1(c.b) ? c.b : 0.0\n );\n}\n\nvoid main() {\n vec4 o = vec4(0.0);\n mainImage(o, gl_FragCoord.xy);\n vec3 rgb = sanitize(o.rgb);\n \n float intensity = (rgb.r + rgb.g + rgb.b) / 3.0;\n vec3 customColor = intensity * uCustomColor;\n vec3 finalColor = mix(rgb, customColor, step(0.5, uUseCustomColor));\n \n float alpha = length(rgb) * uOpacity;\n fragColor = vec4(finalColor, alpha);\n}`;\n\nexport const Plasma = ({\n color = '#ffffff',\n speed = 1,\n direction = 'forward',\n scale = 1,\n opacity = 1,\n mouseInteractive = true\n}) => {\n const containerRef = useRef(null);\n const mousePos = useRef({ x: 0, y: 0 });\n\n useEffect(() => {\n if (!containerRef.current) return;\n const containerEl = containerRef.current;\n\n const useCustomColor = color ? 1.0 : 0.0;\n const customColorRgb = color ? hexToRgb(color) : [1, 1, 1];\n\n const directionMultiplier = direction === 'reverse' ? -1.0 : 1.0;\n\n let renderer;\n try {\n renderer = new Renderer({\n webgl: 2,\n alpha: true,\n antialias: false,\n dpr: Math.min(window.devicePixelRatio || 1, 2)\n });\n } catch {\n return;\n }\n const gl = renderer.gl;\n if (!gl) return;\n const canvas = gl.canvas;\n canvas.style.display = 'block';\n canvas.style.width = '100%';\n canvas.style.height = '100%';\n containerEl.appendChild(canvas);\n\n const geometry = new Triangle(gl);\n\n const program = new Program(gl, {\n vertex: vertex,\n fragment: fragment,\n uniforms: {\n iTime: { value: 0 },\n iResolution: { value: new Float32Array([1, 1]) },\n uCustomColor: { value: new Float32Array(customColorRgb) },\n uUseCustomColor: { value: useCustomColor },\n uSpeed: { value: speed * 0.4 },\n uDirection: { value: directionMultiplier },\n uScale: { value: scale },\n uOpacity: { value: opacity },\n uMouse: { value: new Float32Array([0, 0]) },\n uMouseInteractive: { value: mouseInteractive ? 1.0 : 0.0 }\n }\n });\n\n const mesh = new Mesh(gl, { geometry, program });\n\n const handleMouseMove = e => {\n if (!mouseInteractive) return;\n const rect = containerEl.getBoundingClientRect();\n mousePos.current.x = e.clientX - rect.left;\n mousePos.current.y = e.clientY - rect.top;\n const mouseUniform = program.uniforms.uMouse.value;\n mouseUniform[0] = mousePos.current.x;\n mouseUniform[1] = mousePos.current.y;\n };\n\n if (mouseInteractive) {\n containerEl.addEventListener('mousemove', handleMouseMove);\n }\n\n const setSize = () => {\n const rect = containerEl.getBoundingClientRect();\n const width = Math.max(1, Math.floor(rect.width));\n const height = Math.max(1, Math.floor(rect.height));\n renderer.setSize(width, height);\n const res = program.uniforms.iResolution.value;\n res[0] = gl.drawingBufferWidth;\n res[1] = gl.drawingBufferHeight;\n };\n\n const ro = new ResizeObserver(setSize);\n ro.observe(containerEl);\n setSize();\n\n let raf = 0;\n let contextLost = false;\n let isVisible = true;\n const t0 = performance.now();\n\n const loop = t => {\n if (contextLost || !isVisible) return;\n let timeValue = (t - t0) * 0.001;\n if (direction === 'pingpong') {\n const pingpongDuration = 10;\n const segmentTime = timeValue % pingpongDuration;\n const isForward = Math.floor(timeValue / pingpongDuration) % 2 === 0;\n const u = segmentTime / pingpongDuration;\n const smooth = u * u * (3 - 2 * u);\n const pingpongTime = isForward ? smooth * pingpongDuration : (1 - smooth) * pingpongDuration;\n program.uniforms.uDirection.value = 1.0;\n program.uniforms.iTime.value = pingpongTime;\n } else {\n program.uniforms.iTime.value = timeValue;\n }\n renderer.render({ scene: mesh });\n raf = requestAnimationFrame(loop);\n };\n\n const handleContextLost = (e) => {\n e.preventDefault();\n contextLost = true;\n cancelAnimationFrame(raf);\n };\n const handleContextRestored = () => {\n contextLost = false;\n if (isVisible) {\n cancelAnimationFrame(raf);\n raf = requestAnimationFrame(loop);\n }\n };\n canvas.addEventListener('webglcontextlost', handleContextLost);\n canvas.addEventListener('webglcontextrestored', handleContextRestored);\n\n const io = new IntersectionObserver(([entry]) => {\n const wasVisible = isVisible;\n isVisible = entry.isIntersecting;\n if (isVisible && !wasVisible && !contextLost) {\n cancelAnimationFrame(raf);\n raf = requestAnimationFrame(loop);\n }\n }, { threshold: 0 });\n io.observe(containerEl);\n\n raf = requestAnimationFrame(loop);\n\n return () => {\n cancelAnimationFrame(raf);\n ro.disconnect();\n io.disconnect();\n canvas.removeEventListener('webglcontextlost', handleContextLost);\n canvas.removeEventListener('webglcontextrestored', handleContextRestored);\n if (mouseInteractive && containerEl) {\n containerEl.removeEventListener('mousemove', handleMouseMove);\n }\n try {\n containerEl?.removeChild(canvas);\n } catch {}\n };\n }, [color, speed, direction, scale, opacity, mouseInteractive]);\n\n return
;\n};\n\nexport default Plasma;\n" + "content": "import { useEffect, useRef } from 'react';\nimport { Renderer, Program, Mesh, Triangle } from 'ogl';\nimport './Plasma.css';\n\nconst hexToRgb = hex => {\n const result = /^#?([a-f\\d]{2})([a-f\\d]{2})([a-f\\d]{2})$/i.exec(hex);\n if (!result) return [1, 0.5, 0.2];\n return [parseInt(result[1], 16) / 255, parseInt(result[2], 16) / 255, parseInt(result[3], 16) / 255];\n};\n\nconst vertex = `#version 300 es\nprecision highp float;\nin vec2 position;\nin vec2 uv;\nout vec2 vUv;\nvoid main() {\n vUv = uv;\n gl_Position = vec4(position, 0.0, 1.0);\n}\n`;\n\nconst buildFragment = steps => `#version 300 es\nprecision highp float;\nuniform vec2 iResolution;\nuniform float iTime;\nuniform vec3 uCustomColor;\nuniform float uUseCustomColor;\nuniform float uSpeed;\nuniform float uDirection;\nuniform float uScale;\nuniform float uOpacity;\nuniform vec2 uMouse;\nuniform float uMouseInteractive;\nout vec4 fragColor;\n\nvoid mainImage(out vec4 o, vec2 C) {\n vec2 center = iResolution.xy * 0.5;\n C = (C - center) / uScale + center;\n \n vec2 mouseOffset = (uMouse - center) * 0.0002;\n C += mouseOffset * length(C - center) * step(0.5, uMouseInteractive);\n \n float i, d, z, T = iTime * uSpeed * uDirection;\n vec3 O, p, S;\n\n for (vec2 r = iResolution.xy, Q; ++i < ${steps.toFixed(1)}; O += o.w/d*o.xyz) {\n p = z*normalize(vec3(C-.5*r,r.y)); \n p.z -= 4.; \n S = p;\n d = p.y-T;\n \n p.x += .4*(1.+p.y)*sin(d + p.x*0.1)*cos(.34*d + p.x*0.05); \n Q = p.xz *= mat2(cos(p.y+vec4(0,11,33,0)-T)); \n z+= d = abs(sqrt(length(Q*Q)) - .25*(5.+S.y))/3.+8e-4; \n o = 1.+sin(S.y+p.z*.5+S.z-length(S-p)+vec4(2,1,0,8));\n }\n \n o.xyz = tanh(O/1e4);\n}\n\nbool finite1(float x){ return !(isnan(x) || isinf(x)); }\nvec3 sanitize(vec3 c){\n return vec3(\n finite1(c.r) ? c.r : 0.0,\n finite1(c.g) ? c.g : 0.0,\n finite1(c.b) ? c.b : 0.0\n );\n}\n\nvoid main() {\n vec4 o = vec4(0.0);\n mainImage(o, gl_FragCoord.xy);\n vec3 rgb = sanitize(o.rgb);\n \n float intensity = (rgb.r + rgb.g + rgb.b) / 3.0;\n vec3 customColor = intensity * uCustomColor;\n vec3 finalColor = mix(rgb, customColor, step(0.5, uUseCustomColor));\n \n float alpha = length(rgb) * uOpacity;\n fragColor = vec4(finalColor, alpha);\n}`;\n\nexport const Plasma = ({\n color = '#ffffff',\n speed = 1,\n direction = 'forward',\n scale = 1,\n opacity = 1,\n mouseInteractive = true,\n renderScale = 0.55,\n maxDpr = 1.5,\n targetFps = 30,\n quality = 45,\n}) => {\n const containerRef = useRef(null);\n const mousePos = useRef({ x: 0, y: 0 });\n const pendingMouse = useRef(null);\n\n useEffect(() => {\n if (!containerRef.current) return;\n const containerEl = containerRef.current;\n\n const prefersReducedMotion =\n typeof window !== 'undefined' &&\n window.matchMedia?.('(prefers-reduced-motion: reduce)').matches;\n\n const useCustomColor = color ? 1.0 : 0.0;\n const customColorRgb = color ? hexToRgb(color) : [1, 1, 1];\n\n const directionMultiplier = direction === 'reverse' ? -1.0 : 1.0;\n\n let renderer;\n try {\n renderer = new Renderer({\n webgl: 2,\n alpha: true,\n antialias: false,\n dpr: Math.min(window.devicePixelRatio || 1, maxDpr)\n });\n } catch {\n return;\n }\n const gl = renderer.gl;\n if (!gl) return;\n const canvas = gl.canvas;\n canvas.style.display = 'block';\n canvas.style.width = '100%';\n canvas.style.height = '100%';\n // Rendering at renderScale internally, CSS stretches it back up.\n containerEl.appendChild(canvas);\n\n const geometry = new Triangle(gl);\n\n const program = new Program(gl, {\n vertex: vertex,\n fragment: buildFragment(quality),\n uniforms: {\n iTime: { value: 0 },\n iResolution: { value: new Float32Array([1, 1]) },\n uCustomColor: { value: new Float32Array(customColorRgb) },\n uUseCustomColor: { value: useCustomColor },\n uSpeed: { value: speed * 0.4 },\n uDirection: { value: directionMultiplier },\n uScale: { value: scale },\n uOpacity: { value: opacity },\n uMouse: { value: new Float32Array([0, 0]) },\n uMouseInteractive: { value: mouseInteractive ? 1.0 : 0.0 }\n }\n });\n\n const mesh = new Mesh(gl, { geometry, program });\n\n const handleMouseMove = e => {\n if (!mouseInteractive) return;\n const rect = containerEl.getBoundingClientRect();\n // Store the latest position but don't touch GL state here, the rAF loop picks it up once per rendered frame instead of once per mouse event.\n pendingMouse.current = {\n x: e.clientX - rect.left,\n y: e.clientY - rect.top,\n };\n };\n\n if (mouseInteractive) {\n containerEl.addEventListener('mousemove', handleMouseMove, { passive: true });\n }\n\n let resizePending = false;\n const setSize = () => {\n const rect = containerEl.getBoundingClientRect();\n const width = Math.max(1, Math.floor(rect.width * renderScale));\n const height = Math.max(1, Math.floor(rect.height * renderScale));\n renderer.setSize(width, height);\n\n // renderer.setSize also sets canvas.style.width/height to match the (scaled-down) drawing buffer - override that so the canvas still stretches to fill its container via CSS while the buffer stays small.\n canvas.style.width = '100%';\n canvas.style.height = '100%';\n\n const res = program.uniforms.iResolution.value;\n res[0] = gl.drawingBufferWidth;\n res[1] = gl.drawingBufferHeight;\n };\n\n const ro = new ResizeObserver(() => {\n // Batch rapid resize events (ex. during a window drag) into one setSize per frame.\n if (resizePending) return;\n resizePending = true;\n requestAnimationFrame(() => {\n resizePending = false;\n setSize();\n });\n });\n ro.observe(containerEl);\n setSize();\n\n let raf = 0;\n let contextLost = false;\n let isVisible = true;\n let tabVisible = document.visibilityState !== 'hidden';\n const t0 = performance.now();\n const frameInterval = 1000 / targetFps;\n let lastFrameTime = 0;\n\n const renderStaticFrame = () => {\n program.uniforms.iTime.value = 0;\n renderer.render({ scene: mesh });\n };\n\n const loop = t => {\n if (contextLost || !isVisible || !tabVisible) return;\n\n if (t - lastFrameTime < frameInterval) {\n raf = requestAnimationFrame(loop);\n return;\n }\n lastFrameTime = t;\n\n if (pendingMouse.current) {\n mousePos.current = pendingMouse.current;\n pendingMouse.current = null;\n const mouseUniform = program.uniforms.uMouse.value;\n mouseUniform[0] = mousePos.current.x;\n mouseUniform[1] = mousePos.current.y;\n }\n\n let timeValue = (t - t0) * 0.001;\n if (direction === 'pingpong') {\n const pingpongDuration = 10;\n const segmentTime = timeValue % pingpongDuration;\n const isForward = Math.floor(timeValue / pingpongDuration) % 2 === 0;\n const u = segmentTime / pingpongDuration;\n const smooth = u * u * (3 - 2 * u);\n const pingpongTime = isForward ? smooth * pingpongDuration : (1 - smooth) * pingpongDuration;\n program.uniforms.uDirection.value = 1.0;\n program.uniforms.iTime.value = pingpongTime;\n } else {\n program.uniforms.iTime.value = timeValue;\n }\n renderer.render({ scene: mesh });\n raf = requestAnimationFrame(loop);\n };\n\n const handleContextLost = (e) => {\n e.preventDefault();\n contextLost = true;\n cancelAnimationFrame(raf);\n };\n const handleContextRestored = () => {\n contextLost = false;\n if (isVisible && tabVisible && !prefersReducedMotion) {\n cancelAnimationFrame(raf);\n raf = requestAnimationFrame(loop);\n }\n };\n canvas.addEventListener('webglcontextlost', handleContextLost);\n canvas.addEventListener('webglcontextrestored', handleContextRestored);\n\n const io = new IntersectionObserver(([entry]) => {\n const wasVisible = isVisible;\n isVisible = entry.isIntersecting;\n if (isVisible && !wasVisible && !contextLost && tabVisible && !prefersReducedMotion) {\n cancelAnimationFrame(raf);\n raf = requestAnimationFrame(loop);\n }\n }, { threshold: 0 });\n io.observe(containerEl);\n\n const handleVisibilityChange = () => {\n tabVisible = document.visibilityState !== 'hidden';\n if (tabVisible && isVisible && !contextLost && !prefersReducedMotion) {\n cancelAnimationFrame(raf);\n lastFrameTime = 0;\n raf = requestAnimationFrame(loop);\n } else {\n cancelAnimationFrame(raf);\n }\n };\n document.addEventListener('visibilitychange', handleVisibilityChange);\n\n // Respect prefers-reduced-motion: paint one frame and stop, rather than running a perpetual animation loop for users who've asked not to see motion.\n if (prefersReducedMotion) {\n renderStaticFrame();\n } else {\n raf = requestAnimationFrame(loop);\n }\n\n return () => {\n cancelAnimationFrame(raf);\n ro.disconnect();\n io.disconnect();\n document.removeEventListener('visibilitychange', handleVisibilityChange);\n canvas.removeEventListener('webglcontextlost', handleContextLost);\n canvas.removeEventListener('webglcontextrestored', handleContextRestored);\n if (mouseInteractive && containerEl) {\n containerEl.removeEventListener('mousemove', handleMouseMove);\n }\n try {\n containerEl?.removeChild(canvas);\n } catch {}\n };\n }, [color, speed, direction, scale, opacity, mouseInteractive, renderScale, maxDpr, targetFps, quality]);\n\n return
;\n};\n\nexport default Plasma;\n" } ], "registryDependencies": [], diff --git a/public/r/Plasma-JS-TW.json b/public/r/Plasma-JS-TW.json index f1f4ba7a8..856fb3e64 100644 --- a/public/r/Plasma-JS-TW.json +++ b/public/r/Plasma-JS-TW.json @@ -8,7 +8,7 @@ { "type": "registry:component", "path": "Plasma/Plasma.jsx", - "content": "import { useEffect, useRef } from 'react';\nimport { Renderer, Program, Mesh, Triangle } from 'ogl';\n\nconst hexToRgb = hex => {\n const result = /^#?([a-f\\d]{2})([a-f\\d]{2})([a-f\\d]{2})$/i.exec(hex);\n if (!result) return [1, 0.5, 0.2];\n return [parseInt(result[1], 16) / 255, parseInt(result[2], 16) / 255, parseInt(result[3], 16) / 255];\n};\n\nconst vertex = `#version 300 es\nprecision highp float;\nin vec2 position;\nin vec2 uv;\nout vec2 vUv;\nvoid main() {\n vUv = uv;\n gl_Position = vec4(position, 0.0, 1.0);\n}\n`;\n\nconst fragment = `#version 300 es\nprecision highp float;\nuniform vec2 iResolution;\nuniform float iTime;\nuniform vec3 uCustomColor;\nuniform float uUseCustomColor;\nuniform float uSpeed;\nuniform float uDirection;\nuniform float uScale;\nuniform float uOpacity;\nuniform vec2 uMouse;\nuniform float uMouseInteractive;\nout vec4 fragColor;\n\nvoid mainImage(out vec4 o, vec2 C) {\n vec2 center = iResolution.xy * 0.5;\n C = (C - center) / uScale + center;\n \n vec2 mouseOffset = (uMouse - center) * 0.0002;\n C += mouseOffset * length(C - center) * step(0.5, uMouseInteractive);\n \n float i, d, z, T = iTime * uSpeed * uDirection;\n vec3 O, p, S;\n\n for (vec2 r = iResolution.xy, Q; ++i < 60.; O += o.w/d*o.xyz) {\n p = z*normalize(vec3(C-.5*r,r.y)); \n p.z -= 4.; \n S = p;\n d = p.y-T;\n \n p.x += .4*(1.+p.y)*sin(d + p.x*0.1)*cos(.34*d + p.x*0.05); \n Q = p.xz *= mat2(cos(p.y+vec4(0,11,33,0)-T)); \n z+= d = abs(sqrt(length(Q*Q)) - .25*(5.+S.y))/3.+8e-4; \n o = 1.+sin(S.y+p.z*.5+S.z-length(S-p)+vec4(2,1,0,8));\n }\n \n o.xyz = tanh(O/1e4);\n}\n\nbool finite1(float x){ return !(isnan(x) || isinf(x)); }\nvec3 sanitize(vec3 c){\n return vec3(\n finite1(c.r) ? c.r : 0.0,\n finite1(c.g) ? c.g : 0.0,\n finite1(c.b) ? c.b : 0.0\n );\n}\n\nvoid main() {\n vec4 o = vec4(0.0);\n mainImage(o, gl_FragCoord.xy);\n vec3 rgb = sanitize(o.rgb);\n \n float intensity = (rgb.r + rgb.g + rgb.b) / 3.0;\n vec3 customColor = intensity * uCustomColor;\n vec3 finalColor = mix(rgb, customColor, step(0.5, uUseCustomColor));\n \n float alpha = length(rgb) * uOpacity;\n fragColor = vec4(finalColor, alpha);\n}`;\n\nexport const Plasma = ({\n color = '#ffffff',\n speed = 1,\n direction = 'forward',\n scale = 1,\n opacity = 1,\n mouseInteractive = true\n}) => {\n const containerRef = useRef(null);\n const mousePos = useRef({ x: 0, y: 0 });\n\n useEffect(() => {\n if (!containerRef.current) return;\n const containerEl = containerRef.current;\n\n const useCustomColor = color ? 1.0 : 0.0;\n const customColorRgb = color ? hexToRgb(color) : [1, 1, 1];\n\n const directionMultiplier = direction === 'reverse' ? -1.0 : 1.0;\n\n let renderer;\n try {\n renderer = new Renderer({\n webgl: 2,\n alpha: true,\n antialias: false,\n dpr: Math.min(window.devicePixelRatio || 1, 2)\n });\n } catch {\n return;\n }\n const gl = renderer.gl;\n if (!gl) return;\n const canvas = gl.canvas;\n canvas.style.display = 'block';\n canvas.style.width = '100%';\n canvas.style.height = '100%';\n containerEl.appendChild(canvas);\n\n const geometry = new Triangle(gl);\n\n const program = new Program(gl, {\n vertex: vertex,\n fragment: fragment,\n uniforms: {\n iTime: { value: 0 },\n iResolution: { value: new Float32Array([1, 1]) },\n uCustomColor: { value: new Float32Array(customColorRgb) },\n uUseCustomColor: { value: useCustomColor },\n uSpeed: { value: speed * 0.4 },\n uDirection: { value: directionMultiplier },\n uScale: { value: scale },\n uOpacity: { value: opacity },\n uMouse: { value: new Float32Array([0, 0]) },\n uMouseInteractive: { value: mouseInteractive ? 1.0 : 0.0 }\n }\n });\n\n const mesh = new Mesh(gl, { geometry, program });\n\n const handleMouseMove = e => {\n if (!mouseInteractive) return;\n const rect = containerEl.getBoundingClientRect();\n mousePos.current.x = e.clientX - rect.left;\n mousePos.current.y = e.clientY - rect.top;\n const mouseUniform = program.uniforms.uMouse.value;\n mouseUniform[0] = mousePos.current.x;\n mouseUniform[1] = mousePos.current.y;\n };\n\n if (mouseInteractive) {\n containerEl.addEventListener('mousemove', handleMouseMove);\n }\n\n const setSize = () => {\n const rect = containerEl.getBoundingClientRect();\n const width = Math.max(1, Math.floor(rect.width));\n const height = Math.max(1, Math.floor(rect.height));\n renderer.setSize(width, height);\n const res = program.uniforms.iResolution.value;\n res[0] = gl.drawingBufferWidth;\n res[1] = gl.drawingBufferHeight;\n };\n\n const ro = new ResizeObserver(setSize);\n ro.observe(containerEl);\n setSize();\n\n let raf = 0;\n let contextLost = false;\n let isVisible = true;\n const t0 = performance.now();\n\n const loop = t => {\n if (contextLost || !isVisible) return;\n let timeValue = (t - t0) * 0.001;\n if (direction === 'pingpong') {\n const pingpongDuration = 10;\n const segmentTime = timeValue % pingpongDuration;\n const isForward = Math.floor(timeValue / pingpongDuration) % 2 === 0;\n const u = segmentTime / pingpongDuration;\n const smooth = u * u * (3 - 2 * u);\n const pingpongTime = isForward ? smooth * pingpongDuration : (1 - smooth) * pingpongDuration;\n program.uniforms.uDirection.value = 1.0;\n program.uniforms.iTime.value = pingpongTime;\n } else {\n program.uniforms.iTime.value = timeValue;\n }\n renderer.render({ scene: mesh });\n raf = requestAnimationFrame(loop);\n };\n\n const handleContextLost = (e) => {\n e.preventDefault();\n contextLost = true;\n cancelAnimationFrame(raf);\n };\n const handleContextRestored = () => {\n contextLost = false;\n if (isVisible) {\n cancelAnimationFrame(raf);\n raf = requestAnimationFrame(loop);\n }\n };\n canvas.addEventListener('webglcontextlost', handleContextLost);\n canvas.addEventListener('webglcontextrestored', handleContextRestored);\n\n const io = new IntersectionObserver(([entry]) => {\n const wasVisible = isVisible;\n isVisible = entry.isIntersecting;\n if (isVisible && !wasVisible && !contextLost) {\n cancelAnimationFrame(raf);\n raf = requestAnimationFrame(loop);\n }\n }, { threshold: 0 });\n io.observe(containerEl);\n\n raf = requestAnimationFrame(loop);\n\n return () => {\n cancelAnimationFrame(raf);\n ro.disconnect();\n io.disconnect();\n canvas.removeEventListener('webglcontextlost', handleContextLost);\n canvas.removeEventListener('webglcontextrestored', handleContextRestored);\n if (mouseInteractive && containerEl) {\n containerEl.removeEventListener('mousemove', handleMouseMove);\n }\n try {\n containerEl?.removeChild(canvas);\n } catch {}\n };\n }, [color, speed, direction, scale, opacity, mouseInteractive]);\n\n return
;\n};\n\nexport default Plasma;\n" + "content": "import { useEffect, useRef } from 'react';\nimport { Renderer, Program, Mesh, Triangle } from 'ogl';\n\nconst hexToRgb = hex => {\n const result = /^#?([a-f\\d]{2})([a-f\\d]{2})([a-f\\d]{2})$/i.exec(hex);\n if (!result) return [1, 0.5, 0.2];\n return [parseInt(result[1], 16) / 255, parseInt(result[2], 16) / 255, parseInt(result[3], 16) / 255];\n};\n\nconst vertex = `#version 300 es\nprecision highp float;\nin vec2 position;\nin vec2 uv;\nout vec2 vUv;\nvoid main() {\n vUv = uv;\n gl_Position = vec4(position, 0.0, 1.0);\n}\n`;\n\nconst buildFragment = steps => `#version 300 es\nprecision highp float;\nuniform vec2 iResolution;\nuniform float iTime;\nuniform vec3 uCustomColor;\nuniform float uUseCustomColor;\nuniform float uSpeed;\nuniform float uDirection;\nuniform float uScale;\nuniform float uOpacity;\nuniform vec2 uMouse;\nuniform float uMouseInteractive;\nout vec4 fragColor;\n\nvoid mainImage(out vec4 o, vec2 C) {\n vec2 center = iResolution.xy * 0.5;\n C = (C - center) / uScale + center;\n \n vec2 mouseOffset = (uMouse - center) * 0.0002;\n C += mouseOffset * length(C - center) * step(0.5, uMouseInteractive);\n \n float i, d, z, T = iTime * uSpeed * uDirection;\n vec3 O, p, S;\n\n for (vec2 r = iResolution.xy, Q; ++i < ${steps.toFixed(1)}; O += o.w/d*o.xyz) {\n p = z*normalize(vec3(C-.5*r,r.y)); \n p.z -= 4.; \n S = p;\n d = p.y-T;\n \n p.x += .4*(1.+p.y)*sin(d + p.x*0.1)*cos(.34*d + p.x*0.05); \n Q = p.xz *= mat2(cos(p.y+vec4(0,11,33,0)-T)); \n z+= d = abs(sqrt(length(Q*Q)) - .25*(5.+S.y))/3.+8e-4; \n o = 1.+sin(S.y+p.z*.5+S.z-length(S-p)+vec4(2,1,0,8));\n }\n \n o.xyz = tanh(O/1e4);\n}\n\nbool finite1(float x){ return !(isnan(x) || isinf(x)); }\nvec3 sanitize(vec3 c){\n return vec3(\n finite1(c.r) ? c.r : 0.0,\n finite1(c.g) ? c.g : 0.0,\n finite1(c.b) ? c.b : 0.0\n );\n}\n\nvoid main() {\n vec4 o = vec4(0.0);\n mainImage(o, gl_FragCoord.xy);\n vec3 rgb = sanitize(o.rgb);\n \n float intensity = (rgb.r + rgb.g + rgb.b) / 3.0;\n vec3 customColor = intensity * uCustomColor;\n vec3 finalColor = mix(rgb, customColor, step(0.5, uUseCustomColor));\n \n float alpha = length(rgb) * uOpacity;\n fragColor = vec4(finalColor, alpha);\n}`;\n\nexport const Plasma = ({\n color = '#ffffff',\n speed = 1,\n direction = 'forward',\n scale = 1,\n opacity = 1,\n mouseInteractive = true,\n renderScale = 0.55,\n maxDpr = 1.5,\n targetFps = 30,\n quality = 45,\n}) => {\n const containerRef = useRef(null);\n const mousePos = useRef({ x: 0, y: 0 });\n const pendingMouse = useRef(null);\n\n useEffect(() => {\n if (!containerRef.current) return;\n const containerEl = containerRef.current;\n\n const prefersReducedMotion =\n typeof window !== 'undefined' &&\n window.matchMedia?.('(prefers-reduced-motion: reduce)').matches;\n\n const useCustomColor = color ? 1.0 : 0.0;\n const customColorRgb = color ? hexToRgb(color) : [1, 1, 1];\n\n const directionMultiplier = direction === 'reverse' ? -1.0 : 1.0;\n\n let renderer;\n try {\n renderer = new Renderer({\n webgl: 2,\n alpha: true,\n antialias: false,\n dpr: Math.min(window.devicePixelRatio || 1, maxDpr)\n });\n } catch {\n return;\n }\n const gl = renderer.gl;\n if (!gl) return;\n const canvas = gl.canvas;\n canvas.style.display = 'block';\n canvas.style.width = '100%';\n canvas.style.height = '100%';\n // Rendering at renderScale internally, CSS stretches it back up.\n containerEl.appendChild(canvas);\n\n const geometry = new Triangle(gl);\n\n const program = new Program(gl, {\n vertex: vertex,\n fragment: buildFragment(quality),\n uniforms: {\n iTime: { value: 0 },\n iResolution: { value: new Float32Array([1, 1]) },\n uCustomColor: { value: new Float32Array(customColorRgb) },\n uUseCustomColor: { value: useCustomColor },\n uSpeed: { value: speed * 0.4 },\n uDirection: { value: directionMultiplier },\n uScale: { value: scale },\n uOpacity: { value: opacity },\n uMouse: { value: new Float32Array([0, 0]) },\n uMouseInteractive: { value: mouseInteractive ? 1.0 : 0.0 }\n }\n });\n\n const mesh = new Mesh(gl, { geometry, program });\n\n const handleMouseMove = e => {\n if (!mouseInteractive) return;\n const rect = containerEl.getBoundingClientRect();\n // Store the latest position but don't touch GL state here, the rAF loop picks it up once per rendered frame instead of once per mouse event.\n pendingMouse.current = {\n x: e.clientX - rect.left,\n y: e.clientY - rect.top,\n };\n };\n\n if (mouseInteractive) {\n containerEl.addEventListener('mousemove', handleMouseMove, { passive: true });\n }\n\n let resizePending = false;\n const setSize = () => {\n const rect = containerEl.getBoundingClientRect();\n const width = Math.max(1, Math.floor(rect.width * renderScale));\n const height = Math.max(1, Math.floor(rect.height * renderScale));\n renderer.setSize(width, height);\n\n // renderer.setSize also sets canvas.style.width/height to match the (scaled-down) drawing buffer - override that so the canvas still stretches to fill its container via CSS while the buffer stays small.\n canvas.style.width = '100%';\n canvas.style.height = '100%';\n\n const res = program.uniforms.iResolution.value;\n res[0] = gl.drawingBufferWidth;\n res[1] = gl.drawingBufferHeight;\n };\n\n const ro = new ResizeObserver(() => {\n // Batch rapid resize events (ex. during a window drag) into one setSize per frame.\n if (resizePending) return;\n resizePending = true;\n requestAnimationFrame(() => {\n resizePending = false;\n setSize();\n });\n });\n ro.observe(containerEl);\n setSize();\n\n let raf = 0;\n let contextLost = false;\n let isVisible = true;\n let tabVisible = document.visibilityState !== 'hidden';\n const t0 = performance.now();\n const frameInterval = 1000 / targetFps;\n let lastFrameTime = 0;\n\n const renderStaticFrame = () => {\n program.uniforms.iTime.value = 0;\n renderer.render({ scene: mesh });\n };\n\n const loop = t => {\n if (contextLost || !isVisible || !tabVisible) return;\n\n if (t - lastFrameTime < frameInterval) {\n raf = requestAnimationFrame(loop);\n return;\n }\n lastFrameTime = t;\n\n if (pendingMouse.current) {\n mousePos.current = pendingMouse.current;\n pendingMouse.current = null;\n const mouseUniform = program.uniforms.uMouse.value;\n mouseUniform[0] = mousePos.current.x;\n mouseUniform[1] = mousePos.current.y;\n }\n\n let timeValue = (t - t0) * 0.001;\n if (direction === 'pingpong') {\n const pingpongDuration = 10;\n const segmentTime = timeValue % pingpongDuration;\n const isForward = Math.floor(timeValue / pingpongDuration) % 2 === 0;\n const u = segmentTime / pingpongDuration;\n const smooth = u * u * (3 - 2 * u);\n const pingpongTime = isForward ? smooth * pingpongDuration : (1 - smooth) * pingpongDuration;\n program.uniforms.uDirection.value = 1.0;\n program.uniforms.iTime.value = pingpongTime;\n } else {\n program.uniforms.iTime.value = timeValue;\n }\n renderer.render({ scene: mesh });\n raf = requestAnimationFrame(loop);\n };\n\n const handleContextLost = (e) => {\n e.preventDefault();\n contextLost = true;\n cancelAnimationFrame(raf);\n };\n const handleContextRestored = () => {\n contextLost = false;\n if (isVisible && tabVisible && !prefersReducedMotion) {\n cancelAnimationFrame(raf);\n raf = requestAnimationFrame(loop);\n }\n };\n canvas.addEventListener('webglcontextlost', handleContextLost);\n canvas.addEventListener('webglcontextrestored', handleContextRestored);\n\n const io = new IntersectionObserver(([entry]) => {\n const wasVisible = isVisible;\n isVisible = entry.isIntersecting;\n if (isVisible && !wasVisible && !contextLost && tabVisible && !prefersReducedMotion) {\n cancelAnimationFrame(raf);\n raf = requestAnimationFrame(loop);\n }\n }, { threshold: 0 });\n io.observe(containerEl);\n\n const handleVisibilityChange = () => {\n tabVisible = document.visibilityState !== 'hidden';\n if (tabVisible && isVisible && !contextLost && !prefersReducedMotion) {\n cancelAnimationFrame(raf);\n lastFrameTime = 0;\n raf = requestAnimationFrame(loop);\n } else {\n cancelAnimationFrame(raf);\n }\n };\n document.addEventListener('visibilitychange', handleVisibilityChange);\n\n // Respect prefers-reduced-motion: paint one frame and stop, rather than running a perpetual animation loop for users who've asked not to see motion.\n if (prefersReducedMotion) {\n renderStaticFrame();\n } else {\n raf = requestAnimationFrame(loop);\n }\n\n return () => {\n cancelAnimationFrame(raf);\n ro.disconnect();\n io.disconnect();\n document.removeEventListener('visibilitychange', handleVisibilityChange);\n canvas.removeEventListener('webglcontextlost', handleContextLost);\n canvas.removeEventListener('webglcontextrestored', handleContextRestored);\n if (mouseInteractive && containerEl) {\n containerEl.removeEventListener('mousemove', handleMouseMove);\n }\n try {\n containerEl?.removeChild(canvas);\n } catch {}\n };\n }, [color, speed, direction, scale, opacity, mouseInteractive, renderScale, maxDpr, targetFps, quality]);\n\n return
;\n};\n\nexport default Plasma;\n" } ], "registryDependencies": [], diff --git a/public/r/Plasma-TS-CSS.json b/public/r/Plasma-TS-CSS.json index 5dd429526..4541a69b9 100644 --- a/public/r/Plasma-TS-CSS.json +++ b/public/r/Plasma-TS-CSS.json @@ -13,7 +13,7 @@ { "type": "registry:component", "path": "Plasma/Plasma.tsx", - "content": "import React, { useEffect, useRef } from 'react';\nimport { Renderer, Program, Mesh, Triangle } from 'ogl';\nimport './Plasma.css';\n\ninterface PlasmaProps {\n color?: string;\n speed?: number;\n direction?: 'forward' | 'reverse' | 'pingpong';\n scale?: number;\n opacity?: number;\n mouseInteractive?: boolean;\n}\n\nconst hexToRgb = (hex: string): [number, number, number] => {\n const result = /^#?([a-f\\d]{2})([a-f\\d]{2})([a-f\\d]{2})$/i.exec(hex);\n if (!result) return [1, 0.5, 0.2];\n return [parseInt(result[1], 16) / 255, parseInt(result[2], 16) / 255, parseInt(result[3], 16) / 255];\n};\n\nconst vertex = `#version 300 es\nprecision highp float;\nin vec2 position;\nin vec2 uv;\nout vec2 vUv;\nvoid main() {\n vUv = uv;\n gl_Position = vec4(position, 0.0, 1.0);\n}\n`;\n\nconst fragment = `#version 300 es\nprecision highp float;\nuniform vec2 iResolution;\nuniform float iTime;\nuniform vec3 uCustomColor;\nuniform float uUseCustomColor;\nuniform float uSpeed;\nuniform float uDirection;\nuniform float uScale;\nuniform float uOpacity;\nuniform vec2 uMouse;\nuniform float uMouseInteractive;\nout vec4 fragColor;\n\nvoid mainImage(out vec4 o, vec2 C) {\n vec2 center = iResolution.xy * 0.5;\n C = (C - center) / uScale + center;\n \n vec2 mouseOffset = (uMouse - center) * 0.0002;\n C += mouseOffset * length(C - center) * step(0.5, uMouseInteractive);\n \n float i, d, z, T = iTime * uSpeed * uDirection;\n vec3 O, p, S;\n\n for (vec2 r = iResolution.xy, Q; ++i < 60.; O += o.w/d*o.xyz) {\n p = z*normalize(vec3(C-.5*r,r.y)); \n p.z -= 4.; \n S = p;\n d = p.y-T;\n \n p.x += .4*(1.+p.y)*sin(d + p.x*0.1)*cos(.34*d + p.x*0.05); \n Q = p.xz *= mat2(cos(p.y+vec4(0,11,33,0)-T)); \n z+= d = abs(sqrt(length(Q*Q)) - .25*(5.+S.y))/3.+8e-4; \n o = 1.+sin(S.y+p.z*.5+S.z-length(S-p)+vec4(2,1,0,8));\n }\n \n o.xyz = tanh(O/1e4);\n}\n\nbool finite1(float x){ return !(isnan(x) || isinf(x)); }\nvec3 sanitize(vec3 c){\n return vec3(\n finite1(c.r) ? c.r : 0.0,\n finite1(c.g) ? c.g : 0.0,\n finite1(c.b) ? c.b : 0.0\n );\n}\n\nvoid main() {\n vec4 o = vec4(0.0);\n mainImage(o, gl_FragCoord.xy);\n vec3 rgb = sanitize(o.rgb);\n \n float intensity = (rgb.r + rgb.g + rgb.b) / 3.0;\n vec3 customColor = intensity * uCustomColor;\n vec3 finalColor = mix(rgb, customColor, step(0.5, uUseCustomColor));\n \n float alpha = length(rgb) * uOpacity;\n fragColor = vec4(finalColor, alpha);\n}`;\n\nexport const Plasma: React.FC = ({\n color = '#ffffff',\n speed = 1,\n direction = 'forward',\n scale = 1,\n opacity = 1,\n mouseInteractive = true\n}) => {\n const containerRef = useRef(null);\n const mousePos = useRef({ x: 0, y: 0 });\n\n useEffect(() => {\n if (!containerRef.current) return;\n const containerEl = containerRef.current;\n\n const useCustomColor = color ? 1.0 : 0.0;\n const customColorRgb = color ? hexToRgb(color) : [1, 1, 1];\n\n const directionMultiplier = direction === 'reverse' ? -1.0 : 1.0;\n\n let renderer: Renderer;\n try {\n renderer = new Renderer({\n webgl: 2,\n alpha: true,\n antialias: false,\n dpr: Math.min(window.devicePixelRatio || 1, 2)\n });\n } catch {\n return;\n }\n const gl = renderer.gl;\n if (!gl) return;\n const canvas = gl.canvas as HTMLCanvasElement;\n canvas.style.display = 'block';\n canvas.style.width = '100%';\n canvas.style.height = '100%';\n containerEl.appendChild(canvas);\n\n const geometry = new Triangle(gl);\n\n const program = new Program(gl, {\n vertex: vertex,\n fragment: fragment,\n uniforms: {\n iTime: { value: 0 },\n iResolution: { value: new Float32Array([1, 1]) },\n uCustomColor: { value: new Float32Array(customColorRgb) },\n uUseCustomColor: { value: useCustomColor },\n uSpeed: { value: speed * 0.4 },\n uDirection: { value: directionMultiplier },\n uScale: { value: scale },\n uOpacity: { value: opacity },\n uMouse: { value: new Float32Array([0, 0]) },\n uMouseInteractive: { value: mouseInteractive ? 1.0 : 0.0 }\n }\n });\n\n const mesh = new Mesh(gl, { geometry, program });\n\n const handleMouseMove = (e: MouseEvent) => {\n if (!mouseInteractive) return;\n const rect = containerEl.getBoundingClientRect();\n mousePos.current.x = e.clientX - rect.left;\n mousePos.current.y = e.clientY - rect.top;\n const mouseUniform = program.uniforms.uMouse.value as Float32Array;\n mouseUniform[0] = mousePos.current.x;\n mouseUniform[1] = mousePos.current.y;\n };\n\n if (mouseInteractive) {\n containerEl.addEventListener('mousemove', handleMouseMove);\n }\n\n const setSize = () => {\n const rect = containerEl.getBoundingClientRect();\n const width = Math.max(1, Math.floor(rect.width));\n const height = Math.max(1, Math.floor(rect.height));\n renderer.setSize(width, height);\n const res = program.uniforms.iResolution.value as Float32Array;\n res[0] = gl.drawingBufferWidth;\n res[1] = gl.drawingBufferHeight;\n };\n\n const ro = new ResizeObserver(setSize);\n ro.observe(containerEl);\n setSize();\n\n let raf = 0;\n let contextLost = false;\n let isVisible = true;\n const t0 = performance.now();\n\n const loop = (t: number) => {\n if (contextLost || !isVisible) return;\n let timeValue = (t - t0) * 0.001;\n if (direction === 'pingpong') {\n const pingpongDuration = 10;\n const segmentTime = timeValue % pingpongDuration;\n const isForward = Math.floor(timeValue / pingpongDuration) % 2 === 0;\n const u = segmentTime / pingpongDuration;\n const smooth = u * u * (3 - 2 * u);\n const pingpongTime = isForward ? smooth * pingpongDuration : (1 - smooth) * pingpongDuration;\n (program.uniforms.uDirection as any).value = 1.0;\n (program.uniforms.iTime as any).value = pingpongTime;\n } else {\n (program.uniforms.iTime as any).value = timeValue;\n }\n renderer.render({ scene: mesh });\n raf = requestAnimationFrame(loop);\n };\n\n const handleContextLost = (e: Event) => {\n e.preventDefault();\n contextLost = true;\n cancelAnimationFrame(raf);\n };\n const handleContextRestored = () => {\n contextLost = false;\n if (isVisible) {\n cancelAnimationFrame(raf);\n raf = requestAnimationFrame(loop);\n }\n };\n canvas.addEventListener('webglcontextlost', handleContextLost);\n canvas.addEventListener('webglcontextrestored', handleContextRestored);\n\n const io = new IntersectionObserver(([entry]) => {\n const wasVisible = isVisible;\n isVisible = entry.isIntersecting;\n if (isVisible && !wasVisible && !contextLost) {\n cancelAnimationFrame(raf);\n raf = requestAnimationFrame(loop);\n }\n }, { threshold: 0 });\n io.observe(containerEl);\n\n raf = requestAnimationFrame(loop);\n\n return () => {\n cancelAnimationFrame(raf);\n ro.disconnect();\n io.disconnect();\n canvas.removeEventListener('webglcontextlost', handleContextLost);\n canvas.removeEventListener('webglcontextrestored', handleContextRestored);\n if (mouseInteractive && containerEl) {\n containerEl.removeEventListener('mousemove', handleMouseMove);\n }\n try {\n containerEl?.removeChild(canvas);\n } catch {}\n };\n }, [color, speed, direction, scale, opacity, mouseInteractive]);\n\n return
;\n};\n\nexport default Plasma;\n" + "content": "import React, { useEffect, useRef } from 'react';\nimport { Renderer, Program, Mesh, Triangle } from 'ogl';\nimport './Plasma.css';\n\ninterface PlasmaProps {\n color?: string;\n speed?: number;\n direction?: 'forward' | 'reverse' | 'pingpong';\n scale?: number;\n opacity?: number;\n mouseInteractive?: boolean;\n /** Internal render resolution multiplier. 1 = full res, 0.5 = quarter the pixels. Default 0.55. */\n renderScale?: number;\n /** Hard cap on devicePixelRatio used for rendering. Default 1.5. */\n maxDpr?: number;\n /** Target frame rate for the animation loop. Default 30. */\n targetFps?: number;\n /** Raymarch step count — lower is cheaper, less detailed. Default 45. */\n quality?: number;\n}\n\nconst hexToRgb = (hex: string): [number, number, number] => {\n const result = /^#?([a-f\\d]{2})([a-f\\d]{2})([a-f\\d]{2})$/i.exec(hex);\n if (!result) return [1, 0.5, 0.2];\n return [parseInt(result[1], 16) / 255, parseInt(result[2], 16) / 255, parseInt(result[3], 16) / 255];\n};\n\nconst vertex = `#version 300 es\nprecision highp float;\nin vec2 position;\nin vec2 uv;\nout vec2 vUv;\nvoid main() {\n vUv = uv;\n gl_Position = vec4(position, 0.0, 1.0);\n}\n`;\n\nconst buildFragment = (steps: number) => `#version 300 es\nprecision highp float;\nuniform vec2 iResolution;\nuniform float iTime;\nuniform vec3 uCustomColor;\nuniform float uUseCustomColor;\nuniform float uSpeed;\nuniform float uDirection;\nuniform float uScale;\nuniform float uOpacity;\nuniform vec2 uMouse;\nuniform float uMouseInteractive;\nout vec4 fragColor;\n\nvoid mainImage(out vec4 o, vec2 C) {\n vec2 center = iResolution.xy * 0.5;\n C = (C - center) / uScale + center;\n \n vec2 mouseOffset = (uMouse - center) * 0.0002;\n C += mouseOffset * length(C - center) * step(0.5, uMouseInteractive);\n \n float i, d, z, T = iTime * uSpeed * uDirection;\n vec3 O, p, S;\n\n for (vec2 r = iResolution.xy, Q; ++i < ${steps.toFixed(1)}; O += o.w/d*o.xyz) {\n p = z*normalize(vec3(C-.5*r,r.y)); \n p.z -= 4.; \n S = p;\n d = p.y-T;\n \n p.x += .4*(1.+p.y)*sin(d + p.x*0.1)*cos(.34*d + p.x*0.05); \n Q = p.xz *= mat2(cos(p.y+vec4(0,11,33,0)-T)); \n z+= d = abs(sqrt(length(Q*Q)) - .25*(5.+S.y))/3.+8e-4; \n o = 1.+sin(S.y+p.z*.5+S.z-length(S-p)+vec4(2,1,0,8));\n }\n \n o.xyz = tanh(O/1e4);\n}\n\nbool finite1(float x){ return !(isnan(x) || isinf(x)); }\nvec3 sanitize(vec3 c){\n return vec3(\n finite1(c.r) ? c.r : 0.0,\n finite1(c.g) ? c.g : 0.0,\n finite1(c.b) ? c.b : 0.0\n );\n}\n\nvoid main() {\n vec4 o = vec4(0.0);\n mainImage(o, gl_FragCoord.xy);\n vec3 rgb = sanitize(o.rgb);\n \n float intensity = (rgb.r + rgb.g + rgb.b) / 3.0;\n vec3 customColor = intensity * uCustomColor;\n vec3 finalColor = mix(rgb, customColor, step(0.5, uUseCustomColor));\n \n float alpha = length(rgb) * uOpacity;\n fragColor = vec4(finalColor, alpha);\n}`;\n\nexport const Plasma: React.FC = ({\n color = '#ffffff',\n speed = 1,\n direction = 'forward',\n scale = 1,\n opacity = 1,\n mouseInteractive = true,\n renderScale = 0.55,\n maxDpr = 1.5,\n targetFps = 30,\n quality = 45,\n}) => {\n const containerRef = useRef(null);\n const mousePos = useRef({ x: 0, y: 0 });\n const pendingMouse = useRef<{ x: number; y: number } | null>(null);\n\n useEffect(() => {\n if (!containerRef.current) return;\n const containerEl = containerRef.current;\n\n const prefersReducedMotion =\n typeof window !== 'undefined' &&\n window.matchMedia?.('(prefers-reduced-motion: reduce)').matches;\n\n const useCustomColor = color ? 1.0 : 0.0;\n const customColorRgb = color ? hexToRgb(color) : [1, 1, 1];\n\n const directionMultiplier = direction === 'reverse' ? -1.0 : 1.0;\n\n let renderer: Renderer;\n try {\n renderer = new Renderer({\n webgl: 2,\n alpha: true,\n antialias: false,\n dpr: Math.min(window.devicePixelRatio || 1, maxDpr)\n });\n } catch {\n return;\n }\n const gl = renderer.gl;\n if (!gl) return;\n const canvas = gl.canvas as HTMLCanvasElement;\n canvas.style.display = 'block';\n canvas.style.width = '100%';\n canvas.style.height = '100%';\n // Rendering at renderScale internally, CSS stretches it back up.\n containerEl.appendChild(canvas);\n\n const geometry = new Triangle(gl);\n\n const program = new Program(gl, {\n vertex: vertex,\n fragment: buildFragment(quality),\n uniforms: {\n iTime: { value: 0 },\n iResolution: { value: new Float32Array([1, 1]) },\n uCustomColor: { value: new Float32Array(customColorRgb) },\n uUseCustomColor: { value: useCustomColor },\n uSpeed: { value: speed * 0.4 },\n uDirection: { value: directionMultiplier },\n uScale: { value: scale },\n uOpacity: { value: opacity },\n uMouse: { value: new Float32Array([0, 0]) },\n uMouseInteractive: { value: mouseInteractive ? 1.0 : 0.0 }\n }\n });\n\n const mesh = new Mesh(gl, { geometry, program });\n\n const handleMouseMove = (e: MouseEvent) => {\n if (!mouseInteractive) return;\n const rect = containerEl.getBoundingClientRect();\n // Store the latest position but don't touch GL state here, the rAF loop picks it up once per rendered frame instead of once per mouse event.\n pendingMouse.current = {\n x: e.clientX - rect.left,\n y: e.clientY - rect.top,\n };\n };\n\n if (mouseInteractive) {\n containerEl.addEventListener('mousemove', handleMouseMove, { passive: true });\n }\n\n let resizePending = false;\n const setSize = () => {\n const rect = containerEl.getBoundingClientRect();\n const width = Math.max(1, Math.floor(rect.width * renderScale));\n const height = Math.max(1, Math.floor(rect.height * renderScale));\n renderer.setSize(width, height);\n\n // renderer.setSize also sets canvas.style.width/height to match the (scaled-down) drawing buffer - override that so the canvas still stretches to fill its container via CSS while the buffer stays small.\n canvas.style.width = '100%';\n canvas.style.height = '100%';\n\n const res = program.uniforms.iResolution.value as Float32Array;\n res[0] = gl.drawingBufferWidth;\n res[1] = gl.drawingBufferHeight;\n };\n\n const ro = new ResizeObserver(() => {\n // Batch rapid resize events (ex. during a window drag) into one setSize per frame.\n if (resizePending) return;\n resizePending = true;\n requestAnimationFrame(() => {\n resizePending = false;\n setSize();\n });\n });\n ro.observe(containerEl);\n setSize();\n\n let raf = 0;\n let contextLost = false;\n let isVisible = true;\n let tabVisible = document.visibilityState !== 'hidden';\n const t0 = performance.now();\n const frameInterval = 1000 / targetFps;\n let lastFrameTime = 0;\n\n const renderStaticFrame = () => {\n (program.uniforms.iTime as any).value = 0;\n renderer.render({ scene: mesh });\n };\n\n const loop = (t: number) => {\n if (contextLost || !isVisible || !tabVisible) return;\n\n if (t - lastFrameTime < frameInterval) {\n raf = requestAnimationFrame(loop);\n return;\n }\n lastFrameTime = t;\n\n if (pendingMouse.current) {\n mousePos.current = pendingMouse.current;\n pendingMouse.current = null;\n const mouseUniform = program.uniforms.uMouse.value as Float32Array;\n mouseUniform[0] = mousePos.current.x;\n mouseUniform[1] = mousePos.current.y;\n }\n\n let timeValue = (t - t0) * 0.001;\n if (direction === 'pingpong') {\n const pingpongDuration = 10;\n const segmentTime = timeValue % pingpongDuration;\n const isForward = Math.floor(timeValue / pingpongDuration) % 2 === 0;\n const u = segmentTime / pingpongDuration;\n const smooth = u * u * (3 - 2 * u);\n const pingpongTime = isForward ? smooth * pingpongDuration : (1 - smooth) * pingpongDuration;\n (program.uniforms.uDirection as any).value = 1.0;\n (program.uniforms.iTime as any).value = pingpongTime;\n } else {\n (program.uniforms.iTime as any).value = timeValue;\n }\n renderer.render({ scene: mesh });\n raf = requestAnimationFrame(loop);\n };\n\n const handleContextLost = (e: Event) => {\n e.preventDefault();\n contextLost = true;\n cancelAnimationFrame(raf);\n };\n const handleContextRestored = () => {\n contextLost = false;\n if (isVisible && tabVisible && !prefersReducedMotion) {\n cancelAnimationFrame(raf);\n raf = requestAnimationFrame(loop);\n }\n };\n canvas.addEventListener('webglcontextlost', handleContextLost);\n canvas.addEventListener('webglcontextrestored', handleContextRestored);\n\n const io = new IntersectionObserver(([entry]) => {\n const wasVisible = isVisible;\n isVisible = entry.isIntersecting;\n if (isVisible && !wasVisible && !contextLost && tabVisible && !prefersReducedMotion) {\n cancelAnimationFrame(raf);\n raf = requestAnimationFrame(loop);\n }\n }, { threshold: 0 });\n io.observe(containerEl);\n\n const handleVisibilityChange = () => {\n tabVisible = document.visibilityState !== 'hidden';\n if (tabVisible && isVisible && !contextLost && !prefersReducedMotion) {\n cancelAnimationFrame(raf);\n lastFrameTime = 0;\n raf = requestAnimationFrame(loop);\n } else {\n cancelAnimationFrame(raf);\n }\n };\n document.addEventListener('visibilitychange', handleVisibilityChange);\n\n // Respect prefers-reduced-motion: paint one frame and stop, rather than running a perpetual animation loop for users who've asked not to see motion.\n if (prefersReducedMotion) {\n renderStaticFrame();\n } else {\n raf = requestAnimationFrame(loop);\n }\n\n return () => {\n cancelAnimationFrame(raf);\n ro.disconnect();\n io.disconnect();\n document.removeEventListener('visibilitychange', handleVisibilityChange);\n canvas.removeEventListener('webglcontextlost', handleContextLost);\n canvas.removeEventListener('webglcontextrestored', handleContextRestored);\n if (mouseInteractive && containerEl) {\n containerEl.removeEventListener('mousemove', handleMouseMove);\n }\n try {\n containerEl?.removeChild(canvas);\n } catch {}\n };\n }, [color, speed, direction, scale, opacity, mouseInteractive, renderScale, maxDpr, targetFps, quality]);\n\n return
;\n};\n\nexport default Plasma;\n" } ], "registryDependencies": [], diff --git a/public/r/Plasma-TS-TW.json b/public/r/Plasma-TS-TW.json index ed2398261..69d7d83b7 100644 --- a/public/r/Plasma-TS-TW.json +++ b/public/r/Plasma-TS-TW.json @@ -8,7 +8,7 @@ { "type": "registry:component", "path": "Plasma/Plasma.tsx", - "content": "import React, { useEffect, useRef } from 'react';\nimport { Renderer, Program, Mesh, Triangle } from 'ogl';\n\ninterface PlasmaProps {\n color?: string;\n speed?: number;\n direction?: 'forward' | 'reverse' | 'pingpong';\n scale?: number;\n opacity?: number;\n mouseInteractive?: boolean;\n}\n\nconst hexToRgb = (hex: string): [number, number, number] => {\n const result = /^#?([a-f\\d]{2})([a-f\\d]{2})([a-f\\d]{2})$/i.exec(hex);\n if (!result) return [1, 0.5, 0.2];\n return [parseInt(result[1], 16) / 255, parseInt(result[2], 16) / 255, parseInt(result[3], 16) / 255];\n};\n\nconst vertex = `#version 300 es\nprecision highp float;\nin vec2 position;\nin vec2 uv;\nout vec2 vUv;\nvoid main() {\n vUv = uv;\n gl_Position = vec4(position, 0.0, 1.0);\n}\n`;\n\nconst fragment = `#version 300 es\nprecision highp float;\nuniform vec2 iResolution;\nuniform float iTime;\nuniform vec3 uCustomColor;\nuniform float uUseCustomColor;\nuniform float uSpeed;\nuniform float uDirection;\nuniform float uScale;\nuniform float uOpacity;\nuniform vec2 uMouse;\nuniform float uMouseInteractive;\nout vec4 fragColor;\n\nvoid mainImage(out vec4 o, vec2 C) {\n vec2 center = iResolution.xy * 0.5;\n C = (C - center) / uScale + center;\n \n vec2 mouseOffset = (uMouse - center) * 0.0002;\n C += mouseOffset * length(C - center) * step(0.5, uMouseInteractive);\n \n float i, d, z, T = iTime * uSpeed * uDirection;\n vec3 O, p, S;\n\n for (vec2 r = iResolution.xy, Q; ++i < 60.; O += o.w/d*o.xyz) {\n p = z*normalize(vec3(C-.5*r,r.y)); \n p.z -= 4.; \n S = p;\n d = p.y-T;\n \n p.x += .4*(1.+p.y)*sin(d + p.x*0.1)*cos(.34*d + p.x*0.05); \n Q = p.xz *= mat2(cos(p.y+vec4(0,11,33,0)-T)); \n z+= d = abs(sqrt(length(Q*Q)) - .25*(5.+S.y))/3.+8e-4; \n o = 1.+sin(S.y+p.z*.5+S.z-length(S-p)+vec4(2,1,0,8));\n }\n \n o.xyz = tanh(O/1e4);\n}\n\nbool finite1(float x){ return !(isnan(x) || isinf(x)); }\nvec3 sanitize(vec3 c){\n return vec3(\n finite1(c.r) ? c.r : 0.0,\n finite1(c.g) ? c.g : 0.0,\n finite1(c.b) ? c.b : 0.0\n );\n}\n\nvoid main() {\n vec4 o = vec4(0.0);\n mainImage(o, gl_FragCoord.xy);\n vec3 rgb = sanitize(o.rgb);\n \n float intensity = (rgb.r + rgb.g + rgb.b) / 3.0;\n vec3 customColor = intensity * uCustomColor;\n vec3 finalColor = mix(rgb, customColor, step(0.5, uUseCustomColor));\n \n float alpha = length(rgb) * uOpacity;\n fragColor = vec4(finalColor, alpha);\n}`;\n\nexport const Plasma: React.FC = ({\n color = '#ffffff',\n speed = 1,\n direction = 'forward',\n scale = 1,\n opacity = 1,\n mouseInteractive = true\n}) => {\n const containerRef = useRef(null);\n const mousePos = useRef({ x: 0, y: 0 });\n\n useEffect(() => {\n if (!containerRef.current) return;\n const containerEl = containerRef.current;\n\n const useCustomColor = color ? 1.0 : 0.0;\n const customColorRgb = color ? hexToRgb(color) : [1, 1, 1];\n\n const directionMultiplier = direction === 'reverse' ? -1.0 : 1.0;\n\n let renderer: Renderer;\n try {\n renderer = new Renderer({\n webgl: 2,\n alpha: true,\n antialias: false,\n dpr: Math.min(window.devicePixelRatio || 1, 2)\n });\n } catch {\n return;\n }\n const gl = renderer.gl;\n if (!gl) return;\n const canvas = gl.canvas as HTMLCanvasElement;\n canvas.style.display = 'block';\n canvas.style.width = '100%';\n canvas.style.height = '100%';\n containerEl.appendChild(canvas);\n\n const geometry = new Triangle(gl);\n\n const program = new Program(gl, {\n vertex: vertex,\n fragment: fragment,\n uniforms: {\n iTime: { value: 0 },\n iResolution: { value: new Float32Array([1, 1]) },\n uCustomColor: { value: new Float32Array(customColorRgb) },\n uUseCustomColor: { value: useCustomColor },\n uSpeed: { value: speed * 0.4 },\n uDirection: { value: directionMultiplier },\n uScale: { value: scale },\n uOpacity: { value: opacity },\n uMouse: { value: new Float32Array([0, 0]) },\n uMouseInteractive: { value: mouseInteractive ? 1.0 : 0.0 }\n }\n });\n\n const mesh = new Mesh(gl, { geometry, program });\n\n const handleMouseMove = (e: MouseEvent) => {\n if (!mouseInteractive) return;\n const rect = containerEl.getBoundingClientRect();\n mousePos.current.x = e.clientX - rect.left;\n mousePos.current.y = e.clientY - rect.top;\n const mouseUniform = program.uniforms.uMouse.value as Float32Array;\n mouseUniform[0] = mousePos.current.x;\n mouseUniform[1] = mousePos.current.y;\n };\n\n if (mouseInteractive) {\n containerEl.addEventListener('mousemove', handleMouseMove);\n }\n\n const setSize = () => {\n const rect = containerEl.getBoundingClientRect();\n const width = Math.max(1, Math.floor(rect.width));\n const height = Math.max(1, Math.floor(rect.height));\n renderer.setSize(width, height);\n const res = program.uniforms.iResolution.value as Float32Array;\n res[0] = gl.drawingBufferWidth;\n res[1] = gl.drawingBufferHeight;\n };\n\n const ro = new ResizeObserver(setSize);\n ro.observe(containerEl);\n setSize();\n\n let raf = 0;\n let contextLost = false;\n let isVisible = true;\n const t0 = performance.now();\n\n const loop = (t: number) => {\n if (contextLost || !isVisible) return;\n let timeValue = (t - t0) * 0.001;\n if (direction === 'pingpong') {\n const pingpongDuration = 10;\n const segmentTime = timeValue % pingpongDuration;\n const isForward = Math.floor(timeValue / pingpongDuration) % 2 === 0;\n const u = segmentTime / pingpongDuration;\n const smooth = u * u * (3 - 2 * u);\n const pingpongTime = isForward ? smooth * pingpongDuration : (1 - smooth) * pingpongDuration;\n (program.uniforms.uDirection as any).value = 1.0;\n (program.uniforms.iTime as any).value = pingpongTime;\n } else {\n (program.uniforms.iTime as any).value = timeValue;\n }\n renderer.render({ scene: mesh });\n raf = requestAnimationFrame(loop);\n };\n\n const handleContextLost = (e: Event) => {\n e.preventDefault();\n contextLost = true;\n cancelAnimationFrame(raf);\n };\n const handleContextRestored = () => {\n contextLost = false;\n if (isVisible) {\n cancelAnimationFrame(raf);\n raf = requestAnimationFrame(loop);\n }\n };\n canvas.addEventListener('webglcontextlost', handleContextLost);\n canvas.addEventListener('webglcontextrestored', handleContextRestored);\n\n const io = new IntersectionObserver(([entry]) => {\n const wasVisible = isVisible;\n isVisible = entry.isIntersecting;\n if (isVisible && !wasVisible && !contextLost) {\n cancelAnimationFrame(raf);\n raf = requestAnimationFrame(loop);\n }\n }, { threshold: 0 });\n io.observe(containerEl);\n\n raf = requestAnimationFrame(loop);\n\n return () => {\n cancelAnimationFrame(raf);\n ro.disconnect();\n io.disconnect();\n canvas.removeEventListener('webglcontextlost', handleContextLost);\n canvas.removeEventListener('webglcontextrestored', handleContextRestored);\n if (mouseInteractive && containerEl) {\n containerEl.removeEventListener('mousemove', handleMouseMove);\n }\n try {\n containerEl?.removeChild(canvas);\n } catch {}\n };\n }, [color, speed, direction, scale, opacity, mouseInteractive]);\n\n return
;\n};\n\nexport default Plasma;\n" + "content": "import React, { useEffect, useRef } from \"react\";\nimport { Renderer, Program, Mesh, Triangle } from \"ogl\";\n\ninterface PlasmaProps {\n color?: string;\n speed?: number;\n direction?: \"forward\" | \"reverse\" | \"pingpong\";\n scale?: number;\n opacity?: number;\n mouseInteractive?: boolean;\n /** Internal render resolution multiplier. 1 = full res, 0.5 = quarter the pixels. Default 0.55. */\n renderScale?: number;\n /** Hard cap on devicePixelRatio used for rendering. Default 1.5. */\n maxDpr?: number;\n /** Target frame rate for the animation loop. Default 30. */\n targetFps?: number;\n /** Raymarch step count — lower is cheaper, less detailed. Default 45. */\n quality?: number;\n}\n\nconst hexToRgb = (hex: string): [number, number, number] => {\n const result = /^#?([a-f\\d]{2})([a-f\\d]{2})([a-f\\d]{2})$/i.exec(hex);\n if (!result) return [1, 0.5, 0.2];\n return [\n parseInt(result[1], 16) / 255,\n parseInt(result[2], 16) / 255,\n parseInt(result[3], 16) / 255,\n ];\n};\n\nconst vertex = `#version 300 es\nprecision highp float;\nin vec2 position;\nin vec2 uv;\nout vec2 vUv;\nvoid main() {\n vUv = uv;\n gl_Position = vec4(position, 0.0, 1.0);\n}\n`;\n\nconst buildFragment = (steps: number) => `#version 300 es\nprecision highp float;\nuniform vec2 iResolution;\nuniform float iTime;\nuniform vec3 uCustomColor;\nuniform float uUseCustomColor;\nuniform float uSpeed;\nuniform float uDirection;\nuniform float uScale;\nuniform float uOpacity;\nuniform vec2 uMouse;\nuniform float uMouseInteractive;\nout vec4 fragColor;\n\nvoid mainImage(out vec4 o, vec2 C) {\n vec2 center = iResolution.xy * 0.5;\n C = (C - center) / uScale + center;\n\n vec2 mouseOffset = (uMouse - center) * 0.0002;\n C += mouseOffset * length(C - center) * step(0.5, uMouseInteractive);\n\n float i, d, z, T = iTime * uSpeed * uDirection;\n vec3 O, p, S;\n\n for (vec2 r = iResolution.xy, Q; ++i < ${steps.toFixed(1)}; O += o.w/d*o.xyz) {\n p = z*normalize(vec3(C-.5*r,r.y));\n p.z -= 4.;\n S = p;\n d = p.y-T;\n\n p.x += .4*(1.+p.y)*sin(d + p.x*0.1)*cos(.34*d + p.x*0.05);\n Q = p.xz *= mat2(cos(p.y+vec4(0,11,33,0)-T));\n z+= d = abs(sqrt(length(Q*Q)) - .25*(5.+S.y))/3.+8e-4;\n o = 1.+sin(S.y+p.z*.5+S.z-length(S-p)+vec4(2,1,0,8));\n }\n\n o.xyz = tanh(O/1e4);\n}\n\nbool finite1(float x){ return !(isnan(x) || isinf(x)); }\nvec3 sanitize(vec3 c){\n return vec3(\n finite1(c.r) ? c.r : 0.0,\n finite1(c.g) ? c.g : 0.0,\n finite1(c.b) ? c.b : 0.0\n );\n}\n\nvoid main() {\n vec4 o = vec4(0.0);\n mainImage(o, gl_FragCoord.xy);\n vec3 rgb = sanitize(o.rgb);\n\n float intensity = (rgb.r + rgb.g + rgb.b) / 3.0;\n vec3 customColor = intensity * uCustomColor;\n vec3 finalColor = mix(rgb, customColor, step(0.5, uUseCustomColor));\n\n float alpha = length(rgb) * uOpacity;\n fragColor = vec4(finalColor, alpha);\n}`;\n\nexport const Plasma: React.FC = ({\n color = \"#ffffff\",\n speed = 1,\n direction = \"forward\",\n scale = 1,\n opacity = 1,\n mouseInteractive = true,\n renderScale = 0.55,\n maxDpr = 1.5,\n targetFps = 30,\n quality = 45,\n}) => {\n const containerRef = useRef(null);\n const mousePos = useRef({ x: 0, y: 0 });\n const pendingMouse = useRef<{ x: number; y: number } | null>(null);\n\n useEffect(() => {\n if (!containerRef.current) return;\n const containerEl = containerRef.current;\n\n const prefersReducedMotion =\n typeof window !== \"undefined\" &&\n window.matchMedia?.(\"(prefers-reduced-motion: reduce)\").matches;\n\n const useCustomColor = color ? 1.0 : 0.0;\n const customColorRgb = color ? hexToRgb(color) : [1, 1, 1];\n const directionMultiplier = direction === \"reverse\" ? -1.0 : 1.0;\n\n let renderer: Renderer;\n try {\n renderer = new Renderer({\n webgl: 2,\n alpha: true,\n antialias: false,\n dpr: Math.min(window.devicePixelRatio || 1, maxDpr),\n });\n } catch {\n return;\n }\n const gl = renderer.gl;\n if (!gl) return;\n const canvas = gl.canvas as HTMLCanvasElement;\n canvas.style.display = \"block\";\n canvas.style.width = \"100%\";\n canvas.style.height = \"100%\";\n // Rendering at renderScale internally, CSS stretches it back up.\n containerEl.appendChild(canvas);\n\n const geometry = new Triangle(gl);\n\n const program = new Program(gl, {\n vertex: vertex,\n fragment: buildFragment(quality),\n uniforms: {\n iTime: { value: 0 },\n iResolution: { value: new Float32Array([1, 1]) },\n uCustomColor: { value: new Float32Array(customColorRgb) },\n uUseCustomColor: { value: useCustomColor },\n uSpeed: { value: speed * 0.4 },\n uDirection: { value: directionMultiplier },\n uScale: { value: scale },\n uOpacity: { value: opacity },\n uMouse: { value: new Float32Array([0, 0]) },\n uMouseInteractive: { value: mouseInteractive ? 1.0 : 0.0 },\n },\n });\n\n const mesh = new Mesh(gl, { geometry, program });\n\n const handleMouseMove = (e: MouseEvent) => {\n if (!mouseInteractive) return;\n const rect = containerEl.getBoundingClientRect();\n // Store the latest position but don't touch GL state here, the rAF loop picks it up once per rendered frame instead of once per mouse event.\n pendingMouse.current = {\n x: e.clientX - rect.left,\n y: e.clientY - rect.top,\n };\n };\n\n if (mouseInteractive) {\n containerEl.addEventListener(\"mousemove\", handleMouseMove, {\n passive: true,\n });\n }\n\n let resizePending = false;\n const setSize = () => {\n const rect = containerEl.getBoundingClientRect();\n const width = Math.max(1, Math.floor(rect.width * renderScale));\n const height = Math.max(1, Math.floor(rect.height * renderScale));\n renderer.setSize(width, height);\n\n // renderer.setSize also sets canvas.style.width/height to match the (scaled-down) drawing buffer - override that so the canvas still stretches to fill its container via CSS while the buffer stays small.\n canvas.style.width = \"100%\";\n canvas.style.height = \"100%\";\n\n const res = program.uniforms.iResolution.value as Float32Array;\n res[0] = gl.drawingBufferWidth;\n res[1] = gl.drawingBufferHeight;\n };\n\n const ro = new ResizeObserver(() => {\n // Batch rapid resize events (ex. during a window drag) into one setSize per frame.\n if (resizePending) return;\n resizePending = true;\n requestAnimationFrame(() => {\n resizePending = false;\n setSize();\n });\n });\n ro.observe(containerEl);\n setSize();\n\n let raf = 0;\n let contextLost = false;\n let isVisible = true;\n let tabVisible = document.visibilityState !== \"hidden\";\n const t0 = performance.now();\n const frameInterval = 1000 / targetFps;\n let lastFrameTime = 0;\n\n const renderStaticFrame = () => {\n (program.uniforms.iTime as any).value = 0;\n renderer.render({ scene: mesh });\n };\n\n const loop = (t: number) => {\n if (contextLost || !isVisible || !tabVisible) return;\n\n if (t - lastFrameTime < frameInterval) {\n raf = requestAnimationFrame(loop);\n return;\n }\n lastFrameTime = t;\n\n if (pendingMouse.current) {\n mousePos.current = pendingMouse.current;\n pendingMouse.current = null;\n const mouseUniform = program.uniforms.uMouse.value as Float32Array;\n mouseUniform[0] = mousePos.current.x;\n mouseUniform[1] = mousePos.current.y;\n }\n\n let timeValue = (t - t0) * 0.001;\n if (direction === \"pingpong\") {\n const pingpongDuration = 10;\n const segmentTime = timeValue % pingpongDuration;\n const isForward = Math.floor(timeValue / pingpongDuration) % 2 === 0;\n const u = segmentTime / pingpongDuration;\n const smooth = u * u * (3 - 2 * u);\n const pingpongTime = isForward\n ? smooth * pingpongDuration\n : (1 - smooth) * pingpongDuration;\n (program.uniforms.uDirection as any).value = 1.0;\n (program.uniforms.iTime as any).value = pingpongTime;\n } else {\n (program.uniforms.iTime as any).value = timeValue;\n }\n renderer.render({ scene: mesh });\n raf = requestAnimationFrame(loop);\n };\n\n const handleContextLost = (e: Event) => {\n e.preventDefault();\n contextLost = true;\n cancelAnimationFrame(raf);\n };\n const handleContextRestored = () => {\n contextLost = false;\n if (isVisible && tabVisible && !prefersReducedMotion) {\n cancelAnimationFrame(raf);\n raf = requestAnimationFrame(loop);\n }\n };\n canvas.addEventListener(\"webglcontextlost\", handleContextLost);\n canvas.addEventListener(\"webglcontextrestored\", handleContextRestored);\n\n const io = new IntersectionObserver(\n ([entry]) => {\n const wasVisible = isVisible;\n isVisible = entry.isIntersecting;\n if (\n isVisible &&\n !wasVisible &&\n !contextLost &&\n tabVisible &&\n !prefersReducedMotion\n ) {\n cancelAnimationFrame(raf);\n raf = requestAnimationFrame(loop);\n }\n },\n { threshold: 0 },\n );\n io.observe(containerEl);\n\n const handleVisibilityChange = () => {\n tabVisible = document.visibilityState !== \"hidden\";\n if (tabVisible && isVisible && !contextLost && !prefersReducedMotion) {\n cancelAnimationFrame(raf);\n lastFrameTime = 0;\n raf = requestAnimationFrame(loop);\n } else {\n cancelAnimationFrame(raf);\n }\n };\n document.addEventListener(\"visibilitychange\", handleVisibilityChange);\n\n // Respect prefers-reduced-motion: paint one frame and stop, rather than running a perpetual animation loop for users who've asked not to see motion.\n if (prefersReducedMotion) {\n renderStaticFrame();\n } else {\n raf = requestAnimationFrame(loop);\n }\n\n return () => {\n cancelAnimationFrame(raf);\n ro.disconnect();\n io.disconnect();\n document.removeEventListener(\"visibilitychange\", handleVisibilityChange);\n canvas.removeEventListener(\"webglcontextlost\", handleContextLost);\n canvas.removeEventListener(\"webglcontextrestored\", handleContextRestored);\n if (mouseInteractive && containerEl) {\n containerEl.removeEventListener(\"mousemove\", handleMouseMove);\n }\n try {\n containerEl?.removeChild(canvas);\n } catch {}\n };\n }, [\n color,\n speed,\n direction,\n scale,\n opacity,\n mouseInteractive,\n renderScale,\n maxDpr,\n targetFps,\n quality,\n ]);\n\n return (\n \n );\n};\n\nexport default Plasma;\n" } ], "registryDependencies": [], diff --git a/src/content/Backgrounds/Plasma/Plasma.jsx b/src/content/Backgrounds/Plasma/Plasma.jsx index e3ed835bb..88296a11e 100644 --- a/src/content/Backgrounds/Plasma/Plasma.jsx +++ b/src/content/Backgrounds/Plasma/Plasma.jsx @@ -19,7 +19,7 @@ void main() { } `; -const fragment = `#version 300 es +const buildFragment = steps => `#version 300 es precision highp float; uniform vec2 iResolution; uniform float iTime; @@ -43,7 +43,7 @@ void mainImage(out vec4 o, vec2 C) { float i, d, z, T = iTime * uSpeed * uDirection; vec3 O, p, S; - for (vec2 r = iResolution.xy, Q; ++i < 60.; O += o.w/d*o.xyz) { + for (vec2 r = iResolution.xy, Q; ++i < ${steps.toFixed(1)}; O += o.w/d*o.xyz) { p = z*normalize(vec3(C-.5*r,r.y)); p.z -= 4.; S = p; @@ -86,15 +86,24 @@ export const Plasma = ({ direction = 'forward', scale = 1, opacity = 1, - mouseInteractive = true + mouseInteractive = true, + renderScale = 0.55, + maxDpr = 1.5, + targetFps = 30, + quality = 45, }) => { const containerRef = useRef(null); const mousePos = useRef({ x: 0, y: 0 }); + const pendingMouse = useRef(null); useEffect(() => { if (!containerRef.current) return; const containerEl = containerRef.current; + const prefersReducedMotion = + typeof window !== 'undefined' && + window.matchMedia?.('(prefers-reduced-motion: reduce)').matches; + const useCustomColor = color ? 1.0 : 0.0; const customColorRgb = color ? hexToRgb(color) : [1, 1, 1]; @@ -106,7 +115,7 @@ export const Plasma = ({ webgl: 2, alpha: true, antialias: false, - dpr: Math.min(window.devicePixelRatio || 1, 2) + dpr: Math.min(window.devicePixelRatio || 1, maxDpr) }); } catch { return; @@ -117,13 +126,14 @@ export const Plasma = ({ canvas.style.display = 'block'; canvas.style.width = '100%'; canvas.style.height = '100%'; + // Rendering at renderScale internally, CSS stretches it back up. containerEl.appendChild(canvas); const geometry = new Triangle(gl); const program = new Program(gl, { vertex: vertex, - fragment: fragment, + fragment: buildFragment(quality), uniforms: { iTime: { value: 0 }, iResolution: { value: new Float32Array([1, 1]) }, @@ -143,38 +153,75 @@ export const Plasma = ({ const handleMouseMove = e => { if (!mouseInteractive) return; const rect = containerEl.getBoundingClientRect(); - mousePos.current.x = e.clientX - rect.left; - mousePos.current.y = e.clientY - rect.top; - const mouseUniform = program.uniforms.uMouse.value; - mouseUniform[0] = mousePos.current.x; - mouseUniform[1] = mousePos.current.y; + // Store the latest position but don't touch GL state here, the rAF loop picks it up once per rendered frame instead of once per mouse event. + pendingMouse.current = { + x: e.clientX - rect.left, + y: e.clientY - rect.top, + }; }; if (mouseInteractive) { - containerEl.addEventListener('mousemove', handleMouseMove); + containerEl.addEventListener('mousemove', handleMouseMove, { passive: true }); } + let resizePending = false; const setSize = () => { const rect = containerEl.getBoundingClientRect(); - const width = Math.max(1, Math.floor(rect.width)); - const height = Math.max(1, Math.floor(rect.height)); + const width = Math.max(1, Math.floor(rect.width * renderScale)); + const height = Math.max(1, Math.floor(rect.height * renderScale)); renderer.setSize(width, height); + + // renderer.setSize also sets canvas.style.width/height to match the (scaled-down) drawing buffer - override that so the canvas still stretches to fill its container via CSS while the buffer stays small. + canvas.style.width = '100%'; + canvas.style.height = '100%'; + const res = program.uniforms.iResolution.value; res[0] = gl.drawingBufferWidth; res[1] = gl.drawingBufferHeight; }; - const ro = new ResizeObserver(setSize); + const ro = new ResizeObserver(() => { + // Batch rapid resize events (ex. during a window drag) into one setSize per frame. + if (resizePending) return; + resizePending = true; + requestAnimationFrame(() => { + resizePending = false; + setSize(); + }); + }); ro.observe(containerEl); setSize(); let raf = 0; let contextLost = false; let isVisible = true; + let tabVisible = document.visibilityState !== 'hidden'; const t0 = performance.now(); + const frameInterval = 1000 / targetFps; + let lastFrameTime = 0; + + const renderStaticFrame = () => { + program.uniforms.iTime.value = 0; + renderer.render({ scene: mesh }); + }; const loop = t => { - if (contextLost || !isVisible) return; + if (contextLost || !isVisible || !tabVisible) return; + + if (t - lastFrameTime < frameInterval) { + raf = requestAnimationFrame(loop); + return; + } + lastFrameTime = t; + + if (pendingMouse.current) { + mousePos.current = pendingMouse.current; + pendingMouse.current = null; + const mouseUniform = program.uniforms.uMouse.value; + mouseUniform[0] = mousePos.current.x; + mouseUniform[1] = mousePos.current.y; + } + let timeValue = (t - t0) * 0.001; if (direction === 'pingpong') { const pingpongDuration = 10; @@ -199,7 +246,7 @@ export const Plasma = ({ }; const handleContextRestored = () => { contextLost = false; - if (isVisible) { + if (isVisible && tabVisible && !prefersReducedMotion) { cancelAnimationFrame(raf); raf = requestAnimationFrame(loop); } @@ -210,19 +257,37 @@ export const Plasma = ({ const io = new IntersectionObserver(([entry]) => { const wasVisible = isVisible; isVisible = entry.isIntersecting; - if (isVisible && !wasVisible && !contextLost) { + if (isVisible && !wasVisible && !contextLost && tabVisible && !prefersReducedMotion) { cancelAnimationFrame(raf); raf = requestAnimationFrame(loop); } }, { threshold: 0 }); io.observe(containerEl); - raf = requestAnimationFrame(loop); + const handleVisibilityChange = () => { + tabVisible = document.visibilityState !== 'hidden'; + if (tabVisible && isVisible && !contextLost && !prefersReducedMotion) { + cancelAnimationFrame(raf); + lastFrameTime = 0; + raf = requestAnimationFrame(loop); + } else { + cancelAnimationFrame(raf); + } + }; + document.addEventListener('visibilitychange', handleVisibilityChange); + + // Respect prefers-reduced-motion: paint one frame and stop, rather than running a perpetual animation loop for users who've asked not to see motion. + if (prefersReducedMotion) { + renderStaticFrame(); + } else { + raf = requestAnimationFrame(loop); + } return () => { cancelAnimationFrame(raf); ro.disconnect(); io.disconnect(); + document.removeEventListener('visibilitychange', handleVisibilityChange); canvas.removeEventListener('webglcontextlost', handleContextLost); canvas.removeEventListener('webglcontextrestored', handleContextRestored); if (mouseInteractive && containerEl) { @@ -232,7 +297,7 @@ export const Plasma = ({ containerEl?.removeChild(canvas); } catch {} }; - }, [color, speed, direction, scale, opacity, mouseInteractive]); + }, [color, speed, direction, scale, opacity, mouseInteractive, renderScale, maxDpr, targetFps, quality]); return
; }; diff --git a/src/demo/Backgrounds/PlasmaDemo.jsx b/src/demo/Backgrounds/PlasmaDemo.jsx index 75cd2fa0e..352c33262 100644 --- a/src/demo/Backgrounds/PlasmaDemo.jsx +++ b/src/demo/Backgrounds/PlasmaDemo.jsx @@ -26,12 +26,16 @@ const DEFAULT_PROPS = { direction: 'forward', scale: 1.0, opacity: 1.0, - mouseInteractive: false + mouseInteractive: false, + renderScale: 0.55, + maxDpr: 1.5, + targetFps: 30, + quality: 45 }; const PlasmaDemo = () => { const { props, updateProp, resetProps, hasChanges } = useComponentProps(DEFAULT_PROPS); - const { color, speed, direction, scale, opacity, mouseInteractive } = props; + const { color, speed, direction, scale, opacity, mouseInteractive, renderScale, maxDpr, targetFps, quality } = props; const propData = useMemo( () => [ { @@ -69,6 +73,32 @@ const PlasmaDemo = () => { type: 'boolean', default: 'false', description: 'Whether the plasma responds to mouse movement.' + }, + { + name: 'renderScale', + type: 'number', + default: '0.55', + description: + 'Internal render resolution multiplier. 1 = full res, 0.5 = quarter the pixels. Lower values improve performance.' + }, + { + name: 'maxDpr', + type: 'number', + default: '1.5', + description: + 'Hard cap on devicePixelRatio used for rendering. Lower values improve performance on high-DPI screens.' + }, + { + name: 'targetFps', + type: 'number', + default: '30', + description: 'Target frame rate for the animation loop. Lower values reduce CPU/GPU load.' + }, + { + name: 'quality', + type: 'number', + default: '45', + description: 'Raymarch step count — lower is cheaper but less detailed. Higher values produce smoother plasma.' } ], [] @@ -86,6 +116,10 @@ const PlasmaDemo = () => { scale={scale} opacity={opacity} mouseInteractive={mouseInteractive} + renderScale={renderScale} + maxDpr={maxDpr} + targetFps={targetFps} + quality={quality} /> @@ -93,8 +127,18 @@ const PlasmaDemo = () => { @@ -140,6 +184,42 @@ const PlasmaDemo = () => { onChange={val => updateProp('opacity', val)} /> + updateProp('quality', val)} + /> + + updateProp('renderScale', val)} + /> + + updateProp('targetFps', val)} + /> + + updateProp('maxDpr', val)} + /> + `#version 300 es precision highp float; uniform vec2 iResolution; uniform float iTime; @@ -42,7 +42,7 @@ void mainImage(out vec4 o, vec2 C) { float i, d, z, T = iTime * uSpeed * uDirection; vec3 O, p, S; - for (vec2 r = iResolution.xy, Q; ++i < 60.; O += o.w/d*o.xyz) { + for (vec2 r = iResolution.xy, Q; ++i < ${steps.toFixed(1)}; O += o.w/d*o.xyz) { p = z*normalize(vec3(C-.5*r,r.y)); p.z -= 4.; S = p; @@ -85,15 +85,24 @@ export const Plasma = ({ direction = 'forward', scale = 1, opacity = 1, - mouseInteractive = true + mouseInteractive = true, + renderScale = 0.55, + maxDpr = 1.5, + targetFps = 30, + quality = 45, }) => { const containerRef = useRef(null); const mousePos = useRef({ x: 0, y: 0 }); + const pendingMouse = useRef(null); useEffect(() => { if (!containerRef.current) return; const containerEl = containerRef.current; + const prefersReducedMotion = + typeof window !== 'undefined' && + window.matchMedia?.('(prefers-reduced-motion: reduce)').matches; + const useCustomColor = color ? 1.0 : 0.0; const customColorRgb = color ? hexToRgb(color) : [1, 1, 1]; @@ -105,7 +114,7 @@ export const Plasma = ({ webgl: 2, alpha: true, antialias: false, - dpr: Math.min(window.devicePixelRatio || 1, 2) + dpr: Math.min(window.devicePixelRatio || 1, maxDpr) }); } catch { return; @@ -116,13 +125,14 @@ export const Plasma = ({ canvas.style.display = 'block'; canvas.style.width = '100%'; canvas.style.height = '100%'; + // Rendering at renderScale internally, CSS stretches it back up. containerEl.appendChild(canvas); const geometry = new Triangle(gl); const program = new Program(gl, { vertex: vertex, - fragment: fragment, + fragment: buildFragment(quality), uniforms: { iTime: { value: 0 }, iResolution: { value: new Float32Array([1, 1]) }, @@ -142,38 +152,75 @@ export const Plasma = ({ const handleMouseMove = e => { if (!mouseInteractive) return; const rect = containerEl.getBoundingClientRect(); - mousePos.current.x = e.clientX - rect.left; - mousePos.current.y = e.clientY - rect.top; - const mouseUniform = program.uniforms.uMouse.value; - mouseUniform[0] = mousePos.current.x; - mouseUniform[1] = mousePos.current.y; + // Store the latest position but don't touch GL state here, the rAF loop picks it up once per rendered frame instead of once per mouse event. + pendingMouse.current = { + x: e.clientX - rect.left, + y: e.clientY - rect.top, + }; }; if (mouseInteractive) { - containerEl.addEventListener('mousemove', handleMouseMove); + containerEl.addEventListener('mousemove', handleMouseMove, { passive: true }); } + let resizePending = false; const setSize = () => { const rect = containerEl.getBoundingClientRect(); - const width = Math.max(1, Math.floor(rect.width)); - const height = Math.max(1, Math.floor(rect.height)); + const width = Math.max(1, Math.floor(rect.width * renderScale)); + const height = Math.max(1, Math.floor(rect.height * renderScale)); renderer.setSize(width, height); + + // renderer.setSize also sets canvas.style.width/height to match the (scaled-down) drawing buffer - override that so the canvas still stretches to fill its container via CSS while the buffer stays small. + canvas.style.width = '100%'; + canvas.style.height = '100%'; + const res = program.uniforms.iResolution.value; res[0] = gl.drawingBufferWidth; res[1] = gl.drawingBufferHeight; }; - const ro = new ResizeObserver(setSize); + const ro = new ResizeObserver(() => { + // Batch rapid resize events (ex. during a window drag) into one setSize per frame. + if (resizePending) return; + resizePending = true; + requestAnimationFrame(() => { + resizePending = false; + setSize(); + }); + }); ro.observe(containerEl); setSize(); let raf = 0; let contextLost = false; let isVisible = true; + let tabVisible = document.visibilityState !== 'hidden'; const t0 = performance.now(); + const frameInterval = 1000 / targetFps; + let lastFrameTime = 0; + + const renderStaticFrame = () => { + program.uniforms.iTime.value = 0; + renderer.render({ scene: mesh }); + }; const loop = t => { - if (contextLost || !isVisible) return; + if (contextLost || !isVisible || !tabVisible) return; + + if (t - lastFrameTime < frameInterval) { + raf = requestAnimationFrame(loop); + return; + } + lastFrameTime = t; + + if (pendingMouse.current) { + mousePos.current = pendingMouse.current; + pendingMouse.current = null; + const mouseUniform = program.uniforms.uMouse.value; + mouseUniform[0] = mousePos.current.x; + mouseUniform[1] = mousePos.current.y; + } + let timeValue = (t - t0) * 0.001; if (direction === 'pingpong') { const pingpongDuration = 10; @@ -198,7 +245,7 @@ export const Plasma = ({ }; const handleContextRestored = () => { contextLost = false; - if (isVisible) { + if (isVisible && tabVisible && !prefersReducedMotion) { cancelAnimationFrame(raf); raf = requestAnimationFrame(loop); } @@ -209,19 +256,37 @@ export const Plasma = ({ const io = new IntersectionObserver(([entry]) => { const wasVisible = isVisible; isVisible = entry.isIntersecting; - if (isVisible && !wasVisible && !contextLost) { + if (isVisible && !wasVisible && !contextLost && tabVisible && !prefersReducedMotion) { cancelAnimationFrame(raf); raf = requestAnimationFrame(loop); } }, { threshold: 0 }); io.observe(containerEl); - raf = requestAnimationFrame(loop); + const handleVisibilityChange = () => { + tabVisible = document.visibilityState !== 'hidden'; + if (tabVisible && isVisible && !contextLost && !prefersReducedMotion) { + cancelAnimationFrame(raf); + lastFrameTime = 0; + raf = requestAnimationFrame(loop); + } else { + cancelAnimationFrame(raf); + } + }; + document.addEventListener('visibilitychange', handleVisibilityChange); + + // Respect prefers-reduced-motion: paint one frame and stop, rather than running a perpetual animation loop for users who've asked not to see motion. + if (prefersReducedMotion) { + renderStaticFrame(); + } else { + raf = requestAnimationFrame(loop); + } return () => { cancelAnimationFrame(raf); ro.disconnect(); io.disconnect(); + document.removeEventListener('visibilitychange', handleVisibilityChange); canvas.removeEventListener('webglcontextlost', handleContextLost); canvas.removeEventListener('webglcontextrestored', handleContextRestored); if (mouseInteractive && containerEl) { @@ -231,7 +296,7 @@ export const Plasma = ({ containerEl?.removeChild(canvas); } catch {} }; - }, [color, speed, direction, scale, opacity, mouseInteractive]); + }, [color, speed, direction, scale, opacity, mouseInteractive, renderScale, maxDpr, targetFps, quality]); return
; }; diff --git a/src/tools/background-studio/backgrounds/index.js b/src/tools/background-studio/backgrounds/index.js index 95a1ab733..052b64788 100644 --- a/src/tools/background-studio/backgrounds/index.js +++ b/src/tools/background-studio/backgrounds/index.js @@ -544,10 +544,14 @@ export const BACKGROUNDS = [ props: [ { name: 'color', type: 'color', default: '#ffffff', label: 'Color' }, { name: 'speed', type: 'number', default: 1, min: 0, max: 5, step: 0.1, label: 'Speed' }, - { name: 'direction', type: 'select', default: 'forward', options: ['forward', 'backward'], label: 'Direction' }, + { name: 'direction', type: 'select', default: 'forward', options: ['forward', 'reverse', 'pingpong'], label: 'Direction' }, { name: 'scale', type: 'number', default: 1, min: 0.1, max: 3, step: 0.1, label: 'Scale' }, { name: 'opacity', type: 'number', default: 1, min: 0, max: 1, step: 0.05, label: 'Opacity' }, - { name: 'mouseInteractive', type: 'boolean', default: true, label: 'Mouse Interactive' } + { name: 'mouseInteractive', type: 'boolean', default: true, label: 'Mouse Interactive' }, + { name: 'quality', type: 'number', default: 45, min: 10, max: 80, step: 5, label: 'Quality' }, + { name: 'renderScale', type: 'number', default: 0.55, min: 0.2, max: 1.0, step: 0.05, label: 'Render Scale' }, + { name: 'targetFps', type: 'number', default: 30, min: 10, max: 60, step: 5, label: 'Target FPS' }, + { name: 'maxDpr', type: 'number', default: 1.5, min: 0.5, max: 3.0, step: 0.5, label: 'Max DPR' } ] }, { diff --git a/src/ts-default/Backgrounds/Plasma/Plasma.tsx b/src/ts-default/Backgrounds/Plasma/Plasma.tsx index c22327689..8b25783ef 100644 --- a/src/ts-default/Backgrounds/Plasma/Plasma.tsx +++ b/src/ts-default/Backgrounds/Plasma/Plasma.tsx @@ -9,6 +9,14 @@ interface PlasmaProps { scale?: number; opacity?: number; mouseInteractive?: boolean; + /** Internal render resolution multiplier. 1 = full res, 0.5 = quarter the pixels. Default 0.55. */ + renderScale?: number; + /** Hard cap on devicePixelRatio used for rendering. Default 1.5. */ + maxDpr?: number; + /** Target frame rate for the animation loop. Default 30. */ + targetFps?: number; + /** Raymarch step count — lower is cheaper, less detailed. Default 45. */ + quality?: number; } const hexToRgb = (hex: string): [number, number, number] => { @@ -28,7 +36,7 @@ void main() { } `; -const fragment = `#version 300 es +const buildFragment = (steps: number) => `#version 300 es precision highp float; uniform vec2 iResolution; uniform float iTime; @@ -52,7 +60,7 @@ void mainImage(out vec4 o, vec2 C) { float i, d, z, T = iTime * uSpeed * uDirection; vec3 O, p, S; - for (vec2 r = iResolution.xy, Q; ++i < 60.; O += o.w/d*o.xyz) { + for (vec2 r = iResolution.xy, Q; ++i < ${steps.toFixed(1)}; O += o.w/d*o.xyz) { p = z*normalize(vec3(C-.5*r,r.y)); p.z -= 4.; S = p; @@ -95,15 +103,24 @@ export const Plasma: React.FC = ({ direction = 'forward', scale = 1, opacity = 1, - mouseInteractive = true + mouseInteractive = true, + renderScale = 0.55, + maxDpr = 1.5, + targetFps = 30, + quality = 45, }) => { const containerRef = useRef(null); const mousePos = useRef({ x: 0, y: 0 }); + const pendingMouse = useRef<{ x: number; y: number } | null>(null); useEffect(() => { if (!containerRef.current) return; const containerEl = containerRef.current; + const prefersReducedMotion = + typeof window !== 'undefined' && + window.matchMedia?.('(prefers-reduced-motion: reduce)').matches; + const useCustomColor = color ? 1.0 : 0.0; const customColorRgb = color ? hexToRgb(color) : [1, 1, 1]; @@ -115,7 +132,7 @@ export const Plasma: React.FC = ({ webgl: 2, alpha: true, antialias: false, - dpr: Math.min(window.devicePixelRatio || 1, 2) + dpr: Math.min(window.devicePixelRatio || 1, maxDpr) }); } catch { return; @@ -126,13 +143,14 @@ export const Plasma: React.FC = ({ canvas.style.display = 'block'; canvas.style.width = '100%'; canvas.style.height = '100%'; + // Rendering at renderScale internally, CSS stretches it back up. containerEl.appendChild(canvas); const geometry = new Triangle(gl); const program = new Program(gl, { vertex: vertex, - fragment: fragment, + fragment: buildFragment(quality), uniforms: { iTime: { value: 0 }, iResolution: { value: new Float32Array([1, 1]) }, @@ -152,38 +170,75 @@ export const Plasma: React.FC = ({ const handleMouseMove = (e: MouseEvent) => { if (!mouseInteractive) return; const rect = containerEl.getBoundingClientRect(); - mousePos.current.x = e.clientX - rect.left; - mousePos.current.y = e.clientY - rect.top; - const mouseUniform = program.uniforms.uMouse.value as Float32Array; - mouseUniform[0] = mousePos.current.x; - mouseUniform[1] = mousePos.current.y; + // Store the latest position but don't touch GL state here, the rAF loop picks it up once per rendered frame instead of once per mouse event. + pendingMouse.current = { + x: e.clientX - rect.left, + y: e.clientY - rect.top, + }; }; if (mouseInteractive) { - containerEl.addEventListener('mousemove', handleMouseMove); + containerEl.addEventListener('mousemove', handleMouseMove, { passive: true }); } + let resizePending = false; const setSize = () => { const rect = containerEl.getBoundingClientRect(); - const width = Math.max(1, Math.floor(rect.width)); - const height = Math.max(1, Math.floor(rect.height)); + const width = Math.max(1, Math.floor(rect.width * renderScale)); + const height = Math.max(1, Math.floor(rect.height * renderScale)); renderer.setSize(width, height); + + // renderer.setSize also sets canvas.style.width/height to match the (scaled-down) drawing buffer - override that so the canvas still stretches to fill its container via CSS while the buffer stays small. + canvas.style.width = '100%'; + canvas.style.height = '100%'; + const res = program.uniforms.iResolution.value as Float32Array; res[0] = gl.drawingBufferWidth; res[1] = gl.drawingBufferHeight; }; - const ro = new ResizeObserver(setSize); + const ro = new ResizeObserver(() => { + // Batch rapid resize events (ex. during a window drag) into one setSize per frame. + if (resizePending) return; + resizePending = true; + requestAnimationFrame(() => { + resizePending = false; + setSize(); + }); + }); ro.observe(containerEl); setSize(); let raf = 0; let contextLost = false; let isVisible = true; + let tabVisible = document.visibilityState !== 'hidden'; const t0 = performance.now(); + const frameInterval = 1000 / targetFps; + let lastFrameTime = 0; + + const renderStaticFrame = () => { + (program.uniforms.iTime as any).value = 0; + renderer.render({ scene: mesh }); + }; const loop = (t: number) => { - if (contextLost || !isVisible) return; + if (contextLost || !isVisible || !tabVisible) return; + + if (t - lastFrameTime < frameInterval) { + raf = requestAnimationFrame(loop); + return; + } + lastFrameTime = t; + + if (pendingMouse.current) { + mousePos.current = pendingMouse.current; + pendingMouse.current = null; + const mouseUniform = program.uniforms.uMouse.value as Float32Array; + mouseUniform[0] = mousePos.current.x; + mouseUniform[1] = mousePos.current.y; + } + let timeValue = (t - t0) * 0.001; if (direction === 'pingpong') { const pingpongDuration = 10; @@ -208,7 +263,7 @@ export const Plasma: React.FC = ({ }; const handleContextRestored = () => { contextLost = false; - if (isVisible) { + if (isVisible && tabVisible && !prefersReducedMotion) { cancelAnimationFrame(raf); raf = requestAnimationFrame(loop); } @@ -219,19 +274,37 @@ export const Plasma: React.FC = ({ const io = new IntersectionObserver(([entry]) => { const wasVisible = isVisible; isVisible = entry.isIntersecting; - if (isVisible && !wasVisible && !contextLost) { + if (isVisible && !wasVisible && !contextLost && tabVisible && !prefersReducedMotion) { cancelAnimationFrame(raf); raf = requestAnimationFrame(loop); } }, { threshold: 0 }); io.observe(containerEl); - raf = requestAnimationFrame(loop); + const handleVisibilityChange = () => { + tabVisible = document.visibilityState !== 'hidden'; + if (tabVisible && isVisible && !contextLost && !prefersReducedMotion) { + cancelAnimationFrame(raf); + lastFrameTime = 0; + raf = requestAnimationFrame(loop); + } else { + cancelAnimationFrame(raf); + } + }; + document.addEventListener('visibilitychange', handleVisibilityChange); + + // Respect prefers-reduced-motion: paint one frame and stop, rather than running a perpetual animation loop for users who've asked not to see motion. + if (prefersReducedMotion) { + renderStaticFrame(); + } else { + raf = requestAnimationFrame(loop); + } return () => { cancelAnimationFrame(raf); ro.disconnect(); io.disconnect(); + document.removeEventListener('visibilitychange', handleVisibilityChange); canvas.removeEventListener('webglcontextlost', handleContextLost); canvas.removeEventListener('webglcontextrestored', handleContextRestored); if (mouseInteractive && containerEl) { @@ -241,7 +314,7 @@ export const Plasma: React.FC = ({ containerEl?.removeChild(canvas); } catch {} }; - }, [color, speed, direction, scale, opacity, mouseInteractive]); + }, [color, speed, direction, scale, opacity, mouseInteractive, renderScale, maxDpr, targetFps, quality]); return
; }; diff --git a/src/ts-tailwind/Backgrounds/Plasma/Plasma.tsx b/src/ts-tailwind/Backgrounds/Plasma/Plasma.tsx index 7ed9a85f1..45b60b92a 100644 --- a/src/ts-tailwind/Backgrounds/Plasma/Plasma.tsx +++ b/src/ts-tailwind/Backgrounds/Plasma/Plasma.tsx @@ -1,19 +1,31 @@ -import React, { useEffect, useRef } from 'react'; -import { Renderer, Program, Mesh, Triangle } from 'ogl'; +import React, { useEffect, useRef } from "react"; +import { Renderer, Program, Mesh, Triangle } from "ogl"; interface PlasmaProps { color?: string; speed?: number; - direction?: 'forward' | 'reverse' | 'pingpong'; + direction?: "forward" | "reverse" | "pingpong"; scale?: number; opacity?: number; mouseInteractive?: boolean; + /** Internal render resolution multiplier. 1 = full res, 0.5 = quarter the pixels. Default 0.55. */ + renderScale?: number; + /** Hard cap on devicePixelRatio used for rendering. Default 1.5. */ + maxDpr?: number; + /** Target frame rate for the animation loop. Default 30. */ + targetFps?: number; + /** Raymarch step count — lower is cheaper, less detailed. Default 45. */ + quality?: number; } const hexToRgb = (hex: string): [number, number, number] => { const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex); if (!result) return [1, 0.5, 0.2]; - return [parseInt(result[1], 16) / 255, parseInt(result[2], 16) / 255, parseInt(result[3], 16) / 255]; + return [ + parseInt(result[1], 16) / 255, + parseInt(result[2], 16) / 255, + parseInt(result[3], 16) / 255, + ]; }; const vertex = `#version 300 es @@ -27,7 +39,7 @@ void main() { } `; -const fragment = `#version 300 es +const buildFragment = (steps: number) => `#version 300 es precision highp float; uniform vec2 iResolution; uniform float iTime; @@ -44,25 +56,25 @@ out vec4 fragColor; void mainImage(out vec4 o, vec2 C) { vec2 center = iResolution.xy * 0.5; C = (C - center) / uScale + center; - + vec2 mouseOffset = (uMouse - center) * 0.0002; C += mouseOffset * length(C - center) * step(0.5, uMouseInteractive); - + float i, d, z, T = iTime * uSpeed * uDirection; vec3 O, p, S; - for (vec2 r = iResolution.xy, Q; ++i < 60.; O += o.w/d*o.xyz) { - p = z*normalize(vec3(C-.5*r,r.y)); - p.z -= 4.; + for (vec2 r = iResolution.xy, Q; ++i < ${steps.toFixed(1)}; O += o.w/d*o.xyz) { + p = z*normalize(vec3(C-.5*r,r.y)); + p.z -= 4.; S = p; d = p.y-T; - - p.x += .4*(1.+p.y)*sin(d + p.x*0.1)*cos(.34*d + p.x*0.05); - Q = p.xz *= mat2(cos(p.y+vec4(0,11,33,0)-T)); - z+= d = abs(sqrt(length(Q*Q)) - .25*(5.+S.y))/3.+8e-4; + + p.x += .4*(1.+p.y)*sin(d + p.x*0.1)*cos(.34*d + p.x*0.05); + Q = p.xz *= mat2(cos(p.y+vec4(0,11,33,0)-T)); + z+= d = abs(sqrt(length(Q*Q)) - .25*(5.+S.y))/3.+8e-4; o = 1.+sin(S.y+p.z*.5+S.z-length(S-p)+vec4(2,1,0,8)); } - + o.xyz = tanh(O/1e4); } @@ -79,34 +91,42 @@ void main() { vec4 o = vec4(0.0); mainImage(o, gl_FragCoord.xy); vec3 rgb = sanitize(o.rgb); - + float intensity = (rgb.r + rgb.g + rgb.b) / 3.0; vec3 customColor = intensity * uCustomColor; vec3 finalColor = mix(rgb, customColor, step(0.5, uUseCustomColor)); - + float alpha = length(rgb) * uOpacity; fragColor = vec4(finalColor, alpha); }`; export const Plasma: React.FC = ({ - color = '#ffffff', + color = "#ffffff", speed = 1, - direction = 'forward', + direction = "forward", scale = 1, opacity = 1, - mouseInteractive = true + mouseInteractive = true, + renderScale = 0.55, + maxDpr = 1.5, + targetFps = 30, + quality = 45, }) => { const containerRef = useRef(null); const mousePos = useRef({ x: 0, y: 0 }); + const pendingMouse = useRef<{ x: number; y: number } | null>(null); useEffect(() => { if (!containerRef.current) return; const containerEl = containerRef.current; + const prefersReducedMotion = + typeof window !== "undefined" && + window.matchMedia?.("(prefers-reduced-motion: reduce)").matches; + const useCustomColor = color ? 1.0 : 0.0; const customColorRgb = color ? hexToRgb(color) : [1, 1, 1]; - - const directionMultiplier = direction === 'reverse' ? -1.0 : 1.0; + const directionMultiplier = direction === "reverse" ? -1.0 : 1.0; let renderer: Renderer; try { @@ -114,7 +134,7 @@ export const Plasma: React.FC = ({ webgl: 2, alpha: true, antialias: false, - dpr: Math.min(window.devicePixelRatio || 1, 2) + dpr: Math.min(window.devicePixelRatio || 1, maxDpr), }); } catch { return; @@ -122,16 +142,17 @@ export const Plasma: React.FC = ({ const gl = renderer.gl; if (!gl) return; const canvas = gl.canvas as HTMLCanvasElement; - canvas.style.display = 'block'; - canvas.style.width = '100%'; - canvas.style.height = '100%'; + canvas.style.display = "block"; + canvas.style.width = "100%"; + canvas.style.height = "100%"; + // Rendering at renderScale internally, CSS stretches it back up. containerEl.appendChild(canvas); const geometry = new Triangle(gl); const program = new Program(gl, { vertex: vertex, - fragment: fragment, + fragment: buildFragment(quality), uniforms: { iTime: { value: 0 }, iResolution: { value: new Float32Array([1, 1]) }, @@ -142,8 +163,8 @@ export const Plasma: React.FC = ({ uScale: { value: scale }, uOpacity: { value: opacity }, uMouse: { value: new Float32Array([0, 0]) }, - uMouseInteractive: { value: mouseInteractive ? 1.0 : 0.0 } - } + uMouseInteractive: { value: mouseInteractive ? 1.0 : 0.0 }, + }, }); const mesh = new Mesh(gl, { geometry, program }); @@ -151,46 +172,87 @@ export const Plasma: React.FC = ({ const handleMouseMove = (e: MouseEvent) => { if (!mouseInteractive) return; const rect = containerEl.getBoundingClientRect(); - mousePos.current.x = e.clientX - rect.left; - mousePos.current.y = e.clientY - rect.top; - const mouseUniform = program.uniforms.uMouse.value as Float32Array; - mouseUniform[0] = mousePos.current.x; - mouseUniform[1] = mousePos.current.y; + // Store the latest position but don't touch GL state here, the rAF loop picks it up once per rendered frame instead of once per mouse event. + pendingMouse.current = { + x: e.clientX - rect.left, + y: e.clientY - rect.top, + }; }; if (mouseInteractive) { - containerEl.addEventListener('mousemove', handleMouseMove); + containerEl.addEventListener("mousemove", handleMouseMove, { + passive: true, + }); } + let resizePending = false; const setSize = () => { const rect = containerEl.getBoundingClientRect(); - const width = Math.max(1, Math.floor(rect.width)); - const height = Math.max(1, Math.floor(rect.height)); + const width = Math.max(1, Math.floor(rect.width * renderScale)); + const height = Math.max(1, Math.floor(rect.height * renderScale)); renderer.setSize(width, height); + + // renderer.setSize also sets canvas.style.width/height to match the (scaled-down) drawing buffer - override that so the canvas still stretches to fill its container via CSS while the buffer stays small. + canvas.style.width = "100%"; + canvas.style.height = "100%"; + const res = program.uniforms.iResolution.value as Float32Array; res[0] = gl.drawingBufferWidth; res[1] = gl.drawingBufferHeight; }; - const ro = new ResizeObserver(setSize); + const ro = new ResizeObserver(() => { + // Batch rapid resize events (ex. during a window drag) into one setSize per frame. + if (resizePending) return; + resizePending = true; + requestAnimationFrame(() => { + resizePending = false; + setSize(); + }); + }); ro.observe(containerEl); setSize(); let raf = 0; let contextLost = false; let isVisible = true; + let tabVisible = document.visibilityState !== "hidden"; const t0 = performance.now(); + const frameInterval = 1000 / targetFps; + let lastFrameTime = 0; + + const renderStaticFrame = () => { + (program.uniforms.iTime as any).value = 0; + renderer.render({ scene: mesh }); + }; const loop = (t: number) => { - if (contextLost || !isVisible) return; + if (contextLost || !isVisible || !tabVisible) return; + + if (t - lastFrameTime < frameInterval) { + raf = requestAnimationFrame(loop); + return; + } + lastFrameTime = t; + + if (pendingMouse.current) { + mousePos.current = pendingMouse.current; + pendingMouse.current = null; + const mouseUniform = program.uniforms.uMouse.value as Float32Array; + mouseUniform[0] = mousePos.current.x; + mouseUniform[1] = mousePos.current.y; + } + let timeValue = (t - t0) * 0.001; - if (direction === 'pingpong') { + if (direction === "pingpong") { const pingpongDuration = 10; const segmentTime = timeValue % pingpongDuration; const isForward = Math.floor(timeValue / pingpongDuration) % 2 === 0; const u = segmentTime / pingpongDuration; const smooth = u * u * (3 - 2 * u); - const pingpongTime = isForward ? smooth * pingpongDuration : (1 - smooth) * pingpongDuration; + const pingpongTime = isForward + ? smooth * pingpongDuration + : (1 - smooth) * pingpongDuration; (program.uniforms.uDirection as any).value = 1.0; (program.uniforms.iTime as any).value = pingpongTime; } else { @@ -207,42 +269,85 @@ export const Plasma: React.FC = ({ }; const handleContextRestored = () => { contextLost = false; - if (isVisible) { + if (isVisible && tabVisible && !prefersReducedMotion) { cancelAnimationFrame(raf); raf = requestAnimationFrame(loop); } }; - canvas.addEventListener('webglcontextlost', handleContextLost); - canvas.addEventListener('webglcontextrestored', handleContextRestored); + canvas.addEventListener("webglcontextlost", handleContextLost); + canvas.addEventListener("webglcontextrestored", handleContextRestored); + + const io = new IntersectionObserver( + ([entry]) => { + const wasVisible = isVisible; + isVisible = entry.isIntersecting; + if ( + isVisible && + !wasVisible && + !contextLost && + tabVisible && + !prefersReducedMotion + ) { + cancelAnimationFrame(raf); + raf = requestAnimationFrame(loop); + } + }, + { threshold: 0 }, + ); + io.observe(containerEl); - const io = new IntersectionObserver(([entry]) => { - const wasVisible = isVisible; - isVisible = entry.isIntersecting; - if (isVisible && !wasVisible && !contextLost) { + const handleVisibilityChange = () => { + tabVisible = document.visibilityState !== "hidden"; + if (tabVisible && isVisible && !contextLost && !prefersReducedMotion) { cancelAnimationFrame(raf); + lastFrameTime = 0; raf = requestAnimationFrame(loop); + } else { + cancelAnimationFrame(raf); } - }, { threshold: 0 }); - io.observe(containerEl); + }; + document.addEventListener("visibilitychange", handleVisibilityChange); - raf = requestAnimationFrame(loop); + // Respect prefers-reduced-motion: paint one frame and stop, rather than running a perpetual animation loop for users who've asked not to see motion. + if (prefersReducedMotion) { + renderStaticFrame(); + } else { + raf = requestAnimationFrame(loop); + } return () => { cancelAnimationFrame(raf); ro.disconnect(); io.disconnect(); - canvas.removeEventListener('webglcontextlost', handleContextLost); - canvas.removeEventListener('webglcontextrestored', handleContextRestored); + document.removeEventListener("visibilitychange", handleVisibilityChange); + canvas.removeEventListener("webglcontextlost", handleContextLost); + canvas.removeEventListener("webglcontextrestored", handleContextRestored); if (mouseInteractive && containerEl) { - containerEl.removeEventListener('mousemove', handleMouseMove); + containerEl.removeEventListener("mousemove", handleMouseMove); } try { containerEl?.removeChild(canvas); } catch {} }; - }, [color, speed, direction, scale, opacity, mouseInteractive]); + }, [ + color, + speed, + direction, + scale, + opacity, + mouseInteractive, + renderScale, + maxDpr, + targetFps, + quality, + ]); - return
; + return ( +
+ ); }; export default Plasma; From 4d848e35e6727af4f04b5d2e4f26c2d6905d3a8e Mon Sep 17 00:00:00 2001 From: Elias Date: Tue, 21 Jul 2026 20:25:25 +0200 Subject: [PATCH 2/4] fix: Change position from absolute to relative --- src/ts-tailwind/Backgrounds/Plasma/Plasma.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ts-tailwind/Backgrounds/Plasma/Plasma.tsx b/src/ts-tailwind/Backgrounds/Plasma/Plasma.tsx index 45b60b92a..bc8bbd8e8 100644 --- a/src/ts-tailwind/Backgrounds/Plasma/Plasma.tsx +++ b/src/ts-tailwind/Backgrounds/Plasma/Plasma.tsx @@ -345,7 +345,7 @@ export const Plasma: React.FC = ({ return (
); }; From 1b117349a0b061de760959ce5f5435b8e861b67c Mon Sep 17 00:00:00 2001 From: Elias Gulam Date: Tue, 21 Jul 2026 21:05:14 +0200 Subject: [PATCH 3/4] Updated default to 60 fps in all files --- public/r/Plasma-JS-CSS.json | 2 +- public/r/Plasma-JS-TW.json | 2 +- public/r/Plasma-TS-CSS.json | 2 +- public/r/Plasma-TS-TW.json | 2 +- src/content/Backgrounds/Plasma/Plasma.jsx | 2 +- src/demo/Backgrounds/PlasmaDemo.jsx | 4 ++-- src/tailwind/Backgrounds/Plasma/Plasma.jsx | 2 +- src/tools/background-studio/backgrounds/index.js | 2 +- src/ts-default/Backgrounds/Plasma/Plasma.tsx | 2 +- src/ts-tailwind/Backgrounds/Plasma/Plasma.tsx | 2 +- 10 files changed, 11 insertions(+), 11 deletions(-) diff --git a/public/r/Plasma-JS-CSS.json b/public/r/Plasma-JS-CSS.json index 024519fc8..67d4a721e 100644 --- a/public/r/Plasma-JS-CSS.json +++ b/public/r/Plasma-JS-CSS.json @@ -13,7 +13,7 @@ { "type": "registry:component", "path": "Plasma/Plasma.jsx", - "content": "import { useEffect, useRef } from 'react';\nimport { Renderer, Program, Mesh, Triangle } from 'ogl';\nimport './Plasma.css';\n\nconst hexToRgb = hex => {\n const result = /^#?([a-f\\d]{2})([a-f\\d]{2})([a-f\\d]{2})$/i.exec(hex);\n if (!result) return [1, 0.5, 0.2];\n return [parseInt(result[1], 16) / 255, parseInt(result[2], 16) / 255, parseInt(result[3], 16) / 255];\n};\n\nconst vertex = `#version 300 es\nprecision highp float;\nin vec2 position;\nin vec2 uv;\nout vec2 vUv;\nvoid main() {\n vUv = uv;\n gl_Position = vec4(position, 0.0, 1.0);\n}\n`;\n\nconst buildFragment = steps => `#version 300 es\nprecision highp float;\nuniform vec2 iResolution;\nuniform float iTime;\nuniform vec3 uCustomColor;\nuniform float uUseCustomColor;\nuniform float uSpeed;\nuniform float uDirection;\nuniform float uScale;\nuniform float uOpacity;\nuniform vec2 uMouse;\nuniform float uMouseInteractive;\nout vec4 fragColor;\n\nvoid mainImage(out vec4 o, vec2 C) {\n vec2 center = iResolution.xy * 0.5;\n C = (C - center) / uScale + center;\n \n vec2 mouseOffset = (uMouse - center) * 0.0002;\n C += mouseOffset * length(C - center) * step(0.5, uMouseInteractive);\n \n float i, d, z, T = iTime * uSpeed * uDirection;\n vec3 O, p, S;\n\n for (vec2 r = iResolution.xy, Q; ++i < ${steps.toFixed(1)}; O += o.w/d*o.xyz) {\n p = z*normalize(vec3(C-.5*r,r.y)); \n p.z -= 4.; \n S = p;\n d = p.y-T;\n \n p.x += .4*(1.+p.y)*sin(d + p.x*0.1)*cos(.34*d + p.x*0.05); \n Q = p.xz *= mat2(cos(p.y+vec4(0,11,33,0)-T)); \n z+= d = abs(sqrt(length(Q*Q)) - .25*(5.+S.y))/3.+8e-4; \n o = 1.+sin(S.y+p.z*.5+S.z-length(S-p)+vec4(2,1,0,8));\n }\n \n o.xyz = tanh(O/1e4);\n}\n\nbool finite1(float x){ return !(isnan(x) || isinf(x)); }\nvec3 sanitize(vec3 c){\n return vec3(\n finite1(c.r) ? c.r : 0.0,\n finite1(c.g) ? c.g : 0.0,\n finite1(c.b) ? c.b : 0.0\n );\n}\n\nvoid main() {\n vec4 o = vec4(0.0);\n mainImage(o, gl_FragCoord.xy);\n vec3 rgb = sanitize(o.rgb);\n \n float intensity = (rgb.r + rgb.g + rgb.b) / 3.0;\n vec3 customColor = intensity * uCustomColor;\n vec3 finalColor = mix(rgb, customColor, step(0.5, uUseCustomColor));\n \n float alpha = length(rgb) * uOpacity;\n fragColor = vec4(finalColor, alpha);\n}`;\n\nexport const Plasma = ({\n color = '#ffffff',\n speed = 1,\n direction = 'forward',\n scale = 1,\n opacity = 1,\n mouseInteractive = true,\n renderScale = 0.55,\n maxDpr = 1.5,\n targetFps = 30,\n quality = 45,\n}) => {\n const containerRef = useRef(null);\n const mousePos = useRef({ x: 0, y: 0 });\n const pendingMouse = useRef(null);\n\n useEffect(() => {\n if (!containerRef.current) return;\n const containerEl = containerRef.current;\n\n const prefersReducedMotion =\n typeof window !== 'undefined' &&\n window.matchMedia?.('(prefers-reduced-motion: reduce)').matches;\n\n const useCustomColor = color ? 1.0 : 0.0;\n const customColorRgb = color ? hexToRgb(color) : [1, 1, 1];\n\n const directionMultiplier = direction === 'reverse' ? -1.0 : 1.0;\n\n let renderer;\n try {\n renderer = new Renderer({\n webgl: 2,\n alpha: true,\n antialias: false,\n dpr: Math.min(window.devicePixelRatio || 1, maxDpr)\n });\n } catch {\n return;\n }\n const gl = renderer.gl;\n if (!gl) return;\n const canvas = gl.canvas;\n canvas.style.display = 'block';\n canvas.style.width = '100%';\n canvas.style.height = '100%';\n // Rendering at renderScale internally, CSS stretches it back up.\n containerEl.appendChild(canvas);\n\n const geometry = new Triangle(gl);\n\n const program = new Program(gl, {\n vertex: vertex,\n fragment: buildFragment(quality),\n uniforms: {\n iTime: { value: 0 },\n iResolution: { value: new Float32Array([1, 1]) },\n uCustomColor: { value: new Float32Array(customColorRgb) },\n uUseCustomColor: { value: useCustomColor },\n uSpeed: { value: speed * 0.4 },\n uDirection: { value: directionMultiplier },\n uScale: { value: scale },\n uOpacity: { value: opacity },\n uMouse: { value: new Float32Array([0, 0]) },\n uMouseInteractive: { value: mouseInteractive ? 1.0 : 0.0 }\n }\n });\n\n const mesh = new Mesh(gl, { geometry, program });\n\n const handleMouseMove = e => {\n if (!mouseInteractive) return;\n const rect = containerEl.getBoundingClientRect();\n // Store the latest position but don't touch GL state here, the rAF loop picks it up once per rendered frame instead of once per mouse event.\n pendingMouse.current = {\n x: e.clientX - rect.left,\n y: e.clientY - rect.top,\n };\n };\n\n if (mouseInteractive) {\n containerEl.addEventListener('mousemove', handleMouseMove, { passive: true });\n }\n\n let resizePending = false;\n const setSize = () => {\n const rect = containerEl.getBoundingClientRect();\n const width = Math.max(1, Math.floor(rect.width * renderScale));\n const height = Math.max(1, Math.floor(rect.height * renderScale));\n renderer.setSize(width, height);\n\n // renderer.setSize also sets canvas.style.width/height to match the (scaled-down) drawing buffer - override that so the canvas still stretches to fill its container via CSS while the buffer stays small.\n canvas.style.width = '100%';\n canvas.style.height = '100%';\n\n const res = program.uniforms.iResolution.value;\n res[0] = gl.drawingBufferWidth;\n res[1] = gl.drawingBufferHeight;\n };\n\n const ro = new ResizeObserver(() => {\n // Batch rapid resize events (ex. during a window drag) into one setSize per frame.\n if (resizePending) return;\n resizePending = true;\n requestAnimationFrame(() => {\n resizePending = false;\n setSize();\n });\n });\n ro.observe(containerEl);\n setSize();\n\n let raf = 0;\n let contextLost = false;\n let isVisible = true;\n let tabVisible = document.visibilityState !== 'hidden';\n const t0 = performance.now();\n const frameInterval = 1000 / targetFps;\n let lastFrameTime = 0;\n\n const renderStaticFrame = () => {\n program.uniforms.iTime.value = 0;\n renderer.render({ scene: mesh });\n };\n\n const loop = t => {\n if (contextLost || !isVisible || !tabVisible) return;\n\n if (t - lastFrameTime < frameInterval) {\n raf = requestAnimationFrame(loop);\n return;\n }\n lastFrameTime = t;\n\n if (pendingMouse.current) {\n mousePos.current = pendingMouse.current;\n pendingMouse.current = null;\n const mouseUniform = program.uniforms.uMouse.value;\n mouseUniform[0] = mousePos.current.x;\n mouseUniform[1] = mousePos.current.y;\n }\n\n let timeValue = (t - t0) * 0.001;\n if (direction === 'pingpong') {\n const pingpongDuration = 10;\n const segmentTime = timeValue % pingpongDuration;\n const isForward = Math.floor(timeValue / pingpongDuration) % 2 === 0;\n const u = segmentTime / pingpongDuration;\n const smooth = u * u * (3 - 2 * u);\n const pingpongTime = isForward ? smooth * pingpongDuration : (1 - smooth) * pingpongDuration;\n program.uniforms.uDirection.value = 1.0;\n program.uniforms.iTime.value = pingpongTime;\n } else {\n program.uniforms.iTime.value = timeValue;\n }\n renderer.render({ scene: mesh });\n raf = requestAnimationFrame(loop);\n };\n\n const handleContextLost = (e) => {\n e.preventDefault();\n contextLost = true;\n cancelAnimationFrame(raf);\n };\n const handleContextRestored = () => {\n contextLost = false;\n if (isVisible && tabVisible && !prefersReducedMotion) {\n cancelAnimationFrame(raf);\n raf = requestAnimationFrame(loop);\n }\n };\n canvas.addEventListener('webglcontextlost', handleContextLost);\n canvas.addEventListener('webglcontextrestored', handleContextRestored);\n\n const io = new IntersectionObserver(([entry]) => {\n const wasVisible = isVisible;\n isVisible = entry.isIntersecting;\n if (isVisible && !wasVisible && !contextLost && tabVisible && !prefersReducedMotion) {\n cancelAnimationFrame(raf);\n raf = requestAnimationFrame(loop);\n }\n }, { threshold: 0 });\n io.observe(containerEl);\n\n const handleVisibilityChange = () => {\n tabVisible = document.visibilityState !== 'hidden';\n if (tabVisible && isVisible && !contextLost && !prefersReducedMotion) {\n cancelAnimationFrame(raf);\n lastFrameTime = 0;\n raf = requestAnimationFrame(loop);\n } else {\n cancelAnimationFrame(raf);\n }\n };\n document.addEventListener('visibilitychange', handleVisibilityChange);\n\n // Respect prefers-reduced-motion: paint one frame and stop, rather than running a perpetual animation loop for users who've asked not to see motion.\n if (prefersReducedMotion) {\n renderStaticFrame();\n } else {\n raf = requestAnimationFrame(loop);\n }\n\n return () => {\n cancelAnimationFrame(raf);\n ro.disconnect();\n io.disconnect();\n document.removeEventListener('visibilitychange', handleVisibilityChange);\n canvas.removeEventListener('webglcontextlost', handleContextLost);\n canvas.removeEventListener('webglcontextrestored', handleContextRestored);\n if (mouseInteractive && containerEl) {\n containerEl.removeEventListener('mousemove', handleMouseMove);\n }\n try {\n containerEl?.removeChild(canvas);\n } catch {}\n };\n }, [color, speed, direction, scale, opacity, mouseInteractive, renderScale, maxDpr, targetFps, quality]);\n\n return
;\n};\n\nexport default Plasma;\n" + "content": "import { useEffect, useRef } from 'react';\nimport { Renderer, Program, Mesh, Triangle } from 'ogl';\nimport './Plasma.css';\n\nconst hexToRgb = hex => {\n const result = /^#?([a-f\\d]{2})([a-f\\d]{2})([a-f\\d]{2})$/i.exec(hex);\n if (!result) return [1, 0.5, 0.2];\n return [parseInt(result[1], 16) / 255, parseInt(result[2], 16) / 255, parseInt(result[3], 16) / 255];\n};\n\nconst vertex = `#version 300 es\nprecision highp float;\nin vec2 position;\nin vec2 uv;\nout vec2 vUv;\nvoid main() {\n vUv = uv;\n gl_Position = vec4(position, 0.0, 1.0);\n}\n`;\n\nconst buildFragment = steps => `#version 300 es\nprecision highp float;\nuniform vec2 iResolution;\nuniform float iTime;\nuniform vec3 uCustomColor;\nuniform float uUseCustomColor;\nuniform float uSpeed;\nuniform float uDirection;\nuniform float uScale;\nuniform float uOpacity;\nuniform vec2 uMouse;\nuniform float uMouseInteractive;\nout vec4 fragColor;\n\nvoid mainImage(out vec4 o, vec2 C) {\n vec2 center = iResolution.xy * 0.5;\n C = (C - center) / uScale + center;\n \n vec2 mouseOffset = (uMouse - center) * 0.0002;\n C += mouseOffset * length(C - center) * step(0.5, uMouseInteractive);\n \n float i, d, z, T = iTime * uSpeed * uDirection;\n vec3 O, p, S;\n\n for (vec2 r = iResolution.xy, Q; ++i < ${steps.toFixed(1)}; O += o.w/d*o.xyz) {\n p = z*normalize(vec3(C-.5*r,r.y)); \n p.z -= 4.; \n S = p;\n d = p.y-T;\n \n p.x += .4*(1.+p.y)*sin(d + p.x*0.1)*cos(.34*d + p.x*0.05); \n Q = p.xz *= mat2(cos(p.y+vec4(0,11,33,0)-T)); \n z+= d = abs(sqrt(length(Q*Q)) - .25*(5.+S.y))/3.+8e-4; \n o = 1.+sin(S.y+p.z*.5+S.z-length(S-p)+vec4(2,1,0,8));\n }\n \n o.xyz = tanh(O/1e4);\n}\n\nbool finite1(float x){ return !(isnan(x) || isinf(x)); }\nvec3 sanitize(vec3 c){\n return vec3(\n finite1(c.r) ? c.r : 0.0,\n finite1(c.g) ? c.g : 0.0,\n finite1(c.b) ? c.b : 0.0\n );\n}\n\nvoid main() {\n vec4 o = vec4(0.0);\n mainImage(o, gl_FragCoord.xy);\n vec3 rgb = sanitize(o.rgb);\n \n float intensity = (rgb.r + rgb.g + rgb.b) / 3.0;\n vec3 customColor = intensity * uCustomColor;\n vec3 finalColor = mix(rgb, customColor, step(0.5, uUseCustomColor));\n \n float alpha = length(rgb) * uOpacity;\n fragColor = vec4(finalColor, alpha);\n}`;\n\nexport const Plasma = ({\n color = '#ffffff',\n speed = 1,\n direction = 'forward',\n scale = 1,\n opacity = 1,\n mouseInteractive = true,\n renderScale = 0.55,\n maxDpr = 1.5,\n targetFps = 60,\n quality = 45,\n}) => {\n const containerRef = useRef(null);\n const mousePos = useRef({ x: 0, y: 0 });\n const pendingMouse = useRef(null);\n\n useEffect(() => {\n if (!containerRef.current) return;\n const containerEl = containerRef.current;\n\n const prefersReducedMotion =\n typeof window !== 'undefined' &&\n window.matchMedia?.('(prefers-reduced-motion: reduce)').matches;\n\n const useCustomColor = color ? 1.0 : 0.0;\n const customColorRgb = color ? hexToRgb(color) : [1, 1, 1];\n\n const directionMultiplier = direction === 'reverse' ? -1.0 : 1.0;\n\n let renderer;\n try {\n renderer = new Renderer({\n webgl: 2,\n alpha: true,\n antialias: false,\n dpr: Math.min(window.devicePixelRatio || 1, maxDpr)\n });\n } catch {\n return;\n }\n const gl = renderer.gl;\n if (!gl) return;\n const canvas = gl.canvas;\n canvas.style.display = 'block';\n canvas.style.width = '100%';\n canvas.style.height = '100%';\n // Rendering at renderScale internally, CSS stretches it back up.\n containerEl.appendChild(canvas);\n\n const geometry = new Triangle(gl);\n\n const program = new Program(gl, {\n vertex: vertex,\n fragment: buildFragment(quality),\n uniforms: {\n iTime: { value: 0 },\n iResolution: { value: new Float32Array([1, 1]) },\n uCustomColor: { value: new Float32Array(customColorRgb) },\n uUseCustomColor: { value: useCustomColor },\n uSpeed: { value: speed * 0.4 },\n uDirection: { value: directionMultiplier },\n uScale: { value: scale },\n uOpacity: { value: opacity },\n uMouse: { value: new Float32Array([0, 0]) },\n uMouseInteractive: { value: mouseInteractive ? 1.0 : 0.0 }\n }\n });\n\n const mesh = new Mesh(gl, { geometry, program });\n\n const handleMouseMove = e => {\n if (!mouseInteractive) return;\n const rect = containerEl.getBoundingClientRect();\n // Store the latest position but don't touch GL state here, the rAF loop picks it up once per rendered frame instead of once per mouse event.\n pendingMouse.current = {\n x: e.clientX - rect.left,\n y: e.clientY - rect.top,\n };\n };\n\n if (mouseInteractive) {\n containerEl.addEventListener('mousemove', handleMouseMove, { passive: true });\n }\n\n let resizePending = false;\n const setSize = () => {\n const rect = containerEl.getBoundingClientRect();\n const width = Math.max(1, Math.floor(rect.width * renderScale));\n const height = Math.max(1, Math.floor(rect.height * renderScale));\n renderer.setSize(width, height);\n\n // renderer.setSize also sets canvas.style.width/height to match the (scaled-down) drawing buffer - override that so the canvas still stretches to fill its container via CSS while the buffer stays small.\n canvas.style.width = '100%';\n canvas.style.height = '100%';\n\n const res = program.uniforms.iResolution.value;\n res[0] = gl.drawingBufferWidth;\n res[1] = gl.drawingBufferHeight;\n };\n\n const ro = new ResizeObserver(() => {\n // Batch rapid resize events (ex. during a window drag) into one setSize per frame.\n if (resizePending) return;\n resizePending = true;\n requestAnimationFrame(() => {\n resizePending = false;\n setSize();\n });\n });\n ro.observe(containerEl);\n setSize();\n\n let raf = 0;\n let contextLost = false;\n let isVisible = true;\n let tabVisible = document.visibilityState !== 'hidden';\n const t0 = performance.now();\n const frameInterval = 1000 / targetFps;\n let lastFrameTime = 0;\n\n const renderStaticFrame = () => {\n program.uniforms.iTime.value = 0;\n renderer.render({ scene: mesh });\n };\n\n const loop = t => {\n if (contextLost || !isVisible || !tabVisible) return;\n\n if (t - lastFrameTime < frameInterval) {\n raf = requestAnimationFrame(loop);\n return;\n }\n lastFrameTime = t;\n\n if (pendingMouse.current) {\n mousePos.current = pendingMouse.current;\n pendingMouse.current = null;\n const mouseUniform = program.uniforms.uMouse.value;\n mouseUniform[0] = mousePos.current.x;\n mouseUniform[1] = mousePos.current.y;\n }\n\n let timeValue = (t - t0) * 0.001;\n if (direction === 'pingpong') {\n const pingpongDuration = 10;\n const segmentTime = timeValue % pingpongDuration;\n const isForward = Math.floor(timeValue / pingpongDuration) % 2 === 0;\n const u = segmentTime / pingpongDuration;\n const smooth = u * u * (3 - 2 * u);\n const pingpongTime = isForward ? smooth * pingpongDuration : (1 - smooth) * pingpongDuration;\n program.uniforms.uDirection.value = 1.0;\n program.uniforms.iTime.value = pingpongTime;\n } else {\n program.uniforms.iTime.value = timeValue;\n }\n renderer.render({ scene: mesh });\n raf = requestAnimationFrame(loop);\n };\n\n const handleContextLost = (e) => {\n e.preventDefault();\n contextLost = true;\n cancelAnimationFrame(raf);\n };\n const handleContextRestored = () => {\n contextLost = false;\n if (isVisible && tabVisible && !prefersReducedMotion) {\n cancelAnimationFrame(raf);\n raf = requestAnimationFrame(loop);\n }\n };\n canvas.addEventListener('webglcontextlost', handleContextLost);\n canvas.addEventListener('webglcontextrestored', handleContextRestored);\n\n const io = new IntersectionObserver(([entry]) => {\n const wasVisible = isVisible;\n isVisible = entry.isIntersecting;\n if (isVisible && !wasVisible && !contextLost && tabVisible && !prefersReducedMotion) {\n cancelAnimationFrame(raf);\n raf = requestAnimationFrame(loop);\n }\n }, { threshold: 0 });\n io.observe(containerEl);\n\n const handleVisibilityChange = () => {\n tabVisible = document.visibilityState !== 'hidden';\n if (tabVisible && isVisible && !contextLost && !prefersReducedMotion) {\n cancelAnimationFrame(raf);\n lastFrameTime = 0;\n raf = requestAnimationFrame(loop);\n } else {\n cancelAnimationFrame(raf);\n }\n };\n document.addEventListener('visibilitychange', handleVisibilityChange);\n\n // Respect prefers-reduced-motion: paint one frame and stop, rather than running a perpetual animation loop for users who've asked not to see motion.\n if (prefersReducedMotion) {\n renderStaticFrame();\n } else {\n raf = requestAnimationFrame(loop);\n }\n\n return () => {\n cancelAnimationFrame(raf);\n ro.disconnect();\n io.disconnect();\n document.removeEventListener('visibilitychange', handleVisibilityChange);\n canvas.removeEventListener('webglcontextlost', handleContextLost);\n canvas.removeEventListener('webglcontextrestored', handleContextRestored);\n if (mouseInteractive && containerEl) {\n containerEl.removeEventListener('mousemove', handleMouseMove);\n }\n try {\n containerEl?.removeChild(canvas);\n } catch {}\n };\n }, [color, speed, direction, scale, opacity, mouseInteractive, renderScale, maxDpr, targetFps, quality]);\n\n return
;\n};\n\nexport default Plasma;\n" } ], "registryDependencies": [], diff --git a/public/r/Plasma-JS-TW.json b/public/r/Plasma-JS-TW.json index 856fb3e64..d8a3081e7 100644 --- a/public/r/Plasma-JS-TW.json +++ b/public/r/Plasma-JS-TW.json @@ -8,7 +8,7 @@ { "type": "registry:component", "path": "Plasma/Plasma.jsx", - "content": "import { useEffect, useRef } from 'react';\nimport { Renderer, Program, Mesh, Triangle } from 'ogl';\n\nconst hexToRgb = hex => {\n const result = /^#?([a-f\\d]{2})([a-f\\d]{2})([a-f\\d]{2})$/i.exec(hex);\n if (!result) return [1, 0.5, 0.2];\n return [parseInt(result[1], 16) / 255, parseInt(result[2], 16) / 255, parseInt(result[3], 16) / 255];\n};\n\nconst vertex = `#version 300 es\nprecision highp float;\nin vec2 position;\nin vec2 uv;\nout vec2 vUv;\nvoid main() {\n vUv = uv;\n gl_Position = vec4(position, 0.0, 1.0);\n}\n`;\n\nconst buildFragment = steps => `#version 300 es\nprecision highp float;\nuniform vec2 iResolution;\nuniform float iTime;\nuniform vec3 uCustomColor;\nuniform float uUseCustomColor;\nuniform float uSpeed;\nuniform float uDirection;\nuniform float uScale;\nuniform float uOpacity;\nuniform vec2 uMouse;\nuniform float uMouseInteractive;\nout vec4 fragColor;\n\nvoid mainImage(out vec4 o, vec2 C) {\n vec2 center = iResolution.xy * 0.5;\n C = (C - center) / uScale + center;\n \n vec2 mouseOffset = (uMouse - center) * 0.0002;\n C += mouseOffset * length(C - center) * step(0.5, uMouseInteractive);\n \n float i, d, z, T = iTime * uSpeed * uDirection;\n vec3 O, p, S;\n\n for (vec2 r = iResolution.xy, Q; ++i < ${steps.toFixed(1)}; O += o.w/d*o.xyz) {\n p = z*normalize(vec3(C-.5*r,r.y)); \n p.z -= 4.; \n S = p;\n d = p.y-T;\n \n p.x += .4*(1.+p.y)*sin(d + p.x*0.1)*cos(.34*d + p.x*0.05); \n Q = p.xz *= mat2(cos(p.y+vec4(0,11,33,0)-T)); \n z+= d = abs(sqrt(length(Q*Q)) - .25*(5.+S.y))/3.+8e-4; \n o = 1.+sin(S.y+p.z*.5+S.z-length(S-p)+vec4(2,1,0,8));\n }\n \n o.xyz = tanh(O/1e4);\n}\n\nbool finite1(float x){ return !(isnan(x) || isinf(x)); }\nvec3 sanitize(vec3 c){\n return vec3(\n finite1(c.r) ? c.r : 0.0,\n finite1(c.g) ? c.g : 0.0,\n finite1(c.b) ? c.b : 0.0\n );\n}\n\nvoid main() {\n vec4 o = vec4(0.0);\n mainImage(o, gl_FragCoord.xy);\n vec3 rgb = sanitize(o.rgb);\n \n float intensity = (rgb.r + rgb.g + rgb.b) / 3.0;\n vec3 customColor = intensity * uCustomColor;\n vec3 finalColor = mix(rgb, customColor, step(0.5, uUseCustomColor));\n \n float alpha = length(rgb) * uOpacity;\n fragColor = vec4(finalColor, alpha);\n}`;\n\nexport const Plasma = ({\n color = '#ffffff',\n speed = 1,\n direction = 'forward',\n scale = 1,\n opacity = 1,\n mouseInteractive = true,\n renderScale = 0.55,\n maxDpr = 1.5,\n targetFps = 30,\n quality = 45,\n}) => {\n const containerRef = useRef(null);\n const mousePos = useRef({ x: 0, y: 0 });\n const pendingMouse = useRef(null);\n\n useEffect(() => {\n if (!containerRef.current) return;\n const containerEl = containerRef.current;\n\n const prefersReducedMotion =\n typeof window !== 'undefined' &&\n window.matchMedia?.('(prefers-reduced-motion: reduce)').matches;\n\n const useCustomColor = color ? 1.0 : 0.0;\n const customColorRgb = color ? hexToRgb(color) : [1, 1, 1];\n\n const directionMultiplier = direction === 'reverse' ? -1.0 : 1.0;\n\n let renderer;\n try {\n renderer = new Renderer({\n webgl: 2,\n alpha: true,\n antialias: false,\n dpr: Math.min(window.devicePixelRatio || 1, maxDpr)\n });\n } catch {\n return;\n }\n const gl = renderer.gl;\n if (!gl) return;\n const canvas = gl.canvas;\n canvas.style.display = 'block';\n canvas.style.width = '100%';\n canvas.style.height = '100%';\n // Rendering at renderScale internally, CSS stretches it back up.\n containerEl.appendChild(canvas);\n\n const geometry = new Triangle(gl);\n\n const program = new Program(gl, {\n vertex: vertex,\n fragment: buildFragment(quality),\n uniforms: {\n iTime: { value: 0 },\n iResolution: { value: new Float32Array([1, 1]) },\n uCustomColor: { value: new Float32Array(customColorRgb) },\n uUseCustomColor: { value: useCustomColor },\n uSpeed: { value: speed * 0.4 },\n uDirection: { value: directionMultiplier },\n uScale: { value: scale },\n uOpacity: { value: opacity },\n uMouse: { value: new Float32Array([0, 0]) },\n uMouseInteractive: { value: mouseInteractive ? 1.0 : 0.0 }\n }\n });\n\n const mesh = new Mesh(gl, { geometry, program });\n\n const handleMouseMove = e => {\n if (!mouseInteractive) return;\n const rect = containerEl.getBoundingClientRect();\n // Store the latest position but don't touch GL state here, the rAF loop picks it up once per rendered frame instead of once per mouse event.\n pendingMouse.current = {\n x: e.clientX - rect.left,\n y: e.clientY - rect.top,\n };\n };\n\n if (mouseInteractive) {\n containerEl.addEventListener('mousemove', handleMouseMove, { passive: true });\n }\n\n let resizePending = false;\n const setSize = () => {\n const rect = containerEl.getBoundingClientRect();\n const width = Math.max(1, Math.floor(rect.width * renderScale));\n const height = Math.max(1, Math.floor(rect.height * renderScale));\n renderer.setSize(width, height);\n\n // renderer.setSize also sets canvas.style.width/height to match the (scaled-down) drawing buffer - override that so the canvas still stretches to fill its container via CSS while the buffer stays small.\n canvas.style.width = '100%';\n canvas.style.height = '100%';\n\n const res = program.uniforms.iResolution.value;\n res[0] = gl.drawingBufferWidth;\n res[1] = gl.drawingBufferHeight;\n };\n\n const ro = new ResizeObserver(() => {\n // Batch rapid resize events (ex. during a window drag) into one setSize per frame.\n if (resizePending) return;\n resizePending = true;\n requestAnimationFrame(() => {\n resizePending = false;\n setSize();\n });\n });\n ro.observe(containerEl);\n setSize();\n\n let raf = 0;\n let contextLost = false;\n let isVisible = true;\n let tabVisible = document.visibilityState !== 'hidden';\n const t0 = performance.now();\n const frameInterval = 1000 / targetFps;\n let lastFrameTime = 0;\n\n const renderStaticFrame = () => {\n program.uniforms.iTime.value = 0;\n renderer.render({ scene: mesh });\n };\n\n const loop = t => {\n if (contextLost || !isVisible || !tabVisible) return;\n\n if (t - lastFrameTime < frameInterval) {\n raf = requestAnimationFrame(loop);\n return;\n }\n lastFrameTime = t;\n\n if (pendingMouse.current) {\n mousePos.current = pendingMouse.current;\n pendingMouse.current = null;\n const mouseUniform = program.uniforms.uMouse.value;\n mouseUniform[0] = mousePos.current.x;\n mouseUniform[1] = mousePos.current.y;\n }\n\n let timeValue = (t - t0) * 0.001;\n if (direction === 'pingpong') {\n const pingpongDuration = 10;\n const segmentTime = timeValue % pingpongDuration;\n const isForward = Math.floor(timeValue / pingpongDuration) % 2 === 0;\n const u = segmentTime / pingpongDuration;\n const smooth = u * u * (3 - 2 * u);\n const pingpongTime = isForward ? smooth * pingpongDuration : (1 - smooth) * pingpongDuration;\n program.uniforms.uDirection.value = 1.0;\n program.uniforms.iTime.value = pingpongTime;\n } else {\n program.uniforms.iTime.value = timeValue;\n }\n renderer.render({ scene: mesh });\n raf = requestAnimationFrame(loop);\n };\n\n const handleContextLost = (e) => {\n e.preventDefault();\n contextLost = true;\n cancelAnimationFrame(raf);\n };\n const handleContextRestored = () => {\n contextLost = false;\n if (isVisible && tabVisible && !prefersReducedMotion) {\n cancelAnimationFrame(raf);\n raf = requestAnimationFrame(loop);\n }\n };\n canvas.addEventListener('webglcontextlost', handleContextLost);\n canvas.addEventListener('webglcontextrestored', handleContextRestored);\n\n const io = new IntersectionObserver(([entry]) => {\n const wasVisible = isVisible;\n isVisible = entry.isIntersecting;\n if (isVisible && !wasVisible && !contextLost && tabVisible && !prefersReducedMotion) {\n cancelAnimationFrame(raf);\n raf = requestAnimationFrame(loop);\n }\n }, { threshold: 0 });\n io.observe(containerEl);\n\n const handleVisibilityChange = () => {\n tabVisible = document.visibilityState !== 'hidden';\n if (tabVisible && isVisible && !contextLost && !prefersReducedMotion) {\n cancelAnimationFrame(raf);\n lastFrameTime = 0;\n raf = requestAnimationFrame(loop);\n } else {\n cancelAnimationFrame(raf);\n }\n };\n document.addEventListener('visibilitychange', handleVisibilityChange);\n\n // Respect prefers-reduced-motion: paint one frame and stop, rather than running a perpetual animation loop for users who've asked not to see motion.\n if (prefersReducedMotion) {\n renderStaticFrame();\n } else {\n raf = requestAnimationFrame(loop);\n }\n\n return () => {\n cancelAnimationFrame(raf);\n ro.disconnect();\n io.disconnect();\n document.removeEventListener('visibilitychange', handleVisibilityChange);\n canvas.removeEventListener('webglcontextlost', handleContextLost);\n canvas.removeEventListener('webglcontextrestored', handleContextRestored);\n if (mouseInteractive && containerEl) {\n containerEl.removeEventListener('mousemove', handleMouseMove);\n }\n try {\n containerEl?.removeChild(canvas);\n } catch {}\n };\n }, [color, speed, direction, scale, opacity, mouseInteractive, renderScale, maxDpr, targetFps, quality]);\n\n return
;\n};\n\nexport default Plasma;\n" + "content": "import { useEffect, useRef } from 'react';\nimport { Renderer, Program, Mesh, Triangle } from 'ogl';\n\nconst hexToRgb = hex => {\n const result = /^#?([a-f\\d]{2})([a-f\\d]{2})([a-f\\d]{2})$/i.exec(hex);\n if (!result) return [1, 0.5, 0.2];\n return [parseInt(result[1], 16) / 255, parseInt(result[2], 16) / 255, parseInt(result[3], 16) / 255];\n};\n\nconst vertex = `#version 300 es\nprecision highp float;\nin vec2 position;\nin vec2 uv;\nout vec2 vUv;\nvoid main() {\n vUv = uv;\n gl_Position = vec4(position, 0.0, 1.0);\n}\n`;\n\nconst buildFragment = steps => `#version 300 es\nprecision highp float;\nuniform vec2 iResolution;\nuniform float iTime;\nuniform vec3 uCustomColor;\nuniform float uUseCustomColor;\nuniform float uSpeed;\nuniform float uDirection;\nuniform float uScale;\nuniform float uOpacity;\nuniform vec2 uMouse;\nuniform float uMouseInteractive;\nout vec4 fragColor;\n\nvoid mainImage(out vec4 o, vec2 C) {\n vec2 center = iResolution.xy * 0.5;\n C = (C - center) / uScale + center;\n \n vec2 mouseOffset = (uMouse - center) * 0.0002;\n C += mouseOffset * length(C - center) * step(0.5, uMouseInteractive);\n \n float i, d, z, T = iTime * uSpeed * uDirection;\n vec3 O, p, S;\n\n for (vec2 r = iResolution.xy, Q; ++i < ${steps.toFixed(1)}; O += o.w/d*o.xyz) {\n p = z*normalize(vec3(C-.5*r,r.y)); \n p.z -= 4.; \n S = p;\n d = p.y-T;\n \n p.x += .4*(1.+p.y)*sin(d + p.x*0.1)*cos(.34*d + p.x*0.05); \n Q = p.xz *= mat2(cos(p.y+vec4(0,11,33,0)-T)); \n z+= d = abs(sqrt(length(Q*Q)) - .25*(5.+S.y))/3.+8e-4; \n o = 1.+sin(S.y+p.z*.5+S.z-length(S-p)+vec4(2,1,0,8));\n }\n \n o.xyz = tanh(O/1e4);\n}\n\nbool finite1(float x){ return !(isnan(x) || isinf(x)); }\nvec3 sanitize(vec3 c){\n return vec3(\n finite1(c.r) ? c.r : 0.0,\n finite1(c.g) ? c.g : 0.0,\n finite1(c.b) ? c.b : 0.0\n );\n}\n\nvoid main() {\n vec4 o = vec4(0.0);\n mainImage(o, gl_FragCoord.xy);\n vec3 rgb = sanitize(o.rgb);\n \n float intensity = (rgb.r + rgb.g + rgb.b) / 3.0;\n vec3 customColor = intensity * uCustomColor;\n vec3 finalColor = mix(rgb, customColor, step(0.5, uUseCustomColor));\n \n float alpha = length(rgb) * uOpacity;\n fragColor = vec4(finalColor, alpha);\n}`;\n\nexport const Plasma = ({\n color = '#ffffff',\n speed = 1,\n direction = 'forward',\n scale = 1,\n opacity = 1,\n mouseInteractive = true,\n renderScale = 0.55,\n maxDpr = 1.5,\n targetFps = 60,\n quality = 45,\n}) => {\n const containerRef = useRef(null);\n const mousePos = useRef({ x: 0, y: 0 });\n const pendingMouse = useRef(null);\n\n useEffect(() => {\n if (!containerRef.current) return;\n const containerEl = containerRef.current;\n\n const prefersReducedMotion =\n typeof window !== 'undefined' &&\n window.matchMedia?.('(prefers-reduced-motion: reduce)').matches;\n\n const useCustomColor = color ? 1.0 : 0.0;\n const customColorRgb = color ? hexToRgb(color) : [1, 1, 1];\n\n const directionMultiplier = direction === 'reverse' ? -1.0 : 1.0;\n\n let renderer;\n try {\n renderer = new Renderer({\n webgl: 2,\n alpha: true,\n antialias: false,\n dpr: Math.min(window.devicePixelRatio || 1, maxDpr)\n });\n } catch {\n return;\n }\n const gl = renderer.gl;\n if (!gl) return;\n const canvas = gl.canvas;\n canvas.style.display = 'block';\n canvas.style.width = '100%';\n canvas.style.height = '100%';\n // Rendering at renderScale internally, CSS stretches it back up.\n containerEl.appendChild(canvas);\n\n const geometry = new Triangle(gl);\n\n const program = new Program(gl, {\n vertex: vertex,\n fragment: buildFragment(quality),\n uniforms: {\n iTime: { value: 0 },\n iResolution: { value: new Float32Array([1, 1]) },\n uCustomColor: { value: new Float32Array(customColorRgb) },\n uUseCustomColor: { value: useCustomColor },\n uSpeed: { value: speed * 0.4 },\n uDirection: { value: directionMultiplier },\n uScale: { value: scale },\n uOpacity: { value: opacity },\n uMouse: { value: new Float32Array([0, 0]) },\n uMouseInteractive: { value: mouseInteractive ? 1.0 : 0.0 }\n }\n });\n\n const mesh = new Mesh(gl, { geometry, program });\n\n const handleMouseMove = e => {\n if (!mouseInteractive) return;\n const rect = containerEl.getBoundingClientRect();\n // Store the latest position but don't touch GL state here, the rAF loop picks it up once per rendered frame instead of once per mouse event.\n pendingMouse.current = {\n x: e.clientX - rect.left,\n y: e.clientY - rect.top,\n };\n };\n\n if (mouseInteractive) {\n containerEl.addEventListener('mousemove', handleMouseMove, { passive: true });\n }\n\n let resizePending = false;\n const setSize = () => {\n const rect = containerEl.getBoundingClientRect();\n const width = Math.max(1, Math.floor(rect.width * renderScale));\n const height = Math.max(1, Math.floor(rect.height * renderScale));\n renderer.setSize(width, height);\n\n // renderer.setSize also sets canvas.style.width/height to match the (scaled-down) drawing buffer - override that so the canvas still stretches to fill its container via CSS while the buffer stays small.\n canvas.style.width = '100%';\n canvas.style.height = '100%';\n\n const res = program.uniforms.iResolution.value;\n res[0] = gl.drawingBufferWidth;\n res[1] = gl.drawingBufferHeight;\n };\n\n const ro = new ResizeObserver(() => {\n // Batch rapid resize events (ex. during a window drag) into one setSize per frame.\n if (resizePending) return;\n resizePending = true;\n requestAnimationFrame(() => {\n resizePending = false;\n setSize();\n });\n });\n ro.observe(containerEl);\n setSize();\n\n let raf = 0;\n let contextLost = false;\n let isVisible = true;\n let tabVisible = document.visibilityState !== 'hidden';\n const t0 = performance.now();\n const frameInterval = 1000 / targetFps;\n let lastFrameTime = 0;\n\n const renderStaticFrame = () => {\n program.uniforms.iTime.value = 0;\n renderer.render({ scene: mesh });\n };\n\n const loop = t => {\n if (contextLost || !isVisible || !tabVisible) return;\n\n if (t - lastFrameTime < frameInterval) {\n raf = requestAnimationFrame(loop);\n return;\n }\n lastFrameTime = t;\n\n if (pendingMouse.current) {\n mousePos.current = pendingMouse.current;\n pendingMouse.current = null;\n const mouseUniform = program.uniforms.uMouse.value;\n mouseUniform[0] = mousePos.current.x;\n mouseUniform[1] = mousePos.current.y;\n }\n\n let timeValue = (t - t0) * 0.001;\n if (direction === 'pingpong') {\n const pingpongDuration = 10;\n const segmentTime = timeValue % pingpongDuration;\n const isForward = Math.floor(timeValue / pingpongDuration) % 2 === 0;\n const u = segmentTime / pingpongDuration;\n const smooth = u * u * (3 - 2 * u);\n const pingpongTime = isForward ? smooth * pingpongDuration : (1 - smooth) * pingpongDuration;\n program.uniforms.uDirection.value = 1.0;\n program.uniforms.iTime.value = pingpongTime;\n } else {\n program.uniforms.iTime.value = timeValue;\n }\n renderer.render({ scene: mesh });\n raf = requestAnimationFrame(loop);\n };\n\n const handleContextLost = (e) => {\n e.preventDefault();\n contextLost = true;\n cancelAnimationFrame(raf);\n };\n const handleContextRestored = () => {\n contextLost = false;\n if (isVisible && tabVisible && !prefersReducedMotion) {\n cancelAnimationFrame(raf);\n raf = requestAnimationFrame(loop);\n }\n };\n canvas.addEventListener('webglcontextlost', handleContextLost);\n canvas.addEventListener('webglcontextrestored', handleContextRestored);\n\n const io = new IntersectionObserver(([entry]) => {\n const wasVisible = isVisible;\n isVisible = entry.isIntersecting;\n if (isVisible && !wasVisible && !contextLost && tabVisible && !prefersReducedMotion) {\n cancelAnimationFrame(raf);\n raf = requestAnimationFrame(loop);\n }\n }, { threshold: 0 });\n io.observe(containerEl);\n\n const handleVisibilityChange = () => {\n tabVisible = document.visibilityState !== 'hidden';\n if (tabVisible && isVisible && !contextLost && !prefersReducedMotion) {\n cancelAnimationFrame(raf);\n lastFrameTime = 0;\n raf = requestAnimationFrame(loop);\n } else {\n cancelAnimationFrame(raf);\n }\n };\n document.addEventListener('visibilitychange', handleVisibilityChange);\n\n // Respect prefers-reduced-motion: paint one frame and stop, rather than running a perpetual animation loop for users who've asked not to see motion.\n if (prefersReducedMotion) {\n renderStaticFrame();\n } else {\n raf = requestAnimationFrame(loop);\n }\n\n return () => {\n cancelAnimationFrame(raf);\n ro.disconnect();\n io.disconnect();\n document.removeEventListener('visibilitychange', handleVisibilityChange);\n canvas.removeEventListener('webglcontextlost', handleContextLost);\n canvas.removeEventListener('webglcontextrestored', handleContextRestored);\n if (mouseInteractive && containerEl) {\n containerEl.removeEventListener('mousemove', handleMouseMove);\n }\n try {\n containerEl?.removeChild(canvas);\n } catch {}\n };\n }, [color, speed, direction, scale, opacity, mouseInteractive, renderScale, maxDpr, targetFps, quality]);\n\n return
;\n};\n\nexport default Plasma;\n" } ], "registryDependencies": [], diff --git a/public/r/Plasma-TS-CSS.json b/public/r/Plasma-TS-CSS.json index 4541a69b9..940a778e8 100644 --- a/public/r/Plasma-TS-CSS.json +++ b/public/r/Plasma-TS-CSS.json @@ -13,7 +13,7 @@ { "type": "registry:component", "path": "Plasma/Plasma.tsx", - "content": "import React, { useEffect, useRef } from 'react';\nimport { Renderer, Program, Mesh, Triangle } from 'ogl';\nimport './Plasma.css';\n\ninterface PlasmaProps {\n color?: string;\n speed?: number;\n direction?: 'forward' | 'reverse' | 'pingpong';\n scale?: number;\n opacity?: number;\n mouseInteractive?: boolean;\n /** Internal render resolution multiplier. 1 = full res, 0.5 = quarter the pixels. Default 0.55. */\n renderScale?: number;\n /** Hard cap on devicePixelRatio used for rendering. Default 1.5. */\n maxDpr?: number;\n /** Target frame rate for the animation loop. Default 30. */\n targetFps?: number;\n /** Raymarch step count — lower is cheaper, less detailed. Default 45. */\n quality?: number;\n}\n\nconst hexToRgb = (hex: string): [number, number, number] => {\n const result = /^#?([a-f\\d]{2})([a-f\\d]{2})([a-f\\d]{2})$/i.exec(hex);\n if (!result) return [1, 0.5, 0.2];\n return [parseInt(result[1], 16) / 255, parseInt(result[2], 16) / 255, parseInt(result[3], 16) / 255];\n};\n\nconst vertex = `#version 300 es\nprecision highp float;\nin vec2 position;\nin vec2 uv;\nout vec2 vUv;\nvoid main() {\n vUv = uv;\n gl_Position = vec4(position, 0.0, 1.0);\n}\n`;\n\nconst buildFragment = (steps: number) => `#version 300 es\nprecision highp float;\nuniform vec2 iResolution;\nuniform float iTime;\nuniform vec3 uCustomColor;\nuniform float uUseCustomColor;\nuniform float uSpeed;\nuniform float uDirection;\nuniform float uScale;\nuniform float uOpacity;\nuniform vec2 uMouse;\nuniform float uMouseInteractive;\nout vec4 fragColor;\n\nvoid mainImage(out vec4 o, vec2 C) {\n vec2 center = iResolution.xy * 0.5;\n C = (C - center) / uScale + center;\n \n vec2 mouseOffset = (uMouse - center) * 0.0002;\n C += mouseOffset * length(C - center) * step(0.5, uMouseInteractive);\n \n float i, d, z, T = iTime * uSpeed * uDirection;\n vec3 O, p, S;\n\n for (vec2 r = iResolution.xy, Q; ++i < ${steps.toFixed(1)}; O += o.w/d*o.xyz) {\n p = z*normalize(vec3(C-.5*r,r.y)); \n p.z -= 4.; \n S = p;\n d = p.y-T;\n \n p.x += .4*(1.+p.y)*sin(d + p.x*0.1)*cos(.34*d + p.x*0.05); \n Q = p.xz *= mat2(cos(p.y+vec4(0,11,33,0)-T)); \n z+= d = abs(sqrt(length(Q*Q)) - .25*(5.+S.y))/3.+8e-4; \n o = 1.+sin(S.y+p.z*.5+S.z-length(S-p)+vec4(2,1,0,8));\n }\n \n o.xyz = tanh(O/1e4);\n}\n\nbool finite1(float x){ return !(isnan(x) || isinf(x)); }\nvec3 sanitize(vec3 c){\n return vec3(\n finite1(c.r) ? c.r : 0.0,\n finite1(c.g) ? c.g : 0.0,\n finite1(c.b) ? c.b : 0.0\n );\n}\n\nvoid main() {\n vec4 o = vec4(0.0);\n mainImage(o, gl_FragCoord.xy);\n vec3 rgb = sanitize(o.rgb);\n \n float intensity = (rgb.r + rgb.g + rgb.b) / 3.0;\n vec3 customColor = intensity * uCustomColor;\n vec3 finalColor = mix(rgb, customColor, step(0.5, uUseCustomColor));\n \n float alpha = length(rgb) * uOpacity;\n fragColor = vec4(finalColor, alpha);\n}`;\n\nexport const Plasma: React.FC = ({\n color = '#ffffff',\n speed = 1,\n direction = 'forward',\n scale = 1,\n opacity = 1,\n mouseInteractive = true,\n renderScale = 0.55,\n maxDpr = 1.5,\n targetFps = 30,\n quality = 45,\n}) => {\n const containerRef = useRef(null);\n const mousePos = useRef({ x: 0, y: 0 });\n const pendingMouse = useRef<{ x: number; y: number } | null>(null);\n\n useEffect(() => {\n if (!containerRef.current) return;\n const containerEl = containerRef.current;\n\n const prefersReducedMotion =\n typeof window !== 'undefined' &&\n window.matchMedia?.('(prefers-reduced-motion: reduce)').matches;\n\n const useCustomColor = color ? 1.0 : 0.0;\n const customColorRgb = color ? hexToRgb(color) : [1, 1, 1];\n\n const directionMultiplier = direction === 'reverse' ? -1.0 : 1.0;\n\n let renderer: Renderer;\n try {\n renderer = new Renderer({\n webgl: 2,\n alpha: true,\n antialias: false,\n dpr: Math.min(window.devicePixelRatio || 1, maxDpr)\n });\n } catch {\n return;\n }\n const gl = renderer.gl;\n if (!gl) return;\n const canvas = gl.canvas as HTMLCanvasElement;\n canvas.style.display = 'block';\n canvas.style.width = '100%';\n canvas.style.height = '100%';\n // Rendering at renderScale internally, CSS stretches it back up.\n containerEl.appendChild(canvas);\n\n const geometry = new Triangle(gl);\n\n const program = new Program(gl, {\n vertex: vertex,\n fragment: buildFragment(quality),\n uniforms: {\n iTime: { value: 0 },\n iResolution: { value: new Float32Array([1, 1]) },\n uCustomColor: { value: new Float32Array(customColorRgb) },\n uUseCustomColor: { value: useCustomColor },\n uSpeed: { value: speed * 0.4 },\n uDirection: { value: directionMultiplier },\n uScale: { value: scale },\n uOpacity: { value: opacity },\n uMouse: { value: new Float32Array([0, 0]) },\n uMouseInteractive: { value: mouseInteractive ? 1.0 : 0.0 }\n }\n });\n\n const mesh = new Mesh(gl, { geometry, program });\n\n const handleMouseMove = (e: MouseEvent) => {\n if (!mouseInteractive) return;\n const rect = containerEl.getBoundingClientRect();\n // Store the latest position but don't touch GL state here, the rAF loop picks it up once per rendered frame instead of once per mouse event.\n pendingMouse.current = {\n x: e.clientX - rect.left,\n y: e.clientY - rect.top,\n };\n };\n\n if (mouseInteractive) {\n containerEl.addEventListener('mousemove', handleMouseMove, { passive: true });\n }\n\n let resizePending = false;\n const setSize = () => {\n const rect = containerEl.getBoundingClientRect();\n const width = Math.max(1, Math.floor(rect.width * renderScale));\n const height = Math.max(1, Math.floor(rect.height * renderScale));\n renderer.setSize(width, height);\n\n // renderer.setSize also sets canvas.style.width/height to match the (scaled-down) drawing buffer - override that so the canvas still stretches to fill its container via CSS while the buffer stays small.\n canvas.style.width = '100%';\n canvas.style.height = '100%';\n\n const res = program.uniforms.iResolution.value as Float32Array;\n res[0] = gl.drawingBufferWidth;\n res[1] = gl.drawingBufferHeight;\n };\n\n const ro = new ResizeObserver(() => {\n // Batch rapid resize events (ex. during a window drag) into one setSize per frame.\n if (resizePending) return;\n resizePending = true;\n requestAnimationFrame(() => {\n resizePending = false;\n setSize();\n });\n });\n ro.observe(containerEl);\n setSize();\n\n let raf = 0;\n let contextLost = false;\n let isVisible = true;\n let tabVisible = document.visibilityState !== 'hidden';\n const t0 = performance.now();\n const frameInterval = 1000 / targetFps;\n let lastFrameTime = 0;\n\n const renderStaticFrame = () => {\n (program.uniforms.iTime as any).value = 0;\n renderer.render({ scene: mesh });\n };\n\n const loop = (t: number) => {\n if (contextLost || !isVisible || !tabVisible) return;\n\n if (t - lastFrameTime < frameInterval) {\n raf = requestAnimationFrame(loop);\n return;\n }\n lastFrameTime = t;\n\n if (pendingMouse.current) {\n mousePos.current = pendingMouse.current;\n pendingMouse.current = null;\n const mouseUniform = program.uniforms.uMouse.value as Float32Array;\n mouseUniform[0] = mousePos.current.x;\n mouseUniform[1] = mousePos.current.y;\n }\n\n let timeValue = (t - t0) * 0.001;\n if (direction === 'pingpong') {\n const pingpongDuration = 10;\n const segmentTime = timeValue % pingpongDuration;\n const isForward = Math.floor(timeValue / pingpongDuration) % 2 === 0;\n const u = segmentTime / pingpongDuration;\n const smooth = u * u * (3 - 2 * u);\n const pingpongTime = isForward ? smooth * pingpongDuration : (1 - smooth) * pingpongDuration;\n (program.uniforms.uDirection as any).value = 1.0;\n (program.uniforms.iTime as any).value = pingpongTime;\n } else {\n (program.uniforms.iTime as any).value = timeValue;\n }\n renderer.render({ scene: mesh });\n raf = requestAnimationFrame(loop);\n };\n\n const handleContextLost = (e: Event) => {\n e.preventDefault();\n contextLost = true;\n cancelAnimationFrame(raf);\n };\n const handleContextRestored = () => {\n contextLost = false;\n if (isVisible && tabVisible && !prefersReducedMotion) {\n cancelAnimationFrame(raf);\n raf = requestAnimationFrame(loop);\n }\n };\n canvas.addEventListener('webglcontextlost', handleContextLost);\n canvas.addEventListener('webglcontextrestored', handleContextRestored);\n\n const io = new IntersectionObserver(([entry]) => {\n const wasVisible = isVisible;\n isVisible = entry.isIntersecting;\n if (isVisible && !wasVisible && !contextLost && tabVisible && !prefersReducedMotion) {\n cancelAnimationFrame(raf);\n raf = requestAnimationFrame(loop);\n }\n }, { threshold: 0 });\n io.observe(containerEl);\n\n const handleVisibilityChange = () => {\n tabVisible = document.visibilityState !== 'hidden';\n if (tabVisible && isVisible && !contextLost && !prefersReducedMotion) {\n cancelAnimationFrame(raf);\n lastFrameTime = 0;\n raf = requestAnimationFrame(loop);\n } else {\n cancelAnimationFrame(raf);\n }\n };\n document.addEventListener('visibilitychange', handleVisibilityChange);\n\n // Respect prefers-reduced-motion: paint one frame and stop, rather than running a perpetual animation loop for users who've asked not to see motion.\n if (prefersReducedMotion) {\n renderStaticFrame();\n } else {\n raf = requestAnimationFrame(loop);\n }\n\n return () => {\n cancelAnimationFrame(raf);\n ro.disconnect();\n io.disconnect();\n document.removeEventListener('visibilitychange', handleVisibilityChange);\n canvas.removeEventListener('webglcontextlost', handleContextLost);\n canvas.removeEventListener('webglcontextrestored', handleContextRestored);\n if (mouseInteractive && containerEl) {\n containerEl.removeEventListener('mousemove', handleMouseMove);\n }\n try {\n containerEl?.removeChild(canvas);\n } catch {}\n };\n }, [color, speed, direction, scale, opacity, mouseInteractive, renderScale, maxDpr, targetFps, quality]);\n\n return
;\n};\n\nexport default Plasma;\n" + "content": "import React, { useEffect, useRef } from 'react';\nimport { Renderer, Program, Mesh, Triangle } from 'ogl';\nimport './Plasma.css';\n\ninterface PlasmaProps {\n color?: string;\n speed?: number;\n direction?: 'forward' | 'reverse' | 'pingpong';\n scale?: number;\n opacity?: number;\n mouseInteractive?: boolean;\n /** Internal render resolution multiplier. 1 = full res, 0.5 = quarter the pixels. Default 0.55. */\n renderScale?: number;\n /** Hard cap on devicePixelRatio used for rendering. Default 1.5. */\n maxDpr?: number;\n /** Target frame rate for the animation loop. Default 30. */\n targetFps?: number;\n /** Raymarch step count — lower is cheaper, less detailed. Default 45. */\n quality?: number;\n}\n\nconst hexToRgb = (hex: string): [number, number, number] => {\n const result = /^#?([a-f\\d]{2})([a-f\\d]{2})([a-f\\d]{2})$/i.exec(hex);\n if (!result) return [1, 0.5, 0.2];\n return [parseInt(result[1], 16) / 255, parseInt(result[2], 16) / 255, parseInt(result[3], 16) / 255];\n};\n\nconst vertex = `#version 300 es\nprecision highp float;\nin vec2 position;\nin vec2 uv;\nout vec2 vUv;\nvoid main() {\n vUv = uv;\n gl_Position = vec4(position, 0.0, 1.0);\n}\n`;\n\nconst buildFragment = (steps: number) => `#version 300 es\nprecision highp float;\nuniform vec2 iResolution;\nuniform float iTime;\nuniform vec3 uCustomColor;\nuniform float uUseCustomColor;\nuniform float uSpeed;\nuniform float uDirection;\nuniform float uScale;\nuniform float uOpacity;\nuniform vec2 uMouse;\nuniform float uMouseInteractive;\nout vec4 fragColor;\n\nvoid mainImage(out vec4 o, vec2 C) {\n vec2 center = iResolution.xy * 0.5;\n C = (C - center) / uScale + center;\n \n vec2 mouseOffset = (uMouse - center) * 0.0002;\n C += mouseOffset * length(C - center) * step(0.5, uMouseInteractive);\n \n float i, d, z, T = iTime * uSpeed * uDirection;\n vec3 O, p, S;\n\n for (vec2 r = iResolution.xy, Q; ++i < ${steps.toFixed(1)}; O += o.w/d*o.xyz) {\n p = z*normalize(vec3(C-.5*r,r.y)); \n p.z -= 4.; \n S = p;\n d = p.y-T;\n \n p.x += .4*(1.+p.y)*sin(d + p.x*0.1)*cos(.34*d + p.x*0.05); \n Q = p.xz *= mat2(cos(p.y+vec4(0,11,33,0)-T)); \n z+= d = abs(sqrt(length(Q*Q)) - .25*(5.+S.y))/3.+8e-4; \n o = 1.+sin(S.y+p.z*.5+S.z-length(S-p)+vec4(2,1,0,8));\n }\n \n o.xyz = tanh(O/1e4);\n}\n\nbool finite1(float x){ return !(isnan(x) || isinf(x)); }\nvec3 sanitize(vec3 c){\n return vec3(\n finite1(c.r) ? c.r : 0.0,\n finite1(c.g) ? c.g : 0.0,\n finite1(c.b) ? c.b : 0.0\n );\n}\n\nvoid main() {\n vec4 o = vec4(0.0);\n mainImage(o, gl_FragCoord.xy);\n vec3 rgb = sanitize(o.rgb);\n \n float intensity = (rgb.r + rgb.g + rgb.b) / 3.0;\n vec3 customColor = intensity * uCustomColor;\n vec3 finalColor = mix(rgb, customColor, step(0.5, uUseCustomColor));\n \n float alpha = length(rgb) * uOpacity;\n fragColor = vec4(finalColor, alpha);\n}`;\n\nexport const Plasma: React.FC = ({\n color = '#ffffff',\n speed = 1,\n direction = 'forward',\n scale = 1,\n opacity = 1,\n mouseInteractive = true,\n renderScale = 0.55,\n maxDpr = 1.5,\n targetFps = 60,\n quality = 45,\n}) => {\n const containerRef = useRef(null);\n const mousePos = useRef({ x: 0, y: 0 });\n const pendingMouse = useRef<{ x: number; y: number } | null>(null);\n\n useEffect(() => {\n if (!containerRef.current) return;\n const containerEl = containerRef.current;\n\n const prefersReducedMotion =\n typeof window !== 'undefined' &&\n window.matchMedia?.('(prefers-reduced-motion: reduce)').matches;\n\n const useCustomColor = color ? 1.0 : 0.0;\n const customColorRgb = color ? hexToRgb(color) : [1, 1, 1];\n\n const directionMultiplier = direction === 'reverse' ? -1.0 : 1.0;\n\n let renderer: Renderer;\n try {\n renderer = new Renderer({\n webgl: 2,\n alpha: true,\n antialias: false,\n dpr: Math.min(window.devicePixelRatio || 1, maxDpr)\n });\n } catch {\n return;\n }\n const gl = renderer.gl;\n if (!gl) return;\n const canvas = gl.canvas as HTMLCanvasElement;\n canvas.style.display = 'block';\n canvas.style.width = '100%';\n canvas.style.height = '100%';\n // Rendering at renderScale internally, CSS stretches it back up.\n containerEl.appendChild(canvas);\n\n const geometry = new Triangle(gl);\n\n const program = new Program(gl, {\n vertex: vertex,\n fragment: buildFragment(quality),\n uniforms: {\n iTime: { value: 0 },\n iResolution: { value: new Float32Array([1, 1]) },\n uCustomColor: { value: new Float32Array(customColorRgb) },\n uUseCustomColor: { value: useCustomColor },\n uSpeed: { value: speed * 0.4 },\n uDirection: { value: directionMultiplier },\n uScale: { value: scale },\n uOpacity: { value: opacity },\n uMouse: { value: new Float32Array([0, 0]) },\n uMouseInteractive: { value: mouseInteractive ? 1.0 : 0.0 }\n }\n });\n\n const mesh = new Mesh(gl, { geometry, program });\n\n const handleMouseMove = (e: MouseEvent) => {\n if (!mouseInteractive) return;\n const rect = containerEl.getBoundingClientRect();\n // Store the latest position but don't touch GL state here, the rAF loop picks it up once per rendered frame instead of once per mouse event.\n pendingMouse.current = {\n x: e.clientX - rect.left,\n y: e.clientY - rect.top,\n };\n };\n\n if (mouseInteractive) {\n containerEl.addEventListener('mousemove', handleMouseMove, { passive: true });\n }\n\n let resizePending = false;\n const setSize = () => {\n const rect = containerEl.getBoundingClientRect();\n const width = Math.max(1, Math.floor(rect.width * renderScale));\n const height = Math.max(1, Math.floor(rect.height * renderScale));\n renderer.setSize(width, height);\n\n // renderer.setSize also sets canvas.style.width/height to match the (scaled-down) drawing buffer - override that so the canvas still stretches to fill its container via CSS while the buffer stays small.\n canvas.style.width = '100%';\n canvas.style.height = '100%';\n\n const res = program.uniforms.iResolution.value as Float32Array;\n res[0] = gl.drawingBufferWidth;\n res[1] = gl.drawingBufferHeight;\n };\n\n const ro = new ResizeObserver(() => {\n // Batch rapid resize events (ex. during a window drag) into one setSize per frame.\n if (resizePending) return;\n resizePending = true;\n requestAnimationFrame(() => {\n resizePending = false;\n setSize();\n });\n });\n ro.observe(containerEl);\n setSize();\n\n let raf = 0;\n let contextLost = false;\n let isVisible = true;\n let tabVisible = document.visibilityState !== 'hidden';\n const t0 = performance.now();\n const frameInterval = 1000 / targetFps;\n let lastFrameTime = 0;\n\n const renderStaticFrame = () => {\n (program.uniforms.iTime as any).value = 0;\n renderer.render({ scene: mesh });\n };\n\n const loop = (t: number) => {\n if (contextLost || !isVisible || !tabVisible) return;\n\n if (t - lastFrameTime < frameInterval) {\n raf = requestAnimationFrame(loop);\n return;\n }\n lastFrameTime = t;\n\n if (pendingMouse.current) {\n mousePos.current = pendingMouse.current;\n pendingMouse.current = null;\n const mouseUniform = program.uniforms.uMouse.value as Float32Array;\n mouseUniform[0] = mousePos.current.x;\n mouseUniform[1] = mousePos.current.y;\n }\n\n let timeValue = (t - t0) * 0.001;\n if (direction === 'pingpong') {\n const pingpongDuration = 10;\n const segmentTime = timeValue % pingpongDuration;\n const isForward = Math.floor(timeValue / pingpongDuration) % 2 === 0;\n const u = segmentTime / pingpongDuration;\n const smooth = u * u * (3 - 2 * u);\n const pingpongTime = isForward ? smooth * pingpongDuration : (1 - smooth) * pingpongDuration;\n (program.uniforms.uDirection as any).value = 1.0;\n (program.uniforms.iTime as any).value = pingpongTime;\n } else {\n (program.uniforms.iTime as any).value = timeValue;\n }\n renderer.render({ scene: mesh });\n raf = requestAnimationFrame(loop);\n };\n\n const handleContextLost = (e: Event) => {\n e.preventDefault();\n contextLost = true;\n cancelAnimationFrame(raf);\n };\n const handleContextRestored = () => {\n contextLost = false;\n if (isVisible && tabVisible && !prefersReducedMotion) {\n cancelAnimationFrame(raf);\n raf = requestAnimationFrame(loop);\n }\n };\n canvas.addEventListener('webglcontextlost', handleContextLost);\n canvas.addEventListener('webglcontextrestored', handleContextRestored);\n\n const io = new IntersectionObserver(([entry]) => {\n const wasVisible = isVisible;\n isVisible = entry.isIntersecting;\n if (isVisible && !wasVisible && !contextLost && tabVisible && !prefersReducedMotion) {\n cancelAnimationFrame(raf);\n raf = requestAnimationFrame(loop);\n }\n }, { threshold: 0 });\n io.observe(containerEl);\n\n const handleVisibilityChange = () => {\n tabVisible = document.visibilityState !== 'hidden';\n if (tabVisible && isVisible && !contextLost && !prefersReducedMotion) {\n cancelAnimationFrame(raf);\n lastFrameTime = 0;\n raf = requestAnimationFrame(loop);\n } else {\n cancelAnimationFrame(raf);\n }\n };\n document.addEventListener('visibilitychange', handleVisibilityChange);\n\n // Respect prefers-reduced-motion: paint one frame and stop, rather than running a perpetual animation loop for users who've asked not to see motion.\n if (prefersReducedMotion) {\n renderStaticFrame();\n } else {\n raf = requestAnimationFrame(loop);\n }\n\n return () => {\n cancelAnimationFrame(raf);\n ro.disconnect();\n io.disconnect();\n document.removeEventListener('visibilitychange', handleVisibilityChange);\n canvas.removeEventListener('webglcontextlost', handleContextLost);\n canvas.removeEventListener('webglcontextrestored', handleContextRestored);\n if (mouseInteractive && containerEl) {\n containerEl.removeEventListener('mousemove', handleMouseMove);\n }\n try {\n containerEl?.removeChild(canvas);\n } catch {}\n };\n }, [color, speed, direction, scale, opacity, mouseInteractive, renderScale, maxDpr, targetFps, quality]);\n\n return
;\n};\n\nexport default Plasma;\n" } ], "registryDependencies": [], diff --git a/public/r/Plasma-TS-TW.json b/public/r/Plasma-TS-TW.json index 69d7d83b7..f8089e352 100644 --- a/public/r/Plasma-TS-TW.json +++ b/public/r/Plasma-TS-TW.json @@ -8,7 +8,7 @@ { "type": "registry:component", "path": "Plasma/Plasma.tsx", - "content": "import React, { useEffect, useRef } from \"react\";\nimport { Renderer, Program, Mesh, Triangle } from \"ogl\";\n\ninterface PlasmaProps {\n color?: string;\n speed?: number;\n direction?: \"forward\" | \"reverse\" | \"pingpong\";\n scale?: number;\n opacity?: number;\n mouseInteractive?: boolean;\n /** Internal render resolution multiplier. 1 = full res, 0.5 = quarter the pixels. Default 0.55. */\n renderScale?: number;\n /** Hard cap on devicePixelRatio used for rendering. Default 1.5. */\n maxDpr?: number;\n /** Target frame rate for the animation loop. Default 30. */\n targetFps?: number;\n /** Raymarch step count — lower is cheaper, less detailed. Default 45. */\n quality?: number;\n}\n\nconst hexToRgb = (hex: string): [number, number, number] => {\n const result = /^#?([a-f\\d]{2})([a-f\\d]{2})([a-f\\d]{2})$/i.exec(hex);\n if (!result) return [1, 0.5, 0.2];\n return [\n parseInt(result[1], 16) / 255,\n parseInt(result[2], 16) / 255,\n parseInt(result[3], 16) / 255,\n ];\n};\n\nconst vertex = `#version 300 es\nprecision highp float;\nin vec2 position;\nin vec2 uv;\nout vec2 vUv;\nvoid main() {\n vUv = uv;\n gl_Position = vec4(position, 0.0, 1.0);\n}\n`;\n\nconst buildFragment = (steps: number) => `#version 300 es\nprecision highp float;\nuniform vec2 iResolution;\nuniform float iTime;\nuniform vec3 uCustomColor;\nuniform float uUseCustomColor;\nuniform float uSpeed;\nuniform float uDirection;\nuniform float uScale;\nuniform float uOpacity;\nuniform vec2 uMouse;\nuniform float uMouseInteractive;\nout vec4 fragColor;\n\nvoid mainImage(out vec4 o, vec2 C) {\n vec2 center = iResolution.xy * 0.5;\n C = (C - center) / uScale + center;\n\n vec2 mouseOffset = (uMouse - center) * 0.0002;\n C += mouseOffset * length(C - center) * step(0.5, uMouseInteractive);\n\n float i, d, z, T = iTime * uSpeed * uDirection;\n vec3 O, p, S;\n\n for (vec2 r = iResolution.xy, Q; ++i < ${steps.toFixed(1)}; O += o.w/d*o.xyz) {\n p = z*normalize(vec3(C-.5*r,r.y));\n p.z -= 4.;\n S = p;\n d = p.y-T;\n\n p.x += .4*(1.+p.y)*sin(d + p.x*0.1)*cos(.34*d + p.x*0.05);\n Q = p.xz *= mat2(cos(p.y+vec4(0,11,33,0)-T));\n z+= d = abs(sqrt(length(Q*Q)) - .25*(5.+S.y))/3.+8e-4;\n o = 1.+sin(S.y+p.z*.5+S.z-length(S-p)+vec4(2,1,0,8));\n }\n\n o.xyz = tanh(O/1e4);\n}\n\nbool finite1(float x){ return !(isnan(x) || isinf(x)); }\nvec3 sanitize(vec3 c){\n return vec3(\n finite1(c.r) ? c.r : 0.0,\n finite1(c.g) ? c.g : 0.0,\n finite1(c.b) ? c.b : 0.0\n );\n}\n\nvoid main() {\n vec4 o = vec4(0.0);\n mainImage(o, gl_FragCoord.xy);\n vec3 rgb = sanitize(o.rgb);\n\n float intensity = (rgb.r + rgb.g + rgb.b) / 3.0;\n vec3 customColor = intensity * uCustomColor;\n vec3 finalColor = mix(rgb, customColor, step(0.5, uUseCustomColor));\n\n float alpha = length(rgb) * uOpacity;\n fragColor = vec4(finalColor, alpha);\n}`;\n\nexport const Plasma: React.FC = ({\n color = \"#ffffff\",\n speed = 1,\n direction = \"forward\",\n scale = 1,\n opacity = 1,\n mouseInteractive = true,\n renderScale = 0.55,\n maxDpr = 1.5,\n targetFps = 30,\n quality = 45,\n}) => {\n const containerRef = useRef(null);\n const mousePos = useRef({ x: 0, y: 0 });\n const pendingMouse = useRef<{ x: number; y: number } | null>(null);\n\n useEffect(() => {\n if (!containerRef.current) return;\n const containerEl = containerRef.current;\n\n const prefersReducedMotion =\n typeof window !== \"undefined\" &&\n window.matchMedia?.(\"(prefers-reduced-motion: reduce)\").matches;\n\n const useCustomColor = color ? 1.0 : 0.0;\n const customColorRgb = color ? hexToRgb(color) : [1, 1, 1];\n const directionMultiplier = direction === \"reverse\" ? -1.0 : 1.0;\n\n let renderer: Renderer;\n try {\n renderer = new Renderer({\n webgl: 2,\n alpha: true,\n antialias: false,\n dpr: Math.min(window.devicePixelRatio || 1, maxDpr),\n });\n } catch {\n return;\n }\n const gl = renderer.gl;\n if (!gl) return;\n const canvas = gl.canvas as HTMLCanvasElement;\n canvas.style.display = \"block\";\n canvas.style.width = \"100%\";\n canvas.style.height = \"100%\";\n // Rendering at renderScale internally, CSS stretches it back up.\n containerEl.appendChild(canvas);\n\n const geometry = new Triangle(gl);\n\n const program = new Program(gl, {\n vertex: vertex,\n fragment: buildFragment(quality),\n uniforms: {\n iTime: { value: 0 },\n iResolution: { value: new Float32Array([1, 1]) },\n uCustomColor: { value: new Float32Array(customColorRgb) },\n uUseCustomColor: { value: useCustomColor },\n uSpeed: { value: speed * 0.4 },\n uDirection: { value: directionMultiplier },\n uScale: { value: scale },\n uOpacity: { value: opacity },\n uMouse: { value: new Float32Array([0, 0]) },\n uMouseInteractive: { value: mouseInteractive ? 1.0 : 0.0 },\n },\n });\n\n const mesh = new Mesh(gl, { geometry, program });\n\n const handleMouseMove = (e: MouseEvent) => {\n if (!mouseInteractive) return;\n const rect = containerEl.getBoundingClientRect();\n // Store the latest position but don't touch GL state here, the rAF loop picks it up once per rendered frame instead of once per mouse event.\n pendingMouse.current = {\n x: e.clientX - rect.left,\n y: e.clientY - rect.top,\n };\n };\n\n if (mouseInteractive) {\n containerEl.addEventListener(\"mousemove\", handleMouseMove, {\n passive: true,\n });\n }\n\n let resizePending = false;\n const setSize = () => {\n const rect = containerEl.getBoundingClientRect();\n const width = Math.max(1, Math.floor(rect.width * renderScale));\n const height = Math.max(1, Math.floor(rect.height * renderScale));\n renderer.setSize(width, height);\n\n // renderer.setSize also sets canvas.style.width/height to match the (scaled-down) drawing buffer - override that so the canvas still stretches to fill its container via CSS while the buffer stays small.\n canvas.style.width = \"100%\";\n canvas.style.height = \"100%\";\n\n const res = program.uniforms.iResolution.value as Float32Array;\n res[0] = gl.drawingBufferWidth;\n res[1] = gl.drawingBufferHeight;\n };\n\n const ro = new ResizeObserver(() => {\n // Batch rapid resize events (ex. during a window drag) into one setSize per frame.\n if (resizePending) return;\n resizePending = true;\n requestAnimationFrame(() => {\n resizePending = false;\n setSize();\n });\n });\n ro.observe(containerEl);\n setSize();\n\n let raf = 0;\n let contextLost = false;\n let isVisible = true;\n let tabVisible = document.visibilityState !== \"hidden\";\n const t0 = performance.now();\n const frameInterval = 1000 / targetFps;\n let lastFrameTime = 0;\n\n const renderStaticFrame = () => {\n (program.uniforms.iTime as any).value = 0;\n renderer.render({ scene: mesh });\n };\n\n const loop = (t: number) => {\n if (contextLost || !isVisible || !tabVisible) return;\n\n if (t - lastFrameTime < frameInterval) {\n raf = requestAnimationFrame(loop);\n return;\n }\n lastFrameTime = t;\n\n if (pendingMouse.current) {\n mousePos.current = pendingMouse.current;\n pendingMouse.current = null;\n const mouseUniform = program.uniforms.uMouse.value as Float32Array;\n mouseUniform[0] = mousePos.current.x;\n mouseUniform[1] = mousePos.current.y;\n }\n\n let timeValue = (t - t0) * 0.001;\n if (direction === \"pingpong\") {\n const pingpongDuration = 10;\n const segmentTime = timeValue % pingpongDuration;\n const isForward = Math.floor(timeValue / pingpongDuration) % 2 === 0;\n const u = segmentTime / pingpongDuration;\n const smooth = u * u * (3 - 2 * u);\n const pingpongTime = isForward\n ? smooth * pingpongDuration\n : (1 - smooth) * pingpongDuration;\n (program.uniforms.uDirection as any).value = 1.0;\n (program.uniforms.iTime as any).value = pingpongTime;\n } else {\n (program.uniforms.iTime as any).value = timeValue;\n }\n renderer.render({ scene: mesh });\n raf = requestAnimationFrame(loop);\n };\n\n const handleContextLost = (e: Event) => {\n e.preventDefault();\n contextLost = true;\n cancelAnimationFrame(raf);\n };\n const handleContextRestored = () => {\n contextLost = false;\n if (isVisible && tabVisible && !prefersReducedMotion) {\n cancelAnimationFrame(raf);\n raf = requestAnimationFrame(loop);\n }\n };\n canvas.addEventListener(\"webglcontextlost\", handleContextLost);\n canvas.addEventListener(\"webglcontextrestored\", handleContextRestored);\n\n const io = new IntersectionObserver(\n ([entry]) => {\n const wasVisible = isVisible;\n isVisible = entry.isIntersecting;\n if (\n isVisible &&\n !wasVisible &&\n !contextLost &&\n tabVisible &&\n !prefersReducedMotion\n ) {\n cancelAnimationFrame(raf);\n raf = requestAnimationFrame(loop);\n }\n },\n { threshold: 0 },\n );\n io.observe(containerEl);\n\n const handleVisibilityChange = () => {\n tabVisible = document.visibilityState !== \"hidden\";\n if (tabVisible && isVisible && !contextLost && !prefersReducedMotion) {\n cancelAnimationFrame(raf);\n lastFrameTime = 0;\n raf = requestAnimationFrame(loop);\n } else {\n cancelAnimationFrame(raf);\n }\n };\n document.addEventListener(\"visibilitychange\", handleVisibilityChange);\n\n // Respect prefers-reduced-motion: paint one frame and stop, rather than running a perpetual animation loop for users who've asked not to see motion.\n if (prefersReducedMotion) {\n renderStaticFrame();\n } else {\n raf = requestAnimationFrame(loop);\n }\n\n return () => {\n cancelAnimationFrame(raf);\n ro.disconnect();\n io.disconnect();\n document.removeEventListener(\"visibilitychange\", handleVisibilityChange);\n canvas.removeEventListener(\"webglcontextlost\", handleContextLost);\n canvas.removeEventListener(\"webglcontextrestored\", handleContextRestored);\n if (mouseInteractive && containerEl) {\n containerEl.removeEventListener(\"mousemove\", handleMouseMove);\n }\n try {\n containerEl?.removeChild(canvas);\n } catch {}\n };\n }, [\n color,\n speed,\n direction,\n scale,\n opacity,\n mouseInteractive,\n renderScale,\n maxDpr,\n targetFps,\n quality,\n ]);\n\n return (\n \n );\n};\n\nexport default Plasma;\n" + "content": "import React, { useEffect, useRef } from \"react\";\nimport { Renderer, Program, Mesh, Triangle } from \"ogl\";\n\ninterface PlasmaProps {\n color?: string;\n speed?: number;\n direction?: \"forward\" | \"reverse\" | \"pingpong\";\n scale?: number;\n opacity?: number;\n mouseInteractive?: boolean;\n /** Internal render resolution multiplier. 1 = full res, 0.5 = quarter the pixels. Default 0.55. */\n renderScale?: number;\n /** Hard cap on devicePixelRatio used for rendering. Default 1.5. */\n maxDpr?: number;\n /** Target frame rate for the animation loop. Default 30. */\n targetFps?: number;\n /** Raymarch step count — lower is cheaper, less detailed. Default 45. */\n quality?: number;\n}\n\nconst hexToRgb = (hex: string): [number, number, number] => {\n const result = /^#?([a-f\\d]{2})([a-f\\d]{2})([a-f\\d]{2})$/i.exec(hex);\n if (!result) return [1, 0.5, 0.2];\n return [\n parseInt(result[1], 16) / 255,\n parseInt(result[2], 16) / 255,\n parseInt(result[3], 16) / 255,\n ];\n};\n\nconst vertex = `#version 300 es\nprecision highp float;\nin vec2 position;\nin vec2 uv;\nout vec2 vUv;\nvoid main() {\n vUv = uv;\n gl_Position = vec4(position, 0.0, 1.0);\n}\n`;\n\nconst buildFragment = (steps: number) => `#version 300 es\nprecision highp float;\nuniform vec2 iResolution;\nuniform float iTime;\nuniform vec3 uCustomColor;\nuniform float uUseCustomColor;\nuniform float uSpeed;\nuniform float uDirection;\nuniform float uScale;\nuniform float uOpacity;\nuniform vec2 uMouse;\nuniform float uMouseInteractive;\nout vec4 fragColor;\n\nvoid mainImage(out vec4 o, vec2 C) {\n vec2 center = iResolution.xy * 0.5;\n C = (C - center) / uScale + center;\n\n vec2 mouseOffset = (uMouse - center) * 0.0002;\n C += mouseOffset * length(C - center) * step(0.5, uMouseInteractive);\n\n float i, d, z, T = iTime * uSpeed * uDirection;\n vec3 O, p, S;\n\n for (vec2 r = iResolution.xy, Q; ++i < ${steps.toFixed(1)}; O += o.w/d*o.xyz) {\n p = z*normalize(vec3(C-.5*r,r.y));\n p.z -= 4.;\n S = p;\n d = p.y-T;\n\n p.x += .4*(1.+p.y)*sin(d + p.x*0.1)*cos(.34*d + p.x*0.05);\n Q = p.xz *= mat2(cos(p.y+vec4(0,11,33,0)-T));\n z+= d = abs(sqrt(length(Q*Q)) - .25*(5.+S.y))/3.+8e-4;\n o = 1.+sin(S.y+p.z*.5+S.z-length(S-p)+vec4(2,1,0,8));\n }\n\n o.xyz = tanh(O/1e4);\n}\n\nbool finite1(float x){ return !(isnan(x) || isinf(x)); }\nvec3 sanitize(vec3 c){\n return vec3(\n finite1(c.r) ? c.r : 0.0,\n finite1(c.g) ? c.g : 0.0,\n finite1(c.b) ? c.b : 0.0\n );\n}\n\nvoid main() {\n vec4 o = vec4(0.0);\n mainImage(o, gl_FragCoord.xy);\n vec3 rgb = sanitize(o.rgb);\n\n float intensity = (rgb.r + rgb.g + rgb.b) / 3.0;\n vec3 customColor = intensity * uCustomColor;\n vec3 finalColor = mix(rgb, customColor, step(0.5, uUseCustomColor));\n\n float alpha = length(rgb) * uOpacity;\n fragColor = vec4(finalColor, alpha);\n}`;\n\nexport const Plasma: React.FC = ({\n color = \"#ffffff\",\n speed = 1,\n direction = \"forward\",\n scale = 1,\n opacity = 1,\n mouseInteractive = true,\n renderScale = 0.55,\n maxDpr = 1.5,\n targetFps = 60,\n quality = 45,\n}) => {\n const containerRef = useRef(null);\n const mousePos = useRef({ x: 0, y: 0 });\n const pendingMouse = useRef<{ x: number; y: number } | null>(null);\n\n useEffect(() => {\n if (!containerRef.current) return;\n const containerEl = containerRef.current;\n\n const prefersReducedMotion =\n typeof window !== \"undefined\" &&\n window.matchMedia?.(\"(prefers-reduced-motion: reduce)\").matches;\n\n const useCustomColor = color ? 1.0 : 0.0;\n const customColorRgb = color ? hexToRgb(color) : [1, 1, 1];\n const directionMultiplier = direction === \"reverse\" ? -1.0 : 1.0;\n\n let renderer: Renderer;\n try {\n renderer = new Renderer({\n webgl: 2,\n alpha: true,\n antialias: false,\n dpr: Math.min(window.devicePixelRatio || 1, maxDpr),\n });\n } catch {\n return;\n }\n const gl = renderer.gl;\n if (!gl) return;\n const canvas = gl.canvas as HTMLCanvasElement;\n canvas.style.display = \"block\";\n canvas.style.width = \"100%\";\n canvas.style.height = \"100%\";\n // Rendering at renderScale internally, CSS stretches it back up.\n containerEl.appendChild(canvas);\n\n const geometry = new Triangle(gl);\n\n const program = new Program(gl, {\n vertex: vertex,\n fragment: buildFragment(quality),\n uniforms: {\n iTime: { value: 0 },\n iResolution: { value: new Float32Array([1, 1]) },\n uCustomColor: { value: new Float32Array(customColorRgb) },\n uUseCustomColor: { value: useCustomColor },\n uSpeed: { value: speed * 0.4 },\n uDirection: { value: directionMultiplier },\n uScale: { value: scale },\n uOpacity: { value: opacity },\n uMouse: { value: new Float32Array([0, 0]) },\n uMouseInteractive: { value: mouseInteractive ? 1.0 : 0.0 },\n },\n });\n\n const mesh = new Mesh(gl, { geometry, program });\n\n const handleMouseMove = (e: MouseEvent) => {\n if (!mouseInteractive) return;\n const rect = containerEl.getBoundingClientRect();\n // Store the latest position but don't touch GL state here, the rAF loop picks it up once per rendered frame instead of once per mouse event.\n pendingMouse.current = {\n x: e.clientX - rect.left,\n y: e.clientY - rect.top,\n };\n };\n\n if (mouseInteractive) {\n containerEl.addEventListener(\"mousemove\", handleMouseMove, {\n passive: true,\n });\n }\n\n let resizePending = false;\n const setSize = () => {\n const rect = containerEl.getBoundingClientRect();\n const width = Math.max(1, Math.floor(rect.width * renderScale));\n const height = Math.max(1, Math.floor(rect.height * renderScale));\n renderer.setSize(width, height);\n\n // renderer.setSize also sets canvas.style.width/height to match the (scaled-down) drawing buffer - override that so the canvas still stretches to fill its container via CSS while the buffer stays small.\n canvas.style.width = \"100%\";\n canvas.style.height = \"100%\";\n\n const res = program.uniforms.iResolution.value as Float32Array;\n res[0] = gl.drawingBufferWidth;\n res[1] = gl.drawingBufferHeight;\n };\n\n const ro = new ResizeObserver(() => {\n // Batch rapid resize events (ex. during a window drag) into one setSize per frame.\n if (resizePending) return;\n resizePending = true;\n requestAnimationFrame(() => {\n resizePending = false;\n setSize();\n });\n });\n ro.observe(containerEl);\n setSize();\n\n let raf = 0;\n let contextLost = false;\n let isVisible = true;\n let tabVisible = document.visibilityState !== \"hidden\";\n const t0 = performance.now();\n const frameInterval = 1000 / targetFps;\n let lastFrameTime = 0;\n\n const renderStaticFrame = () => {\n (program.uniforms.iTime as any).value = 0;\n renderer.render({ scene: mesh });\n };\n\n const loop = (t: number) => {\n if (contextLost || !isVisible || !tabVisible) return;\n\n if (t - lastFrameTime < frameInterval) {\n raf = requestAnimationFrame(loop);\n return;\n }\n lastFrameTime = t;\n\n if (pendingMouse.current) {\n mousePos.current = pendingMouse.current;\n pendingMouse.current = null;\n const mouseUniform = program.uniforms.uMouse.value as Float32Array;\n mouseUniform[0] = mousePos.current.x;\n mouseUniform[1] = mousePos.current.y;\n }\n\n let timeValue = (t - t0) * 0.001;\n if (direction === \"pingpong\") {\n const pingpongDuration = 10;\n const segmentTime = timeValue % pingpongDuration;\n const isForward = Math.floor(timeValue / pingpongDuration) % 2 === 0;\n const u = segmentTime / pingpongDuration;\n const smooth = u * u * (3 - 2 * u);\n const pingpongTime = isForward\n ? smooth * pingpongDuration\n : (1 - smooth) * pingpongDuration;\n (program.uniforms.uDirection as any).value = 1.0;\n (program.uniforms.iTime as any).value = pingpongTime;\n } else {\n (program.uniforms.iTime as any).value = timeValue;\n }\n renderer.render({ scene: mesh });\n raf = requestAnimationFrame(loop);\n };\n\n const handleContextLost = (e: Event) => {\n e.preventDefault();\n contextLost = true;\n cancelAnimationFrame(raf);\n };\n const handleContextRestored = () => {\n contextLost = false;\n if (isVisible && tabVisible && !prefersReducedMotion) {\n cancelAnimationFrame(raf);\n raf = requestAnimationFrame(loop);\n }\n };\n canvas.addEventListener(\"webglcontextlost\", handleContextLost);\n canvas.addEventListener(\"webglcontextrestored\", handleContextRestored);\n\n const io = new IntersectionObserver(\n ([entry]) => {\n const wasVisible = isVisible;\n isVisible = entry.isIntersecting;\n if (\n isVisible &&\n !wasVisible &&\n !contextLost &&\n tabVisible &&\n !prefersReducedMotion\n ) {\n cancelAnimationFrame(raf);\n raf = requestAnimationFrame(loop);\n }\n },\n { threshold: 0 },\n );\n io.observe(containerEl);\n\n const handleVisibilityChange = () => {\n tabVisible = document.visibilityState !== \"hidden\";\n if (tabVisible && isVisible && !contextLost && !prefersReducedMotion) {\n cancelAnimationFrame(raf);\n lastFrameTime = 0;\n raf = requestAnimationFrame(loop);\n } else {\n cancelAnimationFrame(raf);\n }\n };\n document.addEventListener(\"visibilitychange\", handleVisibilityChange);\n\n // Respect prefers-reduced-motion: paint one frame and stop, rather than running a perpetual animation loop for users who've asked not to see motion.\n if (prefersReducedMotion) {\n renderStaticFrame();\n } else {\n raf = requestAnimationFrame(loop);\n }\n\n return () => {\n cancelAnimationFrame(raf);\n ro.disconnect();\n io.disconnect();\n document.removeEventListener(\"visibilitychange\", handleVisibilityChange);\n canvas.removeEventListener(\"webglcontextlost\", handleContextLost);\n canvas.removeEventListener(\"webglcontextrestored\", handleContextRestored);\n if (mouseInteractive && containerEl) {\n containerEl.removeEventListener(\"mousemove\", handleMouseMove);\n }\n try {\n containerEl?.removeChild(canvas);\n } catch {}\n };\n }, [\n color,\n speed,\n direction,\n scale,\n opacity,\n mouseInteractive,\n renderScale,\n maxDpr,\n targetFps,\n quality,\n ]);\n\n return (\n \n );\n};\n\nexport default Plasma;\n" } ], "registryDependencies": [], diff --git a/src/content/Backgrounds/Plasma/Plasma.jsx b/src/content/Backgrounds/Plasma/Plasma.jsx index 88296a11e..f5cbdddfb 100644 --- a/src/content/Backgrounds/Plasma/Plasma.jsx +++ b/src/content/Backgrounds/Plasma/Plasma.jsx @@ -89,7 +89,7 @@ export const Plasma = ({ mouseInteractive = true, renderScale = 0.55, maxDpr = 1.5, - targetFps = 30, + targetFps = 60, quality = 45, }) => { const containerRef = useRef(null); diff --git a/src/demo/Backgrounds/PlasmaDemo.jsx b/src/demo/Backgrounds/PlasmaDemo.jsx index 352c33262..ac954eb95 100644 --- a/src/demo/Backgrounds/PlasmaDemo.jsx +++ b/src/demo/Backgrounds/PlasmaDemo.jsx @@ -29,7 +29,7 @@ const DEFAULT_PROPS = { mouseInteractive: false, renderScale: 0.55, maxDpr: 1.5, - targetFps: 30, + targetFps: 60, quality: 45 }; @@ -136,7 +136,7 @@ const PlasmaDemo = () => { mouseInteractive: false, renderScale: 0.55, maxDpr: 1.5, - targetFps: 30, + targetFps: 60, quality: 45 }} /> diff --git a/src/tailwind/Backgrounds/Plasma/Plasma.jsx b/src/tailwind/Backgrounds/Plasma/Plasma.jsx index 4bf3016b1..11771868b 100644 --- a/src/tailwind/Backgrounds/Plasma/Plasma.jsx +++ b/src/tailwind/Backgrounds/Plasma/Plasma.jsx @@ -88,7 +88,7 @@ export const Plasma = ({ mouseInteractive = true, renderScale = 0.55, maxDpr = 1.5, - targetFps = 30, + targetFps = 60, quality = 45, }) => { const containerRef = useRef(null); diff --git a/src/tools/background-studio/backgrounds/index.js b/src/tools/background-studio/backgrounds/index.js index 052b64788..b043f37ae 100644 --- a/src/tools/background-studio/backgrounds/index.js +++ b/src/tools/background-studio/backgrounds/index.js @@ -550,7 +550,7 @@ export const BACKGROUNDS = [ { name: 'mouseInteractive', type: 'boolean', default: true, label: 'Mouse Interactive' }, { name: 'quality', type: 'number', default: 45, min: 10, max: 80, step: 5, label: 'Quality' }, { name: 'renderScale', type: 'number', default: 0.55, min: 0.2, max: 1.0, step: 0.05, label: 'Render Scale' }, - { name: 'targetFps', type: 'number', default: 30, min: 10, max: 60, step: 5, label: 'Target FPS' }, + { name: 'targetFps', type: 'number', default: 60, min: 10, max: 60, step: 5, label: 'Target FPS' }, { name: 'maxDpr', type: 'number', default: 1.5, min: 0.5, max: 3.0, step: 0.5, label: 'Max DPR' } ] }, diff --git a/src/ts-default/Backgrounds/Plasma/Plasma.tsx b/src/ts-default/Backgrounds/Plasma/Plasma.tsx index 8b25783ef..44dadfc74 100644 --- a/src/ts-default/Backgrounds/Plasma/Plasma.tsx +++ b/src/ts-default/Backgrounds/Plasma/Plasma.tsx @@ -106,7 +106,7 @@ export const Plasma: React.FC = ({ mouseInteractive = true, renderScale = 0.55, maxDpr = 1.5, - targetFps = 30, + targetFps = 60, quality = 45, }) => { const containerRef = useRef(null); diff --git a/src/ts-tailwind/Backgrounds/Plasma/Plasma.tsx b/src/ts-tailwind/Backgrounds/Plasma/Plasma.tsx index 45b60b92a..6971a8da1 100644 --- a/src/ts-tailwind/Backgrounds/Plasma/Plasma.tsx +++ b/src/ts-tailwind/Backgrounds/Plasma/Plasma.tsx @@ -109,7 +109,7 @@ export const Plasma: React.FC = ({ mouseInteractive = true, renderScale = 0.55, maxDpr = 1.5, - targetFps = 30, + targetFps = 60, quality = 45, }) => { const containerRef = useRef(null); From 61d7583f44f07bb175494cb5a0a4f95a2547e31b Mon Sep 17 00:00:00 2001 From: Elias Gulam Date: Thu, 23 Jul 2026 19:01:33 +0200 Subject: [PATCH 4/4] fix: Changed "Quality" prop to "Iterations". --- public/r/CursorGrid-JS-CSS.json | 2 +- public/r/CursorGrid-JS-TW.json | 2 +- public/r/CursorGrid-TS-CSS.json | 2 +- public/r/CursorGrid-TS-TW.json | 2 +- public/r/DarkVeil-JS-CSS.json | 2 +- public/r/DarkVeil-TS-CSS.json | 2 +- public/r/DarkVeil-TS-TW.json | 2 +- public/r/ElectricBorder-JS-TW.json | 2 +- public/r/ElectricBorder-TS-TW.json | 2 +- public/r/FaultyTerminal-JS-CSS.json | 2 +- public/r/FaultyTerminal-JS-TW.json | 2 +- public/r/FaultyTerminal-TS-CSS.json | 2 +- public/r/FaultyTerminal-TS-TW.json | 2 +- public/r/Folder-JS-CSS.json | 2 +- public/r/Folder-JS-TW.json | 2 +- public/r/Folder-TS-CSS.json | 2 +- public/r/Folder-TS-TW.json | 2 +- public/r/LaserFlow-JS-CSS.json | 2 +- public/r/LaserFlow-JS-TW.json | 2 +- public/r/LaserFlow-TS-CSS.json | 2 +- public/r/LaserFlow-TS-TW.json | 2 +- public/r/Particles-JS-CSS.json | 2 +- public/r/Particles-JS-TW.json | 2 +- public/r/Particles-TS-CSS.json | 2 +- public/r/Particles-TS-TW.json | 2 +- public/r/Plasma-JS-CSS.json | 2 +- public/r/Plasma-JS-TW.json | 2 +- public/r/Plasma-TS-CSS.json | 2 +- public/r/Plasma-TS-TW.json | 2 +- public/r/PrismaticBurst-JS-CSS.json | 2 +- public/r/PrismaticBurst-JS-TW.json | 2 +- public/r/PrismaticBurst-TS-CSS.json | 2 +- public/r/PrismaticBurst-TS-TW.json | 2 +- public/sitemap.xml | 292 +++++++++--------- src/content/Backgrounds/Plasma/Plasma.jsx | 25 +- src/demo/Backgrounds/PlasmaDemo.jsx | 20 +- src/tailwind/Backgrounds/Plasma/Plasma.jsx | 50 +-- .../background-studio/backgrounds/index.js | 2 +- src/ts-default/Backgrounds/Plasma/Plasma.tsx | 27 +- src/ts-tailwind/Backgrounds/Plasma/Plasma.tsx | 25 +- 40 files changed, 273 insertions(+), 234 deletions(-) diff --git a/public/r/CursorGrid-JS-CSS.json b/public/r/CursorGrid-JS-CSS.json index 7c1792039..86aaaf5e6 100644 --- a/public/r/CursorGrid-JS-CSS.json +++ b/public/r/CursorGrid-JS-CSS.json @@ -13,7 +13,7 @@ { "type": "registry:component", "path": "CursorGrid/CursorGrid.jsx", - "content": "import { useRef, useEffect } from 'react';\nimport './CursorGrid.css';\n\nconst FALLOFF_CURVES = {\n linear: t => t,\n smooth: t => t * t * (3 - 2 * t),\n sharp: t => t * t * t\n};\n\nconst hexToRgb = hex => {\n const h = hex.replace('#', '');\n const v = h.length === 3 ? h.split('').map(c => c + c).join('') : h;\n const num = parseInt(v, 16);\n return [(num >> 16) & 255, (num >> 8) & 255, num & 255];\n};\n\nconst CursorGrid = ({\n cellSize = 70,\n color = '#D946EF',\n radius = 140,\n falloff = 'smooth',\n holdTime = 400,\n fadeDuration = 800,\n lineWidth = 1.2,\n maxOpacity = 1,\n fillOpacity = 0,\n gridOpacity = 0,\n cellRadius = 0,\n clickPulse = true,\n pulseSpeed = 600,\n className = ''\n}) => {\n const containerRef = useRef(null);\n const canvasRef = useRef(null);\n const propsRef = useRef({});\n const wakeRef = useRef(null);\n\n propsRef.current = {\n cellSize,\n color,\n radius,\n falloff,\n holdTime,\n fadeDuration,\n lineWidth,\n maxOpacity,\n fillOpacity,\n gridOpacity,\n cellRadius,\n clickPulse,\n pulseSpeed\n };\n\n useEffect(() => {\n const container = containerRef.current;\n const canvas = canvasRef.current;\n if (!container || !canvas) return;\n\n const ctx = canvas.getContext('2d');\n const dpr = Math.min(window.devicePixelRatio || 1, 2);\n\n // Grid state: one alpha + timestamp pair per cell, indexed row-major.\n let cols = 0;\n let rows = 0;\n let offX = 0;\n let offY = 0;\n let alphas = new Float32Array(0);\n let touched = new Float64Array(0);\n let w = 0;\n let h = 0;\n const pulses = [];\n let raf = 0;\n let running = false;\n let lastFrame = 0;\n\n const rebuild = () => {\n const p = propsRef.current;\n w = container.offsetWidth;\n h = container.offsetHeight;\n canvas.width = Math.max(1, Math.round(w * dpr));\n canvas.height = Math.max(1, Math.round(h * dpr));\n canvas.style.width = `${w}px`;\n canvas.style.height = `${h}px`;\n ctx.setTransform(dpr, 0, 0, dpr, 0, 0);\n cols = Math.ceil(w / p.cellSize) + 1;\n rows = Math.ceil(h / p.cellSize) + 1;\n // Center the lattice so edge cells crop evenly on both sides\n offX = (w - cols * p.cellSize) / 2;\n offY = (h - rows * p.cellSize) / 2;\n alphas = new Float32Array(cols * rows);\n touched = new Float64Array(cols * rows);\n };\n\n const cellCenter = i => {\n const p = propsRef.current;\n const cx = offX + (i % cols) * p.cellSize + p.cellSize / 2;\n const cy = offY + Math.floor(i / cols) * p.cellSize + p.cellSize / 2;\n return [cx, cy];\n };\n\n // Light up every cell whose center falls inside the radius, with the\n // configured falloff curve mapping distance to brightness.\n const energize = (x, y, boost) => {\n const p = propsRef.current;\n const r = Math.max(p.radius, 1);\n const ease = FALLOFF_CURVES[p.falloff] ?? FALLOFF_CURVES.linear;\n const now = performance.now();\n const minCol = Math.max(0, Math.floor((x - r - offX) / p.cellSize));\n const maxCol = Math.min(cols - 1, Math.floor((x + r - offX) / p.cellSize));\n const minRow = Math.max(0, Math.floor((y - r - offY) / p.cellSize));\n const maxRow = Math.min(rows - 1, Math.floor((y + r - offY) / p.cellSize));\n for (let cRow = minRow; cRow <= maxRow; cRow++) {\n for (let cCol = minCol; cCol <= maxCol; cCol++) {\n const i = cRow * cols + cCol;\n const [cx, cy] = cellCenter(i);\n const dist = Math.hypot(cx - x, cy - y);\n if (dist > r) continue;\n const level = ease(1 - dist / r) * p.maxOpacity * (boost ?? 1);\n if (level > alphas[i]) {\n alphas[i] = level;\n touched[i] = now;\n } else if (level > 0) {\n touched[i] = now;\n }\n }\n }\n };\n\n const draw = now => {\n const p = propsRef.current;\n const dt = Math.min(now - lastFrame, 50);\n lastFrame = now;\n ctx.clearRect(0, 0, w, h);\n const [cr, cg, cb] = hexToRgb(p.color);\n\n // Optional faint static lattice\n if (p.gridOpacity > 0) {\n ctx.strokeStyle = `rgba(${cr}, ${cg}, ${cb}, ${p.gridOpacity})`;\n ctx.lineWidth = 1;\n ctx.beginPath();\n for (let cCol = 0; cCol <= cols; cCol++) {\n const x = Math.round(offX + cCol * p.cellSize) + 0.5;\n ctx.moveTo(x, 0);\n ctx.lineTo(x, h);\n }\n for (let cRow = 0; cRow <= rows; cRow++) {\n const y = Math.round(offY + cRow * p.cellSize) + 0.5;\n ctx.moveTo(0, y);\n ctx.lineTo(w, y);\n }\n ctx.stroke();\n }\n\n // Expanding click pulses hand their energy to cells as they pass\n for (let pi = pulses.length - 1; pi >= 0; pi--) {\n const pulse = pulses[pi];\n const age = (now - pulse.t0) / 1000;\n const ringR = age * p.pulseSpeed;\n if (ringR > Math.hypot(w, h)) {\n pulses.splice(pi, 1);\n continue;\n }\n const band = p.cellSize;\n const minCol = Math.max(0, Math.floor((pulse.x - ringR - band - offX) / p.cellSize));\n const maxCol = Math.min(cols - 1, Math.floor((pulse.x + ringR + band - offX) / p.cellSize));\n const minRow = Math.max(0, Math.floor((pulse.y - ringR - band - offY) / p.cellSize));\n const maxRow = Math.min(rows - 1, Math.floor((pulse.y + ringR + band - offY) / p.cellSize));\n for (let cRow = minRow; cRow <= maxRow; cRow++) {\n for (let cCol = minCol; cCol <= maxCol; cCol++) {\n const i = cRow * cols + cCol;\n const [cx, cy] = cellCenter(i);\n const dist = Math.hypot(cx - pulse.x, cy - pulse.y);\n if (Math.abs(dist - ringR) < band / 2 && p.maxOpacity > alphas[i]) {\n alphas[i] = p.maxOpacity;\n touched[i] = now;\n }\n }\n }\n }\n\n let anyVisible = pulses.length > 0;\n const fadeStep = dt / Math.max(p.fadeDuration, 16);\n const half = p.cellSize / 2;\n\n for (let i = 0; i < alphas.length; i++) {\n let a = alphas[i];\n if (a <= 0) continue;\n if (now - touched[i] > p.holdTime) {\n a = Math.max(0, a - fadeStep);\n alphas[i] = a;\n if (a <= 0) continue;\n }\n anyVisible = true;\n\n const [cx, cy] = cellCenter(i);\n const gradient = ctx.createRadialGradient(cx, cy, half * 0.1, cx, cy, p.cellSize);\n gradient.addColorStop(0, `rgba(${cr}, ${cg}, ${cb}, ${a})`);\n gradient.addColorStop(1, `rgba(${cr}, ${cg}, ${cb}, 0)`);\n\n const x = cx - half + 0.5;\n const y = cy - half + 0.5;\n const s = p.cellSize - 1;\n\n ctx.beginPath();\n if (p.cellRadius > 0) {\n ctx.roundRect(x, y, s, s, p.cellRadius);\n } else {\n ctx.rect(x, y, s, s);\n }\n if (p.fillOpacity > 0) {\n ctx.fillStyle = `rgba(${cr}, ${cg}, ${cb}, ${a * p.fillOpacity})`;\n ctx.fill();\n }\n ctx.strokeStyle = gradient;\n ctx.lineWidth = p.lineWidth;\n ctx.stroke();\n }\n\n if (anyVisible) {\n raf = requestAnimationFrame(draw);\n } else {\n running = false;\n if (propsRef.current.gridOpacity <= 0) ctx.clearRect(0, 0, w, h);\n }\n };\n\n const wake = () => {\n if (running) return;\n running = true;\n lastFrame = performance.now();\n raf = requestAnimationFrame(draw);\n };\n wakeRef.current = wake;\n\n const toLocal = e => {\n const rect = canvas.getBoundingClientRect();\n return [e.clientX - rect.left, e.clientY - rect.top];\n };\n\n const onPointerMove = e => {\n const [x, y] = toLocal(e);\n energize(x, y);\n wake();\n };\n\n const onPointerDown = e => {\n if (!propsRef.current.clickPulse) return;\n const [x, y] = toLocal(e);\n pulses.push({ x, y, t0: performance.now() });\n wake();\n };\n\n const ro = new ResizeObserver(() => {\n rebuild();\n wake();\n });\n ro.observe(container);\n rebuild();\n wake();\n\n container.addEventListener('pointermove', onPointerMove);\n container.addEventListener('pointerdown', onPointerDown);\n\n return () => {\n cancelAnimationFrame(raf);\n ro.disconnect();\n container.removeEventListener('pointermove', onPointerMove);\n container.removeEventListener('pointerdown', onPointerDown);\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [cellSize]);\n\n // Repaint static layers when visual props change while idle\n useEffect(() => {\n wakeRef.current?.();\n }, [gridOpacity, color, lineWidth, maxOpacity, fillOpacity, cellRadius]);\n\n return (\n
\n \n
\n );\n};\n\nexport default CursorGrid;\n" + "content": "import { useRef, useEffect } from 'react';\nimport './CursorGrid.css';\n\nconst FALLOFF_CURVES = {\n linear: t => t,\n smooth: t => t * t * (3 - 2 * t),\n sharp: t => t * t * t\n};\n\nconst hexToRgb = hex => {\n const h = hex.replace('#', '');\n const v = h.length === 3 ? h.split('').map(c => c + c).join('') : h;\n const num = parseInt(v.slice(0, 6), 16);\n return [(num >> 16) & 255, (num >> 8) & 255, num & 255];\n};\n\nconst CursorGrid = ({\n cellSize = 70,\n color = '#D946EF',\n radius = 140,\n falloff = 'smooth',\n holdTime = 400,\n fadeDuration = 800,\n lineWidth = 1.2,\n maxOpacity = 1,\n fillOpacity = 0,\n gridOpacity = 0,\n cellRadius = 0,\n clickPulse = true,\n pulseSpeed = 600,\n className = ''\n}) => {\n const containerRef = useRef(null);\n const canvasRef = useRef(null);\n const propsRef = useRef({});\n const wakeRef = useRef(null);\n\n propsRef.current = {\n cellSize,\n color,\n radius,\n falloff,\n holdTime,\n fadeDuration,\n lineWidth,\n maxOpacity,\n fillOpacity,\n gridOpacity,\n cellRadius,\n clickPulse,\n pulseSpeed\n };\n\n useEffect(() => {\n const container = containerRef.current;\n const canvas = canvasRef.current;\n if (!container || !canvas) return;\n\n const ctx = canvas.getContext('2d');\n const dpr = Math.min(window.devicePixelRatio || 1, 2);\n\n // Grid state: one alpha + timestamp pair per cell, indexed row-major.\n let cols = 0;\n let rows = 0;\n let offX = 0;\n let offY = 0;\n let alphas = new Float32Array(0);\n let touched = new Float64Array(0);\n let w = 0;\n let h = 0;\n const pulses = [];\n let raf = 0;\n let running = false;\n let lastFrame = 0;\n\n const rebuild = () => {\n const p = propsRef.current;\n w = container.offsetWidth;\n h = container.offsetHeight;\n canvas.width = Math.max(1, Math.round(w * dpr));\n canvas.height = Math.max(1, Math.round(h * dpr));\n canvas.style.width = `${w}px`;\n canvas.style.height = `${h}px`;\n ctx.setTransform(dpr, 0, 0, dpr, 0, 0);\n cols = Math.ceil(w / p.cellSize) + 1;\n rows = Math.ceil(h / p.cellSize) + 1;\n // Center the lattice so edge cells crop evenly on both sides\n offX = (w - cols * p.cellSize) / 2;\n offY = (h - rows * p.cellSize) / 2;\n alphas = new Float32Array(cols * rows);\n touched = new Float64Array(cols * rows);\n };\n\n const cellCenter = i => {\n const p = propsRef.current;\n const cx = offX + (i % cols) * p.cellSize + p.cellSize / 2;\n const cy = offY + Math.floor(i / cols) * p.cellSize + p.cellSize / 2;\n return [cx, cy];\n };\n\n // Light up every cell whose center falls inside the radius, with the\n // configured falloff curve mapping distance to brightness.\n const energize = (x, y, boost) => {\n const p = propsRef.current;\n const r = Math.max(p.radius, 1);\n const ease = FALLOFF_CURVES[p.falloff] ?? FALLOFF_CURVES.linear;\n const now = performance.now();\n const minCol = Math.max(0, Math.floor((x - r - offX) / p.cellSize));\n const maxCol = Math.min(cols - 1, Math.floor((x + r - offX) / p.cellSize));\n const minRow = Math.max(0, Math.floor((y - r - offY) / p.cellSize));\n const maxRow = Math.min(rows - 1, Math.floor((y + r - offY) / p.cellSize));\n for (let cRow = minRow; cRow <= maxRow; cRow++) {\n for (let cCol = minCol; cCol <= maxCol; cCol++) {\n const i = cRow * cols + cCol;\n const [cx, cy] = cellCenter(i);\n const dist = Math.hypot(cx - x, cy - y);\n if (dist > r) continue;\n const level = ease(1 - dist / r) * p.maxOpacity * (boost ?? 1);\n if (level > alphas[i]) {\n alphas[i] = level;\n touched[i] = now;\n } else if (level > 0) {\n touched[i] = now;\n }\n }\n }\n };\n\n const draw = now => {\n const p = propsRef.current;\n const dt = Math.min(now - lastFrame, 50);\n lastFrame = now;\n ctx.clearRect(0, 0, w, h);\n const [cr, cg, cb] = hexToRgb(p.color);\n\n // Optional faint static lattice\n if (p.gridOpacity > 0) {\n ctx.strokeStyle = `rgba(${cr}, ${cg}, ${cb}, ${p.gridOpacity})`;\n ctx.lineWidth = 1;\n ctx.beginPath();\n for (let cCol = 0; cCol <= cols; cCol++) {\n const x = Math.round(offX + cCol * p.cellSize) + 0.5;\n ctx.moveTo(x, 0);\n ctx.lineTo(x, h);\n }\n for (let cRow = 0; cRow <= rows; cRow++) {\n const y = Math.round(offY + cRow * p.cellSize) + 0.5;\n ctx.moveTo(0, y);\n ctx.lineTo(w, y);\n }\n ctx.stroke();\n }\n\n // Expanding click pulses hand their energy to cells as they pass\n for (let pi = pulses.length - 1; pi >= 0; pi--) {\n const pulse = pulses[pi];\n const age = (now - pulse.t0) / 1000;\n const ringR = age * p.pulseSpeed;\n if (ringR > Math.hypot(w, h)) {\n pulses.splice(pi, 1);\n continue;\n }\n const band = p.cellSize;\n const minCol = Math.max(0, Math.floor((pulse.x - ringR - band - offX) / p.cellSize));\n const maxCol = Math.min(cols - 1, Math.floor((pulse.x + ringR + band - offX) / p.cellSize));\n const minRow = Math.max(0, Math.floor((pulse.y - ringR - band - offY) / p.cellSize));\n const maxRow = Math.min(rows - 1, Math.floor((pulse.y + ringR + band - offY) / p.cellSize));\n for (let cRow = minRow; cRow <= maxRow; cRow++) {\n for (let cCol = minCol; cCol <= maxCol; cCol++) {\n const i = cRow * cols + cCol;\n const [cx, cy] = cellCenter(i);\n const dist = Math.hypot(cx - pulse.x, cy - pulse.y);\n if (Math.abs(dist - ringR) < band / 2 && p.maxOpacity > alphas[i]) {\n alphas[i] = p.maxOpacity;\n touched[i] = now;\n }\n }\n }\n }\n\n let anyVisible = pulses.length > 0;\n const fadeStep = dt / Math.max(p.fadeDuration, 16);\n const half = p.cellSize / 2;\n\n for (let i = 0; i < alphas.length; i++) {\n let a = alphas[i];\n if (a <= 0) continue;\n if (now - touched[i] > p.holdTime) {\n a = Math.max(0, a - fadeStep);\n alphas[i] = a;\n if (a <= 0) continue;\n }\n anyVisible = true;\n\n const [cx, cy] = cellCenter(i);\n const gradient = ctx.createRadialGradient(cx, cy, half * 0.1, cx, cy, p.cellSize);\n gradient.addColorStop(0, `rgba(${cr}, ${cg}, ${cb}, ${a})`);\n gradient.addColorStop(1, `rgba(${cr}, ${cg}, ${cb}, 0)`);\n\n const x = cx - half + 0.5;\n const y = cy - half + 0.5;\n const s = p.cellSize - 1;\n\n ctx.beginPath();\n if (p.cellRadius > 0) {\n ctx.roundRect(x, y, s, s, p.cellRadius);\n } else {\n ctx.rect(x, y, s, s);\n }\n if (p.fillOpacity > 0) {\n ctx.fillStyle = `rgba(${cr}, ${cg}, ${cb}, ${a * p.fillOpacity})`;\n ctx.fill();\n }\n ctx.strokeStyle = gradient;\n ctx.lineWidth = p.lineWidth;\n ctx.stroke();\n }\n\n if (anyVisible) {\n raf = requestAnimationFrame(draw);\n } else {\n running = false;\n if (propsRef.current.gridOpacity <= 0) ctx.clearRect(0, 0, w, h);\n }\n };\n\n const wake = () => {\n if (running) return;\n running = true;\n lastFrame = performance.now();\n raf = requestAnimationFrame(draw);\n };\n wakeRef.current = wake;\n\n const toLocal = e => {\n const rect = canvas.getBoundingClientRect();\n return [e.clientX - rect.left, e.clientY - rect.top];\n };\n\n const onPointerMove = e => {\n const [x, y] = toLocal(e);\n energize(x, y);\n wake();\n };\n\n const onPointerDown = e => {\n if (!propsRef.current.clickPulse) return;\n const [x, y] = toLocal(e);\n pulses.push({ x, y, t0: performance.now() });\n wake();\n };\n\n const ro = new ResizeObserver(() => {\n rebuild();\n wake();\n });\n ro.observe(container);\n rebuild();\n wake();\n\n container.addEventListener('pointermove', onPointerMove);\n container.addEventListener('pointerdown', onPointerDown);\n\n return () => {\n cancelAnimationFrame(raf);\n ro.disconnect();\n container.removeEventListener('pointermove', onPointerMove);\n container.removeEventListener('pointerdown', onPointerDown);\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [cellSize]);\n\n // Repaint static layers when visual props change while idle\n useEffect(() => {\n wakeRef.current?.();\n }, [gridOpacity, color, lineWidth, maxOpacity, fillOpacity, cellRadius]);\n\n return (\n
\n \n
\n );\n};\n\nexport default CursorGrid;\n" } ], "registryDependencies": [], diff --git a/public/r/CursorGrid-JS-TW.json b/public/r/CursorGrid-JS-TW.json index 3558eeb97..ca77589df 100644 --- a/public/r/CursorGrid-JS-TW.json +++ b/public/r/CursorGrid-JS-TW.json @@ -8,7 +8,7 @@ { "type": "registry:component", "path": "CursorGrid/CursorGrid.jsx", - "content": "import { useRef, useEffect } from 'react';\n\nconst FALLOFF_CURVES = {\n linear: t => t,\n smooth: t => t * t * (3 - 2 * t),\n sharp: t => t * t * t\n};\n\nconst hexToRgb = hex => {\n const h = hex.replace('#', '');\n const v = h.length === 3 ? h.split('').map(c => c + c).join('') : h;\n const num = parseInt(v, 16);\n return [(num >> 16) & 255, (num >> 8) & 255, num & 255];\n};\n\nconst CursorGrid = ({\n cellSize = 70,\n color = '#D946EF',\n radius = 140,\n falloff = 'smooth',\n holdTime = 400,\n fadeDuration = 800,\n lineWidth = 1.2,\n maxOpacity = 1,\n fillOpacity = 0,\n gridOpacity = 0,\n cellRadius = 0,\n clickPulse = true,\n pulseSpeed = 600,\n className = ''\n}) => {\n const containerRef = useRef(null);\n const canvasRef = useRef(null);\n const propsRef = useRef({});\n const wakeRef = useRef(null);\n\n propsRef.current = {\n cellSize,\n color,\n radius,\n falloff,\n holdTime,\n fadeDuration,\n lineWidth,\n maxOpacity,\n fillOpacity,\n gridOpacity,\n cellRadius,\n clickPulse,\n pulseSpeed\n };\n\n useEffect(() => {\n const container = containerRef.current;\n const canvas = canvasRef.current;\n if (!container || !canvas) return;\n\n const ctx = canvas.getContext('2d');\n const dpr = Math.min(window.devicePixelRatio || 1, 2);\n\n // Grid state: one alpha + timestamp pair per cell, indexed row-major.\n let cols = 0;\n let rows = 0;\n let offX = 0;\n let offY = 0;\n let alphas = new Float32Array(0);\n let touched = new Float64Array(0);\n let w = 0;\n let h = 0;\n const pulses = [];\n let raf = 0;\n let running = false;\n let lastFrame = 0;\n\n const rebuild = () => {\n const p = propsRef.current;\n w = container.offsetWidth;\n h = container.offsetHeight;\n canvas.width = Math.max(1, Math.round(w * dpr));\n canvas.height = Math.max(1, Math.round(h * dpr));\n canvas.style.width = `${w}px`;\n canvas.style.height = `${h}px`;\n ctx.setTransform(dpr, 0, 0, dpr, 0, 0);\n cols = Math.ceil(w / p.cellSize) + 1;\n rows = Math.ceil(h / p.cellSize) + 1;\n // Center the lattice so edge cells crop evenly on both sides\n offX = (w - cols * p.cellSize) / 2;\n offY = (h - rows * p.cellSize) / 2;\n alphas = new Float32Array(cols * rows);\n touched = new Float64Array(cols * rows);\n };\n\n const cellCenter = i => {\n const p = propsRef.current;\n const cx = offX + (i % cols) * p.cellSize + p.cellSize / 2;\n const cy = offY + Math.floor(i / cols) * p.cellSize + p.cellSize / 2;\n return [cx, cy];\n };\n\n // Light up every cell whose center falls inside the radius, with the\n // configured falloff curve mapping distance to brightness.\n const energize = (x, y, boost) => {\n const p = propsRef.current;\n const r = Math.max(p.radius, 1);\n const ease = FALLOFF_CURVES[p.falloff] ?? FALLOFF_CURVES.linear;\n const now = performance.now();\n const minCol = Math.max(0, Math.floor((x - r - offX) / p.cellSize));\n const maxCol = Math.min(cols - 1, Math.floor((x + r - offX) / p.cellSize));\n const minRow = Math.max(0, Math.floor((y - r - offY) / p.cellSize));\n const maxRow = Math.min(rows - 1, Math.floor((y + r - offY) / p.cellSize));\n for (let cRow = minRow; cRow <= maxRow; cRow++) {\n for (let cCol = minCol; cCol <= maxCol; cCol++) {\n const i = cRow * cols + cCol;\n const [cx, cy] = cellCenter(i);\n const dist = Math.hypot(cx - x, cy - y);\n if (dist > r) continue;\n const level = ease(1 - dist / r) * p.maxOpacity * (boost ?? 1);\n if (level > alphas[i]) {\n alphas[i] = level;\n touched[i] = now;\n } else if (level > 0) {\n touched[i] = now;\n }\n }\n }\n };\n\n const draw = now => {\n const p = propsRef.current;\n const dt = Math.min(now - lastFrame, 50);\n lastFrame = now;\n ctx.clearRect(0, 0, w, h);\n const [cr, cg, cb] = hexToRgb(p.color);\n\n // Optional faint static lattice\n if (p.gridOpacity > 0) {\n ctx.strokeStyle = `rgba(${cr}, ${cg}, ${cb}, ${p.gridOpacity})`;\n ctx.lineWidth = 1;\n ctx.beginPath();\n for (let cCol = 0; cCol <= cols; cCol++) {\n const x = Math.round(offX + cCol * p.cellSize) + 0.5;\n ctx.moveTo(x, 0);\n ctx.lineTo(x, h);\n }\n for (let cRow = 0; cRow <= rows; cRow++) {\n const y = Math.round(offY + cRow * p.cellSize) + 0.5;\n ctx.moveTo(0, y);\n ctx.lineTo(w, y);\n }\n ctx.stroke();\n }\n\n // Expanding click pulses hand their energy to cells as they pass\n for (let pi = pulses.length - 1; pi >= 0; pi--) {\n const pulse = pulses[pi];\n const age = (now - pulse.t0) / 1000;\n const ringR = age * p.pulseSpeed;\n if (ringR > Math.hypot(w, h)) {\n pulses.splice(pi, 1);\n continue;\n }\n const band = p.cellSize;\n const minCol = Math.max(0, Math.floor((pulse.x - ringR - band - offX) / p.cellSize));\n const maxCol = Math.min(cols - 1, Math.floor((pulse.x + ringR + band - offX) / p.cellSize));\n const minRow = Math.max(0, Math.floor((pulse.y - ringR - band - offY) / p.cellSize));\n const maxRow = Math.min(rows - 1, Math.floor((pulse.y + ringR + band - offY) / p.cellSize));\n for (let cRow = minRow; cRow <= maxRow; cRow++) {\n for (let cCol = minCol; cCol <= maxCol; cCol++) {\n const i = cRow * cols + cCol;\n const [cx, cy] = cellCenter(i);\n const dist = Math.hypot(cx - pulse.x, cy - pulse.y);\n if (Math.abs(dist - ringR) < band / 2 && p.maxOpacity > alphas[i]) {\n alphas[i] = p.maxOpacity;\n touched[i] = now;\n }\n }\n }\n }\n\n let anyVisible = pulses.length > 0;\n const fadeStep = dt / Math.max(p.fadeDuration, 16);\n const half = p.cellSize / 2;\n\n for (let i = 0; i < alphas.length; i++) {\n let a = alphas[i];\n if (a <= 0) continue;\n if (now - touched[i] > p.holdTime) {\n a = Math.max(0, a - fadeStep);\n alphas[i] = a;\n if (a <= 0) continue;\n }\n anyVisible = true;\n\n const [cx, cy] = cellCenter(i);\n const gradient = ctx.createRadialGradient(cx, cy, half * 0.1, cx, cy, p.cellSize);\n gradient.addColorStop(0, `rgba(${cr}, ${cg}, ${cb}, ${a})`);\n gradient.addColorStop(1, `rgba(${cr}, ${cg}, ${cb}, 0)`);\n\n const x = cx - half + 0.5;\n const y = cy - half + 0.5;\n const s = p.cellSize - 1;\n\n ctx.beginPath();\n if (p.cellRadius > 0) {\n ctx.roundRect(x, y, s, s, p.cellRadius);\n } else {\n ctx.rect(x, y, s, s);\n }\n if (p.fillOpacity > 0) {\n ctx.fillStyle = `rgba(${cr}, ${cg}, ${cb}, ${a * p.fillOpacity})`;\n ctx.fill();\n }\n ctx.strokeStyle = gradient;\n ctx.lineWidth = p.lineWidth;\n ctx.stroke();\n }\n\n if (anyVisible) {\n raf = requestAnimationFrame(draw);\n } else {\n running = false;\n if (propsRef.current.gridOpacity <= 0) ctx.clearRect(0, 0, w, h);\n }\n };\n\n const wake = () => {\n if (running) return;\n running = true;\n lastFrame = performance.now();\n raf = requestAnimationFrame(draw);\n };\n wakeRef.current = wake;\n\n const toLocal = e => {\n const rect = canvas.getBoundingClientRect();\n return [e.clientX - rect.left, e.clientY - rect.top];\n };\n\n const onPointerMove = e => {\n const [x, y] = toLocal(e);\n energize(x, y);\n wake();\n };\n\n const onPointerDown = e => {\n if (!propsRef.current.clickPulse) return;\n const [x, y] = toLocal(e);\n pulses.push({ x, y, t0: performance.now() });\n wake();\n };\n\n const ro = new ResizeObserver(() => {\n rebuild();\n wake();\n });\n ro.observe(container);\n rebuild();\n wake();\n\n container.addEventListener('pointermove', onPointerMove);\n container.addEventListener('pointerdown', onPointerDown);\n\n return () => {\n cancelAnimationFrame(raf);\n ro.disconnect();\n container.removeEventListener('pointermove', onPointerMove);\n container.removeEventListener('pointerdown', onPointerDown);\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [cellSize]);\n\n // Repaint static layers when visual props change while idle\n useEffect(() => {\n wakeRef.current?.();\n }, [gridOpacity, color, lineWidth, maxOpacity, fillOpacity, cellRadius]);\n\n return (\n
\n \n
\n );\n};\n\nexport default CursorGrid;\n" + "content": "import { useRef, useEffect } from 'react';\n\nconst FALLOFF_CURVES = {\n linear: t => t,\n smooth: t => t * t * (3 - 2 * t),\n sharp: t => t * t * t\n};\n\nconst hexToRgb = hex => {\n const h = hex.replace('#', '');\n const v = h.length === 3 ? h.split('').map(c => c + c).join('') : h;\n const num = parseInt(v.slice(0, 6), 16);\n return [(num >> 16) & 255, (num >> 8) & 255, num & 255];\n};\n\nconst CursorGrid = ({\n cellSize = 70,\n color = '#D946EF',\n radius = 140,\n falloff = 'smooth',\n holdTime = 400,\n fadeDuration = 800,\n lineWidth = 1.2,\n maxOpacity = 1,\n fillOpacity = 0,\n gridOpacity = 0,\n cellRadius = 0,\n clickPulse = true,\n pulseSpeed = 600,\n className = ''\n}) => {\n const containerRef = useRef(null);\n const canvasRef = useRef(null);\n const propsRef = useRef({});\n const wakeRef = useRef(null);\n\n propsRef.current = {\n cellSize,\n color,\n radius,\n falloff,\n holdTime,\n fadeDuration,\n lineWidth,\n maxOpacity,\n fillOpacity,\n gridOpacity,\n cellRadius,\n clickPulse,\n pulseSpeed\n };\n\n useEffect(() => {\n const container = containerRef.current;\n const canvas = canvasRef.current;\n if (!container || !canvas) return;\n\n const ctx = canvas.getContext('2d');\n const dpr = Math.min(window.devicePixelRatio || 1, 2);\n\n // Grid state: one alpha + timestamp pair per cell, indexed row-major.\n let cols = 0;\n let rows = 0;\n let offX = 0;\n let offY = 0;\n let alphas = new Float32Array(0);\n let touched = new Float64Array(0);\n let w = 0;\n let h = 0;\n const pulses = [];\n let raf = 0;\n let running = false;\n let lastFrame = 0;\n\n const rebuild = () => {\n const p = propsRef.current;\n w = container.offsetWidth;\n h = container.offsetHeight;\n canvas.width = Math.max(1, Math.round(w * dpr));\n canvas.height = Math.max(1, Math.round(h * dpr));\n canvas.style.width = `${w}px`;\n canvas.style.height = `${h}px`;\n ctx.setTransform(dpr, 0, 0, dpr, 0, 0);\n cols = Math.ceil(w / p.cellSize) + 1;\n rows = Math.ceil(h / p.cellSize) + 1;\n // Center the lattice so edge cells crop evenly on both sides\n offX = (w - cols * p.cellSize) / 2;\n offY = (h - rows * p.cellSize) / 2;\n alphas = new Float32Array(cols * rows);\n touched = new Float64Array(cols * rows);\n };\n\n const cellCenter = i => {\n const p = propsRef.current;\n const cx = offX + (i % cols) * p.cellSize + p.cellSize / 2;\n const cy = offY + Math.floor(i / cols) * p.cellSize + p.cellSize / 2;\n return [cx, cy];\n };\n\n // Light up every cell whose center falls inside the radius, with the\n // configured falloff curve mapping distance to brightness.\n const energize = (x, y, boost) => {\n const p = propsRef.current;\n const r = Math.max(p.radius, 1);\n const ease = FALLOFF_CURVES[p.falloff] ?? FALLOFF_CURVES.linear;\n const now = performance.now();\n const minCol = Math.max(0, Math.floor((x - r - offX) / p.cellSize));\n const maxCol = Math.min(cols - 1, Math.floor((x + r - offX) / p.cellSize));\n const minRow = Math.max(0, Math.floor((y - r - offY) / p.cellSize));\n const maxRow = Math.min(rows - 1, Math.floor((y + r - offY) / p.cellSize));\n for (let cRow = minRow; cRow <= maxRow; cRow++) {\n for (let cCol = minCol; cCol <= maxCol; cCol++) {\n const i = cRow * cols + cCol;\n const [cx, cy] = cellCenter(i);\n const dist = Math.hypot(cx - x, cy - y);\n if (dist > r) continue;\n const level = ease(1 - dist / r) * p.maxOpacity * (boost ?? 1);\n if (level > alphas[i]) {\n alphas[i] = level;\n touched[i] = now;\n } else if (level > 0) {\n touched[i] = now;\n }\n }\n }\n };\n\n const draw = now => {\n const p = propsRef.current;\n const dt = Math.min(now - lastFrame, 50);\n lastFrame = now;\n ctx.clearRect(0, 0, w, h);\n const [cr, cg, cb] = hexToRgb(p.color);\n\n // Optional faint static lattice\n if (p.gridOpacity > 0) {\n ctx.strokeStyle = `rgba(${cr}, ${cg}, ${cb}, ${p.gridOpacity})`;\n ctx.lineWidth = 1;\n ctx.beginPath();\n for (let cCol = 0; cCol <= cols; cCol++) {\n const x = Math.round(offX + cCol * p.cellSize) + 0.5;\n ctx.moveTo(x, 0);\n ctx.lineTo(x, h);\n }\n for (let cRow = 0; cRow <= rows; cRow++) {\n const y = Math.round(offY + cRow * p.cellSize) + 0.5;\n ctx.moveTo(0, y);\n ctx.lineTo(w, y);\n }\n ctx.stroke();\n }\n\n // Expanding click pulses hand their energy to cells as they pass\n for (let pi = pulses.length - 1; pi >= 0; pi--) {\n const pulse = pulses[pi];\n const age = (now - pulse.t0) / 1000;\n const ringR = age * p.pulseSpeed;\n if (ringR > Math.hypot(w, h)) {\n pulses.splice(pi, 1);\n continue;\n }\n const band = p.cellSize;\n const minCol = Math.max(0, Math.floor((pulse.x - ringR - band - offX) / p.cellSize));\n const maxCol = Math.min(cols - 1, Math.floor((pulse.x + ringR + band - offX) / p.cellSize));\n const minRow = Math.max(0, Math.floor((pulse.y - ringR - band - offY) / p.cellSize));\n const maxRow = Math.min(rows - 1, Math.floor((pulse.y + ringR + band - offY) / p.cellSize));\n for (let cRow = minRow; cRow <= maxRow; cRow++) {\n for (let cCol = minCol; cCol <= maxCol; cCol++) {\n const i = cRow * cols + cCol;\n const [cx, cy] = cellCenter(i);\n const dist = Math.hypot(cx - pulse.x, cy - pulse.y);\n if (Math.abs(dist - ringR) < band / 2 && p.maxOpacity > alphas[i]) {\n alphas[i] = p.maxOpacity;\n touched[i] = now;\n }\n }\n }\n }\n\n let anyVisible = pulses.length > 0;\n const fadeStep = dt / Math.max(p.fadeDuration, 16);\n const half = p.cellSize / 2;\n\n for (let i = 0; i < alphas.length; i++) {\n let a = alphas[i];\n if (a <= 0) continue;\n if (now - touched[i] > p.holdTime) {\n a = Math.max(0, a - fadeStep);\n alphas[i] = a;\n if (a <= 0) continue;\n }\n anyVisible = true;\n\n const [cx, cy] = cellCenter(i);\n const gradient = ctx.createRadialGradient(cx, cy, half * 0.1, cx, cy, p.cellSize);\n gradient.addColorStop(0, `rgba(${cr}, ${cg}, ${cb}, ${a})`);\n gradient.addColorStop(1, `rgba(${cr}, ${cg}, ${cb}, 0)`);\n\n const x = cx - half + 0.5;\n const y = cy - half + 0.5;\n const s = p.cellSize - 1;\n\n ctx.beginPath();\n if (p.cellRadius > 0) {\n ctx.roundRect(x, y, s, s, p.cellRadius);\n } else {\n ctx.rect(x, y, s, s);\n }\n if (p.fillOpacity > 0) {\n ctx.fillStyle = `rgba(${cr}, ${cg}, ${cb}, ${a * p.fillOpacity})`;\n ctx.fill();\n }\n ctx.strokeStyle = gradient;\n ctx.lineWidth = p.lineWidth;\n ctx.stroke();\n }\n\n if (anyVisible) {\n raf = requestAnimationFrame(draw);\n } else {\n running = false;\n if (propsRef.current.gridOpacity <= 0) ctx.clearRect(0, 0, w, h);\n }\n };\n\n const wake = () => {\n if (running) return;\n running = true;\n lastFrame = performance.now();\n raf = requestAnimationFrame(draw);\n };\n wakeRef.current = wake;\n\n const toLocal = e => {\n const rect = canvas.getBoundingClientRect();\n return [e.clientX - rect.left, e.clientY - rect.top];\n };\n\n const onPointerMove = e => {\n const [x, y] = toLocal(e);\n energize(x, y);\n wake();\n };\n\n const onPointerDown = e => {\n if (!propsRef.current.clickPulse) return;\n const [x, y] = toLocal(e);\n pulses.push({ x, y, t0: performance.now() });\n wake();\n };\n\n const ro = new ResizeObserver(() => {\n rebuild();\n wake();\n });\n ro.observe(container);\n rebuild();\n wake();\n\n container.addEventListener('pointermove', onPointerMove);\n container.addEventListener('pointerdown', onPointerDown);\n\n return () => {\n cancelAnimationFrame(raf);\n ro.disconnect();\n container.removeEventListener('pointermove', onPointerMove);\n container.removeEventListener('pointerdown', onPointerDown);\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [cellSize]);\n\n // Repaint static layers when visual props change while idle\n useEffect(() => {\n wakeRef.current?.();\n }, [gridOpacity, color, lineWidth, maxOpacity, fillOpacity, cellRadius]);\n\n return (\n
\n \n
\n );\n};\n\nexport default CursorGrid;\n" } ], "registryDependencies": [], diff --git a/public/r/CursorGrid-TS-CSS.json b/public/r/CursorGrid-TS-CSS.json index feeeb86b5..793e40f4f 100644 --- a/public/r/CursorGrid-TS-CSS.json +++ b/public/r/CursorGrid-TS-CSS.json @@ -13,7 +13,7 @@ { "type": "registry:component", "path": "CursorGrid/CursorGrid.tsx", - "content": "import { useRef, useEffect } from 'react';\nimport './CursorGrid.css';\n\ntype Falloff = 'linear' | 'smooth' | 'sharp';\n\nexport interface CursorGridProps {\n cellSize?: number;\n color?: string;\n radius?: number;\n falloff?: Falloff;\n holdTime?: number;\n fadeDuration?: number;\n lineWidth?: number;\n maxOpacity?: number;\n fillOpacity?: number;\n gridOpacity?: number;\n cellRadius?: number;\n clickPulse?: boolean;\n pulseSpeed?: number;\n className?: string;\n}\n\ninterface GridConfig {\n cellSize: number;\n color: string;\n radius: number;\n falloff: Falloff;\n holdTime: number;\n fadeDuration: number;\n lineWidth: number;\n maxOpacity: number;\n fillOpacity: number;\n gridOpacity: number;\n cellRadius: number;\n clickPulse: boolean;\n pulseSpeed: number;\n}\n\ninterface Pulse {\n x: number;\n y: number;\n t0: number;\n}\n\nconst FALLOFF_CURVES: Record number> = {\n linear: t => t,\n smooth: t => t * t * (3 - 2 * t),\n sharp: t => t * t * t\n};\n\nconst hexToRgb = (hex: string): [number, number, number] => {\n const h = hex.replace('#', '');\n const v = h.length === 3 ? h.split('').map(c => c + c).join('') : h;\n const num = parseInt(v, 16);\n return [(num >> 16) & 255, (num >> 8) & 255, num & 255];\n};\n\nconst CursorGrid = ({\n cellSize = 70,\n color = '#D946EF',\n radius = 140,\n falloff = 'smooth',\n holdTime = 400,\n fadeDuration = 800,\n lineWidth = 1.2,\n maxOpacity = 1,\n fillOpacity = 0,\n gridOpacity = 0,\n cellRadius = 0,\n clickPulse = true,\n pulseSpeed = 600,\n className = ''\n}: CursorGridProps) => {\n const containerRef = useRef(null);\n const canvasRef = useRef(null);\n const propsRef = useRef({} as GridConfig);\n const wakeRef = useRef<(() => void) | null>(null);\n\n propsRef.current = {\n cellSize,\n color,\n radius,\n falloff,\n holdTime,\n fadeDuration,\n lineWidth,\n maxOpacity,\n fillOpacity,\n gridOpacity,\n cellRadius,\n clickPulse,\n pulseSpeed\n };\n\n useEffect(() => {\n const container = containerRef.current;\n const canvas = canvasRef.current;\n if (!container || !canvas) return;\n\n const ctx = canvas.getContext('2d');\n if (!ctx) return;\n const dpr = Math.min(window.devicePixelRatio || 1, 2);\n\n // Grid state: one alpha + timestamp pair per cell, indexed row-major.\n let cols = 0;\n let rows = 0;\n let offX = 0;\n let offY = 0;\n let alphas = new Float32Array(0);\n let touched = new Float64Array(0);\n let w = 0;\n let h = 0;\n const pulses: Pulse[] = [];\n let raf = 0;\n let running = false;\n let lastFrame = 0;\n\n const rebuild = () => {\n const p = propsRef.current;\n w = container.offsetWidth;\n h = container.offsetHeight;\n canvas.width = Math.max(1, Math.round(w * dpr));\n canvas.height = Math.max(1, Math.round(h * dpr));\n canvas.style.width = `${w}px`;\n canvas.style.height = `${h}px`;\n ctx.setTransform(dpr, 0, 0, dpr, 0, 0);\n cols = Math.ceil(w / p.cellSize) + 1;\n rows = Math.ceil(h / p.cellSize) + 1;\n // Center the lattice so edge cells crop evenly on both sides\n offX = (w - cols * p.cellSize) / 2;\n offY = (h - rows * p.cellSize) / 2;\n alphas = new Float32Array(cols * rows);\n touched = new Float64Array(cols * rows);\n };\n\n const cellCenter = (i: number): [number, number] => {\n const p = propsRef.current;\n const cx = offX + (i % cols) * p.cellSize + p.cellSize / 2;\n const cy = offY + Math.floor(i / cols) * p.cellSize + p.cellSize / 2;\n return [cx, cy];\n };\n\n // Light up every cell whose center falls inside the radius, with the\n // configured falloff curve mapping distance to brightness.\n const energize = (x: number, y: number, boost?: number) => {\n const p = propsRef.current;\n const r = Math.max(p.radius, 1);\n const ease = FALLOFF_CURVES[p.falloff] ?? FALLOFF_CURVES.linear;\n const now = performance.now();\n const minCol = Math.max(0, Math.floor((x - r - offX) / p.cellSize));\n const maxCol = Math.min(cols - 1, Math.floor((x + r - offX) / p.cellSize));\n const minRow = Math.max(0, Math.floor((y - r - offY) / p.cellSize));\n const maxRow = Math.min(rows - 1, Math.floor((y + r - offY) / p.cellSize));\n for (let cRow = minRow; cRow <= maxRow; cRow++) {\n for (let cCol = minCol; cCol <= maxCol; cCol++) {\n const i = cRow * cols + cCol;\n const [cx, cy] = cellCenter(i);\n const dist = Math.hypot(cx - x, cy - y);\n if (dist > r) continue;\n const level = ease(1 - dist / r) * p.maxOpacity * (boost ?? 1);\n if (level > alphas[i]) {\n alphas[i] = level;\n touched[i] = now;\n } else if (level > 0) {\n touched[i] = now;\n }\n }\n }\n };\n\n const draw = (now: number) => {\n const p = propsRef.current;\n const dt = Math.min(now - lastFrame, 50);\n lastFrame = now;\n ctx.clearRect(0, 0, w, h);\n const [cr, cg, cb] = hexToRgb(p.color);\n\n // Optional faint static lattice\n if (p.gridOpacity > 0) {\n ctx.strokeStyle = `rgba(${cr}, ${cg}, ${cb}, ${p.gridOpacity})`;\n ctx.lineWidth = 1;\n ctx.beginPath();\n for (let cCol = 0; cCol <= cols; cCol++) {\n const x = Math.round(offX + cCol * p.cellSize) + 0.5;\n ctx.moveTo(x, 0);\n ctx.lineTo(x, h);\n }\n for (let cRow = 0; cRow <= rows; cRow++) {\n const y = Math.round(offY + cRow * p.cellSize) + 0.5;\n ctx.moveTo(0, y);\n ctx.lineTo(w, y);\n }\n ctx.stroke();\n }\n\n // Expanding click pulses hand their energy to cells as they pass\n for (let pi = pulses.length - 1; pi >= 0; pi--) {\n const pulse = pulses[pi];\n const age = (now - pulse.t0) / 1000;\n const ringR = age * p.pulseSpeed;\n if (ringR > Math.hypot(w, h)) {\n pulses.splice(pi, 1);\n continue;\n }\n const band = p.cellSize;\n const minCol = Math.max(0, Math.floor((pulse.x - ringR - band - offX) / p.cellSize));\n const maxCol = Math.min(cols - 1, Math.floor((pulse.x + ringR + band - offX) / p.cellSize));\n const minRow = Math.max(0, Math.floor((pulse.y - ringR - band - offY) / p.cellSize));\n const maxRow = Math.min(rows - 1, Math.floor((pulse.y + ringR + band - offY) / p.cellSize));\n for (let cRow = minRow; cRow <= maxRow; cRow++) {\n for (let cCol = minCol; cCol <= maxCol; cCol++) {\n const i = cRow * cols + cCol;\n const [cx, cy] = cellCenter(i);\n const dist = Math.hypot(cx - pulse.x, cy - pulse.y);\n if (Math.abs(dist - ringR) < band / 2 && p.maxOpacity > alphas[i]) {\n alphas[i] = p.maxOpacity;\n touched[i] = now;\n }\n }\n }\n }\n\n let anyVisible = pulses.length > 0;\n const fadeStep = dt / Math.max(p.fadeDuration, 16);\n const half = p.cellSize / 2;\n\n for (let i = 0; i < alphas.length; i++) {\n let a = alphas[i];\n if (a <= 0) continue;\n if (now - touched[i] > p.holdTime) {\n a = Math.max(0, a - fadeStep);\n alphas[i] = a;\n if (a <= 0) continue;\n }\n anyVisible = true;\n\n const [cx, cy] = cellCenter(i);\n const gradient = ctx.createRadialGradient(cx, cy, half * 0.1, cx, cy, p.cellSize);\n gradient.addColorStop(0, `rgba(${cr}, ${cg}, ${cb}, ${a})`);\n gradient.addColorStop(1, `rgba(${cr}, ${cg}, ${cb}, 0)`);\n\n const x = cx - half + 0.5;\n const y = cy - half + 0.5;\n const s = p.cellSize - 1;\n\n ctx.beginPath();\n if (p.cellRadius > 0) {\n ctx.roundRect(x, y, s, s, p.cellRadius);\n } else {\n ctx.rect(x, y, s, s);\n }\n if (p.fillOpacity > 0) {\n ctx.fillStyle = `rgba(${cr}, ${cg}, ${cb}, ${a * p.fillOpacity})`;\n ctx.fill();\n }\n ctx.strokeStyle = gradient;\n ctx.lineWidth = p.lineWidth;\n ctx.stroke();\n }\n\n if (anyVisible) {\n raf = requestAnimationFrame(draw);\n } else {\n running = false;\n if (propsRef.current.gridOpacity <= 0) ctx.clearRect(0, 0, w, h);\n }\n };\n\n const wake = () => {\n if (running) return;\n running = true;\n lastFrame = performance.now();\n raf = requestAnimationFrame(draw);\n };\n wakeRef.current = wake;\n\n const toLocal = (e: PointerEvent): [number, number] => {\n const rect = canvas.getBoundingClientRect();\n return [e.clientX - rect.left, e.clientY - rect.top];\n };\n\n const onPointerMove = (e: PointerEvent) => {\n const [x, y] = toLocal(e);\n energize(x, y);\n wake();\n };\n\n const onPointerDown = (e: PointerEvent) => {\n if (!propsRef.current.clickPulse) return;\n const [x, y] = toLocal(e);\n pulses.push({ x, y, t0: performance.now() });\n wake();\n };\n\n const ro = new ResizeObserver(() => {\n rebuild();\n wake();\n });\n ro.observe(container);\n rebuild();\n wake();\n\n container.addEventListener('pointermove', onPointerMove);\n container.addEventListener('pointerdown', onPointerDown);\n\n return () => {\n cancelAnimationFrame(raf);\n ro.disconnect();\n container.removeEventListener('pointermove', onPointerMove);\n container.removeEventListener('pointerdown', onPointerDown);\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [cellSize]);\n\n // Repaint static layers when visual props change while idle\n useEffect(() => {\n wakeRef.current?.();\n }, [gridOpacity, color, lineWidth, maxOpacity, fillOpacity, cellRadius]);\n\n return (\n
\n \n
\n );\n};\n\nexport default CursorGrid;\n" + "content": "import { useRef, useEffect } from 'react';\nimport './CursorGrid.css';\n\ntype Falloff = 'linear' | 'smooth' | 'sharp';\n\nexport interface CursorGridProps {\n cellSize?: number;\n color?: string;\n radius?: number;\n falloff?: Falloff;\n holdTime?: number;\n fadeDuration?: number;\n lineWidth?: number;\n maxOpacity?: number;\n fillOpacity?: number;\n gridOpacity?: number;\n cellRadius?: number;\n clickPulse?: boolean;\n pulseSpeed?: number;\n className?: string;\n}\n\ninterface GridConfig {\n cellSize: number;\n color: string;\n radius: number;\n falloff: Falloff;\n holdTime: number;\n fadeDuration: number;\n lineWidth: number;\n maxOpacity: number;\n fillOpacity: number;\n gridOpacity: number;\n cellRadius: number;\n clickPulse: boolean;\n pulseSpeed: number;\n}\n\ninterface Pulse {\n x: number;\n y: number;\n t0: number;\n}\n\nconst FALLOFF_CURVES: Record number> = {\n linear: t => t,\n smooth: t => t * t * (3 - 2 * t),\n sharp: t => t * t * t\n};\n\nconst hexToRgb = (hex: string): [number, number, number] => {\n const h = hex.replace('#', '');\n const v = h.length === 3 ? h.split('').map(c => c + c).join('') : h;\n const num = parseInt(v.slice(0, 6), 16);\n return [(num >> 16) & 255, (num >> 8) & 255, num & 255];\n};\n\nconst CursorGrid = ({\n cellSize = 70,\n color = '#D946EF',\n radius = 140,\n falloff = 'smooth',\n holdTime = 400,\n fadeDuration = 800,\n lineWidth = 1.2,\n maxOpacity = 1,\n fillOpacity = 0,\n gridOpacity = 0,\n cellRadius = 0,\n clickPulse = true,\n pulseSpeed = 600,\n className = ''\n}: CursorGridProps) => {\n const containerRef = useRef(null);\n const canvasRef = useRef(null);\n const propsRef = useRef({} as GridConfig);\n const wakeRef = useRef<(() => void) | null>(null);\n\n propsRef.current = {\n cellSize,\n color,\n radius,\n falloff,\n holdTime,\n fadeDuration,\n lineWidth,\n maxOpacity,\n fillOpacity,\n gridOpacity,\n cellRadius,\n clickPulse,\n pulseSpeed\n };\n\n useEffect(() => {\n const container = containerRef.current;\n const canvas = canvasRef.current;\n if (!container || !canvas) return;\n\n const ctx = canvas.getContext('2d');\n if (!ctx) return;\n const dpr = Math.min(window.devicePixelRatio || 1, 2);\n\n // Grid state: one alpha + timestamp pair per cell, indexed row-major.\n let cols = 0;\n let rows = 0;\n let offX = 0;\n let offY = 0;\n let alphas = new Float32Array(0);\n let touched = new Float64Array(0);\n let w = 0;\n let h = 0;\n const pulses: Pulse[] = [];\n let raf = 0;\n let running = false;\n let lastFrame = 0;\n\n const rebuild = () => {\n const p = propsRef.current;\n w = container.offsetWidth;\n h = container.offsetHeight;\n canvas.width = Math.max(1, Math.round(w * dpr));\n canvas.height = Math.max(1, Math.round(h * dpr));\n canvas.style.width = `${w}px`;\n canvas.style.height = `${h}px`;\n ctx.setTransform(dpr, 0, 0, dpr, 0, 0);\n cols = Math.ceil(w / p.cellSize) + 1;\n rows = Math.ceil(h / p.cellSize) + 1;\n // Center the lattice so edge cells crop evenly on both sides\n offX = (w - cols * p.cellSize) / 2;\n offY = (h - rows * p.cellSize) / 2;\n alphas = new Float32Array(cols * rows);\n touched = new Float64Array(cols * rows);\n };\n\n const cellCenter = (i: number): [number, number] => {\n const p = propsRef.current;\n const cx = offX + (i % cols) * p.cellSize + p.cellSize / 2;\n const cy = offY + Math.floor(i / cols) * p.cellSize + p.cellSize / 2;\n return [cx, cy];\n };\n\n // Light up every cell whose center falls inside the radius, with the\n // configured falloff curve mapping distance to brightness.\n const energize = (x: number, y: number, boost?: number) => {\n const p = propsRef.current;\n const r = Math.max(p.radius, 1);\n const ease = FALLOFF_CURVES[p.falloff] ?? FALLOFF_CURVES.linear;\n const now = performance.now();\n const minCol = Math.max(0, Math.floor((x - r - offX) / p.cellSize));\n const maxCol = Math.min(cols - 1, Math.floor((x + r - offX) / p.cellSize));\n const minRow = Math.max(0, Math.floor((y - r - offY) / p.cellSize));\n const maxRow = Math.min(rows - 1, Math.floor((y + r - offY) / p.cellSize));\n for (let cRow = minRow; cRow <= maxRow; cRow++) {\n for (let cCol = minCol; cCol <= maxCol; cCol++) {\n const i = cRow * cols + cCol;\n const [cx, cy] = cellCenter(i);\n const dist = Math.hypot(cx - x, cy - y);\n if (dist > r) continue;\n const level = ease(1 - dist / r) * p.maxOpacity * (boost ?? 1);\n if (level > alphas[i]) {\n alphas[i] = level;\n touched[i] = now;\n } else if (level > 0) {\n touched[i] = now;\n }\n }\n }\n };\n\n const draw = (now: number) => {\n const p = propsRef.current;\n const dt = Math.min(now - lastFrame, 50);\n lastFrame = now;\n ctx.clearRect(0, 0, w, h);\n const [cr, cg, cb] = hexToRgb(p.color);\n\n // Optional faint static lattice\n if (p.gridOpacity > 0) {\n ctx.strokeStyle = `rgba(${cr}, ${cg}, ${cb}, ${p.gridOpacity})`;\n ctx.lineWidth = 1;\n ctx.beginPath();\n for (let cCol = 0; cCol <= cols; cCol++) {\n const x = Math.round(offX + cCol * p.cellSize) + 0.5;\n ctx.moveTo(x, 0);\n ctx.lineTo(x, h);\n }\n for (let cRow = 0; cRow <= rows; cRow++) {\n const y = Math.round(offY + cRow * p.cellSize) + 0.5;\n ctx.moveTo(0, y);\n ctx.lineTo(w, y);\n }\n ctx.stroke();\n }\n\n // Expanding click pulses hand their energy to cells as they pass\n for (let pi = pulses.length - 1; pi >= 0; pi--) {\n const pulse = pulses[pi];\n const age = (now - pulse.t0) / 1000;\n const ringR = age * p.pulseSpeed;\n if (ringR > Math.hypot(w, h)) {\n pulses.splice(pi, 1);\n continue;\n }\n const band = p.cellSize;\n const minCol = Math.max(0, Math.floor((pulse.x - ringR - band - offX) / p.cellSize));\n const maxCol = Math.min(cols - 1, Math.floor((pulse.x + ringR + band - offX) / p.cellSize));\n const minRow = Math.max(0, Math.floor((pulse.y - ringR - band - offY) / p.cellSize));\n const maxRow = Math.min(rows - 1, Math.floor((pulse.y + ringR + band - offY) / p.cellSize));\n for (let cRow = minRow; cRow <= maxRow; cRow++) {\n for (let cCol = minCol; cCol <= maxCol; cCol++) {\n const i = cRow * cols + cCol;\n const [cx, cy] = cellCenter(i);\n const dist = Math.hypot(cx - pulse.x, cy - pulse.y);\n if (Math.abs(dist - ringR) < band / 2 && p.maxOpacity > alphas[i]) {\n alphas[i] = p.maxOpacity;\n touched[i] = now;\n }\n }\n }\n }\n\n let anyVisible = pulses.length > 0;\n const fadeStep = dt / Math.max(p.fadeDuration, 16);\n const half = p.cellSize / 2;\n\n for (let i = 0; i < alphas.length; i++) {\n let a = alphas[i];\n if (a <= 0) continue;\n if (now - touched[i] > p.holdTime) {\n a = Math.max(0, a - fadeStep);\n alphas[i] = a;\n if (a <= 0) continue;\n }\n anyVisible = true;\n\n const [cx, cy] = cellCenter(i);\n const gradient = ctx.createRadialGradient(cx, cy, half * 0.1, cx, cy, p.cellSize);\n gradient.addColorStop(0, `rgba(${cr}, ${cg}, ${cb}, ${a})`);\n gradient.addColorStop(1, `rgba(${cr}, ${cg}, ${cb}, 0)`);\n\n const x = cx - half + 0.5;\n const y = cy - half + 0.5;\n const s = p.cellSize - 1;\n\n ctx.beginPath();\n if (p.cellRadius > 0) {\n ctx.roundRect(x, y, s, s, p.cellRadius);\n } else {\n ctx.rect(x, y, s, s);\n }\n if (p.fillOpacity > 0) {\n ctx.fillStyle = `rgba(${cr}, ${cg}, ${cb}, ${a * p.fillOpacity})`;\n ctx.fill();\n }\n ctx.strokeStyle = gradient;\n ctx.lineWidth = p.lineWidth;\n ctx.stroke();\n }\n\n if (anyVisible) {\n raf = requestAnimationFrame(draw);\n } else {\n running = false;\n if (propsRef.current.gridOpacity <= 0) ctx.clearRect(0, 0, w, h);\n }\n };\n\n const wake = () => {\n if (running) return;\n running = true;\n lastFrame = performance.now();\n raf = requestAnimationFrame(draw);\n };\n wakeRef.current = wake;\n\n const toLocal = (e: PointerEvent): [number, number] => {\n const rect = canvas.getBoundingClientRect();\n return [e.clientX - rect.left, e.clientY - rect.top];\n };\n\n const onPointerMove = (e: PointerEvent) => {\n const [x, y] = toLocal(e);\n energize(x, y);\n wake();\n };\n\n const onPointerDown = (e: PointerEvent) => {\n if (!propsRef.current.clickPulse) return;\n const [x, y] = toLocal(e);\n pulses.push({ x, y, t0: performance.now() });\n wake();\n };\n\n const ro = new ResizeObserver(() => {\n rebuild();\n wake();\n });\n ro.observe(container);\n rebuild();\n wake();\n\n container.addEventListener('pointermove', onPointerMove);\n container.addEventListener('pointerdown', onPointerDown);\n\n return () => {\n cancelAnimationFrame(raf);\n ro.disconnect();\n container.removeEventListener('pointermove', onPointerMove);\n container.removeEventListener('pointerdown', onPointerDown);\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [cellSize]);\n\n // Repaint static layers when visual props change while idle\n useEffect(() => {\n wakeRef.current?.();\n }, [gridOpacity, color, lineWidth, maxOpacity, fillOpacity, cellRadius]);\n\n return (\n
\n \n
\n );\n};\n\nexport default CursorGrid;\n" } ], "registryDependencies": [], diff --git a/public/r/CursorGrid-TS-TW.json b/public/r/CursorGrid-TS-TW.json index 5e3afe44e..02895748e 100644 --- a/public/r/CursorGrid-TS-TW.json +++ b/public/r/CursorGrid-TS-TW.json @@ -8,7 +8,7 @@ { "type": "registry:component", "path": "CursorGrid/CursorGrid.tsx", - "content": "import { useRef, useEffect } from 'react';\n\ntype Falloff = 'linear' | 'smooth' | 'sharp';\n\nexport interface CursorGridProps {\n cellSize?: number;\n color?: string;\n radius?: number;\n falloff?: Falloff;\n holdTime?: number;\n fadeDuration?: number;\n lineWidth?: number;\n maxOpacity?: number;\n fillOpacity?: number;\n gridOpacity?: number;\n cellRadius?: number;\n clickPulse?: boolean;\n pulseSpeed?: number;\n className?: string;\n}\n\ninterface GridConfig {\n cellSize: number;\n color: string;\n radius: number;\n falloff: Falloff;\n holdTime: number;\n fadeDuration: number;\n lineWidth: number;\n maxOpacity: number;\n fillOpacity: number;\n gridOpacity: number;\n cellRadius: number;\n clickPulse: boolean;\n pulseSpeed: number;\n}\n\ninterface Pulse {\n x: number;\n y: number;\n t0: number;\n}\n\nconst FALLOFF_CURVES: Record number> = {\n linear: t => t,\n smooth: t => t * t * (3 - 2 * t),\n sharp: t => t * t * t\n};\n\nconst hexToRgb = (hex: string): [number, number, number] => {\n const h = hex.replace('#', '');\n const v = h.length === 3 ? h.split('').map(c => c + c).join('') : h;\n const num = parseInt(v, 16);\n return [(num >> 16) & 255, (num >> 8) & 255, num & 255];\n};\n\nconst CursorGrid = ({\n cellSize = 70,\n color = '#D946EF',\n radius = 140,\n falloff = 'smooth',\n holdTime = 400,\n fadeDuration = 800,\n lineWidth = 1.2,\n maxOpacity = 1,\n fillOpacity = 0,\n gridOpacity = 0,\n cellRadius = 0,\n clickPulse = true,\n pulseSpeed = 600,\n className = ''\n}: CursorGridProps) => {\n const containerRef = useRef(null);\n const canvasRef = useRef(null);\n const propsRef = useRef({} as GridConfig);\n const wakeRef = useRef<(() => void) | null>(null);\n\n propsRef.current = {\n cellSize,\n color,\n radius,\n falloff,\n holdTime,\n fadeDuration,\n lineWidth,\n maxOpacity,\n fillOpacity,\n gridOpacity,\n cellRadius,\n clickPulse,\n pulseSpeed\n };\n\n useEffect(() => {\n const container = containerRef.current;\n const canvas = canvasRef.current;\n if (!container || !canvas) return;\n\n const ctx = canvas.getContext('2d');\n if (!ctx) return;\n const dpr = Math.min(window.devicePixelRatio || 1, 2);\n\n // Grid state: one alpha + timestamp pair per cell, indexed row-major.\n let cols = 0;\n let rows = 0;\n let offX = 0;\n let offY = 0;\n let alphas = new Float32Array(0);\n let touched = new Float64Array(0);\n let w = 0;\n let h = 0;\n const pulses: Pulse[] = [];\n let raf = 0;\n let running = false;\n let lastFrame = 0;\n\n const rebuild = () => {\n const p = propsRef.current;\n w = container.offsetWidth;\n h = container.offsetHeight;\n canvas.width = Math.max(1, Math.round(w * dpr));\n canvas.height = Math.max(1, Math.round(h * dpr));\n canvas.style.width = `${w}px`;\n canvas.style.height = `${h}px`;\n ctx.setTransform(dpr, 0, 0, dpr, 0, 0);\n cols = Math.ceil(w / p.cellSize) + 1;\n rows = Math.ceil(h / p.cellSize) + 1;\n // Center the lattice so edge cells crop evenly on both sides\n offX = (w - cols * p.cellSize) / 2;\n offY = (h - rows * p.cellSize) / 2;\n alphas = new Float32Array(cols * rows);\n touched = new Float64Array(cols * rows);\n };\n\n const cellCenter = (i: number): [number, number] => {\n const p = propsRef.current;\n const cx = offX + (i % cols) * p.cellSize + p.cellSize / 2;\n const cy = offY + Math.floor(i / cols) * p.cellSize + p.cellSize / 2;\n return [cx, cy];\n };\n\n // Light up every cell whose center falls inside the radius, with the\n // configured falloff curve mapping distance to brightness.\n const energize = (x: number, y: number, boost?: number) => {\n const p = propsRef.current;\n const r = Math.max(p.radius, 1);\n const ease = FALLOFF_CURVES[p.falloff] ?? FALLOFF_CURVES.linear;\n const now = performance.now();\n const minCol = Math.max(0, Math.floor((x - r - offX) / p.cellSize));\n const maxCol = Math.min(cols - 1, Math.floor((x + r - offX) / p.cellSize));\n const minRow = Math.max(0, Math.floor((y - r - offY) / p.cellSize));\n const maxRow = Math.min(rows - 1, Math.floor((y + r - offY) / p.cellSize));\n for (let cRow = minRow; cRow <= maxRow; cRow++) {\n for (let cCol = minCol; cCol <= maxCol; cCol++) {\n const i = cRow * cols + cCol;\n const [cx, cy] = cellCenter(i);\n const dist = Math.hypot(cx - x, cy - y);\n if (dist > r) continue;\n const level = ease(1 - dist / r) * p.maxOpacity * (boost ?? 1);\n if (level > alphas[i]) {\n alphas[i] = level;\n touched[i] = now;\n } else if (level > 0) {\n touched[i] = now;\n }\n }\n }\n };\n\n const draw = (now: number) => {\n const p = propsRef.current;\n const dt = Math.min(now - lastFrame, 50);\n lastFrame = now;\n ctx.clearRect(0, 0, w, h);\n const [cr, cg, cb] = hexToRgb(p.color);\n\n // Optional faint static lattice\n if (p.gridOpacity > 0) {\n ctx.strokeStyle = `rgba(${cr}, ${cg}, ${cb}, ${p.gridOpacity})`;\n ctx.lineWidth = 1;\n ctx.beginPath();\n for (let cCol = 0; cCol <= cols; cCol++) {\n const x = Math.round(offX + cCol * p.cellSize) + 0.5;\n ctx.moveTo(x, 0);\n ctx.lineTo(x, h);\n }\n for (let cRow = 0; cRow <= rows; cRow++) {\n const y = Math.round(offY + cRow * p.cellSize) + 0.5;\n ctx.moveTo(0, y);\n ctx.lineTo(w, y);\n }\n ctx.stroke();\n }\n\n // Expanding click pulses hand their energy to cells as they pass\n for (let pi = pulses.length - 1; pi >= 0; pi--) {\n const pulse = pulses[pi];\n const age = (now - pulse.t0) / 1000;\n const ringR = age * p.pulseSpeed;\n if (ringR > Math.hypot(w, h)) {\n pulses.splice(pi, 1);\n continue;\n }\n const band = p.cellSize;\n const minCol = Math.max(0, Math.floor((pulse.x - ringR - band - offX) / p.cellSize));\n const maxCol = Math.min(cols - 1, Math.floor((pulse.x + ringR + band - offX) / p.cellSize));\n const minRow = Math.max(0, Math.floor((pulse.y - ringR - band - offY) / p.cellSize));\n const maxRow = Math.min(rows - 1, Math.floor((pulse.y + ringR + band - offY) / p.cellSize));\n for (let cRow = minRow; cRow <= maxRow; cRow++) {\n for (let cCol = minCol; cCol <= maxCol; cCol++) {\n const i = cRow * cols + cCol;\n const [cx, cy] = cellCenter(i);\n const dist = Math.hypot(cx - pulse.x, cy - pulse.y);\n if (Math.abs(dist - ringR) < band / 2 && p.maxOpacity > alphas[i]) {\n alphas[i] = p.maxOpacity;\n touched[i] = now;\n }\n }\n }\n }\n\n let anyVisible = pulses.length > 0;\n const fadeStep = dt / Math.max(p.fadeDuration, 16);\n const half = p.cellSize / 2;\n\n for (let i = 0; i < alphas.length; i++) {\n let a = alphas[i];\n if (a <= 0) continue;\n if (now - touched[i] > p.holdTime) {\n a = Math.max(0, a - fadeStep);\n alphas[i] = a;\n if (a <= 0) continue;\n }\n anyVisible = true;\n\n const [cx, cy] = cellCenter(i);\n const gradient = ctx.createRadialGradient(cx, cy, half * 0.1, cx, cy, p.cellSize);\n gradient.addColorStop(0, `rgba(${cr}, ${cg}, ${cb}, ${a})`);\n gradient.addColorStop(1, `rgba(${cr}, ${cg}, ${cb}, 0)`);\n\n const x = cx - half + 0.5;\n const y = cy - half + 0.5;\n const s = p.cellSize - 1;\n\n ctx.beginPath();\n if (p.cellRadius > 0) {\n ctx.roundRect(x, y, s, s, p.cellRadius);\n } else {\n ctx.rect(x, y, s, s);\n }\n if (p.fillOpacity > 0) {\n ctx.fillStyle = `rgba(${cr}, ${cg}, ${cb}, ${a * p.fillOpacity})`;\n ctx.fill();\n }\n ctx.strokeStyle = gradient;\n ctx.lineWidth = p.lineWidth;\n ctx.stroke();\n }\n\n if (anyVisible) {\n raf = requestAnimationFrame(draw);\n } else {\n running = false;\n if (propsRef.current.gridOpacity <= 0) ctx.clearRect(0, 0, w, h);\n }\n };\n\n const wake = () => {\n if (running) return;\n running = true;\n lastFrame = performance.now();\n raf = requestAnimationFrame(draw);\n };\n wakeRef.current = wake;\n\n const toLocal = (e: PointerEvent): [number, number] => {\n const rect = canvas.getBoundingClientRect();\n return [e.clientX - rect.left, e.clientY - rect.top];\n };\n\n const onPointerMove = (e: PointerEvent) => {\n const [x, y] = toLocal(e);\n energize(x, y);\n wake();\n };\n\n const onPointerDown = (e: PointerEvent) => {\n if (!propsRef.current.clickPulse) return;\n const [x, y] = toLocal(e);\n pulses.push({ x, y, t0: performance.now() });\n wake();\n };\n\n const ro = new ResizeObserver(() => {\n rebuild();\n wake();\n });\n ro.observe(container);\n rebuild();\n wake();\n\n container.addEventListener('pointermove', onPointerMove);\n container.addEventListener('pointerdown', onPointerDown);\n\n return () => {\n cancelAnimationFrame(raf);\n ro.disconnect();\n container.removeEventListener('pointermove', onPointerMove);\n container.removeEventListener('pointerdown', onPointerDown);\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [cellSize]);\n\n // Repaint static layers when visual props change while idle\n useEffect(() => {\n wakeRef.current?.();\n }, [gridOpacity, color, lineWidth, maxOpacity, fillOpacity, cellRadius]);\n\n return (\n
\n \n
\n );\n};\n\nexport default CursorGrid;\n" + "content": "import { useRef, useEffect } from 'react';\n\ntype Falloff = 'linear' | 'smooth' | 'sharp';\n\nexport interface CursorGridProps {\n cellSize?: number;\n color?: string;\n radius?: number;\n falloff?: Falloff;\n holdTime?: number;\n fadeDuration?: number;\n lineWidth?: number;\n maxOpacity?: number;\n fillOpacity?: number;\n gridOpacity?: number;\n cellRadius?: number;\n clickPulse?: boolean;\n pulseSpeed?: number;\n className?: string;\n}\n\ninterface GridConfig {\n cellSize: number;\n color: string;\n radius: number;\n falloff: Falloff;\n holdTime: number;\n fadeDuration: number;\n lineWidth: number;\n maxOpacity: number;\n fillOpacity: number;\n gridOpacity: number;\n cellRadius: number;\n clickPulse: boolean;\n pulseSpeed: number;\n}\n\ninterface Pulse {\n x: number;\n y: number;\n t0: number;\n}\n\nconst FALLOFF_CURVES: Record number> = {\n linear: t => t,\n smooth: t => t * t * (3 - 2 * t),\n sharp: t => t * t * t\n};\n\nconst hexToRgb = (hex: string): [number, number, number] => {\n const h = hex.replace('#', '');\n const v = h.length === 3 ? h.split('').map(c => c + c).join('') : h;\n const num = parseInt(v.slice(0, 6), 16);\n return [(num >> 16) & 255, (num >> 8) & 255, num & 255];\n};\n\nconst CursorGrid = ({\n cellSize = 70,\n color = '#D946EF',\n radius = 140,\n falloff = 'smooth',\n holdTime = 400,\n fadeDuration = 800,\n lineWidth = 1.2,\n maxOpacity = 1,\n fillOpacity = 0,\n gridOpacity = 0,\n cellRadius = 0,\n clickPulse = true,\n pulseSpeed = 600,\n className = ''\n}: CursorGridProps) => {\n const containerRef = useRef(null);\n const canvasRef = useRef(null);\n const propsRef = useRef({} as GridConfig);\n const wakeRef = useRef<(() => void) | null>(null);\n\n propsRef.current = {\n cellSize,\n color,\n radius,\n falloff,\n holdTime,\n fadeDuration,\n lineWidth,\n maxOpacity,\n fillOpacity,\n gridOpacity,\n cellRadius,\n clickPulse,\n pulseSpeed\n };\n\n useEffect(() => {\n const container = containerRef.current;\n const canvas = canvasRef.current;\n if (!container || !canvas) return;\n\n const ctx = canvas.getContext('2d');\n if (!ctx) return;\n const dpr = Math.min(window.devicePixelRatio || 1, 2);\n\n // Grid state: one alpha + timestamp pair per cell, indexed row-major.\n let cols = 0;\n let rows = 0;\n let offX = 0;\n let offY = 0;\n let alphas = new Float32Array(0);\n let touched = new Float64Array(0);\n let w = 0;\n let h = 0;\n const pulses: Pulse[] = [];\n let raf = 0;\n let running = false;\n let lastFrame = 0;\n\n const rebuild = () => {\n const p = propsRef.current;\n w = container.offsetWidth;\n h = container.offsetHeight;\n canvas.width = Math.max(1, Math.round(w * dpr));\n canvas.height = Math.max(1, Math.round(h * dpr));\n canvas.style.width = `${w}px`;\n canvas.style.height = `${h}px`;\n ctx.setTransform(dpr, 0, 0, dpr, 0, 0);\n cols = Math.ceil(w / p.cellSize) + 1;\n rows = Math.ceil(h / p.cellSize) + 1;\n // Center the lattice so edge cells crop evenly on both sides\n offX = (w - cols * p.cellSize) / 2;\n offY = (h - rows * p.cellSize) / 2;\n alphas = new Float32Array(cols * rows);\n touched = new Float64Array(cols * rows);\n };\n\n const cellCenter = (i: number): [number, number] => {\n const p = propsRef.current;\n const cx = offX + (i % cols) * p.cellSize + p.cellSize / 2;\n const cy = offY + Math.floor(i / cols) * p.cellSize + p.cellSize / 2;\n return [cx, cy];\n };\n\n // Light up every cell whose center falls inside the radius, with the\n // configured falloff curve mapping distance to brightness.\n const energize = (x: number, y: number, boost?: number) => {\n const p = propsRef.current;\n const r = Math.max(p.radius, 1);\n const ease = FALLOFF_CURVES[p.falloff] ?? FALLOFF_CURVES.linear;\n const now = performance.now();\n const minCol = Math.max(0, Math.floor((x - r - offX) / p.cellSize));\n const maxCol = Math.min(cols - 1, Math.floor((x + r - offX) / p.cellSize));\n const minRow = Math.max(0, Math.floor((y - r - offY) / p.cellSize));\n const maxRow = Math.min(rows - 1, Math.floor((y + r - offY) / p.cellSize));\n for (let cRow = minRow; cRow <= maxRow; cRow++) {\n for (let cCol = minCol; cCol <= maxCol; cCol++) {\n const i = cRow * cols + cCol;\n const [cx, cy] = cellCenter(i);\n const dist = Math.hypot(cx - x, cy - y);\n if (dist > r) continue;\n const level = ease(1 - dist / r) * p.maxOpacity * (boost ?? 1);\n if (level > alphas[i]) {\n alphas[i] = level;\n touched[i] = now;\n } else if (level > 0) {\n touched[i] = now;\n }\n }\n }\n };\n\n const draw = (now: number) => {\n const p = propsRef.current;\n const dt = Math.min(now - lastFrame, 50);\n lastFrame = now;\n ctx.clearRect(0, 0, w, h);\n const [cr, cg, cb] = hexToRgb(p.color);\n\n // Optional faint static lattice\n if (p.gridOpacity > 0) {\n ctx.strokeStyle = `rgba(${cr}, ${cg}, ${cb}, ${p.gridOpacity})`;\n ctx.lineWidth = 1;\n ctx.beginPath();\n for (let cCol = 0; cCol <= cols; cCol++) {\n const x = Math.round(offX + cCol * p.cellSize) + 0.5;\n ctx.moveTo(x, 0);\n ctx.lineTo(x, h);\n }\n for (let cRow = 0; cRow <= rows; cRow++) {\n const y = Math.round(offY + cRow * p.cellSize) + 0.5;\n ctx.moveTo(0, y);\n ctx.lineTo(w, y);\n }\n ctx.stroke();\n }\n\n // Expanding click pulses hand their energy to cells as they pass\n for (let pi = pulses.length - 1; pi >= 0; pi--) {\n const pulse = pulses[pi];\n const age = (now - pulse.t0) / 1000;\n const ringR = age * p.pulseSpeed;\n if (ringR > Math.hypot(w, h)) {\n pulses.splice(pi, 1);\n continue;\n }\n const band = p.cellSize;\n const minCol = Math.max(0, Math.floor((pulse.x - ringR - band - offX) / p.cellSize));\n const maxCol = Math.min(cols - 1, Math.floor((pulse.x + ringR + band - offX) / p.cellSize));\n const minRow = Math.max(0, Math.floor((pulse.y - ringR - band - offY) / p.cellSize));\n const maxRow = Math.min(rows - 1, Math.floor((pulse.y + ringR + band - offY) / p.cellSize));\n for (let cRow = minRow; cRow <= maxRow; cRow++) {\n for (let cCol = minCol; cCol <= maxCol; cCol++) {\n const i = cRow * cols + cCol;\n const [cx, cy] = cellCenter(i);\n const dist = Math.hypot(cx - pulse.x, cy - pulse.y);\n if (Math.abs(dist - ringR) < band / 2 && p.maxOpacity > alphas[i]) {\n alphas[i] = p.maxOpacity;\n touched[i] = now;\n }\n }\n }\n }\n\n let anyVisible = pulses.length > 0;\n const fadeStep = dt / Math.max(p.fadeDuration, 16);\n const half = p.cellSize / 2;\n\n for (let i = 0; i < alphas.length; i++) {\n let a = alphas[i];\n if (a <= 0) continue;\n if (now - touched[i] > p.holdTime) {\n a = Math.max(0, a - fadeStep);\n alphas[i] = a;\n if (a <= 0) continue;\n }\n anyVisible = true;\n\n const [cx, cy] = cellCenter(i);\n const gradient = ctx.createRadialGradient(cx, cy, half * 0.1, cx, cy, p.cellSize);\n gradient.addColorStop(0, `rgba(${cr}, ${cg}, ${cb}, ${a})`);\n gradient.addColorStop(1, `rgba(${cr}, ${cg}, ${cb}, 0)`);\n\n const x = cx - half + 0.5;\n const y = cy - half + 0.5;\n const s = p.cellSize - 1;\n\n ctx.beginPath();\n if (p.cellRadius > 0) {\n ctx.roundRect(x, y, s, s, p.cellRadius);\n } else {\n ctx.rect(x, y, s, s);\n }\n if (p.fillOpacity > 0) {\n ctx.fillStyle = `rgba(${cr}, ${cg}, ${cb}, ${a * p.fillOpacity})`;\n ctx.fill();\n }\n ctx.strokeStyle = gradient;\n ctx.lineWidth = p.lineWidth;\n ctx.stroke();\n }\n\n if (anyVisible) {\n raf = requestAnimationFrame(draw);\n } else {\n running = false;\n if (propsRef.current.gridOpacity <= 0) ctx.clearRect(0, 0, w, h);\n }\n };\n\n const wake = () => {\n if (running) return;\n running = true;\n lastFrame = performance.now();\n raf = requestAnimationFrame(draw);\n };\n wakeRef.current = wake;\n\n const toLocal = (e: PointerEvent): [number, number] => {\n const rect = canvas.getBoundingClientRect();\n return [e.clientX - rect.left, e.clientY - rect.top];\n };\n\n const onPointerMove = (e: PointerEvent) => {\n const [x, y] = toLocal(e);\n energize(x, y);\n wake();\n };\n\n const onPointerDown = (e: PointerEvent) => {\n if (!propsRef.current.clickPulse) return;\n const [x, y] = toLocal(e);\n pulses.push({ x, y, t0: performance.now() });\n wake();\n };\n\n const ro = new ResizeObserver(() => {\n rebuild();\n wake();\n });\n ro.observe(container);\n rebuild();\n wake();\n\n container.addEventListener('pointermove', onPointerMove);\n container.addEventListener('pointerdown', onPointerDown);\n\n return () => {\n cancelAnimationFrame(raf);\n ro.disconnect();\n container.removeEventListener('pointermove', onPointerMove);\n container.removeEventListener('pointerdown', onPointerDown);\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [cellSize]);\n\n // Repaint static layers when visual props change while idle\n useEffect(() => {\n wakeRef.current?.();\n }, [gridOpacity, color, lineWidth, maxOpacity, fillOpacity, cellRadius]);\n\n return (\n
\n \n
\n );\n};\n\nexport default CursorGrid;\n" } ], "registryDependencies": [], diff --git a/public/r/DarkVeil-JS-CSS.json b/public/r/DarkVeil-JS-CSS.json index 9b41ffe97..12a0f0fd0 100644 --- a/public/r/DarkVeil-JS-CSS.json +++ b/public/r/DarkVeil-JS-CSS.json @@ -13,7 +13,7 @@ { "type": "registry:component", "path": "DarkVeil/DarkVeil.jsx", - "content": "import { useRef, useEffect } from 'react';\nimport { Renderer, Program, Mesh, Triangle, Vec2 } from 'ogl';\nimport './DarkVeil.css';\n\nconst vertex = `\nattribute vec2 position;\nvoid main(){gl_Position=vec4(position,0.0,1.0);}\n`;\n\nconst fragment = `\n#ifdef GL_ES\nprecision lowp float;\n#endif\nuniform vec2 uResolution;\nuniform float uTime;\nuniform float uHueShift;\nuniform float uNoise;\nuniform float uScan;\nuniform float uScanFreq;\nuniform float uWarp;\n#define iTime uTime\n#define iResolution uResolution\n\nvec4 buf[8];\nfloat rand(vec2 c){return fract(sin(dot(c,vec2(12.9898,78.233)))*43758.5453);}\n\nmat3 rgb2yiq=mat3(0.299,0.587,0.114,0.596,-0.274,-0.322,0.211,-0.523,0.312);\nmat3 yiq2rgb=mat3(1.0,0.956,0.621,1.0,-0.272,-0.647,1.0,-1.106,1.703);\n\nvec3 hueShiftRGB(vec3 col,float deg){\n vec3 yiq=rgb2yiq*col;\n float rad=radians(deg);\n float cosh=cos(rad),sinh=sin(rad);\n vec3 yiqShift=vec3(yiq.x,yiq.y*cosh-yiq.z*sinh,yiq.y*sinh+yiq.z*cosh);\n return clamp(yiq2rgb*yiqShift,0.0,1.0);\n}\n\nvec4 sigmoid(vec4 x){return 1./(1.+exp(-x));}\n\nvec4 cppn_fn(vec2 coordinate,float in0,float in1,float in2){\n buf[6]=vec4(coordinate.x,coordinate.y,0.3948333106474662+in0,0.36+in1);\n buf[7]=vec4(0.14+in2,sqrt(coordinate.x*coordinate.x+coordinate.y*coordinate.y),0.,0.);\n buf[0]=mat4(vec4(6.5404263,-3.6126034,0.7590882,-1.13613),vec4(2.4582713,3.1660357,1.2219609,0.06276096),vec4(-5.478085,-6.159632,1.8701609,-4.7742867),vec4(6.039214,-5.542865,-0.90925294,3.251348))*buf[6]+mat4(vec4(0.8473259,-5.722911,3.975766,1.6522468),vec4(-0.24321538,0.5839259,-1.7661959,-5.350116),vec4(0.,0.,0.,0.),vec4(0.,0.,0.,0.))*buf[7]+vec4(0.21808943,1.1243913,-1.7969975,5.0294676);\n buf[1]=mat4(vec4(-3.3522482,-6.0612736,0.55641043,-4.4719114),vec4(0.8631464,1.7432913,5.643898,1.6106541),vec4(2.4941394,-3.5012043,1.7184316,6.357333),vec4(3.310376,8.209261,1.1355612,-1.165539))*buf[6]+mat4(vec4(5.24046,-13.034365,0.009859298,15.870829),vec4(2.987511,3.129433,-0.89023495,-1.6822904),vec4(0.,0.,0.,0.),vec4(0.,0.,0.,0.))*buf[7]+vec4(-5.9457836,-6.573602,-0.8812491,1.5436668);\n buf[0]=sigmoid(buf[0]);buf[1]=sigmoid(buf[1]);\n buf[2]=mat4(vec4(-15.219568,8.095543,-2.429353,-1.9381982),vec4(-5.951362,4.3115187,2.6393783,1.274315),vec4(-7.3145227,6.7297835,5.2473326,5.9411426),vec4(5.0796127,8.979051,-1.7278991,-1.158976))*buf[6]+mat4(vec4(-11.967154,-11.608155,6.1486754,11.237008),vec4(2.124141,-6.263192,-1.7050359,-0.7021966),vec4(0.,0.,0.,0.),vec4(0.,0.,0.,0.))*buf[7]+vec4(-4.17164,-3.2281182,-4.576417,-3.6401186);\n buf[3]=mat4(vec4(3.1832156,-13.738922,1.879223,3.233465),vec4(0.64300746,12.768129,1.9141049,0.50990224),vec4(-0.049295485,4.4807224,1.4733979,1.801449),vec4(5.0039253,13.000481,3.3991797,-4.5561905))*buf[6]+mat4(vec4(-0.1285731,7.720628,-3.1425676,4.742367),vec4(0.6393625,3.714393,-0.8108378,-0.39174938),vec4(0.,0.,0.,0.),vec4(0.,0.,0.,0.))*buf[7]+vec4(-1.1811101,-21.621881,0.7851888,1.2329718);\n buf[2]=sigmoid(buf[2]);buf[3]=sigmoid(buf[3]);\n buf[4]=mat4(vec4(5.214916,-7.183024,2.7228765,2.6592617),vec4(-5.601878,-25.3591,4.067988,0.4602802),vec4(-10.57759,24.286327,21.102104,37.546658),vec4(4.3024497,-1.9625226,2.3458803,-1.372816))*buf[0]+mat4(vec4(-17.6526,-10.507558,2.2587414,12.462782),vec4(6.265566,-502.75443,-12.642513,0.9112289),vec4(-10.983244,20.741234,-9.701768,-0.7635988),vec4(5.383626,1.4819539,-4.1911616,-4.8444734))*buf[1]+mat4(vec4(12.785233,-16.345072,-0.39901125,1.7955981),vec4(-30.48365,-1.8345358,1.4542528,-1.1118771),vec4(19.872723,-7.337935,-42.941723,-98.52709),vec4(8.337645,-2.7312303,-2.2927687,-36.142323))*buf[2]+mat4(vec4(-16.298317,3.5471997,-0.44300047,-9.444417),vec4(57.5077,-35.609753,16.163465,-4.1534753),vec4(-0.07470326,-3.8656476,-7.0901804,3.1523974),vec4(-12.559385,-7.077619,1.490437,-0.8211543))*buf[3]+vec4(-7.67914,15.927437,1.3207729,-1.6686112);\n buf[5]=mat4(vec4(-1.4109162,-0.372762,-3.770383,-21.367174),vec4(-6.2103205,-9.35908,0.92529047,8.82561),vec4(11.460242,-22.348068,13.625772,-18.693201),vec4(-0.3429052,-3.9905605,-2.4626114,-0.45033523))*buf[0]+mat4(vec4(7.3481627,-4.3661838,-6.3037653,-3.868115),vec4(1.5462853,6.5488915,1.9701879,-0.58291394),vec4(6.5858274,-2.2180402,3.7127688,-1.3730392),vec4(-5.7973905,10.134961,-2.3395722,-5.965605))*buf[1]+mat4(vec4(-2.5132585,-6.6685553,-1.4029363,-0.16285264),vec4(-0.37908727,0.53738135,4.389061,-1.3024765),vec4(-0.70647055,2.0111287,-5.1659346,-3.728635),vec4(-13.562562,10.487719,-0.9173751,-2.6487076))*buf[2]+mat4(vec4(-8.645013,6.5546675,-6.3944063,-5.5933375),vec4(-0.57783127,-1.077275,36.91025,5.736769),vec4(14.283112,3.7146652,7.1452246,-4.5958776),vec4(2.7192075,3.6021907,-4.366337,-2.3653464))*buf[3]+vec4(-5.9000807,-4.329569,1.2427121,8.59503);\n buf[4]=sigmoid(buf[4]);buf[5]=sigmoid(buf[5]);\n buf[6]=mat4(vec4(-1.61102,0.7970257,1.4675229,0.20917463),vec4(-28.793737,-7.1390953,1.5025433,4.656581),vec4(-10.94861,39.66238,0.74318546,-10.095605),vec4(-0.7229728,-1.5483948,0.7301322,2.1687684))*buf[0]+mat4(vec4(3.2547753,21.489103,-1.0194173,-3.3100595),vec4(-3.7316632,-3.3792162,-7.223193,-0.23685838),vec4(13.1804495,0.7916005,5.338587,5.687114),vec4(-4.167605,-17.798311,-6.815736,-1.6451967))*buf[1]+mat4(vec4(0.604885,-7.800309,-7.213122,-2.741014),vec4(-3.522382,-0.12359311,-0.5258442,0.43852118),vec4(9.6752825,-22.853785,2.062431,0.099892326),vec4(-4.3196306,-17.730087,2.5184598,5.30267))*buf[2]+mat4(vec4(-6.545563,-15.790176,-6.0438633,-5.415399),vec4(-43.591583,28.551912,-16.00161,18.84728),vec4(4.212382,8.394307,3.0958717,8.657522),vec4(-5.0237565,-4.450633,-4.4768,-5.5010443))*buf[3]+mat4(vec4(1.6985557,-67.05806,6.897715,1.9004834),vec4(1.8680354,2.3915145,2.5231109,4.081538),vec4(11.158006,1.7294737,2.0738268,7.386411),vec4(-4.256034,-306.24686,8.258898,-17.132736))*buf[4]+mat4(vec4(1.6889864,-4.5852966,3.8534803,-6.3482175),vec4(1.3543309,-1.2640043,9.932754,2.9079645),vec4(-5.2770967,0.07150358,-0.13962056,3.3269649),vec4(28.34703,-4.918278,6.1044083,4.085355))*buf[5]+vec4(6.6818056,12.522166,-3.7075126,-4.104386);\n buf[7]=mat4(vec4(-8.265602,-4.7027016,5.098234,0.7509808),vec4(8.6507845,-17.15949,16.51939,-8.884479),vec4(-4.036479,-2.3946867,-2.6055532,-1.9866527),vec4(-2.2167742,-1.8135649,-5.9759874,4.8846445))*buf[0]+mat4(vec4(6.7790847,3.5076547,-2.8191125,-2.7028968),vec4(-5.743024,-0.27844876,1.4958696,-5.0517144),vec4(13.122226,15.735168,-2.9397483,-4.101023),vec4(-14.375265,-5.030483,-6.2599335,2.9848232))*buf[1]+mat4(vec4(4.0950394,-0.94011575,-5.674733,4.755022),vec4(4.3809423,4.8310084,1.7425908,-3.437416),vec4(2.117492,0.16342592,-104.56341,16.949184),vec4(-5.22543,-2.994248,3.8350096,-1.9364246))*buf[2]+mat4(vec4(-5.900337,1.7946124,-13.604192,-3.8060522),vec4(6.6583457,31.911177,25.164474,91.81147),vec4(11.840538,4.1503043,-0.7314397,6.768467),vec4(-6.3967767,4.034772,6.1714606,-0.32874924))*buf[3]+mat4(vec4(3.4992442,-196.91893,-8.923708,2.8142626),vec4(3.4806502,-3.1846354,5.1725626,5.1804223),vec4(-2.4009497,15.585794,1.2863957,2.0252278),vec4(-71.25271,-62.441242,-8.138444,0.50670296))*buf[4]+mat4(vec4(-12.291733,-11.176166,-7.3474145,4.390294),vec4(10.805477,5.6337385,-0.9385842,-4.7348723),vec4(-12.869276,-7.039391,5.3029537,7.5436664),vec4(1.4593618,8.91898,3.5101583,5.840625))*buf[5]+vec4(2.2415268,-6.705987,-0.98861027,-2.117676);\n buf[6]=sigmoid(buf[6]);buf[7]=sigmoid(buf[7]);\n buf[0]=mat4(vec4(1.6794263,1.3817469,2.9625452,0.),vec4(-1.8834411,-1.4806935,-3.5924516,0.),vec4(-1.3279216,-1.0918057,-2.3124623,0.),vec4(0.2662234,0.23235129,0.44178495,0.))*buf[0]+mat4(vec4(-0.6299101,-0.5945583,-0.9125601,0.),vec4(0.17828953,0.18300213,0.18182953,0.),vec4(-2.96544,-2.5819945,-4.9001055,0.),vec4(1.4195864,1.1868085,2.5176322,0.))*buf[1]+mat4(vec4(-1.2584374,-1.0552157,-2.1688404,0.),vec4(-0.7200217,-0.52666044,-1.438251,0.),vec4(0.15345335,0.15196142,0.272854,0.),vec4(0.945728,0.8861938,1.2766753,0.))*buf[2]+mat4(vec4(-2.4218085,-1.968602,-4.35166,0.),vec4(-22.683098,-18.0544,-41.954372,0.),vec4(0.63792,0.5470648,1.1078634,0.),vec4(-1.5489894,-1.3075932,-2.6444845,0.))*buf[3]+mat4(vec4(-0.49252132,-0.39877754,-0.91366625,0.),vec4(0.95609266,0.7923952,1.640221,0.),vec4(0.30616966,0.15693925,0.8639857,0.),vec4(1.1825981,0.94504964,2.176963,0.))*buf[4]+mat4(vec4(0.35446745,0.3293795,0.59547555,0.),vec4(-0.58784515,-0.48177817,-1.0614829,0.),vec4(2.5271258,1.9991658,4.6846647,0.),vec4(0.13042648,0.08864098,0.30187556,0.))*buf[5]+mat4(vec4(-1.7718065,-1.4033192,-3.3355875,0.),vec4(3.1664357,2.638297,5.378702,0.),vec4(-3.1724713,-2.6107926,-5.549295,0.),vec4(-2.851368,-2.249092,-5.3013067,0.))*buf[6]+mat4(vec4(1.5203838,1.2212278,2.8404984,0.),vec4(1.5210563,1.2651345,2.683903,0.),vec4(2.9789467,2.4364579,5.2347264,0.),vec4(2.2270417,1.8825914,3.8028636,0.))*buf[7]+vec4(-1.5468478,-3.6171484,0.24762098,0.);\n buf[0]=sigmoid(buf[0]);\n return vec4(buf[0].x,buf[0].y,buf[0].z,1.);\n}\n\nvoid mainImage(out vec4 fragColor,in vec2 fragCoord){\n vec2 uv=fragCoord/uResolution.xy*2.-1.;\n uv.y*=-1.;\n uv+=uWarp*vec2(sin(uv.y*6.283+uTime*0.5),cos(uv.x*6.283+uTime*0.5))*0.05;\n fragColor=cppn_fn(uv,0.1*sin(0.3*uTime),0.1*sin(0.69*uTime),0.1*sin(0.44*uTime));\n}\n\nvoid main(){\n vec4 col;mainImage(col,gl_FragCoord.xy);\n col.rgb=hueShiftRGB(col.rgb,uHueShift);\n float scanline_val=sin(gl_FragCoord.y*uScanFreq)*0.5+0.5;\n col.rgb*=1.-(scanline_val*scanline_val)*uScan;\n col.rgb+=(rand(gl_FragCoord.xy+uTime)-0.5)*uNoise;\n gl_FragColor=vec4(clamp(col.rgb,0.0,1.0),1.0);\n}\n`;\n\nexport default function DarkVeil({\n hueShift = 0,\n noiseIntensity = 0,\n scanlineIntensity = 0,\n speed = 0.5,\n scanlineFrequency = 0,\n warpAmount = 0,\n resolutionScale = 1\n}) {\n const ref = useRef(null);\n useEffect(() => {\n const canvas = ref.current;\n const parent = canvas.parentElement;\n\n const renderer = new Renderer({\n dpr: Math.min(window.devicePixelRatio, 2),\n canvas\n });\n\n const gl = renderer.gl;\n const geometry = new Triangle(gl);\n\n const program = new Program(gl, {\n vertex,\n fragment,\n uniforms: {\n uTime: { value: 0 },\n uResolution: { value: new Vec2() },\n uHueShift: { value: hueShift },\n uNoise: { value: noiseIntensity },\n uScan: { value: scanlineIntensity },\n uScanFreq: { value: scanlineFrequency },\n uWarp: { value: warpAmount }\n }\n });\n\n const mesh = new Mesh(gl, { geometry, program });\n\n const resize = () => {\n const w = parent.clientWidth,\n h = parent.clientHeight;\n renderer.setSize(w * resolutionScale, h * resolutionScale);\n program.uniforms.uResolution.value.set(w, h);\n };\n\n window.addEventListener('resize', resize);\n resize();\n\n const start = performance.now();\n let frame = 0;\n\n const loop = () => {\n program.uniforms.uTime.value = ((performance.now() - start) / 1000) * speed;\n program.uniforms.uHueShift.value = hueShift;\n program.uniforms.uNoise.value = noiseIntensity;\n program.uniforms.uScan.value = scanlineIntensity;\n program.uniforms.uScanFreq.value = scanlineFrequency;\n program.uniforms.uWarp.value = warpAmount;\n renderer.render({ scene: mesh });\n frame = requestAnimationFrame(loop);\n };\n\n loop();\n\n return () => {\n cancelAnimationFrame(frame);\n window.removeEventListener('resize', resize);\n };\n }, [hueShift, noiseIntensity, scanlineIntensity, speed, scanlineFrequency, warpAmount, resolutionScale]);\n return ;\n}\n" + "content": "import { useRef, useEffect } from 'react';\nimport { Renderer, Program, Mesh, Triangle, Vec2 } from 'ogl';\nimport './DarkVeil.css';\n\nconst vertex = `\nattribute vec2 position;\nvoid main(){gl_Position=vec4(position,0.0,1.0);}\n`;\n\nconst fragment = `\n#ifdef GL_ES\nprecision lowp float;\n#endif\nuniform vec2 uResolution;\nuniform float uTime;\nuniform float uHueShift;\nuniform float uNoise;\nuniform float uScan;\nuniform float uScanFreq;\nuniform float uWarp;\n#define iTime uTime\n#define iResolution uResolution\n\nvec4 buf[8];\nfloat rand(vec2 c){return fract(sin(dot(c,vec2(12.9898,78.233)))*43758.5453);}\n\nmat3 rgb2yiq=mat3(0.299,0.587,0.114,0.596,-0.274,-0.322,0.211,-0.523,0.312);\nmat3 yiq2rgb=mat3(1.0,0.956,0.621,1.0,-0.272,-0.647,1.0,-1.106,1.703);\n\nvec3 hueShiftRGB(vec3 col,float deg){\n vec3 yiq=rgb2yiq*col;\n float rad=radians(deg);\n float cosh=cos(rad),sinh=sin(rad);\n vec3 yiqShift=vec3(yiq.x,yiq.y*cosh-yiq.z*sinh,yiq.y*sinh+yiq.z*cosh);\n return clamp(yiq2rgb*yiqShift,0.0,1.0);\n}\n\nvec4 sigmoid(vec4 x){return 1./(1.+exp(-x));}\n\nvec4 cppn_fn(vec2 coordinate,float in0,float in1,float in2){\n buf[6]=vec4(coordinate.x,coordinate.y,0.3948333106474662+in0,0.36+in1);\n buf[7]=vec4(0.14+in2,sqrt(coordinate.x*coordinate.x+coordinate.y*coordinate.y),0.,0.);\n buf[0]=mat4(vec4(6.5404263,-3.6126034,0.7590882,-1.13613),vec4(2.4582713,3.1660357,1.2219609,0.06276096),vec4(-5.478085,-6.159632,1.8701609,-4.7742867),vec4(6.039214,-5.542865,-0.90925294,3.251348))*buf[6]+mat4(vec4(0.8473259,-5.722911,3.975766,1.6522468),vec4(-0.24321538,0.5839259,-1.7661959,-5.350116),vec4(0.,0.,0.,0.),vec4(0.,0.,0.,0.))*buf[7]+vec4(0.21808943,1.1243913,-1.7969975,5.0294676);\n buf[1]=mat4(vec4(-3.3522482,-6.0612736,0.55641043,-4.4719114),vec4(0.8631464,1.7432913,5.643898,1.6106541),vec4(2.4941394,-3.5012043,1.7184316,6.357333),vec4(3.310376,8.209261,1.1355612,-1.165539))*buf[6]+mat4(vec4(5.24046,-13.034365,0.009859298,15.870829),vec4(2.987511,3.129433,-0.89023495,-1.6822904),vec4(0.,0.,0.,0.),vec4(0.,0.,0.,0.))*buf[7]+vec4(-5.9457836,-6.573602,-0.8812491,1.5436668);\n buf[0]=sigmoid(buf[0]);buf[1]=sigmoid(buf[1]);\n buf[2]=mat4(vec4(-15.219568,8.095543,-2.429353,-1.9381982),vec4(-5.951362,4.3115187,2.6393783,1.274315),vec4(-7.3145227,6.7297835,5.2473326,5.9411426),vec4(5.0796127,8.979051,-1.7278991,-1.158976))*buf[6]+mat4(vec4(-11.967154,-11.608155,6.1486754,11.237008),vec4(2.124141,-6.263192,-1.7050359,-0.7021966),vec4(0.,0.,0.,0.),vec4(0.,0.,0.,0.))*buf[7]+vec4(-4.17164,-3.2281182,-4.576417,-3.6401186);\n buf[3]=mat4(vec4(3.1832156,-13.738922,1.879223,3.233465),vec4(0.64300746,12.768129,1.9141049,0.50990224),vec4(-0.049295485,4.4807224,1.4733979,1.801449),vec4(5.0039253,13.000481,3.3991797,-4.5561905))*buf[6]+mat4(vec4(-0.1285731,7.720628,-3.1425676,4.742367),vec4(0.6393625,3.714393,-0.8108378,-0.39174938),vec4(0.,0.,0.,0.),vec4(0.,0.,0.,0.))*buf[7]+vec4(-1.1811101,-21.621881,0.7851888,1.2329718);\n buf[2]=sigmoid(buf[2]);buf[3]=sigmoid(buf[3]);\n buf[4]=mat4(vec4(5.214916,-7.183024,2.7228765,2.6592617),vec4(-5.601878,-25.3591,4.067988,0.4602802),vec4(-10.57759,24.286327,21.102104,37.546658),vec4(4.3024497,-1.9625226,2.3458803,-1.372816))*buf[0]+mat4(vec4(-17.6526,-10.507558,2.2587414,12.462782),vec4(6.265566,-502.75443,-12.642513,0.9112289),vec4(-10.983244,20.741234,-9.701768,-0.7635988),vec4(5.383626,1.4819539,-4.1911616,-4.8444734))*buf[1]+mat4(vec4(12.785233,-16.345072,-0.39901125,1.7955981),vec4(-30.48365,-1.8345358,1.4542528,-1.1118771),vec4(19.872723,-7.337935,-42.941723,-98.52709),vec4(8.337645,-2.7312303,-2.2927687,-36.142323))*buf[2]+mat4(vec4(-16.298317,3.5471997,-0.44300047,-9.444417),vec4(57.5077,-35.609753,16.163465,-4.1534753),vec4(-0.07470326,-3.8656476,-7.0901804,3.1523974),vec4(-12.559385,-7.077619,1.490437,-0.8211543))*buf[3]+vec4(-7.67914,15.927437,1.3207729,-1.6686112);\n buf[5]=mat4(vec4(-1.4109162,-0.372762,-3.770383,-21.367174),vec4(-6.2103205,-9.35908,0.92529047,8.82561),vec4(11.460242,-22.348068,13.625772,-18.693201),vec4(-0.3429052,-3.9905605,-2.4626114,-0.45033523))*buf[0]+mat4(vec4(7.3481627,-4.3661838,-6.3037653,-3.868115),vec4(1.5462853,6.5488915,1.9701879,-0.58291394),vec4(6.5858274,-2.2180402,3.7127688,-1.3730392),vec4(-5.7973905,10.134961,-2.3395722,-5.965605))*buf[1]+mat4(vec4(-2.5132585,-6.6685553,-1.4029363,-0.16285264),vec4(-0.37908727,0.53738135,4.389061,-1.3024765),vec4(-0.70647055,2.0111287,-5.1659346,-3.728635),vec4(-13.562562,10.487719,-0.9173751,-2.6487076))*buf[2]+mat4(vec4(-8.645013,6.5546675,-6.3944063,-5.5933375),vec4(-0.57783127,-1.077275,36.91025,5.736769),vec4(14.283112,3.7146652,7.1452246,-4.5958776),vec4(2.7192075,3.6021907,-4.366337,-2.3653464))*buf[3]+vec4(-5.9000807,-4.329569,1.2427121,8.59503);\n buf[4]=sigmoid(buf[4]);buf[5]=sigmoid(buf[5]);\n buf[6]=mat4(vec4(-1.61102,0.7970257,1.4675229,0.20917463),vec4(-28.793737,-7.1390953,1.5025433,4.656581),vec4(-10.94861,39.66238,0.74318546,-10.095605),vec4(-0.7229728,-1.5483948,0.7301322,2.1687684))*buf[0]+mat4(vec4(3.2547753,21.489103,-1.0194173,-3.3100595),vec4(-3.7316632,-3.3792162,-7.223193,-0.23685838),vec4(13.1804495,0.7916005,5.338587,5.687114),vec4(-4.167605,-17.798311,-6.815736,-1.6451967))*buf[1]+mat4(vec4(0.604885,-7.800309,-7.213122,-2.741014),vec4(-3.522382,-0.12359311,-0.5258442,0.43852118),vec4(9.6752825,-22.853785,2.062431,0.099892326),vec4(-4.3196306,-17.730087,2.5184598,5.30267))*buf[2]+mat4(vec4(-6.545563,-15.790176,-6.0438633,-5.415399),vec4(-43.591583,28.551912,-16.00161,18.84728),vec4(4.212382,8.394307,3.0958717,8.657522),vec4(-5.0237565,-4.450633,-4.4768,-5.5010443))*buf[3]+mat4(vec4(1.6985557,-67.05806,6.897715,1.9004834),vec4(1.8680354,2.3915145,2.5231109,4.081538),vec4(11.158006,1.7294737,2.0738268,7.386411),vec4(-4.256034,-306.24686,8.258898,-17.132736))*buf[4]+mat4(vec4(1.6889864,-4.5852966,3.8534803,-6.3482175),vec4(1.3543309,-1.2640043,9.932754,2.9079645),vec4(-5.2770967,0.07150358,-0.13962056,3.3269649),vec4(28.34703,-4.918278,6.1044083,4.085355))*buf[5]+vec4(6.6818056,12.522166,-3.7075126,-4.104386);\n buf[7]=mat4(vec4(-8.265602,-4.7027016,5.098234,0.7509808),vec4(8.6507845,-17.15949,16.51939,-8.884479),vec4(-4.036479,-2.3946867,-2.6055532,-1.9866527),vec4(-2.2167742,-1.8135649,-5.9759874,4.8846445))*buf[0]+mat4(vec4(6.7790847,3.5076547,-2.8191125,-2.7028968),vec4(-5.743024,-0.27844876,1.4958696,-5.0517144),vec4(13.122226,15.735168,-2.9397483,-4.101023),vec4(-14.375265,-5.030483,-6.2599335,2.9848232))*buf[1]+mat4(vec4(4.0950394,-0.94011575,-5.674733,4.755022),vec4(4.3809423,4.8310084,1.7425908,-3.437416),vec4(2.117492,0.16342592,-104.56341,16.949184),vec4(-5.22543,-2.994248,3.8350096,-1.9364246))*buf[2]+mat4(vec4(-5.900337,1.7946124,-13.604192,-3.8060522),vec4(6.6583457,31.911177,25.164474,91.81147),vec4(11.840538,4.1503043,-0.7314397,6.768467),vec4(-6.3967767,4.034772,6.1714606,-0.32874924))*buf[3]+mat4(vec4(3.4992442,-196.91893,-8.923708,2.8142626),vec4(3.4806502,-3.1846354,5.1725626,5.1804223),vec4(-2.4009497,15.585794,1.2863957,2.0252278),vec4(-71.25271,-62.441242,-8.138444,0.50670296))*buf[4]+mat4(vec4(-12.291733,-11.176166,-7.3474145,4.390294),vec4(10.805477,5.6337385,-0.9385842,-4.7348723),vec4(-12.869276,-7.039391,5.3029537,7.5436664),vec4(1.4593618,8.91898,3.5101583,5.840625))*buf[5]+vec4(2.2415268,-6.705987,-0.98861027,-2.117676);\n buf[6]=sigmoid(buf[6]);buf[7]=sigmoid(buf[7]);\n buf[0]=mat4(vec4(1.6794263,1.3817469,2.9625452,0.),vec4(-1.8834411,-1.4806935,-3.5924516,0.),vec4(-1.3279216,-1.0918057,-2.3124623,0.),vec4(0.2662234,0.23235129,0.44178495,0.))*buf[0]+mat4(vec4(-0.6299101,-0.5945583,-0.9125601,0.),vec4(0.17828953,0.18300213,0.18182953,0.),vec4(-2.96544,-2.5819945,-4.9001055,0.),vec4(1.4195864,1.1868085,2.5176322,0.))*buf[1]+mat4(vec4(-1.2584374,-1.0552157,-2.1688404,0.),vec4(-0.7200217,-0.52666044,-1.438251,0.),vec4(0.15345335,0.15196142,0.272854,0.),vec4(0.945728,0.8861938,1.2766753,0.))*buf[2]+mat4(vec4(-2.4218085,-1.968602,-4.35166,0.),vec4(-22.683098,-18.0544,-41.954372,0.),vec4(0.63792,0.5470648,1.1078634,0.),vec4(-1.5489894,-1.3075932,-2.6444845,0.))*buf[3]+mat4(vec4(-0.49252132,-0.39877754,-0.91366625,0.),vec4(0.95609266,0.7923952,1.640221,0.),vec4(0.30616966,0.15693925,0.8639857,0.),vec4(1.1825981,0.94504964,2.176963,0.))*buf[4]+mat4(vec4(0.35446745,0.3293795,0.59547555,0.),vec4(-0.58784515,-0.48177817,-1.0614829,0.),vec4(2.5271258,1.9991658,4.6846647,0.),vec4(0.13042648,0.08864098,0.30187556,0.))*buf[5]+mat4(vec4(-1.7718065,-1.4033192,-3.3355875,0.),vec4(3.1664357,2.638297,5.378702,0.),vec4(-3.1724713,-2.6107926,-5.549295,0.),vec4(-2.851368,-2.249092,-5.3013067,0.))*buf[6]+mat4(vec4(1.5203838,1.2212278,2.8404984,0.),vec4(1.5210563,1.2651345,2.683903,0.),vec4(2.9789467,2.4364579,5.2347264,0.),vec4(2.2270417,1.8825914,3.8028636,0.))*buf[7]+vec4(-1.5468478,-3.6171484,0.24762098,0.);\n buf[0]=sigmoid(buf[0]);\n return vec4(buf[0].x,buf[0].y,buf[0].z,1.);\n}\n\nvoid mainImage(out vec4 fragColor,in vec2 fragCoord){\n vec2 uv=fragCoord/uResolution.xy*2.-1.;\n uv.x *= uResolution.x / uResolution.y;\n uv.y*=-1.;\n uv+=uWarp*vec2(sin(uv.y*6.283+uTime*0.5),cos(uv.x*6.283+uTime*0.5))*0.05;\n fragColor=cppn_fn(uv,0.1*sin(0.3*uTime),0.1*sin(0.69*uTime),0.1*sin(0.44*uTime));\n}\n\nvoid main(){\n vec4 col;mainImage(col,gl_FragCoord.xy);\n col.rgb=hueShiftRGB(col.rgb,uHueShift);\n float scanline_val=sin(gl_FragCoord.y*uScanFreq)*0.5+0.5;\n col.rgb*=1.-(scanline_val*scanline_val)*uScan;\n col.rgb+=(rand(gl_FragCoord.xy+uTime)-0.5)*uNoise;\n gl_FragColor=vec4(clamp(col.rgb,0.0,1.0),1.0);\n}\n`;\n\nexport default function DarkVeil({\n hueShift = 0,\n noiseIntensity = 0,\n scanlineIntensity = 0,\n speed = 0.5,\n scanlineFrequency = 0,\n warpAmount = 0,\n resolutionScale = 1\n }) {\n const ref = useRef(null);\n useEffect(() => {\n const canvas = ref.current;\n const parent = canvas.parentElement;\n\n const renderer = new Renderer({\n dpr: Math.min(window.devicePixelRatio, 2),\n canvas\n });\n\n const gl = renderer.gl;\n const geometry = new Triangle(gl);\n\n const program = new Program(gl, {\n vertex,\n fragment,\n uniforms: {\n uTime: { value: 0 },\n uResolution: { value: new Vec2() },\n uHueShift: { value: hueShift },\n uNoise: { value: noiseIntensity },\n uScan: { value: scanlineIntensity },\n uScanFreq: { value: scanlineFrequency },\n uWarp: { value: warpAmount }\n }\n });\n\n const mesh = new Mesh(gl, { geometry, program });\n\n const resize = () => {\n const w = parent.clientWidth,\n h = parent.clientHeight;\n renderer.setSize(w * resolutionScale, h * resolutionScale);\n program.uniforms.uResolution.value.set(w, h);\n };\n\n window.addEventListener('resize', resize);\n resize();\n\n const start = performance.now();\n let frame = 0;\n\n const loop = () => {\n program.uniforms.uTime.value = ((performance.now() - start) / 1000) * speed;\n program.uniforms.uHueShift.value = hueShift;\n program.uniforms.uNoise.value = noiseIntensity;\n program.uniforms.uScan.value = scanlineIntensity;\n program.uniforms.uScanFreq.value = scanlineFrequency;\n program.uniforms.uWarp.value = warpAmount;\n renderer.render({ scene: mesh });\n frame = requestAnimationFrame(loop);\n };\n\n loop();\n\n return () => {\n cancelAnimationFrame(frame);\n window.removeEventListener('resize', resize);\n };\n }, [hueShift, noiseIntensity, scanlineIntensity, speed, scanlineFrequency, warpAmount, resolutionScale]);\n\n return ;\n}" } ], "registryDependencies": [], diff --git a/public/r/DarkVeil-TS-CSS.json b/public/r/DarkVeil-TS-CSS.json index 267c60db6..c1b4f12d9 100644 --- a/public/r/DarkVeil-TS-CSS.json +++ b/public/r/DarkVeil-TS-CSS.json @@ -13,7 +13,7 @@ { "type": "registry:component", "path": "DarkVeil/DarkVeil.tsx", - "content": "import { useRef, useEffect } from 'react';\nimport { Renderer, Program, Mesh, Triangle, Vec2 } from 'ogl';\nimport './DarkVeil.css';\n\nconst vertex = `\nattribute vec2 position;\nvoid main(){gl_Position=vec4(position,0.0,1.0);}\n`;\n\nconst fragment = `\n#ifdef GL_ES\nprecision lowp float;\n#endif\nuniform vec2 uResolution;\nuniform float uTime;\nuniform float uHueShift;\nuniform float uNoise;\nuniform float uScan;\nuniform float uScanFreq;\nuniform float uWarp;\n#define iTime uTime\n#define iResolution uResolution\n\nvec4 buf[8];\nfloat rand(vec2 c){return fract(sin(dot(c,vec2(12.9898,78.233)))*43758.5453);}\n\nmat3 rgb2yiq=mat3(0.299,0.587,0.114,0.596,-0.274,-0.322,0.211,-0.523,0.312);\nmat3 yiq2rgb=mat3(1.0,0.956,0.621,1.0,-0.272,-0.647,1.0,-1.106,1.703);\n\nvec3 hueShiftRGB(vec3 col,float deg){\n vec3 yiq=rgb2yiq*col;\n float rad=radians(deg);\n float cosh=cos(rad),sinh=sin(rad);\n vec3 yiqShift=vec3(yiq.x,yiq.y*cosh-yiq.z*sinh,yiq.y*sinh+yiq.z*cosh);\n return clamp(yiq2rgb*yiqShift,0.0,1.0);\n}\n\nvec4 sigmoid(vec4 x){return 1./(1.+exp(-x));}\n\nvec4 cppn_fn(vec2 coordinate,float in0,float in1,float in2){\n buf[6]=vec4(coordinate.x,coordinate.y,0.3948333106474662+in0,0.36+in1);\n buf[7]=vec4(0.14+in2,sqrt(coordinate.x*coordinate.x+coordinate.y*coordinate.y),0.,0.);\n buf[0]=mat4(vec4(6.5404263,-3.6126034,0.7590882,-1.13613),vec4(2.4582713,3.1660357,1.2219609,0.06276096),vec4(-5.478085,-6.159632,1.8701609,-4.7742867),vec4(6.039214,-5.542865,-0.90925294,3.251348))*buf[6]+mat4(vec4(0.8473259,-5.722911,3.975766,1.6522468),vec4(-0.24321538,0.5839259,-1.7661959,-5.350116),vec4(0.,0.,0.,0.),vec4(0.,0.,0.,0.))*buf[7]+vec4(0.21808943,1.1243913,-1.7969975,5.0294676);\n buf[1]=mat4(vec4(-3.3522482,-6.0612736,0.55641043,-4.4719114),vec4(0.8631464,1.7432913,5.643898,1.6106541),vec4(2.4941394,-3.5012043,1.7184316,6.357333),vec4(3.310376,8.209261,1.1355612,-1.165539))*buf[6]+mat4(vec4(5.24046,-13.034365,0.009859298,15.870829),vec4(2.987511,3.129433,-0.89023495,-1.6822904),vec4(0.,0.,0.,0.),vec4(0.,0.,0.,0.))*buf[7]+vec4(-5.9457836,-6.573602,-0.8812491,1.5436668);\n buf[0]=sigmoid(buf[0]);buf[1]=sigmoid(buf[1]);\n buf[2]=mat4(vec4(-15.219568,8.095543,-2.429353,-1.9381982),vec4(-5.951362,4.3115187,2.6393783,1.274315),vec4(-7.3145227,6.7297835,5.2473326,5.9411426),vec4(5.0796127,8.979051,-1.7278991,-1.158976))*buf[6]+mat4(vec4(-11.967154,-11.608155,6.1486754,11.237008),vec4(2.124141,-6.263192,-1.7050359,-0.7021966),vec4(0.,0.,0.,0.),vec4(0.,0.,0.,0.))*buf[7]+vec4(-4.17164,-3.2281182,-4.576417,-3.6401186);\n buf[3]=mat4(vec4(3.1832156,-13.738922,1.879223,3.233465),vec4(0.64300746,12.768129,1.9141049,0.50990224),vec4(-0.049295485,4.4807224,1.4733979,1.801449),vec4(5.0039253,13.000481,3.3991797,-4.5561905))*buf[6]+mat4(vec4(-0.1285731,7.720628,-3.1425676,4.742367),vec4(0.6393625,3.714393,-0.8108378,-0.39174938),vec4(0.,0.,0.,0.),vec4(0.,0.,0.,0.))*buf[7]+vec4(-1.1811101,-21.621881,0.7851888,1.2329718);\n buf[2]=sigmoid(buf[2]);buf[3]=sigmoid(buf[3]);\n buf[4]=mat4(vec4(5.214916,-7.183024,2.7228765,2.6592617),vec4(-5.601878,-25.3591,4.067988,0.4602802),vec4(-10.57759,24.286327,21.102104,37.546658),vec4(4.3024497,-1.9625226,2.3458803,-1.372816))*buf[0]+mat4(vec4(-17.6526,-10.507558,2.2587414,12.462782),vec4(6.265566,-502.75443,-12.642513,0.9112289),vec4(-10.983244,20.741234,-9.701768,-0.7635988),vec4(5.383626,1.4819539,-4.1911616,-4.8444734))*buf[1]+mat4(vec4(12.785233,-16.345072,-0.39901125,1.7955981),vec4(-30.48365,-1.8345358,1.4542528,-1.1118771),vec4(19.872723,-7.337935,-42.941723,-98.52709),vec4(8.337645,-2.7312303,-2.2927687,-36.142323))*buf[2]+mat4(vec4(-16.298317,3.5471997,-0.44300047,-9.444417),vec4(57.5077,-35.609753,16.163465,-4.1534753),vec4(-0.07470326,-3.8656476,-7.0901804,3.1523974),vec4(-12.559385,-7.077619,1.490437,-0.8211543))*buf[3]+vec4(-7.67914,15.927437,1.3207729,-1.6686112);\n buf[5]=mat4(vec4(-1.4109162,-0.372762,-3.770383,-21.367174),vec4(-6.2103205,-9.35908,0.92529047,8.82561),vec4(11.460242,-22.348068,13.625772,-18.693201),vec4(-0.3429052,-3.9905605,-2.4626114,-0.45033523))*buf[0]+mat4(vec4(7.3481627,-4.3661838,-6.3037653,-3.868115),vec4(1.5462853,6.5488915,1.9701879,-0.58291394),vec4(6.5858274,-2.2180402,3.7127688,-1.3730392),vec4(-5.7973905,10.134961,-2.3395722,-5.965605))*buf[1]+mat4(vec4(-2.5132585,-6.6685553,-1.4029363,-0.16285264),vec4(-0.37908727,0.53738135,4.389061,-1.3024765),vec4(-0.70647055,2.0111287,-5.1659346,-3.728635),vec4(-13.562562,10.487719,-0.9173751,-2.6487076))*buf[2]+mat4(vec4(-8.645013,6.5546675,-6.3944063,-5.5933375),vec4(-0.57783127,-1.077275,36.91025,5.736769),vec4(14.283112,3.7146652,7.1452246,-4.5958776),vec4(2.7192075,3.6021907,-4.366337,-2.3653464))*buf[3]+vec4(-5.9000807,-4.329569,1.2427121,8.59503);\n buf[4]=sigmoid(buf[4]);buf[5]=sigmoid(buf[5]);\n buf[6]=mat4(vec4(-1.61102,0.7970257,1.4675229,0.20917463),vec4(-28.793737,-7.1390953,1.5025433,4.656581),vec4(-10.94861,39.66238,0.74318546,-10.095605),vec4(-0.7229728,-1.5483948,0.7301322,2.1687684))*buf[0]+mat4(vec4(3.2547753,21.489103,-1.0194173,-3.3100595),vec4(-3.7316632,-3.3792162,-7.223193,-0.23685838),vec4(13.1804495,0.7916005,5.338587,5.687114),vec4(-4.167605,-17.798311,-6.815736,-1.6451967))*buf[1]+mat4(vec4(0.604885,-7.800309,-7.213122,-2.741014),vec4(-3.522382,-0.12359311,-0.5258442,0.43852118),vec4(9.6752825,-22.853785,2.062431,0.099892326),vec4(-4.3196306,-17.730087,2.5184598,5.30267))*buf[2]+mat4(vec4(-6.545563,-15.790176,-6.0438633,-5.415399),vec4(-43.591583,28.551912,-16.00161,18.84728),vec4(4.212382,8.394307,3.0958717,8.657522),vec4(-5.0237565,-4.450633,-4.4768,-5.5010443))*buf[3]+mat4(vec4(1.6985557,-67.05806,6.897715,1.9004834),vec4(1.8680354,2.3915145,2.5231109,4.081538),vec4(11.158006,1.7294737,2.0738268,7.386411),vec4(-4.256034,-306.24686,8.258898,-17.132736))*buf[4]+mat4(vec4(1.6889864,-4.5852966,3.8534803,-6.3482175),vec4(1.3543309,-1.2640043,9.932754,2.9079645),vec4(-5.2770967,0.07150358,-0.13962056,3.3269649),vec4(28.34703,-4.918278,6.1044083,4.085355))*buf[5]+vec4(6.6818056,12.522166,-3.7075126,-4.104386);\n buf[7]=mat4(vec4(-8.265602,-4.7027016,5.098234,0.7509808),vec4(8.6507845,-17.15949,16.51939,-8.884479),vec4(-4.036479,-2.3946867,-2.6055532,-1.9866527),vec4(-2.2167742,-1.8135649,-5.9759874,4.8846445))*buf[0]+mat4(vec4(6.7790847,3.5076547,-2.8191125,-2.7028968),vec4(-5.743024,-0.27844876,1.4958696,-5.0517144),vec4(13.122226,15.735168,-2.9397483,-4.101023),vec4(-14.375265,-5.030483,-6.2599335,2.9848232))*buf[1]+mat4(vec4(4.0950394,-0.94011575,-5.674733,4.755022),vec4(4.3809423,4.8310084,1.7425908,-3.437416),vec4(2.117492,0.16342592,-104.56341,16.949184),vec4(-5.22543,-2.994248,3.8350096,-1.9364246))*buf[2]+mat4(vec4(-5.900337,1.7946124,-13.604192,-3.8060522),vec4(6.6583457,31.911177,25.164474,91.81147),vec4(11.840538,4.1503043,-0.7314397,6.768467),vec4(-6.3967767,4.034772,6.1714606,-0.32874924))*buf[3]+mat4(vec4(3.4992442,-196.91893,-8.923708,2.8142626),vec4(3.4806502,-3.1846354,5.1725626,5.1804223),vec4(-2.4009497,15.585794,1.2863957,2.0252278),vec4(-71.25271,-62.441242,-8.138444,0.50670296))*buf[4]+mat4(vec4(-12.291733,-11.176166,-7.3474145,4.390294),vec4(10.805477,5.6337385,-0.9385842,-4.7348723),vec4(-12.869276,-7.039391,5.3029537,7.5436664),vec4(1.4593618,8.91898,3.5101583,5.840625))*buf[5]+vec4(2.2415268,-6.705987,-0.98861027,-2.117676);\n buf[6]=sigmoid(buf[6]);buf[7]=sigmoid(buf[7]);\n buf[0]=mat4(vec4(1.6794263,1.3817469,2.9625452,0.),vec4(-1.8834411,-1.4806935,-3.5924516,0.),vec4(-1.3279216,-1.0918057,-2.3124623,0.),vec4(0.2662234,0.23235129,0.44178495,0.))*buf[0]+mat4(vec4(-0.6299101,-0.5945583,-0.9125601,0.),vec4(0.17828953,0.18300213,0.18182953,0.),vec4(-2.96544,-2.5819945,-4.9001055,0.),vec4(1.4195864,1.1868085,2.5176322,0.))*buf[1]+mat4(vec4(-1.2584374,-1.0552157,-2.1688404,0.),vec4(-0.7200217,-0.52666044,-1.438251,0.),vec4(0.15345335,0.15196142,0.272854,0.),vec4(0.945728,0.8861938,1.2766753,0.))*buf[2]+mat4(vec4(-2.4218085,-1.968602,-4.35166,0.),vec4(-22.683098,-18.0544,-41.954372,0.),vec4(0.63792,0.5470648,1.1078634,0.),vec4(-1.5489894,-1.3075932,-2.6444845,0.))*buf[3]+mat4(vec4(-0.49252132,-0.39877754,-0.91366625,0.),vec4(0.95609266,0.7923952,1.640221,0.),vec4(0.30616966,0.15693925,0.8639857,0.),vec4(1.1825981,0.94504964,2.176963,0.))*buf[4]+mat4(vec4(0.35446745,0.3293795,0.59547555,0.),vec4(-0.58784515,-0.48177817,-1.0614829,0.),vec4(2.5271258,1.9991658,4.6846647,0.),vec4(0.13042648,0.08864098,0.30187556,0.))*buf[5]+mat4(vec4(-1.7718065,-1.4033192,-3.3355875,0.),vec4(3.1664357,2.638297,5.378702,0.),vec4(-3.1724713,-2.6107926,-5.549295,0.),vec4(-2.851368,-2.249092,-5.3013067,0.))*buf[6]+mat4(vec4(1.5203838,1.2212278,2.8404984,0.),vec4(1.5210563,1.2651345,2.683903,0.),vec4(2.9789467,2.4364579,5.2347264,0.),vec4(2.2270417,1.8825914,3.8028636,0.))*buf[7]+vec4(-1.5468478,-3.6171484,0.24762098,0.);\n buf[0]=sigmoid(buf[0]);\n return vec4(buf[0].x,buf[0].y,buf[0].z,1.);\n}\n\nvoid mainImage(out vec4 fragColor,in vec2 fragCoord){\n vec2 uv=fragCoord/uResolution.xy*2.-1.;\n uv.y*=-1.;\n uv+=uWarp*vec2(sin(uv.y*6.283+uTime*0.5),cos(uv.x*6.283+uTime*0.5))*0.05;\n fragColor=cppn_fn(uv,0.1*sin(0.3*uTime),0.1*sin(0.69*uTime),0.1*sin(0.44*uTime));\n}\n\nvoid main(){\n vec4 col;mainImage(col,gl_FragCoord.xy);\n col.rgb=hueShiftRGB(col.rgb,uHueShift);\n float scanline_val=sin(gl_FragCoord.y*uScanFreq)*0.5+0.5;\n col.rgb*=1.-(scanline_val*scanline_val)*uScan;\n col.rgb+=(rand(gl_FragCoord.xy+uTime)-0.5)*uNoise;\n gl_FragColor=vec4(clamp(col.rgb,0.0,1.0),1.0);\n}\n`;\n\ntype Props = {\n hueShift?: number;\n noiseIntensity?: number;\n scanlineIntensity?: number;\n speed?: number;\n scanlineFrequency?: number;\n warpAmount?: number;\n resolutionScale?: number;\n};\n\nexport default function DarkVeil({\n hueShift = 0,\n noiseIntensity = 0,\n scanlineIntensity = 0,\n speed = 0.5,\n scanlineFrequency = 0,\n warpAmount = 0,\n resolutionScale = 1\n}: Props) {\n const ref = useRef(null);\n useEffect(() => {\n const canvas = ref.current as HTMLCanvasElement;\n const parent = canvas.parentElement as HTMLElement;\n\n const renderer = new Renderer({\n dpr: Math.min(window.devicePixelRatio, 2),\n canvas\n });\n\n const gl = renderer.gl;\n const geometry = new Triangle(gl);\n\n const program = new Program(gl, {\n vertex,\n fragment,\n uniforms: {\n uTime: { value: 0 },\n uResolution: { value: new Vec2() },\n uHueShift: { value: hueShift },\n uNoise: { value: noiseIntensity },\n uScan: { value: scanlineIntensity },\n uScanFreq: { value: scanlineFrequency },\n uWarp: { value: warpAmount }\n }\n });\n\n const mesh = new Mesh(gl, { geometry, program });\n\n const resize = () => {\n const w = parent.clientWidth,\n h = parent.clientHeight;\n renderer.setSize(w * resolutionScale, h * resolutionScale);\n program.uniforms.uResolution.value.set(w, h);\n };\n\n window.addEventListener('resize', resize);\n resize();\n\n const start = performance.now();\n let frame = 0;\n\n const loop = () => {\n program.uniforms.uTime.value = ((performance.now() - start) / 1000) * speed;\n program.uniforms.uHueShift.value = hueShift;\n program.uniforms.uNoise.value = noiseIntensity;\n program.uniforms.uScan.value = scanlineIntensity;\n program.uniforms.uScanFreq.value = scanlineFrequency;\n program.uniforms.uWarp.value = warpAmount;\n renderer.render({ scene: mesh });\n frame = requestAnimationFrame(loop);\n };\n\n loop();\n\n return () => {\n cancelAnimationFrame(frame);\n window.removeEventListener('resize', resize);\n };\n }, [hueShift, noiseIntensity, scanlineIntensity, speed, scanlineFrequency, warpAmount, resolutionScale]);\n return ;\n}\n" + "content": "import { useRef, useEffect } from 'react';\nimport { Renderer, Program, Mesh, Triangle, Vec2 } from 'ogl';\nimport './DarkVeil.css';\n\nconst vertex = `\nattribute vec2 position;\nvoid main(){gl_Position=vec4(position,0.0,1.0);}\n`;\n\nconst fragment = `\n#ifdef GL_ES\nprecision lowp float;\n#endif\nuniform vec2 uResolution;\nuniform float uTime;\nuniform float uHueShift;\nuniform float uNoise;\nuniform float uScan;\nuniform float uScanFreq;\nuniform float uWarp;\n#define iTime uTime\n#define iResolution uResolution\n\nvec4 buf[8];\nfloat rand(vec2 c){return fract(sin(dot(c,vec2(12.9898,78.233)))*43758.5453);}\n\nmat3 rgb2yiq=mat3(0.299,0.587,0.114,0.596,-0.274,-0.322,0.211,-0.523,0.312);\nmat3 yiq2rgb=mat3(1.0,0.956,0.621,1.0,-0.272,-0.647,1.0,-1.106,1.703);\n\nvec3 hueShiftRGB(vec3 col,float deg){\n vec3 yiq=rgb2yiq*col;\n float rad=radians(deg);\n float cosh=cos(rad),sinh=sin(rad);\n vec3 yiqShift=vec3(yiq.x,yiq.y*cosh-yiq.z*sinh,yiq.y*sinh+yiq.z*cosh);\n return clamp(yiq2rgb*yiqShift,0.0,1.0);\n}\n\nvec4 sigmoid(vec4 x){return 1./(1.+exp(-x));}\n\nvec4 cppn_fn(vec2 coordinate,float in0,float in1,float in2){\n buf[6]=vec4(coordinate.x,coordinate.y,0.3948333106474662+in0,0.36+in1);\n buf[7]=vec4(0.14+in2,sqrt(coordinate.x*coordinate.x+coordinate.y*coordinate.y),0.,0.);\n buf[0]=mat4(vec4(6.5404263,-3.6126034,0.7590882,-1.13613),vec4(2.4582713,3.1660357,1.2219609,0.06276096),vec4(-5.478085,-6.159632,1.8701609,-4.7742867),vec4(6.039214,-5.542865,-0.90925294,3.251348))*buf[6]+mat4(vec4(0.8473259,-5.722911,3.975766,1.6522468),vec4(-0.24321538,0.5839259,-1.7661959,-5.350116),vec4(0.,0.,0.,0.),vec4(0.,0.,0.,0.))*buf[7]+vec4(0.21808943,1.1243913,-1.7969975,5.0294676);\n buf[1]=mat4(vec4(-3.3522482,-6.0612736,0.55641043,-4.4719114),vec4(0.8631464,1.7432913,5.643898,1.6106541),vec4(2.4941394,-3.5012043,1.7184316,6.357333),vec4(3.310376,8.209261,1.1355612,-1.165539))*buf[6]+mat4(vec4(5.24046,-13.034365,0.009859298,15.870829),vec4(2.987511,3.129433,-0.89023495,-1.6822904),vec4(0.,0.,0.,0.),vec4(0.,0.,0.,0.))*buf[7]+vec4(-5.9457836,-6.573602,-0.8812491,1.5436668);\n buf[0]=sigmoid(buf[0]);buf[1]=sigmoid(buf[1]);\n buf[2]=mat4(vec4(-15.219568,8.095543,-2.429353,-1.9381982),vec4(-5.951362,4.3115187,2.6393783,1.274315),vec4(-7.3145227,6.7297835,5.2473326,5.9411426),vec4(5.0796127,8.979051,-1.7278991,-1.158976))*buf[6]+mat4(vec4(-11.967154,-11.608155,6.1486754,11.237008),vec4(2.124141,-6.263192,-1.7050359,-0.7021966),vec4(0.,0.,0.,0.),vec4(0.,0.,0.,0.))*buf[7]+vec4(-4.17164,-3.2281182,-4.576417,-3.6401186);\n buf[3]=mat4(vec4(3.1832156,-13.738922,1.879223,3.233465),vec4(0.64300746,12.768129,1.9141049,0.50990224),vec4(-0.049295485,4.4807224,1.4733979,1.801449),vec4(5.0039253,13.000481,3.3991797,-4.5561905))*buf[6]+mat4(vec4(-0.1285731,7.720628,-3.1425676,4.742367),vec4(0.6393625,3.714393,-0.8108378,-0.39174938),vec4(0.,0.,0.,0.),vec4(0.,0.,0.,0.))*buf[7]+vec4(-1.1811101,-21.621881,0.7851888,1.2329718);\n buf[2]=sigmoid(buf[2]);buf[3]=sigmoid(buf[3]);\n buf[4]=mat4(vec4(5.214916,-7.183024,2.7228765,2.6592617),vec4(-5.601878,-25.3591,4.067988,0.4602802),vec4(-10.57759,24.286327,21.102104,37.546658),vec4(4.3024497,-1.9625226,2.3458803,-1.372816))*buf[0]+mat4(vec4(-17.6526,-10.507558,2.2587414,12.462782),vec4(6.265566,-502.75443,-12.642513,0.9112289),vec4(-10.983244,20.741234,-9.701768,-0.7635988),vec4(5.383626,1.4819539,-4.1911616,-4.8444734))*buf[1]+mat4(vec4(12.785233,-16.345072,-0.39901125,1.7955981),vec4(-30.48365,-1.8345358,1.4542528,-1.1118771),vec4(19.872723,-7.337935,-42.941723,-98.52709),vec4(8.337645,-2.7312303,-2.2927687,-36.142323))*buf[2]+mat4(vec4(-16.298317,3.5471997,-0.44300047,-9.444417),vec4(57.5077,-35.609753,16.163465,-4.1534753),vec4(-0.07470326,-3.8656476,-7.0901804,3.1523974),vec4(-12.559385,-7.077619,1.490437,-0.8211543))*buf[3]+vec4(-7.67914,15.927437,1.3207729,-1.6686112);\n buf[5]=mat4(vec4(-1.4109162,-0.372762,-3.770383,-21.367174),vec4(-6.2103205,-9.35908,0.92529047,8.82561),vec4(11.460242,-22.348068,13.625772,-18.693201),vec4(-0.3429052,-3.9905605,-2.4626114,-0.45033523))*buf[0]+mat4(vec4(7.3481627,-4.3661838,-6.3037653,-3.868115),vec4(1.5462853,6.5488915,1.9701879,-0.58291394),vec4(6.5858274,-2.2180402,3.7127688,-1.3730392),vec4(-5.7973905,10.134961,-2.3395722,-5.965605))*buf[1]+mat4(vec4(-2.5132585,-6.6685553,-1.4029363,-0.16285264),vec4(-0.37908727,0.53738135,4.389061,-1.3024765),vec4(-0.70647055,2.0111287,-5.1659346,-3.728635),vec4(-13.562562,10.487719,-0.9173751,-2.6487076))*buf[2]+mat4(vec4(-8.645013,6.5546675,-6.3944063,-5.5933375),vec4(-0.57783127,-1.077275,36.91025,5.736769),vec4(14.283112,3.7146652,7.1452246,-4.5958776),vec4(2.7192075,3.6021907,-4.366337,-2.3653464))*buf[3]+vec4(-5.9000807,-4.329569,1.2427121,8.59503);\n buf[4]=sigmoid(buf[4]);buf[5]=sigmoid(buf[5]);\n buf[6]=mat4(vec4(-1.61102,0.7970257,1.4675229,0.20917463),vec4(-28.793737,-7.1390953,1.5025433,4.656581),vec4(-10.94861,39.66238,0.74318546,-10.095605),vec4(-0.7229728,-1.5483948,0.7301322,2.1687684))*buf[0]+mat4(vec4(3.2547753,21.489103,-1.0194173,-3.3100595),vec4(-3.7316632,-3.3792162,-7.223193,-0.23685838),vec4(13.1804495,0.7916005,5.338587,5.687114),vec4(-4.167605,-17.798311,-6.815736,-1.6451967))*buf[1]+mat4(vec4(0.604885,-7.800309,-7.213122,-2.741014),vec4(-3.522382,-0.12359311,-0.5258442,0.43852118),vec4(9.6752825,-22.853785,2.062431,0.099892326),vec4(-4.3196306,-17.730087,2.5184598,5.30267))*buf[2]+mat4(vec4(-6.545563,-15.790176,-6.0438633,-5.415399),vec4(-43.591583,28.551912,-16.00161,18.84728),vec4(4.212382,8.394307,3.0958717,8.657522),vec4(-5.0237565,-4.450633,-4.4768,-5.5010443))*buf[3]+mat4(vec4(1.6985557,-67.05806,6.897715,1.9004834),vec4(1.8680354,2.3915145,2.5231109,4.081538),vec4(11.158006,1.7294737,2.0738268,7.386411),vec4(-4.256034,-306.24686,8.258898,-17.132736))*buf[4]+mat4(vec4(1.6889864,-4.5852966,3.8534803,-6.3482175),vec4(1.3543309,-1.2640043,9.932754,2.9079645),vec4(-5.2770967,0.07150358,-0.13962056,3.3269649),vec4(28.34703,-4.918278,6.1044083,4.085355))*buf[5]+vec4(6.6818056,12.522166,-3.7075126,-4.104386);\n buf[7]=mat4(vec4(-8.265602,-4.7027016,5.098234,0.7509808),vec4(8.6507845,-17.15949,16.51939,-8.884479),vec4(-4.036479,-2.3946867,-2.6055532,-1.9866527),vec4(-2.2167742,-1.8135649,-5.9759874,4.8846445))*buf[0]+mat4(vec4(6.7790847,3.5076547,-2.8191125,-2.7028968),vec4(-5.743024,-0.27844876,1.4958696,-5.0517144),vec4(13.122226,15.735168,-2.9397483,-4.101023),vec4(-14.375265,-5.030483,-6.2599335,2.9848232))*buf[1]+mat4(vec4(4.0950394,-0.94011575,-5.674733,4.755022),vec4(4.3809423,4.8310084,1.7425908,-3.437416),vec4(2.117492,0.16342592,-104.56341,16.949184),vec4(-5.22543,-2.994248,3.8350096,-1.9364246))*buf[2]+mat4(vec4(-5.900337,1.7946124,-13.604192,-3.8060522),vec4(6.6583457,31.911177,25.164474,91.81147),vec4(11.840538,4.1503043,-0.7314397,6.768467),vec4(-6.3967767,4.034772,6.1714606,-0.32874924))*buf[3]+mat4(vec4(3.4992442,-196.91893,-8.923708,2.8142626),vec4(3.4806502,-3.1846354,5.1725626,5.1804223),vec4(-2.4009497,15.585794,1.2863957,2.0252278),vec4(-71.25271,-62.441242,-8.138444,0.50670296))*buf[4]+mat4(vec4(-12.291733,-11.176166,-7.3474145,4.390294),vec4(10.805477,5.6337385,-0.9385842,-4.7348723),vec4(-12.869276,-7.039391,5.3029537,7.5436664),vec4(1.4593618,8.91898,3.5101583,5.840625))*buf[5]+vec4(2.2415268,-6.705987,-0.98861027,-2.117676);\n buf[6]=sigmoid(buf[6]);buf[7]=sigmoid(buf[7]);\n buf[0]=mat4(vec4(1.6794263,1.3817469,2.9625452,0.),vec4(-1.8834411,-1.4806935,-3.5924516,0.),vec4(-1.3279216,-1.0918057,-2.3124623,0.),vec4(0.2662234,0.23235129,0.44178495,0.))*buf[0]+mat4(vec4(-0.6299101,-0.5945583,-0.9125601,0.),vec4(0.17828953,0.18300213,0.18182953,0.),vec4(-2.96544,-2.5819945,-4.9001055,0.),vec4(1.4195864,1.1868085,2.5176322,0.))*buf[1]+mat4(vec4(-1.2584374,-1.0552157,-2.1688404,0.),vec4(-0.7200217,-0.52666044,-1.438251,0.),vec4(0.15345335,0.15196142,0.272854,0.),vec4(0.945728,0.8861938,1.2766753,0.))*buf[2]+mat4(vec4(-2.4218085,-1.968602,-4.35166,0.),vec4(-22.683098,-18.0544,-41.954372,0.),vec4(0.63792,0.5470648,1.1078634,0.),vec4(-1.5489894,-1.3075932,-2.6444845,0.))*buf[3]+mat4(vec4(-0.49252132,-0.39877754,-0.91366625,0.),vec4(0.95609266,0.7923952,1.640221,0.),vec4(0.30616966,0.15693925,0.8639857,0.),vec4(1.1825981,0.94504964,2.176963,0.))*buf[4]+mat4(vec4(0.35446745,0.3293795,0.59547555,0.),vec4(-0.58784515,-0.48177817,-1.0614829,0.),vec4(2.5271258,1.9991658,4.6846647,0.),vec4(0.13042648,0.08864098,0.30187556,0.))*buf[5]+mat4(vec4(-1.7718065,-1.4033192,-3.3355875,0.),vec4(3.1664357,2.638297,5.378702,0.),vec4(-3.1724713,-2.6107926,-5.549295,0.),vec4(-2.851368,-2.249092,-5.3013067,0.))*buf[6]+mat4(vec4(1.5203838,1.2212278,2.8404984,0.),vec4(1.5210563,1.2651345,2.683903,0.),vec4(2.9789467,2.4364579,5.2347264,0.),vec4(2.2270417,1.8825914,3.8028636,0.))*buf[7]+vec4(-1.5468478,-3.6171484,0.24762098,0.);\n buf[0]=sigmoid(buf[0]);\n return vec4(buf[0].x,buf[0].y,buf[0].z,1.);\n}\n\nvoid mainImage(out vec4 fragColor,in vec2 fragCoord){\n vec2 uv=fragCoord/uResolution.xy*2.-1.;\n uv.x*=uResolution.x/uResolution.y;\n uv.y*=-1.;\n uv+=uWarp*vec2(sin(uv.y*6.283+uTime*0.5),cos(uv.x*6.283+uTime*0.5))*0.05;\n fragColor=cppn_fn(uv,0.1*sin(0.3*uTime),0.1*sin(0.69*uTime),0.1*sin(0.44*uTime));\n}\n\nvoid main(){\n vec4 col;mainImage(col,gl_FragCoord.xy);\n col.rgb=hueShiftRGB(col.rgb,uHueShift);\n float scanline_val=sin(gl_FragCoord.y*uScanFreq)*0.5+0.5;\n col.rgb*=1.-(scanline_val*scanline_val)*uScan;\n col.rgb+=(rand(gl_FragCoord.xy+uTime)-0.5)*uNoise;\n gl_FragColor=vec4(clamp(col.rgb,0.0,1.0),1.0);\n}\n`;\n\ntype Props = {\n hueShift?: number;\n noiseIntensity?: number;\n scanlineIntensity?: number;\n speed?: number;\n scanlineFrequency?: number;\n warpAmount?: number;\n resolutionScale?: number;\n};\n\nexport default function DarkVeil({\n hueShift = 0,\n noiseIntensity = 0,\n scanlineIntensity = 0,\n speed = 0.5,\n scanlineFrequency = 0,\n warpAmount = 0,\n resolutionScale = 1\n }: Props) {\n const ref = useRef(null);\n useEffect(() => {\n const canvas = ref.current as HTMLCanvasElement;\n const parent = canvas.parentElement as HTMLElement;\n\n const renderer = new Renderer({\n dpr: Math.min(window.devicePixelRatio, 2),\n canvas\n });\n\n const gl = renderer.gl;\n const geometry = new Triangle(gl);\n\n const program = new Program(gl, {\n vertex,\n fragment,\n uniforms: {\n uTime: { value: 0 },\n uResolution: { value: new Vec2() },\n uHueShift: { value: hueShift },\n uNoise: { value: noiseIntensity },\n uScan: { value: scanlineIntensity },\n uScanFreq: { value: scanlineFrequency },\n uWarp: { value: warpAmount }\n }\n });\n\n const mesh = new Mesh(gl, { geometry, program });\n\n const resize = () => {\n const w = parent.clientWidth,\n h = parent.clientHeight;\n renderer.setSize(w * resolutionScale, h * resolutionScale);\n program.uniforms.uResolution.value.set(w, h);\n };\n\n window.addEventListener('resize', resize);\n resize();\n\n const start = performance.now();\n let frame = 0;\n\n const loop = () => {\n program.uniforms.uTime.value = ((performance.now() - start) / 1000) * speed;\n program.uniforms.uHueShift.value = hueShift;\n program.uniforms.uNoise.value = noiseIntensity;\n program.uniforms.uScan.value = scanlineIntensity;\n program.uniforms.uScanFreq.value = scanlineFrequency;\n program.uniforms.uWarp.value = warpAmount;\n renderer.render({ scene: mesh });\n frame = requestAnimationFrame(loop);\n };\n\n loop();\n\n return () => {\n cancelAnimationFrame(frame);\n window.removeEventListener('resize', resize);\n };\n }, [hueShift, noiseIntensity, scanlineIntensity, speed, scanlineFrequency, warpAmount, resolutionScale]);\n return ;\n}" } ], "registryDependencies": [], diff --git a/public/r/DarkVeil-TS-TW.json b/public/r/DarkVeil-TS-TW.json index 2682fb8b3..ec30394ee 100644 --- a/public/r/DarkVeil-TS-TW.json +++ b/public/r/DarkVeil-TS-TW.json @@ -8,7 +8,7 @@ { "type": "registry:component", "path": "DarkVeil/DarkVeil.tsx", - "content": "import { useRef, useEffect } from 'react';\nimport { Renderer, Program, Mesh, Triangle, Vec2 } from 'ogl';\n\nconst vertex = `\nattribute vec2 position;\nvoid main(){gl_Position=vec4(position,0.0,1.0);}\n`;\n\nconst fragment = `\n#ifdef GL_ES\nprecision lowp float;\n#endif\nuniform vec2 uResolution;\nuniform float uTime;\nuniform float uHueShift;\nuniform float uNoise;\nuniform float uScan;\nuniform float uScanFreq;\nuniform float uWarp;\n#define iTime uTime\n#define iResolution uResolution\n\nvec4 buf[8];\nfloat rand(vec2 c){return fract(sin(dot(c,vec2(12.9898,78.233)))*43758.5453);}\n\nmat3 rgb2yiq=mat3(0.299,0.587,0.114,0.596,-0.274,-0.322,0.211,-0.523,0.312);\nmat3 yiq2rgb=mat3(1.0,0.956,0.621,1.0,-0.272,-0.647,1.0,-1.106,1.703);\n\nvec3 hueShiftRGB(vec3 col,float deg){\n vec3 yiq=rgb2yiq*col;\n float rad=radians(deg);\n float cosh=cos(rad),sinh=sin(rad);\n vec3 yiqShift=vec3(yiq.x,yiq.y*cosh-yiq.z*sinh,yiq.y*sinh+yiq.z*cosh);\n return clamp(yiq2rgb*yiqShift,0.0,1.0);\n}\n\nvec4 sigmoid(vec4 x){return 1./(1.+exp(-x));}\n\nvec4 cppn_fn(vec2 coordinate,float in0,float in1,float in2){\n buf[6]=vec4(coordinate.x,coordinate.y,0.3948333106474662+in0,0.36+in1);\n buf[7]=vec4(0.14+in2,sqrt(coordinate.x*coordinate.x+coordinate.y*coordinate.y),0.,0.);\n buf[0]=mat4(vec4(6.5404263,-3.6126034,0.7590882,-1.13613),vec4(2.4582713,3.1660357,1.2219609,0.06276096),vec4(-5.478085,-6.159632,1.8701609,-4.7742867),vec4(6.039214,-5.542865,-0.90925294,3.251348))*buf[6]+mat4(vec4(0.8473259,-5.722911,3.975766,1.6522468),vec4(-0.24321538,0.5839259,-1.7661959,-5.350116),vec4(0.,0.,0.,0.),vec4(0.,0.,0.,0.))*buf[7]+vec4(0.21808943,1.1243913,-1.7969975,5.0294676);\n buf[1]=mat4(vec4(-3.3522482,-6.0612736,0.55641043,-4.4719114),vec4(0.8631464,1.7432913,5.643898,1.6106541),vec4(2.4941394,-3.5012043,1.7184316,6.357333),vec4(3.310376,8.209261,1.1355612,-1.165539))*buf[6]+mat4(vec4(5.24046,-13.034365,0.009859298,15.870829),vec4(2.987511,3.129433,-0.89023495,-1.6822904),vec4(0.,0.,0.,0.),vec4(0.,0.,0.,0.))*buf[7]+vec4(-5.9457836,-6.573602,-0.8812491,1.5436668);\n buf[0]=sigmoid(buf[0]);buf[1]=sigmoid(buf[1]);\n buf[2]=mat4(vec4(-15.219568,8.095543,-2.429353,-1.9381982),vec4(-5.951362,4.3115187,2.6393783,1.274315),vec4(-7.3145227,6.7297835,5.2473326,5.9411426),vec4(5.0796127,8.979051,-1.7278991,-1.158976))*buf[6]+mat4(vec4(-11.967154,-11.608155,6.1486754,11.237008),vec4(2.124141,-6.263192,-1.7050359,-0.7021966),vec4(0.,0.,0.,0.),vec4(0.,0.,0.,0.))*buf[7]+vec4(-4.17164,-3.2281182,-4.576417,-3.6401186);\n buf[3]=mat4(vec4(3.1832156,-13.738922,1.879223,3.233465),vec4(0.64300746,12.768129,1.9141049,0.50990224),vec4(-0.049295485,4.4807224,1.4733979,1.801449),vec4(5.0039253,13.000481,3.3991797,-4.5561905))*buf[6]+mat4(vec4(-0.1285731,7.720628,-3.1425676,4.742367),vec4(0.6393625,3.714393,-0.8108378,-0.39174938),vec4(0.,0.,0.,0.),vec4(0.,0.,0.,0.))*buf[7]+vec4(-1.1811101,-21.621881,0.7851888,1.2329718);\n buf[2]=sigmoid(buf[2]);buf[3]=sigmoid(buf[3]);\n buf[4]=mat4(vec4(5.214916,-7.183024,2.7228765,2.6592617),vec4(-5.601878,-25.3591,4.067988,0.4602802),vec4(-10.57759,24.286327,21.102104,37.546658),vec4(4.3024497,-1.9625226,2.3458803,-1.372816))*buf[0]+mat4(vec4(-17.6526,-10.507558,2.2587414,12.462782),vec4(6.265566,-502.75443,-12.642513,0.9112289),vec4(-10.983244,20.741234,-9.701768,-0.7635988),vec4(5.383626,1.4819539,-4.1911616,-4.8444734))*buf[1]+mat4(vec4(12.785233,-16.345072,-0.39901125,1.7955981),vec4(-30.48365,-1.8345358,1.4542528,-1.1118771),vec4(19.872723,-7.337935,-42.941723,-98.52709),vec4(8.337645,-2.7312303,-2.2927687,-36.142323))*buf[2]+mat4(vec4(-16.298317,3.5471997,-0.44300047,-9.444417),vec4(57.5077,-35.609753,16.163465,-4.1534753),vec4(-0.07470326,-3.8656476,-7.0901804,3.1523974),vec4(-12.559385,-7.077619,1.490437,-0.8211543))*buf[3]+vec4(-7.67914,15.927437,1.3207729,-1.6686112);\n buf[5]=mat4(vec4(-1.4109162,-0.372762,-3.770383,-21.367174),vec4(-6.2103205,-9.35908,0.92529047,8.82561),vec4(11.460242,-22.348068,13.625772,-18.693201),vec4(-0.3429052,-3.9905605,-2.4626114,-0.45033523))*buf[0]+mat4(vec4(7.3481627,-4.3661838,-6.3037653,-3.868115),vec4(1.5462853,6.5488915,1.9701879,-0.58291394),vec4(6.5858274,-2.2180402,3.7127688,-1.3730392),vec4(-5.7973905,10.134961,-2.3395722,-5.965605))*buf[1]+mat4(vec4(-2.5132585,-6.6685553,-1.4029363,-0.16285264),vec4(-0.37908727,0.53738135,4.389061,-1.3024765),vec4(-0.70647055,2.0111287,-5.1659346,-3.728635),vec4(-13.562562,10.487719,-0.9173751,-2.6487076))*buf[2]+mat4(vec4(-8.645013,6.5546675,-6.3944063,-5.5933375),vec4(-0.57783127,-1.077275,36.91025,5.736769),vec4(14.283112,3.7146652,7.1452246,-4.5958776),vec4(2.7192075,3.6021907,-4.366337,-2.3653464))*buf[3]+vec4(-5.9000807,-4.329569,1.2427121,8.59503);\n buf[4]=sigmoid(buf[4]);buf[5]=sigmoid(buf[5]);\n buf[6]=mat4(vec4(-1.61102,0.7970257,1.4675229,0.20917463),vec4(-28.793737,-7.1390953,1.5025433,4.656581),vec4(-10.94861,39.66238,0.74318546,-10.095605),vec4(-0.7229728,-1.5483948,0.7301322,2.1687684))*buf[0]+mat4(vec4(3.2547753,21.489103,-1.0194173,-3.3100595),vec4(-3.7316632,-3.3792162,-7.223193,-0.23685838),vec4(13.1804495,0.7916005,5.338587,5.687114),vec4(-4.167605,-17.798311,-6.815736,-1.6451967))*buf[1]+mat4(vec4(0.604885,-7.800309,-7.213122,-2.741014),vec4(-3.522382,-0.12359311,-0.5258442,0.43852118),vec4(9.6752825,-22.853785,2.062431,0.099892326),vec4(-4.3196306,-17.730087,2.5184598,5.30267))*buf[2]+mat4(vec4(-6.545563,-15.790176,-6.0438633,-5.415399),vec4(-43.591583,28.551912,-16.00161,18.84728),vec4(4.212382,8.394307,3.0958717,8.657522),vec4(-5.0237565,-4.450633,-4.4768,-5.5010443))*buf[3]+mat4(vec4(1.6985557,-67.05806,6.897715,1.9004834),vec4(1.8680354,2.3915145,2.5231109,4.081538),vec4(11.158006,1.7294737,2.0738268,7.386411),vec4(-4.256034,-306.24686,8.258898,-17.132736))*buf[4]+mat4(vec4(1.6889864,-4.5852966,3.8534803,-6.3482175),vec4(1.3543309,-1.2640043,9.932754,2.9079645),vec4(-5.2770967,0.07150358,-0.13962056,3.3269649),vec4(28.34703,-4.918278,6.1044083,4.085355))*buf[5]+vec4(6.6818056,12.522166,-3.7075126,-4.104386);\n buf[7]=mat4(vec4(-8.265602,-4.7027016,5.098234,0.7509808),vec4(8.6507845,-17.15949,16.51939,-8.884479),vec4(-4.036479,-2.3946867,-2.6055532,-1.9866527),vec4(-2.2167742,-1.8135649,-5.9759874,4.8846445))*buf[0]+mat4(vec4(6.7790847,3.5076547,-2.8191125,-2.7028968),vec4(-5.743024,-0.27844876,1.4958696,-5.0517144),vec4(13.122226,15.735168,-2.9397483,-4.101023),vec4(-14.375265,-5.030483,-6.2599335,2.9848232))*buf[1]+mat4(vec4(4.0950394,-0.94011575,-5.674733,4.755022),vec4(4.3809423,4.8310084,1.7425908,-3.437416),vec4(2.117492,0.16342592,-104.56341,16.949184),vec4(-5.22543,-2.994248,3.8350096,-1.9364246))*buf[2]+mat4(vec4(-5.900337,1.7946124,-13.604192,-3.8060522),vec4(6.6583457,31.911177,25.164474,91.81147),vec4(11.840538,4.1503043,-0.7314397,6.768467),vec4(-6.3967767,4.034772,6.1714606,-0.32874924))*buf[3]+mat4(vec4(3.4992442,-196.91893,-8.923708,2.8142626),vec4(3.4806502,-3.1846354,5.1725626,5.1804223),vec4(-2.4009497,15.585794,1.2863957,2.0252278),vec4(-71.25271,-62.441242,-8.138444,0.50670296))*buf[4]+mat4(vec4(-12.291733,-11.176166,-7.3474145,4.390294),vec4(10.805477,5.6337385,-0.9385842,-4.7348723),vec4(-12.869276,-7.039391,5.3029537,7.5436664),vec4(1.4593618,8.91898,3.5101583,5.840625))*buf[5]+vec4(2.2415268,-6.705987,-0.98861027,-2.117676);\n buf[6]=sigmoid(buf[6]);buf[7]=sigmoid(buf[7]);\n buf[0]=mat4(vec4(1.6794263,1.3817469,2.9625452,0.),vec4(-1.8834411,-1.4806935,-3.5924516,0.),vec4(-1.3279216,-1.0918057,-2.3124623,0.),vec4(0.2662234,0.23235129,0.44178495,0.))*buf[0]+mat4(vec4(-0.6299101,-0.5945583,-0.9125601,0.),vec4(0.17828953,0.18300213,0.18182953,0.),vec4(-2.96544,-2.5819945,-4.9001055,0.),vec4(1.4195864,1.1868085,2.5176322,0.))*buf[1]+mat4(vec4(-1.2584374,-1.0552157,-2.1688404,0.),vec4(-0.7200217,-0.52666044,-1.438251,0.),vec4(0.15345335,0.15196142,0.272854,0.),vec4(0.945728,0.8861938,1.2766753,0.))*buf[2]+mat4(vec4(-2.4218085,-1.968602,-4.35166,0.),vec4(-22.683098,-18.0544,-41.954372,0.),vec4(0.63792,0.5470648,1.1078634,0.),vec4(-1.5489894,-1.3075932,-2.6444845,0.))*buf[3]+mat4(vec4(-0.49252132,-0.39877754,-0.91366625,0.),vec4(0.95609266,0.7923952,1.640221,0.),vec4(0.30616966,0.15693925,0.8639857,0.),vec4(1.1825981,0.94504964,2.176963,0.))*buf[4]+mat4(vec4(0.35446745,0.3293795,0.59547555,0.),vec4(-0.58784515,-0.48177817,-1.0614829,0.),vec4(2.5271258,1.9991658,4.6846647,0.),vec4(0.13042648,0.08864098,0.30187556,0.))*buf[5]+mat4(vec4(-1.7718065,-1.4033192,-3.3355875,0.),vec4(3.1664357,2.638297,5.378702,0.),vec4(-3.1724713,-2.6107926,-5.549295,0.),vec4(-2.851368,-2.249092,-5.3013067,0.))*buf[6]+mat4(vec4(1.5203838,1.2212278,2.8404984,0.),vec4(1.5210563,1.2651345,2.683903,0.),vec4(2.9789467,2.4364579,5.2347264,0.),vec4(2.2270417,1.8825914,3.8028636,0.))*buf[7]+vec4(-1.5468478,-3.6171484,0.24762098,0.);\n buf[0]=sigmoid(buf[0]);\n return vec4(buf[0].x,buf[0].y,buf[0].z,1.);\n}\n\nvoid mainImage(out vec4 fragColor,in vec2 fragCoord){\n vec2 uv=fragCoord/uResolution.xy*2.-1.;\n uv.y*=-1.;\n uv+=uWarp*vec2(sin(uv.y*6.283+uTime*0.5),cos(uv.x*6.283+uTime*0.5))*0.05;\n fragColor=cppn_fn(uv,0.1*sin(0.3*uTime),0.1*sin(0.69*uTime),0.1*sin(0.44*uTime));\n}\n\nvoid main(){\n vec4 col;mainImage(col,gl_FragCoord.xy);\n col.rgb=hueShiftRGB(col.rgb,uHueShift);\n float scanline_val=sin(gl_FragCoord.y*uScanFreq)*0.5+0.5;\n col.rgb*=1.-(scanline_val*scanline_val)*uScan;\n col.rgb+=(rand(gl_FragCoord.xy+uTime)-0.5)*uNoise;\n gl_FragColor=vec4(clamp(col.rgb,0.0,1.0),1.0);\n}\n`;\n\ntype Props = {\n hueShift?: number;\n noiseIntensity?: number;\n scanlineIntensity?: number;\n speed?: number;\n scanlineFrequency?: number;\n warpAmount?: number;\n resolutionScale?: number;\n};\n\nexport default function DarkVeil({\n hueShift = 0,\n noiseIntensity = 0,\n scanlineIntensity = 0,\n speed = 0.5,\n scanlineFrequency = 0,\n warpAmount = 0,\n resolutionScale = 1\n}: Props) {\n const ref = useRef(null);\n useEffect(() => {\n const canvas = ref.current as HTMLCanvasElement;\n const parent = canvas.parentElement as HTMLElement;\n\n const renderer = new Renderer({\n dpr: Math.min(window.devicePixelRatio, 2),\n canvas\n });\n\n const gl = renderer.gl;\n const geometry = new Triangle(gl);\n\n const program = new Program(gl, {\n vertex,\n fragment,\n uniforms: {\n uTime: { value: 0 },\n uResolution: { value: new Vec2() },\n uHueShift: { value: hueShift },\n uNoise: { value: noiseIntensity },\n uScan: { value: scanlineIntensity },\n uScanFreq: { value: scanlineFrequency },\n uWarp: { value: warpAmount }\n }\n });\n\n const mesh = new Mesh(gl, { geometry, program });\n\n const resize = () => {\n const w = parent.clientWidth,\n h = parent.clientHeight;\n renderer.setSize(w * resolutionScale, h * resolutionScale);\n program.uniforms.uResolution.value.set(w, h);\n };\n\n window.addEventListener('resize', resize);\n resize();\n\n const start = performance.now();\n let frame = 0;\n\n const loop = () => {\n program.uniforms.uTime.value = ((performance.now() - start) / 1000) * speed;\n program.uniforms.uHueShift.value = hueShift;\n program.uniforms.uNoise.value = noiseIntensity;\n program.uniforms.uScan.value = scanlineIntensity;\n program.uniforms.uScanFreq.value = scanlineFrequency;\n program.uniforms.uWarp.value = warpAmount;\n renderer.render({ scene: mesh });\n frame = requestAnimationFrame(loop);\n };\n\n loop();\n\n return () => {\n cancelAnimationFrame(frame);\n window.removeEventListener('resize', resize);\n };\n }, [hueShift, noiseIntensity, scanlineIntensity, speed, scanlineFrequency, warpAmount, resolutionScale]);\n return ;\n}\n" + "content": "import { useRef, useEffect } from 'react';\nimport { Renderer, Program, Mesh, Triangle, Vec2 } from 'ogl';\n\nconst vertex = `\nattribute vec2 position;\nvoid main(){gl_Position=vec4(position,0.0,1.0);}\n`;\n\nconst fragment = `\n#ifdef GL_ES\nprecision lowp float;\n#endif\nuniform vec2 uResolution;\nuniform float uTime;\nuniform float uHueShift;\nuniform float uNoise;\nuniform float uScan;\nuniform float uScanFreq;\nuniform float uWarp;\n#define iTime uTime\n#define iResolution uResolution\n\nvec4 buf[8];\nfloat rand(vec2 c){return fract(sin(dot(c,vec2(12.9898,78.233)))*43758.5453);}\n\nmat3 rgb2yiq=mat3(0.299,0.587,0.114,0.596,-0.274,-0.322,0.211,-0.523,0.312);\nmat3 yiq2rgb=mat3(1.0,0.956,0.621,1.0,-0.272,-0.647,1.0,-1.106,1.703);\n\nvec3 hueShiftRGB(vec3 col,float deg){\n vec3 yiq=rgb2yiq*col;\n float rad=radians(deg);\n float cosh=cos(rad),sinh=sin(rad);\n vec3 yiqShift=vec3(yiq.x,yiq.y*cosh-yiq.z*sinh,yiq.y*sinh+yiq.z*cosh);\n return clamp(yiq2rgb*yiqShift,0.0,1.0);\n}\n\nvec4 sigmoid(vec4 x){return 1./(1.+exp(-x));}\n\nvec4 cppn_fn(vec2 coordinate,float in0,float in1,float in2){\n buf[6]=vec4(coordinate.x,coordinate.y,0.3948333106474662+in0,0.36+in1);\n buf[7]=vec4(0.14+in2,sqrt(coordinate.x*coordinate.x+coordinate.y*coordinate.y),0.,0.);\n buf[0]=mat4(vec4(6.5404263,-3.6126034,0.7590882,-1.13613),vec4(2.4582713,3.1660357,1.2219609,0.06276096),vec4(-5.478085,-6.159632,1.8701609,-4.7742867),vec4(6.039214,-5.542865,-0.90925294,3.251348))*buf[6]+mat4(vec4(0.8473259,-5.722911,3.975766,1.6522468),vec4(-0.24321538,0.5839259,-1.7661959,-5.350116),vec4(0.,0.,0.,0.),vec4(0.,0.,0.,0.))*buf[7]+vec4(0.21808943,1.1243913,-1.7969975,5.0294676);\n buf[1]=mat4(vec4(-3.3522482,-6.0612736,0.55641043,-4.4719114),vec4(0.8631464,1.7432913,5.643898,1.6106541),vec4(2.4941394,-3.5012043,1.7184316,6.357333),vec4(3.310376,8.209261,1.1355612,-1.165539))*buf[6]+mat4(vec4(5.24046,-13.034365,0.009859298,15.870829),vec4(2.987511,3.129433,-0.89023495,-1.6822904),vec4(0.,0.,0.,0.),vec4(0.,0.,0.,0.))*buf[7]+vec4(-5.9457836,-6.573602,-0.8812491,1.5436668);\n buf[0]=sigmoid(buf[0]);buf[1]=sigmoid(buf[1]);\n buf[2]=mat4(vec4(-15.219568,8.095543,-2.429353,-1.9381982),vec4(-5.951362,4.3115187,2.6393783,1.274315),vec4(-7.3145227,6.7297835,5.2473326,5.9411426),vec4(5.0796127,8.979051,-1.7278991,-1.158976))*buf[6]+mat4(vec4(-11.967154,-11.608155,6.1486754,11.237008),vec4(2.124141,-6.263192,-1.7050359,-0.7021966),vec4(0.,0.,0.,0.),vec4(0.,0.,0.,0.))*buf[7]+vec4(-4.17164,-3.2281182,-4.576417,-3.6401186);\n buf[3]=mat4(vec4(3.1832156,-13.738922,1.879223,3.233465),vec4(0.64300746,12.768129,1.9141049,0.50990224),vec4(-0.049295485,4.4807224,1.4733979,1.801449),vec4(5.0039253,13.000481,3.3991797,-4.5561905))*buf[6]+mat4(vec4(-0.1285731,7.720628,-3.1425676,4.742367),vec4(0.6393625,3.714393,-0.8108378,-0.39174938),vec4(0.,0.,0.,0.),vec4(0.,0.,0.,0.))*buf[7]+vec4(-1.1811101,-21.621881,0.7851888,1.2329718);\n buf[2]=sigmoid(buf[2]);buf[3]=sigmoid(buf[3]);\n buf[4]=mat4(vec4(5.214916,-7.183024,2.7228765,2.6592617),vec4(-5.601878,-25.3591,4.067988,0.4602802),vec4(-10.57759,24.286327,21.102104,37.546658),vec4(4.3024497,-1.9625226,2.3458803,-1.372816))*buf[0]+mat4(vec4(-17.6526,-10.507558,2.2587414,12.462782),vec4(6.265566,-502.75443,-12.642513,0.9112289),vec4(-10.983244,20.741234,-9.701768,-0.7635988),vec4(5.383626,1.4819539,-4.1911616,-4.8444734))*buf[1]+mat4(vec4(12.785233,-16.345072,-0.39901125,1.7955981),vec4(-30.48365,-1.8345358,1.4542528,-1.1118771),vec4(19.872723,-7.337935,-42.941723,-98.52709),vec4(8.337645,-2.7312303,-2.2927687,-36.142323))*buf[2]+mat4(vec4(-16.298317,3.5471997,-0.44300047,-9.444417),vec4(57.5077,-35.609753,16.163465,-4.1534753),vec4(-0.07470326,-3.8656476,-7.0901804,3.1523974),vec4(-12.559385,-7.077619,1.490437,-0.8211543))*buf[3]+vec4(-7.67914,15.927437,1.3207729,-1.6686112);\n buf[5]=mat4(vec4(-1.4109162,-0.372762,-3.770383,-21.367174),vec4(-6.2103205,-9.35908,0.92529047,8.82561),vec4(11.460242,-22.348068,13.625772,-18.693201),vec4(-0.3429052,-3.9905605,-2.4626114,-0.45033523))*buf[0]+mat4(vec4(7.3481627,-4.3661838,-6.3037653,-3.868115),vec4(1.5462853,6.5488915,1.9701879,-0.58291394),vec4(6.5858274,-2.2180402,3.7127688,-1.3730392),vec4(-5.7973905,10.134961,-2.3395722,-5.965605))*buf[1]+mat4(vec4(-2.5132585,-6.6685553,-1.4029363,-0.16285264),vec4(-0.37908727,0.53738135,4.389061,-1.3024765),vec4(-0.70647055,2.0111287,-5.1659346,-3.728635),vec4(-13.562562,10.487719,-0.9173751,-2.6487076))*buf[2]+mat4(vec4(-8.645013,6.5546675,-6.3944063,-5.5933375),vec4(-0.57783127,-1.077275,36.91025,5.736769),vec4(14.283112,3.7146652,7.1452246,-4.5958776),vec4(2.7192075,3.6021907,-4.366337,-2.3653464))*buf[3]+vec4(-5.9000807,-4.329569,1.2427121,8.59503);\n buf[4]=sigmoid(buf[4]);buf[5]=sigmoid(buf[5]);\n buf[6]=mat4(vec4(-1.61102,0.7970257,1.4675229,0.20917463),vec4(-28.793737,-7.1390953,1.5025433,4.656581),vec4(-10.94861,39.66238,0.74318546,-10.095605),vec4(-0.7229728,-1.5483948,0.7301322,2.1687684))*buf[0]+mat4(vec4(3.2547753,21.489103,-1.0194173,-3.3100595),vec4(-3.7316632,-3.3792162,-7.223193,-0.23685838),vec4(13.1804495,0.7916005,5.338587,5.687114),vec4(-4.167605,-17.798311,-6.815736,-1.6451967))*buf[1]+mat4(vec4(0.604885,-7.800309,-7.213122,-2.741014),vec4(-3.522382,-0.12359311,-0.5258442,0.43852118),vec4(9.6752825,-22.853785,2.062431,0.099892326),vec4(-4.3196306,-17.730087,2.5184598,5.30267))*buf[2]+mat4(vec4(-6.545563,-15.790176,-6.0438633,-5.415399),vec4(-43.591583,28.551912,-16.00161,18.84728),vec4(4.212382,8.394307,3.0958717,8.657522),vec4(-5.0237565,-4.450633,-4.4768,-5.5010443))*buf[3]+mat4(vec4(1.6985557,-67.05806,6.897715,1.9004834),vec4(1.8680354,2.3915145,2.5231109,4.081538),vec4(11.158006,1.7294737,2.0738268,7.386411),vec4(-4.256034,-306.24686,8.258898,-17.132736))*buf[4]+mat4(vec4(1.6889864,-4.5852966,3.8534803,-6.3482175),vec4(1.3543309,-1.2640043,9.932754,2.9079645),vec4(-5.2770967,0.07150358,-0.13962056,3.3269649),vec4(28.34703,-4.918278,6.1044083,4.085355))*buf[5]+vec4(6.6818056,12.522166,-3.7075126,-4.104386);\n buf[7]=mat4(vec4(-8.265602,-4.7027016,5.098234,0.7509808),vec4(8.6507845,-17.15949,16.51939,-8.884479),vec4(-4.036479,-2.3946867,-2.6055532,-1.9866527),vec4(-2.2167742,-1.8135649,-5.9759874,4.8846445))*buf[0]+mat4(vec4(6.7790847,3.5076547,-2.8191125,-2.7028968),vec4(-5.743024,-0.27844876,1.4958696,-5.0517144),vec4(13.122226,15.735168,-2.9397483,-4.101023),vec4(-14.375265,-5.030483,-6.2599335,2.9848232))*buf[1]+mat4(vec4(4.0950394,-0.94011575,-5.674733,4.755022),vec4(4.3809423,4.8310084,1.7425908,-3.437416),vec4(2.117492,0.16342592,-104.56341,16.949184),vec4(-5.22543,-2.994248,3.8350096,-1.9364246))*buf[2]+mat4(vec4(-5.900337,1.7946124,-13.604192,-3.8060522),vec4(6.6583457,31.911177,25.164474,91.81147),vec4(11.840538,4.1503043,-0.7314397,6.768467),vec4(-6.3967767,4.034772,6.1714606,-0.32874924))*buf[3]+mat4(vec4(3.4992442,-196.91893,-8.923708,2.8142626),vec4(3.4806502,-3.1846354,5.1725626,5.1804223),vec4(-2.4009497,15.585794,1.2863957,2.0252278),vec4(-71.25271,-62.441242,-8.138444,0.50670296))*buf[4]+mat4(vec4(-12.291733,-11.176166,-7.3474145,4.390294),vec4(10.805477,5.6337385,-0.9385842,-4.7348723),vec4(-12.869276,-7.039391,5.3029537,7.5436664),vec4(1.4593618,8.91898,3.5101583,5.840625))*buf[5]+vec4(2.2415268,-6.705987,-0.98861027,-2.117676);\n buf[6]=sigmoid(buf[6]);buf[7]=sigmoid(buf[7]);\n buf[0]=mat4(vec4(1.6794263,1.3817469,2.9625452,0.),vec4(-1.8834411,-1.4806935,-3.5924516,0.),vec4(-1.3279216,-1.0918057,-2.3124623,0.),vec4(0.2662234,0.23235129,0.44178495,0.))*buf[0]+mat4(vec4(-0.6299101,-0.5945583,-0.9125601,0.),vec4(0.17828953,0.18300213,0.18182953,0.),vec4(-2.96544,-2.5819945,-4.9001055,0.),vec4(1.4195864,1.1868085,2.5176322,0.))*buf[1]+mat4(vec4(-1.2584374,-1.0552157,-2.1688404,0.),vec4(-0.7200217,-0.52666044,-1.438251,0.),vec4(0.15345335,0.15196142,0.272854,0.),vec4(0.945728,0.8861938,1.2766753,0.))*buf[2]+mat4(vec4(-2.4218085,-1.968602,-4.35166,0.),vec4(-22.683098,-18.0544,-41.954372,0.),vec4(0.63792,0.5470648,1.1078634,0.),vec4(-1.5489894,-1.3075932,-2.6444845,0.))*buf[3]+mat4(vec4(-0.49252132,-0.39877754,-0.91366625,0.),vec4(0.95609266,0.7923952,1.640221,0.),vec4(0.30616966,0.15693925,0.8639857,0.),vec4(1.1825981,0.94504964,2.176963,0.))*buf[4]+mat4(vec4(0.35446745,0.3293795,0.59547555,0.),vec4(-0.58784515,-0.48177817,-1.0614829,0.),vec4(2.5271258,1.9991658,4.6846647,0.),vec4(0.13042648,0.08864098,0.30187556,0.))*buf[5]+mat4(vec4(-1.7718065,-1.4033192,-3.3355875,0.),vec4(3.1664357,2.638297,5.378702,0.),vec4(-3.1724713,-2.6107926,-5.549295,0.),vec4(-2.851368,-2.249092,-5.3013067,0.))*buf[6]+mat4(vec4(1.5203838,1.2212278,2.8404984,0.),vec4(1.5210563,1.2651345,2.683903,0.),vec4(2.9789467,2.4364579,5.2347264,0.),vec4(2.2270417,1.8825914,3.8028636,0.))*buf[7]+vec4(-1.5468478,-3.6171484,0.24762098,0.);\n buf[0]=sigmoid(buf[0]);\n return vec4(buf[0].x,buf[0].y,buf[0].z,1.);\n}\n\nvoid mainImage(out vec4 fragColor,in vec2 fragCoord){\n vec2 uv=fragCoord/uResolution.xy*2.-1.;\n uv.x*=uResolution.x/uResolution.y;\n uv.y*=-1.;\n uv+=uWarp*vec2(sin(uv.y*6.283+uTime*0.5),cos(uv.x*6.283+uTime*0.5))*0.05;\n fragColor=cppn_fn(uv,0.1*sin(0.3*uTime),0.1*sin(0.69*uTime),0.1*sin(0.44*uTime));\n}\n\nvoid main(){\n vec4 col;mainImage(col,gl_FragCoord.xy);\n col.rgb=hueShiftRGB(col.rgb,uHueShift);\n float scanline_val=sin(gl_FragCoord.y*uScanFreq)*0.5+0.5;\n col.rgb*=1.-(scanline_val*scanline_val)*uScan;\n col.rgb+=(rand(gl_FragCoord.xy+uTime)-0.5)*uNoise;\n gl_FragColor=vec4(clamp(col.rgb,0.0,1.0),1.0);\n}\n`;\n\ntype Props = {\n hueShift?: number;\n noiseIntensity?: number;\n scanlineIntensity?: number;\n speed?: number;\n scanlineFrequency?: number;\n warpAmount?: number;\n resolutionScale?: number;\n};\n\nexport default function DarkVeil({\n hueShift = 0,\n noiseIntensity = 0,\n scanlineIntensity = 0,\n speed = 0.5,\n scanlineFrequency = 0,\n warpAmount = 0,\n resolutionScale = 1\n }: Props) {\n const ref = useRef(null);\n\n useEffect(() => {\n const canvas = ref.current as HTMLCanvasElement;\n const parent = canvas.parentElement as HTMLElement;\n\n const renderer = new Renderer({\n dpr: Math.min(window.devicePixelRatio, 2),\n canvas\n });\n\n const gl = renderer.gl;\n const geometry = new Triangle(gl);\n\n const program = new Program(gl, {\n vertex,\n fragment,\n uniforms: {\n uTime: { value: 0 },\n uResolution: { value: new Vec2() },\n uHueShift: { value: hueShift },\n uNoise: { value: noiseIntensity },\n uScan: { value: scanlineIntensity },\n uScanFreq: { value: scanlineFrequency },\n uWarp: { value: warpAmount }\n }\n });\n\n const mesh = new Mesh(gl, { geometry, program });\n\n const resize = () => {\n const w = parent.clientWidth,\n h = parent.clientHeight;\n renderer.setSize(w * resolutionScale, h * resolutionScale);\n program.uniforms.uResolution.value.set(w, h);\n };\n\n window.addEventListener('resize', resize);\n resize();\n\n const start = performance.now();\n let frame = 0;\n\n const loop = () => {\n program.uniforms.uTime.value = ((performance.now() - start) / 1000) * speed;\n program.uniforms.uHueShift.value = hueShift;\n program.uniforms.uNoise.value = noiseIntensity;\n program.uniforms.uScan.value = scanlineIntensity;\n program.uniforms.uScanFreq.value = scanlineFrequency;\n program.uniforms.uWarp.value = warpAmount;\n renderer.render({ scene: mesh });\n frame = requestAnimationFrame(loop);\n };\n\n loop();\n\n return () => {\n cancelAnimationFrame(frame);\n window.removeEventListener('resize', resize);\n };\n }, [hueShift, noiseIntensity, scanlineIntensity, speed, scanlineFrequency, warpAmount, resolutionScale]);\n\n return ;\n}" } ], "registryDependencies": [], diff --git a/public/r/ElectricBorder-JS-TW.json b/public/r/ElectricBorder-JS-TW.json index 86dddb5be..cfddbd7b5 100644 --- a/public/r/ElectricBorder-JS-TW.json +++ b/public/r/ElectricBorder-JS-TW.json @@ -8,7 +8,7 @@ { "type": "registry:component", "path": "ElectricBorder/ElectricBorder.jsx", - "content": "import { useEffect, useRef, useCallback } from 'react';\n\nfunction hexToRgba(hex, alpha = 1) {\n if (!hex) return `rgba(0,0,0,${alpha})`;\n let h = hex.replace('#', '');\n if (h.length === 3) {\n h = h\n .split('')\n .map(c => c + c)\n .join('');\n }\n const int = parseInt(h, 16);\n const r = (int >> 16) & 255;\n const g = (int >> 8) & 255;\n const b = int & 255;\n return `rgba(${r}, ${g}, ${b}, ${alpha})`;\n}\n\nconst ElectricBorder = ({\n children,\n color = '#5227FF',\n speed = 1,\n chaos = 0.12,\n borderRadius = 24,\n className,\n style\n}) => {\n const canvasRef = useRef(null);\n const containerRef = useRef(null);\n const animationRef = useRef(null);\n const timeRef = useRef(0);\n const lastFrameTimeRef = useRef(0);\n\n const random = useCallback(x => {\n return (Math.sin(x * 12.9898) * 43758.5453) % 1;\n }, []);\n\n const noise2D = useCallback(\n (x, y) => {\n const i = Math.floor(x);\n const j = Math.floor(y);\n const fx = x - i;\n const fy = y - j;\n\n const a = random(i + j * 57);\n const b = random(i + 1 + j * 57);\n const c = random(i + (j + 1) * 57);\n const d = random(i + 1 + (j + 1) * 57);\n\n const ux = fx * fx * (3.0 - 2.0 * fx);\n const uy = fy * fy * (3.0 - 2.0 * fy);\n\n return a * (1 - ux) * (1 - uy) + b * ux * (1 - uy) + c * (1 - ux) * uy + d * ux * uy;\n },\n [random]\n );\n\n const octavedNoise = useCallback(\n (x, octaves, lacunarity, gain, baseAmplitude, baseFrequency, time, seed, baseFlatness) => {\n let y = 0;\n let amplitude = baseAmplitude;\n let frequency = baseFrequency;\n\n for (let i = 0; i < octaves; i++) {\n let octaveAmplitude = amplitude;\n if (i === 0) {\n octaveAmplitude *= baseFlatness;\n }\n y += octaveAmplitude * noise2D(frequency * x + seed * 100, time * frequency * 0.3);\n frequency *= lacunarity;\n amplitude *= gain;\n }\n\n return y;\n },\n [noise2D]\n );\n\n const getCornerPoint = useCallback((centerX, centerY, radius, startAngle, arcLength, progress) => {\n const angle = startAngle + progress * arcLength;\n return {\n x: centerX + radius * Math.cos(angle),\n y: centerY + radius * Math.sin(angle)\n };\n }, []);\n\n const getRoundedRectPoint = useCallback(\n (t, left, top, width, height, radius) => {\n const straightWidth = width - 2 * radius;\n const straightHeight = height - 2 * radius;\n const cornerArc = (Math.PI * radius) / 2;\n const totalPerimeter = 2 * straightWidth + 2 * straightHeight + 4 * cornerArc;\n const distance = t * totalPerimeter;\n\n let accumulated = 0;\n\n if (distance <= accumulated + straightWidth) {\n const progress = (distance - accumulated) / straightWidth;\n return { x: left + radius + progress * straightWidth, y: top };\n }\n accumulated += straightWidth;\n\n if (distance <= accumulated + cornerArc) {\n const progress = (distance - accumulated) / cornerArc;\n return getCornerPoint(left + width - radius, top + radius, radius, -Math.PI / 2, Math.PI / 2, progress);\n }\n accumulated += cornerArc;\n\n if (distance <= accumulated + straightHeight) {\n const progress = (distance - accumulated) / straightHeight;\n return { x: left + width, y: top + radius + progress * straightHeight };\n }\n accumulated += straightHeight;\n\n if (distance <= accumulated + cornerArc) {\n const progress = (distance - accumulated) / cornerArc;\n return getCornerPoint(left + width - radius, top + height - radius, radius, 0, Math.PI / 2, progress);\n }\n accumulated += cornerArc;\n\n if (distance <= accumulated + straightWidth) {\n const progress = (distance - accumulated) / straightWidth;\n return { x: left + width - radius - progress * straightWidth, y: top + height };\n }\n accumulated += straightWidth;\n\n if (distance <= accumulated + cornerArc) {\n const progress = (distance - accumulated) / cornerArc;\n return getCornerPoint(left + radius, top + height - radius, radius, Math.PI / 2, Math.PI / 2, progress);\n }\n accumulated += cornerArc;\n\n if (distance <= accumulated + straightHeight) {\n const progress = (distance - accumulated) / straightHeight;\n return { x: left, y: top + height - radius - progress * straightHeight };\n }\n accumulated += straightHeight;\n\n const progress = (distance - accumulated) / cornerArc;\n return getCornerPoint(left + radius, top + radius, radius, Math.PI, Math.PI / 2, progress);\n },\n [getCornerPoint]\n );\n\n useEffect(() => {\n const canvas = canvasRef.current;\n const container = containerRef.current;\n if (!canvas || !container) return;\n\n const ctx = canvas.getContext('2d');\n if (!ctx) return;\n\n const octaves = 10;\n const lacunarity = 1.6;\n const gain = 0.7;\n const amplitude = chaos;\n const frequency = 10;\n const baseFlatness = 0;\n const displacement = 60;\n const borderOffset = 60;\n\n const updateSize = () => {\n const rect = container.getBoundingClientRect();\n const width = rect.width + borderOffset * 2;\n const height = rect.height + borderOffset * 2;\n\n const dpr = Math.min(window.devicePixelRatio || 1, 2);\n canvas.width = width * dpr;\n canvas.height = height * dpr;\n canvas.style.width = `${width}px`;\n canvas.style.height = `${height}px`;\n ctx.scale(dpr, dpr);\n\n return { width, height };\n };\n\n let { width, height } = updateSize();\n let lastDpr = Math.min(window.devicePixelRatio || 1, 2);\n\n const drawElectricBorder = currentTime => {\n if (!canvas || !ctx) return;\n\n const dpr = Math.min(window.devicePixelRatio || 1, 2);\n if (dpr !== lastDpr) {\n lastDpr = dpr;\n const newSize = updateSize();\n width = newSize.width;\n height = newSize.height;\n }\n\n const deltaTime = (currentTime - lastFrameTimeRef.current) / 1000;\n timeRef.current += deltaTime * speed;\n lastFrameTimeRef.current = currentTime;\n\n ctx.setTransform(1, 0, 0, 1, 0, 0);\n ctx.clearRect(0, 0, canvas.width, canvas.height);\n ctx.scale(dpr, dpr);\n\n ctx.strokeStyle = color;\n ctx.lineWidth = 1;\n ctx.lineCap = 'round';\n ctx.lineJoin = 'round';\n\n const scale = displacement;\n const left = borderOffset;\n const top = borderOffset;\n const borderWidth = width - 2 * borderOffset;\n const borderHeight = height - 2 * borderOffset;\n const maxRadius = Math.min(borderWidth, borderHeight) / 2;\n const radius = Math.min(borderRadius, maxRadius);\n\n const approximatePerimeter = 2 * (borderWidth + borderHeight) + 2 * Math.PI * radius;\n const sampleCount = Math.floor(approximatePerimeter / 2);\n\n ctx.beginPath();\n\n for (let i = 0; i <= sampleCount; i++) {\n const progress = i / sampleCount;\n\n const point = getRoundedRectPoint(progress, left, top, borderWidth, borderHeight, radius);\n\n const xNoise = octavedNoise(\n progress * 8,\n octaves,\n lacunarity,\n gain,\n amplitude,\n frequency,\n timeRef.current,\n 0,\n baseFlatness\n );\n const yNoise = octavedNoise(\n progress * 8,\n octaves,\n lacunarity,\n gain,\n amplitude,\n frequency,\n timeRef.current,\n 1,\n baseFlatness\n );\n\n const displacedX = point.x + xNoise * scale;\n const displacedY = point.y + yNoise * scale;\n\n if (i === 0) {\n ctx.moveTo(displacedX, displacedY);\n } else {\n ctx.lineTo(displacedX, displacedY);\n }\n }\n\n ctx.closePath();\n ctx.stroke();\n\n animationRef.current = requestAnimationFrame(drawElectricBorder);\n };\n\n const resizeObserver = new ResizeObserver(() => {\n const newSize = updateSize();\n width = newSize.width;\n height = newSize.height;\n });\n resizeObserver.observe(container);\n\n animationRef.current = requestAnimationFrame(drawElectricBorder);\n\n return () => {\n if (animationRef.current) {\n cancelAnimationFrame(animationRef.current);\n }\n resizeObserver.disconnect();\n };\n }, [color, speed, chaos, borderRadius, octavedNoise, getRoundedRectPoint]);\n\n return (\n \n
\n \n
\n
\n \n \n \n
\n
{children}
\n
\n );\n};\n\nexport default ElectricBorder;\n" + "content": "import { useEffect, useRef, useCallback } from 'react';\n\nfunction hexToRgba(hex, alpha = 1) {\n if (!hex) return `rgba(0,0,0,${alpha})`;\n let h = hex.replace('#', '');\n if (h.length === 3) {\n h = h\n .split('')\n .map(c => c + c)\n .join('');\n }\n const int = parseInt(h.slice(0, 6), 16);\n const r = (int >> 16) & 255;\n const g = (int >> 8) & 255;\n const b = int & 255;\n return `rgba(${r}, ${g}, ${b}, ${alpha})`;\n}\n\nconst ElectricBorder = ({\n children,\n color = '#5227FF',\n speed = 1,\n chaos = 0.12,\n borderRadius = 24,\n className,\n style\n}) => {\n const canvasRef = useRef(null);\n const containerRef = useRef(null);\n const animationRef = useRef(null);\n const timeRef = useRef(0);\n const lastFrameTimeRef = useRef(0);\n\n const random = useCallback(x => {\n return (Math.sin(x * 12.9898) * 43758.5453) % 1;\n }, []);\n\n const noise2D = useCallback(\n (x, y) => {\n const i = Math.floor(x);\n const j = Math.floor(y);\n const fx = x - i;\n const fy = y - j;\n\n const a = random(i + j * 57);\n const b = random(i + 1 + j * 57);\n const c = random(i + (j + 1) * 57);\n const d = random(i + 1 + (j + 1) * 57);\n\n const ux = fx * fx * (3.0 - 2.0 * fx);\n const uy = fy * fy * (3.0 - 2.0 * fy);\n\n return a * (1 - ux) * (1 - uy) + b * ux * (1 - uy) + c * (1 - ux) * uy + d * ux * uy;\n },\n [random]\n );\n\n const octavedNoise = useCallback(\n (x, octaves, lacunarity, gain, baseAmplitude, baseFrequency, time, seed, baseFlatness) => {\n let y = 0;\n let amplitude = baseAmplitude;\n let frequency = baseFrequency;\n\n for (let i = 0; i < octaves; i++) {\n let octaveAmplitude = amplitude;\n if (i === 0) {\n octaveAmplitude *= baseFlatness;\n }\n y += octaveAmplitude * noise2D(frequency * x + seed * 100, time * frequency * 0.3);\n frequency *= lacunarity;\n amplitude *= gain;\n }\n\n return y;\n },\n [noise2D]\n );\n\n const getCornerPoint = useCallback((centerX, centerY, radius, startAngle, arcLength, progress) => {\n const angle = startAngle + progress * arcLength;\n return {\n x: centerX + radius * Math.cos(angle),\n y: centerY + radius * Math.sin(angle)\n };\n }, []);\n\n const getRoundedRectPoint = useCallback(\n (t, left, top, width, height, radius) => {\n const straightWidth = width - 2 * radius;\n const straightHeight = height - 2 * radius;\n const cornerArc = (Math.PI * radius) / 2;\n const totalPerimeter = 2 * straightWidth + 2 * straightHeight + 4 * cornerArc;\n const distance = t * totalPerimeter;\n\n let accumulated = 0;\n\n if (distance <= accumulated + straightWidth) {\n const progress = (distance - accumulated) / straightWidth;\n return { x: left + radius + progress * straightWidth, y: top };\n }\n accumulated += straightWidth;\n\n if (distance <= accumulated + cornerArc) {\n const progress = (distance - accumulated) / cornerArc;\n return getCornerPoint(left + width - radius, top + radius, radius, -Math.PI / 2, Math.PI / 2, progress);\n }\n accumulated += cornerArc;\n\n if (distance <= accumulated + straightHeight) {\n const progress = (distance - accumulated) / straightHeight;\n return { x: left + width, y: top + radius + progress * straightHeight };\n }\n accumulated += straightHeight;\n\n if (distance <= accumulated + cornerArc) {\n const progress = (distance - accumulated) / cornerArc;\n return getCornerPoint(left + width - radius, top + height - radius, radius, 0, Math.PI / 2, progress);\n }\n accumulated += cornerArc;\n\n if (distance <= accumulated + straightWidth) {\n const progress = (distance - accumulated) / straightWidth;\n return { x: left + width - radius - progress * straightWidth, y: top + height };\n }\n accumulated += straightWidth;\n\n if (distance <= accumulated + cornerArc) {\n const progress = (distance - accumulated) / cornerArc;\n return getCornerPoint(left + radius, top + height - radius, radius, Math.PI / 2, Math.PI / 2, progress);\n }\n accumulated += cornerArc;\n\n if (distance <= accumulated + straightHeight) {\n const progress = (distance - accumulated) / straightHeight;\n return { x: left, y: top + height - radius - progress * straightHeight };\n }\n accumulated += straightHeight;\n\n const progress = (distance - accumulated) / cornerArc;\n return getCornerPoint(left + radius, top + radius, radius, Math.PI, Math.PI / 2, progress);\n },\n [getCornerPoint]\n );\n\n useEffect(() => {\n const canvas = canvasRef.current;\n const container = containerRef.current;\n if (!canvas || !container) return;\n\n const ctx = canvas.getContext('2d');\n if (!ctx) return;\n\n const octaves = 10;\n const lacunarity = 1.6;\n const gain = 0.7;\n const amplitude = chaos;\n const frequency = 10;\n const baseFlatness = 0;\n const displacement = 60;\n const borderOffset = 60;\n\n const updateSize = () => {\n const rect = container.getBoundingClientRect();\n const width = rect.width + borderOffset * 2;\n const height = rect.height + borderOffset * 2;\n\n const dpr = Math.min(window.devicePixelRatio || 1, 2);\n canvas.width = width * dpr;\n canvas.height = height * dpr;\n canvas.style.width = `${width}px`;\n canvas.style.height = `${height}px`;\n ctx.scale(dpr, dpr);\n\n return { width, height };\n };\n\n let { width, height } = updateSize();\n let lastDpr = Math.min(window.devicePixelRatio || 1, 2);\n\n const drawElectricBorder = currentTime => {\n if (!canvas || !ctx) return;\n\n const dpr = Math.min(window.devicePixelRatio || 1, 2);\n if (dpr !== lastDpr) {\n lastDpr = dpr;\n const newSize = updateSize();\n width = newSize.width;\n height = newSize.height;\n }\n\n const deltaTime = (currentTime - lastFrameTimeRef.current) / 1000;\n timeRef.current += deltaTime * speed;\n lastFrameTimeRef.current = currentTime;\n\n ctx.setTransform(1, 0, 0, 1, 0, 0);\n ctx.clearRect(0, 0, canvas.width, canvas.height);\n ctx.scale(dpr, dpr);\n\n ctx.strokeStyle = color;\n ctx.lineWidth = 1;\n ctx.lineCap = 'round';\n ctx.lineJoin = 'round';\n\n const scale = displacement;\n const left = borderOffset;\n const top = borderOffset;\n const borderWidth = width - 2 * borderOffset;\n const borderHeight = height - 2 * borderOffset;\n const maxRadius = Math.min(borderWidth, borderHeight) / 2;\n const radius = Math.min(borderRadius, maxRadius);\n\n const approximatePerimeter = 2 * (borderWidth + borderHeight) + 2 * Math.PI * radius;\n const sampleCount = Math.floor(approximatePerimeter / 2);\n\n ctx.beginPath();\n\n for (let i = 0; i <= sampleCount; i++) {\n const progress = i / sampleCount;\n\n const point = getRoundedRectPoint(progress, left, top, borderWidth, borderHeight, radius);\n\n const xNoise = octavedNoise(\n progress * 8,\n octaves,\n lacunarity,\n gain,\n amplitude,\n frequency,\n timeRef.current,\n 0,\n baseFlatness\n );\n const yNoise = octavedNoise(\n progress * 8,\n octaves,\n lacunarity,\n gain,\n amplitude,\n frequency,\n timeRef.current,\n 1,\n baseFlatness\n );\n\n const displacedX = point.x + xNoise * scale;\n const displacedY = point.y + yNoise * scale;\n\n if (i === 0) {\n ctx.moveTo(displacedX, displacedY);\n } else {\n ctx.lineTo(displacedX, displacedY);\n }\n }\n\n ctx.closePath();\n ctx.stroke();\n\n animationRef.current = requestAnimationFrame(drawElectricBorder);\n };\n\n const resizeObserver = new ResizeObserver(() => {\n const newSize = updateSize();\n width = newSize.width;\n height = newSize.height;\n });\n resizeObserver.observe(container);\n\n animationRef.current = requestAnimationFrame(drawElectricBorder);\n\n return () => {\n if (animationRef.current) {\n cancelAnimationFrame(animationRef.current);\n }\n resizeObserver.disconnect();\n };\n }, [color, speed, chaos, borderRadius, octavedNoise, getRoundedRectPoint]);\n\n return (\n \n
\n \n
\n
\n \n \n \n
\n
{children}
\n
\n );\n};\n\nexport default ElectricBorder;\n" } ], "registryDependencies": [], diff --git a/public/r/ElectricBorder-TS-TW.json b/public/r/ElectricBorder-TS-TW.json index c0ceeac3a..a05aa84b7 100644 --- a/public/r/ElectricBorder-TS-TW.json +++ b/public/r/ElectricBorder-TS-TW.json @@ -8,7 +8,7 @@ { "type": "registry:component", "path": "ElectricBorder/ElectricBorder.tsx", - "content": "import React, { useEffect, useRef, useCallback, CSSProperties, ReactNode } from 'react';\n\nfunction hexToRgba(hex: string, alpha: number = 1): string {\n if (!hex) return `rgba(0,0,0,${alpha})`;\n let h = hex.replace('#', '');\n if (h.length === 3) {\n h = h\n .split('')\n .map(c => c + c)\n .join('');\n }\n const int = parseInt(h, 16);\n const r = (int >> 16) & 255;\n const g = (int >> 8) & 255;\n const b = int & 255;\n return `rgba(${r}, ${g}, ${b}, ${alpha})`;\n}\n\ninterface ElectricBorderProps {\n children?: ReactNode;\n color?: string;\n speed?: number;\n chaos?: number;\n borderRadius?: number;\n className?: string;\n style?: CSSProperties;\n}\n\nconst ElectricBorder: React.FC = ({\n children,\n color = '#5227FF',\n speed = 1,\n chaos = 0.12,\n borderRadius = 24,\n className,\n style\n}) => {\n const canvasRef = useRef(null);\n const containerRef = useRef(null);\n const animationRef = useRef(null);\n const timeRef = useRef(0);\n const lastFrameTimeRef = useRef(0);\n\n const random = useCallback((x: number): number => {\n return (Math.sin(x * 12.9898) * 43758.5453) % 1;\n }, []);\n\n const noise2D = useCallback(\n (x: number, y: number): number => {\n const i = Math.floor(x);\n const j = Math.floor(y);\n const fx = x - i;\n const fy = y - j;\n\n const a = random(i + j * 57);\n const b = random(i + 1 + j * 57);\n const c = random(i + (j + 1) * 57);\n const d = random(i + 1 + (j + 1) * 57);\n\n const ux = fx * fx * (3.0 - 2.0 * fx);\n const uy = fy * fy * (3.0 - 2.0 * fy);\n\n return a * (1 - ux) * (1 - uy) + b * ux * (1 - uy) + c * (1 - ux) * uy + d * ux * uy;\n },\n [random]\n );\n\n const octavedNoise = useCallback(\n (\n x: number,\n octaves: number,\n lacunarity: number,\n gain: number,\n baseAmplitude: number,\n baseFrequency: number,\n time: number,\n seed: number,\n baseFlatness: number\n ): number => {\n let y = 0;\n let amplitude = baseAmplitude;\n let frequency = baseFrequency;\n\n for (let i = 0; i < octaves; i++) {\n let octaveAmplitude = amplitude;\n if (i === 0) {\n octaveAmplitude *= baseFlatness;\n }\n y += octaveAmplitude * noise2D(frequency * x + seed * 100, time * frequency * 0.3);\n frequency *= lacunarity;\n amplitude *= gain;\n }\n\n return y;\n },\n [noise2D]\n );\n\n const getCornerPoint = useCallback(\n (\n centerX: number,\n centerY: number,\n radius: number,\n startAngle: number,\n arcLength: number,\n progress: number\n ): { x: number; y: number } => {\n const angle = startAngle + progress * arcLength;\n return {\n x: centerX + radius * Math.cos(angle),\n y: centerY + radius * Math.sin(angle)\n };\n },\n []\n );\n\n const getRoundedRectPoint = useCallback(\n (t: number, left: number, top: number, width: number, height: number, radius: number): { x: number; y: number } => {\n const straightWidth = width - 2 * radius;\n const straightHeight = height - 2 * radius;\n const cornerArc = (Math.PI * radius) / 2;\n const totalPerimeter = 2 * straightWidth + 2 * straightHeight + 4 * cornerArc;\n const distance = t * totalPerimeter;\n\n let accumulated = 0;\n\n if (distance <= accumulated + straightWidth) {\n const progress = (distance - accumulated) / straightWidth;\n return { x: left + radius + progress * straightWidth, y: top };\n }\n accumulated += straightWidth;\n\n if (distance <= accumulated + cornerArc) {\n const progress = (distance - accumulated) / cornerArc;\n return getCornerPoint(left + width - radius, top + radius, radius, -Math.PI / 2, Math.PI / 2, progress);\n }\n accumulated += cornerArc;\n\n if (distance <= accumulated + straightHeight) {\n const progress = (distance - accumulated) / straightHeight;\n return { x: left + width, y: top + radius + progress * straightHeight };\n }\n accumulated += straightHeight;\n\n if (distance <= accumulated + cornerArc) {\n const progress = (distance - accumulated) / cornerArc;\n return getCornerPoint(left + width - radius, top + height - radius, radius, 0, Math.PI / 2, progress);\n }\n accumulated += cornerArc;\n\n if (distance <= accumulated + straightWidth) {\n const progress = (distance - accumulated) / straightWidth;\n return { x: left + width - radius - progress * straightWidth, y: top + height };\n }\n accumulated += straightWidth;\n\n if (distance <= accumulated + cornerArc) {\n const progress = (distance - accumulated) / cornerArc;\n return getCornerPoint(left + radius, top + height - radius, radius, Math.PI / 2, Math.PI / 2, progress);\n }\n accumulated += cornerArc;\n\n if (distance <= accumulated + straightHeight) {\n const progress = (distance - accumulated) / straightHeight;\n return { x: left, y: top + height - radius - progress * straightHeight };\n }\n accumulated += straightHeight;\n\n const progress = (distance - accumulated) / cornerArc;\n return getCornerPoint(left + radius, top + radius, radius, Math.PI, Math.PI / 2, progress);\n },\n [getCornerPoint]\n );\n\n useEffect(() => {\n const canvas = canvasRef.current;\n const container = containerRef.current;\n if (!canvas || !container) return;\n\n const ctx = canvas.getContext('2d');\n if (!ctx) return;\n\n const octaves = 10;\n const lacunarity = 1.6;\n const gain = 0.7;\n const amplitude = chaos;\n const frequency = 10;\n const baseFlatness = 0;\n const displacement = 60;\n const borderOffset = 60;\n\n const updateSize = () => {\n const rect = container.getBoundingClientRect();\n const width = rect.width + borderOffset * 2;\n const height = rect.height + borderOffset * 2;\n\n const dpr = Math.min(window.devicePixelRatio || 1, 2);\n canvas.width = width * dpr;\n canvas.height = height * dpr;\n canvas.style.width = `${width}px`;\n canvas.style.height = `${height}px`;\n ctx.scale(dpr, dpr);\n\n return { width, height };\n };\n\n let { width, height } = updateSize();\n let lastDpr = Math.min(window.devicePixelRatio || 1, 2);\n\n const drawElectricBorder = (currentTime: number) => {\n if (!canvas || !ctx) return;\n\n const dpr = Math.min(window.devicePixelRatio || 1, 2);\n if (dpr !== lastDpr) {\n lastDpr = dpr;\n const newSize = updateSize();\n width = newSize.width;\n height = newSize.height;\n }\n\n const deltaTime = (currentTime - lastFrameTimeRef.current) / 1000;\n timeRef.current += deltaTime * speed;\n lastFrameTimeRef.current = currentTime;\n\n ctx.setTransform(1, 0, 0, 1, 0, 0);\n ctx.clearRect(0, 0, canvas.width, canvas.height);\n ctx.scale(dpr, dpr);\n\n ctx.strokeStyle = color;\n ctx.lineWidth = 1;\n ctx.lineCap = 'round';\n ctx.lineJoin = 'round';\n\n const scale = displacement;\n const left = borderOffset;\n const top = borderOffset;\n const borderWidth = width - 2 * borderOffset;\n const borderHeight = height - 2 * borderOffset;\n const maxRadius = Math.min(borderWidth, borderHeight) / 2;\n const radius = Math.min(borderRadius, maxRadius);\n\n const approximatePerimeter = 2 * (borderWidth + borderHeight) + 2 * Math.PI * radius;\n const sampleCount = Math.floor(approximatePerimeter / 2);\n\n ctx.beginPath();\n\n for (let i = 0; i <= sampleCount; i++) {\n const progress = i / sampleCount;\n\n const point = getRoundedRectPoint(progress, left, top, borderWidth, borderHeight, radius);\n\n const xNoise = octavedNoise(\n progress * 8,\n octaves,\n lacunarity,\n gain,\n amplitude,\n frequency,\n timeRef.current,\n 0,\n baseFlatness\n );\n const yNoise = octavedNoise(\n progress * 8,\n octaves,\n lacunarity,\n gain,\n amplitude,\n frequency,\n timeRef.current,\n 1,\n baseFlatness\n );\n\n const displacedX = point.x + xNoise * scale;\n const displacedY = point.y + yNoise * scale;\n\n if (i === 0) {\n ctx.moveTo(displacedX, displacedY);\n } else {\n ctx.lineTo(displacedX, displacedY);\n }\n }\n\n ctx.closePath();\n ctx.stroke();\n\n animationRef.current = requestAnimationFrame(drawElectricBorder);\n };\n\n const resizeObserver = new ResizeObserver(() => {\n const newSize = updateSize();\n width = newSize.width;\n height = newSize.height;\n });\n resizeObserver.observe(container);\n\n animationRef.current = requestAnimationFrame(drawElectricBorder);\n\n return () => {\n if (animationRef.current) {\n cancelAnimationFrame(animationRef.current);\n }\n resizeObserver.disconnect();\n };\n }, [color, speed, chaos, borderRadius, octavedNoise, getRoundedRectPoint]);\n\n return (\n \n
\n \n
\n
\n \n \n \n
\n
{children}
\n
\n );\n};\n\nexport default ElectricBorder;\n" + "content": "import React, { useEffect, useRef, useCallback, CSSProperties, ReactNode } from 'react';\n\nfunction hexToRgba(hex: string, alpha: number = 1): string {\n if (!hex) return `rgba(0,0,0,${alpha})`;\n let h = hex.replace('#', '');\n if (h.length === 3) {\n h = h\n .split('')\n .map(c => c + c)\n .join('');\n }\n const int = parseInt(h.slice(0, 6), 16);\n const r = (int >> 16) & 255;\n const g = (int >> 8) & 255;\n const b = int & 255;\n return `rgba(${r}, ${g}, ${b}, ${alpha})`;\n}\n\ninterface ElectricBorderProps {\n children?: ReactNode;\n color?: string;\n speed?: number;\n chaos?: number;\n borderRadius?: number;\n className?: string;\n style?: CSSProperties;\n}\n\nconst ElectricBorder: React.FC = ({\n children,\n color = '#5227FF',\n speed = 1,\n chaos = 0.12,\n borderRadius = 24,\n className,\n style\n}) => {\n const canvasRef = useRef(null);\n const containerRef = useRef(null);\n const animationRef = useRef(null);\n const timeRef = useRef(0);\n const lastFrameTimeRef = useRef(0);\n\n const random = useCallback((x: number): number => {\n return (Math.sin(x * 12.9898) * 43758.5453) % 1;\n }, []);\n\n const noise2D = useCallback(\n (x: number, y: number): number => {\n const i = Math.floor(x);\n const j = Math.floor(y);\n const fx = x - i;\n const fy = y - j;\n\n const a = random(i + j * 57);\n const b = random(i + 1 + j * 57);\n const c = random(i + (j + 1) * 57);\n const d = random(i + 1 + (j + 1) * 57);\n\n const ux = fx * fx * (3.0 - 2.0 * fx);\n const uy = fy * fy * (3.0 - 2.0 * fy);\n\n return a * (1 - ux) * (1 - uy) + b * ux * (1 - uy) + c * (1 - ux) * uy + d * ux * uy;\n },\n [random]\n );\n\n const octavedNoise = useCallback(\n (\n x: number,\n octaves: number,\n lacunarity: number,\n gain: number,\n baseAmplitude: number,\n baseFrequency: number,\n time: number,\n seed: number,\n baseFlatness: number\n ): number => {\n let y = 0;\n let amplitude = baseAmplitude;\n let frequency = baseFrequency;\n\n for (let i = 0; i < octaves; i++) {\n let octaveAmplitude = amplitude;\n if (i === 0) {\n octaveAmplitude *= baseFlatness;\n }\n y += octaveAmplitude * noise2D(frequency * x + seed * 100, time * frequency * 0.3);\n frequency *= lacunarity;\n amplitude *= gain;\n }\n\n return y;\n },\n [noise2D]\n );\n\n const getCornerPoint = useCallback(\n (\n centerX: number,\n centerY: number,\n radius: number,\n startAngle: number,\n arcLength: number,\n progress: number\n ): { x: number; y: number } => {\n const angle = startAngle + progress * arcLength;\n return {\n x: centerX + radius * Math.cos(angle),\n y: centerY + radius * Math.sin(angle)\n };\n },\n []\n );\n\n const getRoundedRectPoint = useCallback(\n (t: number, left: number, top: number, width: number, height: number, radius: number): { x: number; y: number } => {\n const straightWidth = width - 2 * radius;\n const straightHeight = height - 2 * radius;\n const cornerArc = (Math.PI * radius) / 2;\n const totalPerimeter = 2 * straightWidth + 2 * straightHeight + 4 * cornerArc;\n const distance = t * totalPerimeter;\n\n let accumulated = 0;\n\n if (distance <= accumulated + straightWidth) {\n const progress = (distance - accumulated) / straightWidth;\n return { x: left + radius + progress * straightWidth, y: top };\n }\n accumulated += straightWidth;\n\n if (distance <= accumulated + cornerArc) {\n const progress = (distance - accumulated) / cornerArc;\n return getCornerPoint(left + width - radius, top + radius, radius, -Math.PI / 2, Math.PI / 2, progress);\n }\n accumulated += cornerArc;\n\n if (distance <= accumulated + straightHeight) {\n const progress = (distance - accumulated) / straightHeight;\n return { x: left + width, y: top + radius + progress * straightHeight };\n }\n accumulated += straightHeight;\n\n if (distance <= accumulated + cornerArc) {\n const progress = (distance - accumulated) / cornerArc;\n return getCornerPoint(left + width - radius, top + height - radius, radius, 0, Math.PI / 2, progress);\n }\n accumulated += cornerArc;\n\n if (distance <= accumulated + straightWidth) {\n const progress = (distance - accumulated) / straightWidth;\n return { x: left + width - radius - progress * straightWidth, y: top + height };\n }\n accumulated += straightWidth;\n\n if (distance <= accumulated + cornerArc) {\n const progress = (distance - accumulated) / cornerArc;\n return getCornerPoint(left + radius, top + height - radius, radius, Math.PI / 2, Math.PI / 2, progress);\n }\n accumulated += cornerArc;\n\n if (distance <= accumulated + straightHeight) {\n const progress = (distance - accumulated) / straightHeight;\n return { x: left, y: top + height - radius - progress * straightHeight };\n }\n accumulated += straightHeight;\n\n const progress = (distance - accumulated) / cornerArc;\n return getCornerPoint(left + radius, top + radius, radius, Math.PI, Math.PI / 2, progress);\n },\n [getCornerPoint]\n );\n\n useEffect(() => {\n const canvas = canvasRef.current;\n const container = containerRef.current;\n if (!canvas || !container) return;\n\n const ctx = canvas.getContext('2d');\n if (!ctx) return;\n\n const octaves = 10;\n const lacunarity = 1.6;\n const gain = 0.7;\n const amplitude = chaos;\n const frequency = 10;\n const baseFlatness = 0;\n const displacement = 60;\n const borderOffset = 60;\n\n const updateSize = () => {\n const rect = container.getBoundingClientRect();\n const width = rect.width + borderOffset * 2;\n const height = rect.height + borderOffset * 2;\n\n const dpr = Math.min(window.devicePixelRatio || 1, 2);\n canvas.width = width * dpr;\n canvas.height = height * dpr;\n canvas.style.width = `${width}px`;\n canvas.style.height = `${height}px`;\n ctx.scale(dpr, dpr);\n\n return { width, height };\n };\n\n let { width, height } = updateSize();\n let lastDpr = Math.min(window.devicePixelRatio || 1, 2);\n\n const drawElectricBorder = (currentTime: number) => {\n if (!canvas || !ctx) return;\n\n const dpr = Math.min(window.devicePixelRatio || 1, 2);\n if (dpr !== lastDpr) {\n lastDpr = dpr;\n const newSize = updateSize();\n width = newSize.width;\n height = newSize.height;\n }\n\n const deltaTime = (currentTime - lastFrameTimeRef.current) / 1000;\n timeRef.current += deltaTime * speed;\n lastFrameTimeRef.current = currentTime;\n\n ctx.setTransform(1, 0, 0, 1, 0, 0);\n ctx.clearRect(0, 0, canvas.width, canvas.height);\n ctx.scale(dpr, dpr);\n\n ctx.strokeStyle = color;\n ctx.lineWidth = 1;\n ctx.lineCap = 'round';\n ctx.lineJoin = 'round';\n\n const scale = displacement;\n const left = borderOffset;\n const top = borderOffset;\n const borderWidth = width - 2 * borderOffset;\n const borderHeight = height - 2 * borderOffset;\n const maxRadius = Math.min(borderWidth, borderHeight) / 2;\n const radius = Math.min(borderRadius, maxRadius);\n\n const approximatePerimeter = 2 * (borderWidth + borderHeight) + 2 * Math.PI * radius;\n const sampleCount = Math.floor(approximatePerimeter / 2);\n\n ctx.beginPath();\n\n for (let i = 0; i <= sampleCount; i++) {\n const progress = i / sampleCount;\n\n const point = getRoundedRectPoint(progress, left, top, borderWidth, borderHeight, radius);\n\n const xNoise = octavedNoise(\n progress * 8,\n octaves,\n lacunarity,\n gain,\n amplitude,\n frequency,\n timeRef.current,\n 0,\n baseFlatness\n );\n const yNoise = octavedNoise(\n progress * 8,\n octaves,\n lacunarity,\n gain,\n amplitude,\n frequency,\n timeRef.current,\n 1,\n baseFlatness\n );\n\n const displacedX = point.x + xNoise * scale;\n const displacedY = point.y + yNoise * scale;\n\n if (i === 0) {\n ctx.moveTo(displacedX, displacedY);\n } else {\n ctx.lineTo(displacedX, displacedY);\n }\n }\n\n ctx.closePath();\n ctx.stroke();\n\n animationRef.current = requestAnimationFrame(drawElectricBorder);\n };\n\n const resizeObserver = new ResizeObserver(() => {\n const newSize = updateSize();\n width = newSize.width;\n height = newSize.height;\n });\n resizeObserver.observe(container);\n\n animationRef.current = requestAnimationFrame(drawElectricBorder);\n\n return () => {\n if (animationRef.current) {\n cancelAnimationFrame(animationRef.current);\n }\n resizeObserver.disconnect();\n };\n }, [color, speed, chaos, borderRadius, octavedNoise, getRoundedRectPoint]);\n\n return (\n \n
\n \n
\n
\n \n \n \n
\n
{children}
\n
\n );\n};\n\nexport default ElectricBorder;\n" } ], "registryDependencies": [], diff --git a/public/r/FaultyTerminal-JS-CSS.json b/public/r/FaultyTerminal-JS-CSS.json index ee1dafc29..00fafa267 100644 --- a/public/r/FaultyTerminal-JS-CSS.json +++ b/public/r/FaultyTerminal-JS-CSS.json @@ -13,7 +13,7 @@ { "type": "registry:component", "path": "FaultyTerminal/FaultyTerminal.jsx", - "content": "import { Renderer, Program, Mesh, Color, Triangle } from 'ogl';\nimport { useEffect, useRef, useMemo, useCallback } from 'react';\nimport './FaultyTerminal.css';\n\nconst vertexShader = `\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 fragmentShader = `\nprecision mediump float;\n\nvarying vec2 vUv;\n\nuniform float iTime;\nuniform vec3 iResolution;\nuniform float uScale;\n\nuniform vec2 uGridMul;\nuniform float uDigitSize;\nuniform float uScanlineIntensity;\nuniform float uGlitchAmount;\nuniform float uFlickerAmount;\nuniform float uNoiseAmp;\nuniform float uChromaticAberration;\nuniform float uDither;\nuniform float uCurvature;\nuniform vec3 uTint;\nuniform vec2 uMouse;\nuniform float uMouseStrength;\nuniform float uUseMouse;\nuniform float uPageLoadProgress;\nuniform float uUsePageLoadAnimation;\nuniform float uBrightness;\n\nfloat time;\n\nfloat hash21(vec2 p){\n p = fract(p * 234.56);\n p += dot(p, p + 34.56);\n return fract(p.x * p.y);\n}\n\nfloat noise(vec2 p)\n{\n return sin(p.x * 10.0) * sin(p.y * (3.0 + sin(time * 0.090909))) + 0.2; \n}\n\nmat2 rotate(float angle)\n{\n float c = cos(angle);\n float s = sin(angle);\n return mat2(c, -s, s, c);\n}\n\nfloat fbm(vec2 p)\n{\n p *= 1.1;\n float f = 0.0;\n float amp = 0.5 * uNoiseAmp;\n \n mat2 modify0 = rotate(time * 0.02);\n f += amp * noise(p);\n p = modify0 * p * 2.0;\n amp *= 0.454545;\n \n mat2 modify1 = rotate(time * 0.02);\n f += amp * noise(p);\n p = modify1 * p * 2.0;\n amp *= 0.454545;\n \n mat2 modify2 = rotate(time * 0.08);\n f += amp * noise(p);\n \n return f;\n}\n\nfloat pattern(vec2 p, out vec2 q, out vec2 r) {\n vec2 offset1 = vec2(1.0);\n vec2 offset0 = vec2(0.0);\n mat2 rot01 = rotate(0.1 * time);\n mat2 rot1 = rotate(0.1);\n \n q = vec2(fbm(p + offset1), fbm(rot01 * p + offset1));\n r = vec2(fbm(rot1 * q + offset0), fbm(q + offset0));\n return fbm(p + r);\n}\n\nfloat digit(vec2 p){\n vec2 grid = uGridMul * 15.0;\n vec2 s = floor(p * grid) / grid;\n p = p * grid;\n vec2 q, r;\n float intensity = pattern(s * 0.1, q, r) * 1.3 - 0.03;\n \n if(uUseMouse > 0.5){\n vec2 mouseWorld = uMouse * uScale;\n float distToMouse = distance(s, mouseWorld);\n float mouseInfluence = exp(-distToMouse * 8.0) * uMouseStrength * 10.0;\n intensity += mouseInfluence;\n \n float ripple = sin(distToMouse * 20.0 - iTime * 5.0) * 0.1 * mouseInfluence;\n intensity += ripple;\n }\n \n if(uUsePageLoadAnimation > 0.5){\n float cellRandom = fract(sin(dot(s, vec2(12.9898, 78.233))) * 43758.5453);\n float cellDelay = cellRandom * 0.8;\n float cellProgress = clamp((uPageLoadProgress - cellDelay) / 0.2, 0.0, 1.0);\n \n float fadeAlpha = smoothstep(0.0, 1.0, cellProgress);\n intensity *= fadeAlpha;\n }\n \n p = fract(p);\n p *= uDigitSize;\n \n float px5 = p.x * 5.0;\n float py5 = (1.0 - p.y) * 5.0;\n float x = fract(px5);\n float y = fract(py5);\n \n float i = floor(py5) - 2.0;\n float j = floor(px5) - 2.0;\n float n = i * i + j * j;\n float f = n * 0.0625;\n \n float isOn = step(0.1, intensity - f);\n float brightness = isOn * (0.2 + y * 0.8) * (0.75 + x * 0.25);\n \n return step(0.0, p.x) * step(p.x, 1.0) * step(0.0, p.y) * step(p.y, 1.0) * brightness;\n}\n\nfloat onOff(float a, float b, float c)\n{\n return step(c, sin(iTime + a * cos(iTime * b))) * uFlickerAmount;\n}\n\nfloat displace(vec2 look)\n{\n float y = look.y - mod(iTime * 0.25, 1.0);\n float window = 1.0 / (1.0 + 50.0 * y * y);\n return sin(look.y * 20.0 + iTime) * 0.0125 * onOff(4.0, 2.0, 0.8) * (1.0 + cos(iTime * 60.0)) * window;\n}\n\nvec3 getColor(vec2 p){\n \n float bar = step(mod(p.y + time * 20.0, 1.0), 0.2) * 0.4 + 1.0;\n bar *= uScanlineIntensity;\n \n float displacement = displace(p);\n p.x += displacement;\n\n if (uGlitchAmount != 1.0) {\n float extra = displacement * (uGlitchAmount - 1.0);\n p.x += extra;\n }\n\n float middle = digit(p);\n \n const float off = 0.002;\n float sum = digit(p + vec2(-off, -off)) + digit(p + vec2(0.0, -off)) + digit(p + vec2(off, -off)) +\n digit(p + vec2(-off, 0.0)) + digit(p + vec2(0.0, 0.0)) + digit(p + vec2(off, 0.0)) +\n digit(p + vec2(-off, off)) + digit(p + vec2(0.0, off)) + digit(p + vec2(off, off));\n \n vec3 baseColor = vec3(0.9) * middle + sum * 0.1 * vec3(1.0) * bar;\n return baseColor;\n}\n\nvec2 barrel(vec2 uv){\n vec2 c = uv * 2.0 - 1.0;\n float r2 = dot(c, c);\n c *= 1.0 + uCurvature * r2;\n return c * 0.5 + 0.5;\n}\n\nvoid main() {\n time = iTime * 0.333333;\n vec2 uv = vUv;\n\n if(uCurvature != 0.0){\n uv = barrel(uv);\n }\n \n vec2 p = uv * uScale;\n vec3 col = getColor(p);\n\n if(uChromaticAberration != 0.0){\n vec2 ca = vec2(uChromaticAberration) / iResolution.xy;\n col.r = getColor(p + ca).r;\n col.b = getColor(p - ca).b;\n }\n\n col *= uTint;\n col *= uBrightness;\n\n if(uDither > 0.0){\n float rnd = hash21(gl_FragCoord.xy);\n col += (rnd - 0.5) * (uDither * 0.003922);\n }\n\n gl_FragColor = vec4(col, 1.0);\n}\n`;\n\nfunction hexToRgb(hex) {\n let h = hex.replace('#', '').trim();\n if (h.length === 3)\n h = h\n .split('')\n .map(c => c + c)\n .join('');\n const num = parseInt(h, 16);\n return [((num >> 16) & 255) / 255, ((num >> 8) & 255) / 255, (num & 255) / 255];\n}\n\nexport default function FaultyTerminal({\n scale = 1,\n gridMul = [2, 1],\n digitSize = 1.5,\n timeScale = 0.3,\n pause = false,\n scanlineIntensity = 0.3,\n glitchAmount = 1,\n flickerAmount = 1,\n noiseAmp = 0,\n chromaticAberration = 0,\n dither = 0,\n curvature = 0.2,\n tint = '#ffffff',\n mouseReact = true,\n mouseStrength = 0.2,\n dpr = Math.min(window.devicePixelRatio || 1, 2),\n pageLoadAnimation = true,\n brightness = 1,\n className,\n style,\n ...rest\n}) {\n const containerRef = useRef(null);\n const programRef = useRef(null);\n const rendererRef = useRef(null);\n const mouseRef = useRef({ x: 0.5, y: 0.5 });\n const smoothMouseRef = useRef({ x: 0.5, y: 0.5 });\n const frozenTimeRef = useRef(0);\n const rafRef = useRef(0);\n const loadAnimationStartRef = useRef(0);\n const timeOffsetRef = useRef(Math.random() * 100);\n\n const tintVec = useMemo(() => hexToRgb(tint), [tint]);\n\n const ditherValue = useMemo(() => (typeof dither === 'boolean' ? (dither ? 1 : 0) : dither), [dither]);\n\n const handleMouseMove = useCallback(e => {\n const ctn = containerRef.current;\n if (!ctn) return;\n const rect = ctn.getBoundingClientRect();\n const x = (e.clientX - rect.left) / rect.width;\n const y = 1 - (e.clientY - rect.top) / rect.height;\n mouseRef.current = { x, y };\n }, []);\n\n useEffect(() => {\n const ctn = containerRef.current;\n if (!ctn) return;\n\n const renderer = new Renderer({ dpr });\n rendererRef.current = renderer;\n const gl = renderer.gl;\n gl.clearColor(0, 0, 0, 1);\n\n const geometry = new Triangle(gl);\n\n const program = new Program(gl, {\n vertex: vertexShader,\n fragment: fragmentShader,\n uniforms: {\n iTime: { value: 0 },\n iResolution: {\n value: new Color(gl.canvas.width, gl.canvas.height, gl.canvas.width / gl.canvas.height)\n },\n uScale: { value: scale },\n\n uGridMul: { value: new Float32Array(gridMul) },\n uDigitSize: { value: digitSize },\n uScanlineIntensity: { value: scanlineIntensity },\n uGlitchAmount: { value: glitchAmount },\n uFlickerAmount: { value: flickerAmount },\n uNoiseAmp: { value: noiseAmp },\n uChromaticAberration: { value: chromaticAberration },\n uDither: { value: ditherValue },\n uCurvature: { value: curvature },\n uTint: { value: new Color(tintVec[0], tintVec[1], tintVec[2]) },\n uMouse: {\n value: new Float32Array([smoothMouseRef.current.x, smoothMouseRef.current.y])\n },\n uMouseStrength: { value: mouseStrength },\n uUseMouse: { value: mouseReact ? 1 : 0 },\n uPageLoadProgress: { value: pageLoadAnimation ? 0 : 1 },\n uUsePageLoadAnimation: { value: pageLoadAnimation ? 1 : 0 },\n uBrightness: { value: brightness }\n }\n });\n programRef.current = program;\n\n const mesh = new Mesh(gl, { geometry, program });\n\n function resize() {\n if (!ctn || !renderer) return;\n renderer.setSize(ctn.offsetWidth, ctn.offsetHeight);\n program.uniforms.iResolution.value = new Color(\n gl.canvas.width,\n gl.canvas.height,\n gl.canvas.width / gl.canvas.height\n );\n }\n\n const resizeObserver = new ResizeObserver(() => resize());\n resizeObserver.observe(ctn);\n resize();\n\n const update = t => {\n rafRef.current = requestAnimationFrame(update);\n\n if (pageLoadAnimation && loadAnimationStartRef.current === 0) {\n loadAnimationStartRef.current = t;\n }\n\n if (!pause) {\n const elapsed = (t * 0.001 + timeOffsetRef.current) * timeScale;\n program.uniforms.iTime.value = elapsed;\n frozenTimeRef.current = elapsed;\n } else {\n program.uniforms.iTime.value = frozenTimeRef.current;\n }\n\n if (pageLoadAnimation && loadAnimationStartRef.current > 0) {\n const animationDuration = 2000;\n const animationElapsed = t - loadAnimationStartRef.current;\n const progress = Math.min(animationElapsed / animationDuration, 1);\n program.uniforms.uPageLoadProgress.value = progress;\n }\n\n if (mouseReact) {\n const dampingFactor = 0.08;\n const smoothMouse = smoothMouseRef.current;\n const mouse = mouseRef.current;\n smoothMouse.x += (mouse.x - smoothMouse.x) * dampingFactor;\n smoothMouse.y += (mouse.y - smoothMouse.y) * dampingFactor;\n\n const mouseUniform = program.uniforms.uMouse.value;\n mouseUniform[0] = smoothMouse.x;\n mouseUniform[1] = smoothMouse.y;\n }\n\n renderer.render({ scene: mesh });\n };\n rafRef.current = requestAnimationFrame(update);\n ctn.appendChild(gl.canvas);\n\n if (mouseReact) ctn.addEventListener('mousemove', handleMouseMove);\n\n return () => {\n cancelAnimationFrame(rafRef.current);\n resizeObserver.disconnect();\n if (mouseReact) ctn.removeEventListener('mousemove', handleMouseMove);\n if (gl.canvas.parentElement === ctn) ctn.removeChild(gl.canvas);\n gl.getExtension('WEBGL_lose_context')?.loseContext();\n loadAnimationStartRef.current = 0;\n timeOffsetRef.current = Math.random() * 100;\n };\n }, [\n dpr,\n pause,\n timeScale,\n scale,\n gridMul,\n digitSize,\n scanlineIntensity,\n glitchAmount,\n flickerAmount,\n noiseAmp,\n chromaticAberration,\n ditherValue,\n curvature,\n tintVec,\n mouseReact,\n mouseStrength,\n pageLoadAnimation,\n brightness,\n handleMouseMove\n ]);\n\n return
;\n}\n" + "content": "import { Renderer, Program, Mesh, Color, Triangle } from 'ogl';\nimport { useEffect, useRef, useMemo, useCallback } from 'react';\nimport './FaultyTerminal.css';\n\nconst vertexShader = `\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 fragmentShader = `\nprecision mediump float;\n\nvarying vec2 vUv;\n\nuniform float iTime;\nuniform vec3 iResolution;\nuniform float uScale;\n\nuniform vec2 uGridMul;\nuniform float uDigitSize;\nuniform float uScanlineIntensity;\nuniform float uGlitchAmount;\nuniform float uFlickerAmount;\nuniform float uNoiseAmp;\nuniform float uChromaticAberration;\nuniform float uDither;\nuniform float uCurvature;\nuniform vec3 uTint;\nuniform vec2 uMouse;\nuniform float uMouseStrength;\nuniform float uUseMouse;\nuniform float uPageLoadProgress;\nuniform float uUsePageLoadAnimation;\nuniform float uBrightness;\n\nfloat time;\n\nfloat hash21(vec2 p){\n p = fract(p * 234.56);\n p += dot(p, p + 34.56);\n return fract(p.x * p.y);\n}\n\nfloat noise(vec2 p)\n{\n return sin(p.x * 10.0) * sin(p.y * (3.0 + sin(time * 0.090909))) + 0.2; \n}\n\nmat2 rotate(float angle)\n{\n float c = cos(angle);\n float s = sin(angle);\n return mat2(c, -s, s, c);\n}\n\nfloat fbm(vec2 p)\n{\n p *= 1.1;\n float f = 0.0;\n float amp = 0.5 * uNoiseAmp;\n \n mat2 modify0 = rotate(time * 0.02);\n f += amp * noise(p);\n p = modify0 * p * 2.0;\n amp *= 0.454545;\n \n mat2 modify1 = rotate(time * 0.02);\n f += amp * noise(p);\n p = modify1 * p * 2.0;\n amp *= 0.454545;\n \n mat2 modify2 = rotate(time * 0.08);\n f += amp * noise(p);\n \n return f;\n}\n\nfloat pattern(vec2 p, out vec2 q, out vec2 r) {\n vec2 offset1 = vec2(1.0);\n vec2 offset0 = vec2(0.0);\n mat2 rot01 = rotate(0.1 * time);\n mat2 rot1 = rotate(0.1);\n \n q = vec2(fbm(p + offset1), fbm(rot01 * p + offset1));\n r = vec2(fbm(rot1 * q + offset0), fbm(q + offset0));\n return fbm(p + r);\n}\n\nfloat digit(vec2 p){\n vec2 grid = uGridMul * 15.0;\n vec2 s = floor(p * grid) / grid;\n p = p * grid;\n vec2 q, r;\n float intensity = pattern(s * 0.1, q, r) * 1.3 - 0.03;\n \n if(uUseMouse > 0.5){\n vec2 mouseWorld = uMouse * uScale;\n float distToMouse = distance(s, mouseWorld);\n float mouseInfluence = exp(-distToMouse * 8.0) * uMouseStrength * 10.0;\n intensity += mouseInfluence;\n \n float ripple = sin(distToMouse * 20.0 - iTime * 5.0) * 0.1 * mouseInfluence;\n intensity += ripple;\n }\n \n if(uUsePageLoadAnimation > 0.5){\n float cellRandom = fract(sin(dot(s, vec2(12.9898, 78.233))) * 43758.5453);\n float cellDelay = cellRandom * 0.8;\n float cellProgress = clamp((uPageLoadProgress - cellDelay) / 0.2, 0.0, 1.0);\n \n float fadeAlpha = smoothstep(0.0, 1.0, cellProgress);\n intensity *= fadeAlpha;\n }\n \n p = fract(p);\n p *= uDigitSize;\n \n float px5 = p.x * 5.0;\n float py5 = (1.0 - p.y) * 5.0;\n float x = fract(px5);\n float y = fract(py5);\n \n float i = floor(py5) - 2.0;\n float j = floor(px5) - 2.0;\n float n = i * i + j * j;\n float f = n * 0.0625;\n \n float isOn = step(0.1, intensity - f);\n float brightness = isOn * (0.2 + y * 0.8) * (0.75 + x * 0.25);\n \n return step(0.0, p.x) * step(p.x, 1.0) * step(0.0, p.y) * step(p.y, 1.0) * brightness;\n}\n\nfloat onOff(float a, float b, float c)\n{\n return step(c, sin(iTime + a * cos(iTime * b))) * uFlickerAmount;\n}\n\nfloat displace(vec2 look)\n{\n float y = look.y - mod(iTime * 0.25, 1.0);\n float window = 1.0 / (1.0 + 50.0 * y * y);\n return sin(look.y * 20.0 + iTime) * 0.0125 * onOff(4.0, 2.0, 0.8) * (1.0 + cos(iTime * 60.0)) * window;\n}\n\nvec3 getColor(vec2 p){\n \n float bar = step(mod(p.y + time * 20.0, 1.0), 0.2) * 0.4 + 1.0;\n bar *= uScanlineIntensity;\n \n float displacement = displace(p);\n p.x += displacement;\n\n if (uGlitchAmount != 1.0) {\n float extra = displacement * (uGlitchAmount - 1.0);\n p.x += extra;\n }\n\n float middle = digit(p);\n \n const float off = 0.002;\n float sum = digit(p + vec2(-off, -off)) + digit(p + vec2(0.0, -off)) + digit(p + vec2(off, -off)) +\n digit(p + vec2(-off, 0.0)) + digit(p + vec2(0.0, 0.0)) + digit(p + vec2(off, 0.0)) +\n digit(p + vec2(-off, off)) + digit(p + vec2(0.0, off)) + digit(p + vec2(off, off));\n \n vec3 baseColor = vec3(0.9) * middle + sum * 0.1 * vec3(1.0) * bar;\n return baseColor;\n}\n\nvec2 barrel(vec2 uv){\n vec2 c = uv * 2.0 - 1.0;\n float r2 = dot(c, c);\n c *= 1.0 + uCurvature * r2;\n return c * 0.5 + 0.5;\n}\n\nvoid main() {\n time = iTime * 0.333333;\n vec2 uv = vUv;\n\n if(uCurvature != 0.0){\n uv = barrel(uv);\n }\n \n vec2 p = uv * uScale;\n vec3 col = getColor(p);\n\n if(uChromaticAberration != 0.0){\n vec2 ca = vec2(uChromaticAberration) / iResolution.xy;\n col.r = getColor(p + ca).r;\n col.b = getColor(p - ca).b;\n }\n\n col *= uTint;\n col *= uBrightness;\n\n if(uDither > 0.0){\n float rnd = hash21(gl_FragCoord.xy);\n col += (rnd - 0.5) * (uDither * 0.003922);\n }\n\n gl_FragColor = vec4(col, 1.0);\n}\n`;\n\nfunction hexToRgb(hex) {\n let h = hex.replace('#', '').trim();\n if (h.length === 3)\n h = h\n .split('')\n .map(c => c + c)\n .join('');\n const num = parseInt(h.slice(0, 6), 16);\n return [((num >> 16) & 255) / 255, ((num >> 8) & 255) / 255, (num & 255) / 255];\n}\n\nexport default function FaultyTerminal({\n scale = 1,\n gridMul = [2, 1],\n digitSize = 1.5,\n timeScale = 0.3,\n pause = false,\n scanlineIntensity = 0.3,\n glitchAmount = 1,\n flickerAmount = 1,\n noiseAmp = 0,\n chromaticAberration = 0,\n dither = 0,\n curvature = 0.2,\n tint = '#ffffff',\n mouseReact = true,\n mouseStrength = 0.2,\n dpr = Math.min(window.devicePixelRatio || 1, 2),\n pageLoadAnimation = true,\n brightness = 1,\n className,\n style,\n ...rest\n}) {\n const containerRef = useRef(null);\n const programRef = useRef(null);\n const rendererRef = useRef(null);\n const mouseRef = useRef({ x: 0.5, y: 0.5 });\n const smoothMouseRef = useRef({ x: 0.5, y: 0.5 });\n const frozenTimeRef = useRef(0);\n const rafRef = useRef(0);\n const loadAnimationStartRef = useRef(0);\n const timeOffsetRef = useRef(Math.random() * 100);\n\n const tintVec = useMemo(() => hexToRgb(tint), [tint]);\n\n const ditherValue = useMemo(() => (typeof dither === 'boolean' ? (dither ? 1 : 0) : dither), [dither]);\n\n const handleMouseMove = useCallback(e => {\n const ctn = containerRef.current;\n if (!ctn) return;\n const rect = ctn.getBoundingClientRect();\n const x = (e.clientX - rect.left) / rect.width;\n const y = 1 - (e.clientY - rect.top) / rect.height;\n mouseRef.current = { x, y };\n }, []);\n\n useEffect(() => {\n const ctn = containerRef.current;\n if (!ctn) return;\n\n const renderer = new Renderer({ dpr });\n rendererRef.current = renderer;\n const gl = renderer.gl;\n gl.clearColor(0, 0, 0, 1);\n\n const geometry = new Triangle(gl);\n\n const program = new Program(gl, {\n vertex: vertexShader,\n fragment: fragmentShader,\n uniforms: {\n iTime: { value: 0 },\n iResolution: {\n value: new Color(gl.canvas.width, gl.canvas.height, gl.canvas.width / gl.canvas.height)\n },\n uScale: { value: scale },\n\n uGridMul: { value: new Float32Array(gridMul) },\n uDigitSize: { value: digitSize },\n uScanlineIntensity: { value: scanlineIntensity },\n uGlitchAmount: { value: glitchAmount },\n uFlickerAmount: { value: flickerAmount },\n uNoiseAmp: { value: noiseAmp },\n uChromaticAberration: { value: chromaticAberration },\n uDither: { value: ditherValue },\n uCurvature: { value: curvature },\n uTint: { value: new Color(tintVec[0], tintVec[1], tintVec[2]) },\n uMouse: {\n value: new Float32Array([smoothMouseRef.current.x, smoothMouseRef.current.y])\n },\n uMouseStrength: { value: mouseStrength },\n uUseMouse: { value: mouseReact ? 1 : 0 },\n uPageLoadProgress: { value: pageLoadAnimation ? 0 : 1 },\n uUsePageLoadAnimation: { value: pageLoadAnimation ? 1 : 0 },\n uBrightness: { value: brightness }\n }\n });\n programRef.current = program;\n\n const mesh = new Mesh(gl, { geometry, program });\n\n function resize() {\n if (!ctn || !renderer) return;\n renderer.setSize(ctn.offsetWidth, ctn.offsetHeight);\n program.uniforms.iResolution.value = new Color(\n gl.canvas.width,\n gl.canvas.height,\n gl.canvas.width / gl.canvas.height\n );\n }\n\n const resizeObserver = new ResizeObserver(() => resize());\n resizeObserver.observe(ctn);\n resize();\n\n const update = t => {\n rafRef.current = requestAnimationFrame(update);\n\n if (pageLoadAnimation && loadAnimationStartRef.current === 0) {\n loadAnimationStartRef.current = t;\n }\n\n if (!pause) {\n const elapsed = (t * 0.001 + timeOffsetRef.current) * timeScale;\n program.uniforms.iTime.value = elapsed;\n frozenTimeRef.current = elapsed;\n } else {\n program.uniforms.iTime.value = frozenTimeRef.current;\n }\n\n if (pageLoadAnimation && loadAnimationStartRef.current > 0) {\n const animationDuration = 2000;\n const animationElapsed = t - loadAnimationStartRef.current;\n const progress = Math.min(animationElapsed / animationDuration, 1);\n program.uniforms.uPageLoadProgress.value = progress;\n }\n\n if (mouseReact) {\n const dampingFactor = 0.08;\n const smoothMouse = smoothMouseRef.current;\n const mouse = mouseRef.current;\n smoothMouse.x += (mouse.x - smoothMouse.x) * dampingFactor;\n smoothMouse.y += (mouse.y - smoothMouse.y) * dampingFactor;\n\n const mouseUniform = program.uniforms.uMouse.value;\n mouseUniform[0] = smoothMouse.x;\n mouseUniform[1] = smoothMouse.y;\n }\n\n renderer.render({ scene: mesh });\n };\n rafRef.current = requestAnimationFrame(update);\n ctn.appendChild(gl.canvas);\n\n if (mouseReact) ctn.addEventListener('mousemove', handleMouseMove);\n\n return () => {\n cancelAnimationFrame(rafRef.current);\n resizeObserver.disconnect();\n if (mouseReact) ctn.removeEventListener('mousemove', handleMouseMove);\n if (gl.canvas.parentElement === ctn) ctn.removeChild(gl.canvas);\n gl.getExtension('WEBGL_lose_context')?.loseContext();\n loadAnimationStartRef.current = 0;\n timeOffsetRef.current = Math.random() * 100;\n };\n }, [\n dpr,\n pause,\n timeScale,\n scale,\n gridMul,\n digitSize,\n scanlineIntensity,\n glitchAmount,\n flickerAmount,\n noiseAmp,\n chromaticAberration,\n ditherValue,\n curvature,\n tintVec,\n mouseReact,\n mouseStrength,\n pageLoadAnimation,\n brightness,\n handleMouseMove\n ]);\n\n return
;\n}\n" } ], "registryDependencies": [], diff --git a/public/r/FaultyTerminal-JS-TW.json b/public/r/FaultyTerminal-JS-TW.json index 6e327324d..f0f1f5518 100644 --- a/public/r/FaultyTerminal-JS-TW.json +++ b/public/r/FaultyTerminal-JS-TW.json @@ -8,7 +8,7 @@ { "type": "registry:component", "path": "FaultyTerminal/FaultyTerminal.jsx", - "content": "import { Renderer, Program, Mesh, Color, Triangle } from 'ogl';\nimport { useEffect, useRef, useMemo, useCallback } from 'react';\n\nconst vertexShader = `\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 fragmentShader = `\nprecision mediump float;\n\nvarying vec2 vUv;\n\nuniform float iTime;\nuniform vec3 iResolution;\nuniform float uScale;\n\nuniform vec2 uGridMul;\nuniform float uDigitSize;\nuniform float uScanlineIntensity;\nuniform float uGlitchAmount;\nuniform float uFlickerAmount;\nuniform float uNoiseAmp;\nuniform float uChromaticAberration;\nuniform float uDither;\nuniform float uCurvature;\nuniform vec3 uTint;\nuniform vec2 uMouse;\nuniform float uMouseStrength;\nuniform float uUseMouse;\nuniform float uPageLoadProgress;\nuniform float uUsePageLoadAnimation;\nuniform float uBrightness;\n\nfloat time;\n\nfloat hash21(vec2 p){\n p = fract(p * 234.56);\n p += dot(p, p + 34.56);\n return fract(p.x * p.y);\n}\n\nfloat noise(vec2 p)\n{\n return sin(p.x * 10.0) * sin(p.y * (3.0 + sin(time * 0.090909))) + 0.2; \n}\n\nmat2 rotate(float angle)\n{\n float c = cos(angle);\n float s = sin(angle);\n return mat2(c, -s, s, c);\n}\n\nfloat fbm(vec2 p)\n{\n p *= 1.1;\n float f = 0.0;\n float amp = 0.5 * uNoiseAmp;\n \n mat2 modify0 = rotate(time * 0.02);\n f += amp * noise(p);\n p = modify0 * p * 2.0;\n amp *= 0.454545;\n \n mat2 modify1 = rotate(time * 0.02);\n f += amp * noise(p);\n p = modify1 * p * 2.0;\n amp *= 0.454545;\n \n mat2 modify2 = rotate(time * 0.08);\n f += amp * noise(p);\n \n return f;\n}\n\nfloat pattern(vec2 p, out vec2 q, out vec2 r) {\n vec2 offset1 = vec2(1.0);\n vec2 offset0 = vec2(0.0);\n mat2 rot01 = rotate(0.1 * time);\n mat2 rot1 = rotate(0.1);\n \n q = vec2(fbm(p + offset1), fbm(rot01 * p + offset1));\n r = vec2(fbm(rot1 * q + offset0), fbm(q + offset0));\n return fbm(p + r);\n}\n\nfloat digit(vec2 p){\n vec2 grid = uGridMul * 15.0;\n vec2 s = floor(p * grid) / grid;\n p = p * grid;\n vec2 q, r;\n float intensity = pattern(s * 0.1, q, r) * 1.3 - 0.03;\n \n if(uUseMouse > 0.5){\n vec2 mouseWorld = uMouse * uScale;\n float distToMouse = distance(s, mouseWorld);\n float mouseInfluence = exp(-distToMouse * 8.0) * uMouseStrength * 10.0;\n intensity += mouseInfluence;\n \n float ripple = sin(distToMouse * 20.0 - iTime * 5.0) * 0.1 * mouseInfluence;\n intensity += ripple;\n }\n \n if(uUsePageLoadAnimation > 0.5){\n float cellRandom = fract(sin(dot(s, vec2(12.9898, 78.233))) * 43758.5453);\n float cellDelay = cellRandom * 0.8;\n float cellProgress = clamp((uPageLoadProgress - cellDelay) / 0.2, 0.0, 1.0);\n \n float fadeAlpha = smoothstep(0.0, 1.0, cellProgress);\n intensity *= fadeAlpha;\n }\n \n p = fract(p);\n p *= uDigitSize;\n \n float px5 = p.x * 5.0;\n float py5 = (1.0 - p.y) * 5.0;\n float x = fract(px5);\n float y = fract(py5);\n \n float i = floor(py5) - 2.0;\n float j = floor(px5) - 2.0;\n float n = i * i + j * j;\n float f = n * 0.0625;\n \n float isOn = step(0.1, intensity - f);\n float brightness = isOn * (0.2 + y * 0.8) * (0.75 + x * 0.25);\n \n return step(0.0, p.x) * step(p.x, 1.0) * step(0.0, p.y) * step(p.y, 1.0) * brightness;\n}\n\nfloat onOff(float a, float b, float c)\n{\n return step(c, sin(iTime + a * cos(iTime * b))) * uFlickerAmount;\n}\n\nfloat displace(vec2 look)\n{\n float y = look.y - mod(iTime * 0.25, 1.0);\n float window = 1.0 / (1.0 + 50.0 * y * y);\n return sin(look.y * 20.0 + iTime) * 0.0125 * onOff(4.0, 2.0, 0.8) * (1.0 + cos(iTime * 60.0)) * window;\n}\n\nvec3 getColor(vec2 p){\n \n float bar = step(mod(p.y + time * 20.0, 1.0), 0.2) * 0.4 + 1.0;\n bar *= uScanlineIntensity;\n \n float displacement = displace(p);\n p.x += displacement;\n\n if (uGlitchAmount != 1.0) {\n float extra = displacement * (uGlitchAmount - 1.0);\n p.x += extra;\n }\n\n float middle = digit(p);\n \n const float off = 0.002;\n float sum = digit(p + vec2(-off, -off)) + digit(p + vec2(0.0, -off)) + digit(p + vec2(off, -off)) +\n digit(p + vec2(-off, 0.0)) + digit(p + vec2(0.0, 0.0)) + digit(p + vec2(off, 0.0)) +\n digit(p + vec2(-off, off)) + digit(p + vec2(0.0, off)) + digit(p + vec2(off, off));\n \n vec3 baseColor = vec3(0.9) * middle + sum * 0.1 * vec3(1.0) * bar;\n return baseColor;\n}\n\nvec2 barrel(vec2 uv){\n vec2 c = uv * 2.0 - 1.0;\n float r2 = dot(c, c);\n c *= 1.0 + uCurvature * r2;\n return c * 0.5 + 0.5;\n}\n\nvoid main() {\n time = iTime * 0.333333;\n vec2 uv = vUv;\n\n if(uCurvature != 0.0){\n uv = barrel(uv);\n }\n \n vec2 p = uv * uScale;\n vec3 col = getColor(p);\n\n if(uChromaticAberration != 0.0){\n vec2 ca = vec2(uChromaticAberration) / iResolution.xy;\n col.r = getColor(p + ca).r;\n col.b = getColor(p - ca).b;\n }\n\n col *= uTint;\n col *= uBrightness;\n\n if(uDither > 0.0){\n float rnd = hash21(gl_FragCoord.xy);\n col += (rnd - 0.5) * (uDither * 0.003922);\n }\n\n gl_FragColor = vec4(col, 1.0);\n}\n`;\n\nfunction hexToRgb(hex) {\n let h = hex.replace('#', '').trim();\n if (h.length === 3)\n h = h\n .split('')\n .map(c => c + c)\n .join('');\n const num = parseInt(h, 16);\n return [((num >> 16) & 255) / 255, ((num >> 8) & 255) / 255, (num & 255) / 255];\n}\n\nexport default function FaultyTerminal({\n scale = 1,\n gridMul = [2, 1],\n digitSize = 1.5,\n timeScale = 0.3,\n pause = false,\n scanlineIntensity = 0.3,\n glitchAmount = 1,\n flickerAmount = 1,\n noiseAmp = 1,\n chromaticAberration = 0,\n dither = 0,\n curvature = 0.2,\n tint = '#ffffff',\n mouseReact = true,\n mouseStrength = 0.2,\n dpr = Math.min(window.devicePixelRatio || 1, 2),\n pageLoadAnimation = true,\n brightness = 1,\n className,\n style,\n ...rest\n}) {\n const containerRef = useRef(null);\n const programRef = useRef(null);\n const rendererRef = useRef(null);\n const mouseRef = useRef({ x: 0.5, y: 0.5 });\n const smoothMouseRef = useRef({ x: 0.5, y: 0.5 });\n const frozenTimeRef = useRef(0);\n const rafRef = useRef(0);\n const loadAnimationStartRef = useRef(0);\n const timeOffsetRef = useRef(Math.random() * 100);\n\n const tintVec = useMemo(() => hexToRgb(tint), [tint]);\n\n const ditherValue = useMemo(() => (typeof dither === 'boolean' ? (dither ? 1 : 0) : dither), [dither]);\n\n const handleMouseMove = useCallback(e => {\n const ctn = containerRef.current;\n if (!ctn) return;\n const rect = ctn.getBoundingClientRect();\n const x = (e.clientX - rect.left) / rect.width;\n const y = 1 - (e.clientY - rect.top) / rect.height;\n mouseRef.current = { x, y };\n }, []);\n\n useEffect(() => {\n const ctn = containerRef.current;\n if (!ctn) return;\n\n const renderer = new Renderer({ dpr });\n rendererRef.current = renderer;\n const gl = renderer.gl;\n gl.clearColor(0, 0, 0, 1);\n\n const geometry = new Triangle(gl);\n\n const program = new Program(gl, {\n vertex: vertexShader,\n fragment: fragmentShader,\n uniforms: {\n iTime: { value: 0 },\n iResolution: {\n value: new Color(gl.canvas.width, gl.canvas.height, gl.canvas.width / gl.canvas.height)\n },\n uScale: { value: scale },\n\n uGridMul: { value: new Float32Array(gridMul) },\n uDigitSize: { value: digitSize },\n uScanlineIntensity: { value: scanlineIntensity },\n uGlitchAmount: { value: glitchAmount },\n uFlickerAmount: { value: flickerAmount },\n uNoiseAmp: { value: noiseAmp },\n uChromaticAberration: { value: chromaticAberration },\n uDither: { value: ditherValue },\n uCurvature: { value: curvature },\n uTint: { value: new Color(tintVec[0], tintVec[1], tintVec[2]) },\n uMouse: {\n value: new Float32Array([smoothMouseRef.current.x, smoothMouseRef.current.y])\n },\n uMouseStrength: { value: mouseStrength },\n uUseMouse: { value: mouseReact ? 1 : 0 },\n uPageLoadProgress: { value: pageLoadAnimation ? 0 : 1 },\n uUsePageLoadAnimation: { value: pageLoadAnimation ? 1 : 0 },\n uBrightness: { value: brightness }\n }\n });\n programRef.current = program;\n\n const mesh = new Mesh(gl, { geometry, program });\n\n function resize() {\n if (!ctn || !renderer) return;\n renderer.setSize(ctn.offsetWidth, ctn.offsetHeight);\n program.uniforms.iResolution.value = new Color(\n gl.canvas.width,\n gl.canvas.height,\n gl.canvas.width / gl.canvas.height\n );\n }\n\n const resizeObserver = new ResizeObserver(() => resize());\n resizeObserver.observe(ctn);\n resize();\n\n const update = t => {\n rafRef.current = requestAnimationFrame(update);\n\n if (pageLoadAnimation && loadAnimationStartRef.current === 0) {\n loadAnimationStartRef.current = t;\n }\n\n if (!pause) {\n const elapsed = (t * 0.001 + timeOffsetRef.current) * timeScale;\n program.uniforms.iTime.value = elapsed;\n frozenTimeRef.current = elapsed;\n } else {\n program.uniforms.iTime.value = frozenTimeRef.current;\n }\n\n if (pageLoadAnimation && loadAnimationStartRef.current > 0) {\n const animationDuration = 2000;\n const animationElapsed = t - loadAnimationStartRef.current;\n const progress = Math.min(animationElapsed / animationDuration, 1);\n program.uniforms.uPageLoadProgress.value = progress;\n }\n\n if (mouseReact) {\n const dampingFactor = 0.08;\n const smoothMouse = smoothMouseRef.current;\n const mouse = mouseRef.current;\n smoothMouse.x += (mouse.x - smoothMouse.x) * dampingFactor;\n smoothMouse.y += (mouse.y - smoothMouse.y) * dampingFactor;\n\n const mouseUniform = program.uniforms.uMouse.value;\n mouseUniform[0] = smoothMouse.x;\n mouseUniform[1] = smoothMouse.y;\n }\n\n renderer.render({ scene: mesh });\n };\n rafRef.current = requestAnimationFrame(update);\n ctn.appendChild(gl.canvas);\n\n if (mouseReact) ctn.addEventListener('mousemove', handleMouseMove);\n\n return () => {\n cancelAnimationFrame(rafRef.current);\n resizeObserver.disconnect();\n if (mouseReact) ctn.removeEventListener('mousemove', handleMouseMove);\n if (gl.canvas.parentElement === ctn) ctn.removeChild(gl.canvas);\n gl.getExtension('WEBGL_lose_context')?.loseContext();\n loadAnimationStartRef.current = 0;\n timeOffsetRef.current = Math.random() * 100;\n };\n }, [\n dpr,\n pause,\n timeScale,\n scale,\n gridMul,\n digitSize,\n scanlineIntensity,\n glitchAmount,\n flickerAmount,\n noiseAmp,\n chromaticAberration,\n ditherValue,\n curvature,\n tintVec,\n mouseReact,\n mouseStrength,\n pageLoadAnimation,\n brightness,\n handleMouseMove\n ]);\n\n return (\n
\n );\n}\n" + "content": "import { Renderer, Program, Mesh, Color, Triangle } from 'ogl';\nimport { useEffect, useRef, useMemo, useCallback } from 'react';\n\nconst vertexShader = `\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 fragmentShader = `\nprecision mediump float;\n\nvarying vec2 vUv;\n\nuniform float iTime;\nuniform vec3 iResolution;\nuniform float uScale;\n\nuniform vec2 uGridMul;\nuniform float uDigitSize;\nuniform float uScanlineIntensity;\nuniform float uGlitchAmount;\nuniform float uFlickerAmount;\nuniform float uNoiseAmp;\nuniform float uChromaticAberration;\nuniform float uDither;\nuniform float uCurvature;\nuniform vec3 uTint;\nuniform vec2 uMouse;\nuniform float uMouseStrength;\nuniform float uUseMouse;\nuniform float uPageLoadProgress;\nuniform float uUsePageLoadAnimation;\nuniform float uBrightness;\n\nfloat time;\n\nfloat hash21(vec2 p){\n p = fract(p * 234.56);\n p += dot(p, p + 34.56);\n return fract(p.x * p.y);\n}\n\nfloat noise(vec2 p)\n{\n return sin(p.x * 10.0) * sin(p.y * (3.0 + sin(time * 0.090909))) + 0.2; \n}\n\nmat2 rotate(float angle)\n{\n float c = cos(angle);\n float s = sin(angle);\n return mat2(c, -s, s, c);\n}\n\nfloat fbm(vec2 p)\n{\n p *= 1.1;\n float f = 0.0;\n float amp = 0.5 * uNoiseAmp;\n \n mat2 modify0 = rotate(time * 0.02);\n f += amp * noise(p);\n p = modify0 * p * 2.0;\n amp *= 0.454545;\n \n mat2 modify1 = rotate(time * 0.02);\n f += amp * noise(p);\n p = modify1 * p * 2.0;\n amp *= 0.454545;\n \n mat2 modify2 = rotate(time * 0.08);\n f += amp * noise(p);\n \n return f;\n}\n\nfloat pattern(vec2 p, out vec2 q, out vec2 r) {\n vec2 offset1 = vec2(1.0);\n vec2 offset0 = vec2(0.0);\n mat2 rot01 = rotate(0.1 * time);\n mat2 rot1 = rotate(0.1);\n \n q = vec2(fbm(p + offset1), fbm(rot01 * p + offset1));\n r = vec2(fbm(rot1 * q + offset0), fbm(q + offset0));\n return fbm(p + r);\n}\n\nfloat digit(vec2 p){\n vec2 grid = uGridMul * 15.0;\n vec2 s = floor(p * grid) / grid;\n p = p * grid;\n vec2 q, r;\n float intensity = pattern(s * 0.1, q, r) * 1.3 - 0.03;\n \n if(uUseMouse > 0.5){\n vec2 mouseWorld = uMouse * uScale;\n float distToMouse = distance(s, mouseWorld);\n float mouseInfluence = exp(-distToMouse * 8.0) * uMouseStrength * 10.0;\n intensity += mouseInfluence;\n \n float ripple = sin(distToMouse * 20.0 - iTime * 5.0) * 0.1 * mouseInfluence;\n intensity += ripple;\n }\n \n if(uUsePageLoadAnimation > 0.5){\n float cellRandom = fract(sin(dot(s, vec2(12.9898, 78.233))) * 43758.5453);\n float cellDelay = cellRandom * 0.8;\n float cellProgress = clamp((uPageLoadProgress - cellDelay) / 0.2, 0.0, 1.0);\n \n float fadeAlpha = smoothstep(0.0, 1.0, cellProgress);\n intensity *= fadeAlpha;\n }\n \n p = fract(p);\n p *= uDigitSize;\n \n float px5 = p.x * 5.0;\n float py5 = (1.0 - p.y) * 5.0;\n float x = fract(px5);\n float y = fract(py5);\n \n float i = floor(py5) - 2.0;\n float j = floor(px5) - 2.0;\n float n = i * i + j * j;\n float f = n * 0.0625;\n \n float isOn = step(0.1, intensity - f);\n float brightness = isOn * (0.2 + y * 0.8) * (0.75 + x * 0.25);\n \n return step(0.0, p.x) * step(p.x, 1.0) * step(0.0, p.y) * step(p.y, 1.0) * brightness;\n}\n\nfloat onOff(float a, float b, float c)\n{\n return step(c, sin(iTime + a * cos(iTime * b))) * uFlickerAmount;\n}\n\nfloat displace(vec2 look)\n{\n float y = look.y - mod(iTime * 0.25, 1.0);\n float window = 1.0 / (1.0 + 50.0 * y * y);\n return sin(look.y * 20.0 + iTime) * 0.0125 * onOff(4.0, 2.0, 0.8) * (1.0 + cos(iTime * 60.0)) * window;\n}\n\nvec3 getColor(vec2 p){\n \n float bar = step(mod(p.y + time * 20.0, 1.0), 0.2) * 0.4 + 1.0;\n bar *= uScanlineIntensity;\n \n float displacement = displace(p);\n p.x += displacement;\n\n if (uGlitchAmount != 1.0) {\n float extra = displacement * (uGlitchAmount - 1.0);\n p.x += extra;\n }\n\n float middle = digit(p);\n \n const float off = 0.002;\n float sum = digit(p + vec2(-off, -off)) + digit(p + vec2(0.0, -off)) + digit(p + vec2(off, -off)) +\n digit(p + vec2(-off, 0.0)) + digit(p + vec2(0.0, 0.0)) + digit(p + vec2(off, 0.0)) +\n digit(p + vec2(-off, off)) + digit(p + vec2(0.0, off)) + digit(p + vec2(off, off));\n \n vec3 baseColor = vec3(0.9) * middle + sum * 0.1 * vec3(1.0) * bar;\n return baseColor;\n}\n\nvec2 barrel(vec2 uv){\n vec2 c = uv * 2.0 - 1.0;\n float r2 = dot(c, c);\n c *= 1.0 + uCurvature * r2;\n return c * 0.5 + 0.5;\n}\n\nvoid main() {\n time = iTime * 0.333333;\n vec2 uv = vUv;\n\n if(uCurvature != 0.0){\n uv = barrel(uv);\n }\n \n vec2 p = uv * uScale;\n vec3 col = getColor(p);\n\n if(uChromaticAberration != 0.0){\n vec2 ca = vec2(uChromaticAberration) / iResolution.xy;\n col.r = getColor(p + ca).r;\n col.b = getColor(p - ca).b;\n }\n\n col *= uTint;\n col *= uBrightness;\n\n if(uDither > 0.0){\n float rnd = hash21(gl_FragCoord.xy);\n col += (rnd - 0.5) * (uDither * 0.003922);\n }\n\n gl_FragColor = vec4(col, 1.0);\n}\n`;\n\nfunction hexToRgb(hex) {\n let h = hex.replace('#', '').trim();\n if (h.length === 3)\n h = h\n .split('')\n .map(c => c + c)\n .join('');\n const num = parseInt(h.slice(0, 6), 16);\n return [((num >> 16) & 255) / 255, ((num >> 8) & 255) / 255, (num & 255) / 255];\n}\n\nexport default function FaultyTerminal({\n scale = 1,\n gridMul = [2, 1],\n digitSize = 1.5,\n timeScale = 0.3,\n pause = false,\n scanlineIntensity = 0.3,\n glitchAmount = 1,\n flickerAmount = 1,\n noiseAmp = 1,\n chromaticAberration = 0,\n dither = 0,\n curvature = 0.2,\n tint = '#ffffff',\n mouseReact = true,\n mouseStrength = 0.2,\n dpr = Math.min(window.devicePixelRatio || 1, 2),\n pageLoadAnimation = true,\n brightness = 1,\n className,\n style,\n ...rest\n}) {\n const containerRef = useRef(null);\n const programRef = useRef(null);\n const rendererRef = useRef(null);\n const mouseRef = useRef({ x: 0.5, y: 0.5 });\n const smoothMouseRef = useRef({ x: 0.5, y: 0.5 });\n const frozenTimeRef = useRef(0);\n const rafRef = useRef(0);\n const loadAnimationStartRef = useRef(0);\n const timeOffsetRef = useRef(Math.random() * 100);\n\n const tintVec = useMemo(() => hexToRgb(tint), [tint]);\n\n const ditherValue = useMemo(() => (typeof dither === 'boolean' ? (dither ? 1 : 0) : dither), [dither]);\n\n const handleMouseMove = useCallback(e => {\n const ctn = containerRef.current;\n if (!ctn) return;\n const rect = ctn.getBoundingClientRect();\n const x = (e.clientX - rect.left) / rect.width;\n const y = 1 - (e.clientY - rect.top) / rect.height;\n mouseRef.current = { x, y };\n }, []);\n\n useEffect(() => {\n const ctn = containerRef.current;\n if (!ctn) return;\n\n const renderer = new Renderer({ dpr });\n rendererRef.current = renderer;\n const gl = renderer.gl;\n gl.clearColor(0, 0, 0, 1);\n\n const geometry = new Triangle(gl);\n\n const program = new Program(gl, {\n vertex: vertexShader,\n fragment: fragmentShader,\n uniforms: {\n iTime: { value: 0 },\n iResolution: {\n value: new Color(gl.canvas.width, gl.canvas.height, gl.canvas.width / gl.canvas.height)\n },\n uScale: { value: scale },\n\n uGridMul: { value: new Float32Array(gridMul) },\n uDigitSize: { value: digitSize },\n uScanlineIntensity: { value: scanlineIntensity },\n uGlitchAmount: { value: glitchAmount },\n uFlickerAmount: { value: flickerAmount },\n uNoiseAmp: { value: noiseAmp },\n uChromaticAberration: { value: chromaticAberration },\n uDither: { value: ditherValue },\n uCurvature: { value: curvature },\n uTint: { value: new Color(tintVec[0], tintVec[1], tintVec[2]) },\n uMouse: {\n value: new Float32Array([smoothMouseRef.current.x, smoothMouseRef.current.y])\n },\n uMouseStrength: { value: mouseStrength },\n uUseMouse: { value: mouseReact ? 1 : 0 },\n uPageLoadProgress: { value: pageLoadAnimation ? 0 : 1 },\n uUsePageLoadAnimation: { value: pageLoadAnimation ? 1 : 0 },\n uBrightness: { value: brightness }\n }\n });\n programRef.current = program;\n\n const mesh = new Mesh(gl, { geometry, program });\n\n function resize() {\n if (!ctn || !renderer) return;\n renderer.setSize(ctn.offsetWidth, ctn.offsetHeight);\n program.uniforms.iResolution.value = new Color(\n gl.canvas.width,\n gl.canvas.height,\n gl.canvas.width / gl.canvas.height\n );\n }\n\n const resizeObserver = new ResizeObserver(() => resize());\n resizeObserver.observe(ctn);\n resize();\n\n const update = t => {\n rafRef.current = requestAnimationFrame(update);\n\n if (pageLoadAnimation && loadAnimationStartRef.current === 0) {\n loadAnimationStartRef.current = t;\n }\n\n if (!pause) {\n const elapsed = (t * 0.001 + timeOffsetRef.current) * timeScale;\n program.uniforms.iTime.value = elapsed;\n frozenTimeRef.current = elapsed;\n } else {\n program.uniforms.iTime.value = frozenTimeRef.current;\n }\n\n if (pageLoadAnimation && loadAnimationStartRef.current > 0) {\n const animationDuration = 2000;\n const animationElapsed = t - loadAnimationStartRef.current;\n const progress = Math.min(animationElapsed / animationDuration, 1);\n program.uniforms.uPageLoadProgress.value = progress;\n }\n\n if (mouseReact) {\n const dampingFactor = 0.08;\n const smoothMouse = smoothMouseRef.current;\n const mouse = mouseRef.current;\n smoothMouse.x += (mouse.x - smoothMouse.x) * dampingFactor;\n smoothMouse.y += (mouse.y - smoothMouse.y) * dampingFactor;\n\n const mouseUniform = program.uniforms.uMouse.value;\n mouseUniform[0] = smoothMouse.x;\n mouseUniform[1] = smoothMouse.y;\n }\n\n renderer.render({ scene: mesh });\n };\n rafRef.current = requestAnimationFrame(update);\n ctn.appendChild(gl.canvas);\n\n if (mouseReact) ctn.addEventListener('mousemove', handleMouseMove);\n\n return () => {\n cancelAnimationFrame(rafRef.current);\n resizeObserver.disconnect();\n if (mouseReact) ctn.removeEventListener('mousemove', handleMouseMove);\n if (gl.canvas.parentElement === ctn) ctn.removeChild(gl.canvas);\n gl.getExtension('WEBGL_lose_context')?.loseContext();\n loadAnimationStartRef.current = 0;\n timeOffsetRef.current = Math.random() * 100;\n };\n }, [\n dpr,\n pause,\n timeScale,\n scale,\n gridMul,\n digitSize,\n scanlineIntensity,\n glitchAmount,\n flickerAmount,\n noiseAmp,\n chromaticAberration,\n ditherValue,\n curvature,\n tintVec,\n mouseReact,\n mouseStrength,\n pageLoadAnimation,\n brightness,\n handleMouseMove\n ]);\n\n return (\n
\n );\n}\n" } ], "registryDependencies": [], diff --git a/public/r/FaultyTerminal-TS-CSS.json b/public/r/FaultyTerminal-TS-CSS.json index 05b62750d..eed7ee21e 100644 --- a/public/r/FaultyTerminal-TS-CSS.json +++ b/public/r/FaultyTerminal-TS-CSS.json @@ -13,7 +13,7 @@ { "type": "registry:component", "path": "FaultyTerminal/FaultyTerminal.tsx", - "content": "import { Renderer, Program, Mesh, Color, Triangle } from 'ogl';\nimport React, { useEffect, useRef, useMemo, useCallback } from 'react';\nimport './FaultyTerminal.css';\n\ntype Vec2 = [number, number];\n\nexport interface FaultyTerminalProps extends React.HTMLAttributes {\n scale?: number;\n gridMul?: Vec2;\n digitSize?: number;\n timeScale?: number;\n pause?: boolean;\n scanlineIntensity?: number;\n glitchAmount?: number;\n flickerAmount?: number;\n noiseAmp?: number;\n chromaticAberration?: number;\n dither?: number | boolean;\n curvature?: number;\n tint?: string;\n mouseReact?: boolean;\n mouseStrength?: number;\n dpr?: number;\n pageLoadAnimation?: boolean;\n brightness?: number;\n}\n\nconst vertexShader = `\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 fragmentShader = `\nprecision mediump float;\n\nvarying vec2 vUv;\n\nuniform float iTime;\nuniform vec3 iResolution;\nuniform float uScale;\n\nuniform vec2 uGridMul;\nuniform float uDigitSize;\nuniform float uScanlineIntensity;\nuniform float uGlitchAmount;\nuniform float uFlickerAmount;\nuniform float uNoiseAmp;\nuniform float uChromaticAberration;\nuniform float uDither;\nuniform float uCurvature;\nuniform vec3 uTint;\nuniform vec2 uMouse;\nuniform float uMouseStrength;\nuniform float uUseMouse;\nuniform float uPageLoadProgress;\nuniform float uUsePageLoadAnimation;\nuniform float uBrightness;\n\nfloat time;\n\nfloat hash21(vec2 p){\n p = fract(p * 234.56);\n p += dot(p, p + 34.56);\n return fract(p.x * p.y);\n}\n\nfloat noise(vec2 p)\n{\n return sin(p.x * 10.0) * sin(p.y * (3.0 + sin(time * 0.090909))) + 0.2; \n}\n\nmat2 rotate(float angle)\n{\n float c = cos(angle);\n float s = sin(angle);\n return mat2(c, -s, s, c);\n}\n\nfloat fbm(vec2 p)\n{\n p *= 1.1;\n float f = 0.0;\n float amp = 0.5 * uNoiseAmp;\n \n mat2 modify0 = rotate(time * 0.02);\n f += amp * noise(p);\n p = modify0 * p * 2.0;\n amp *= 0.454545;\n \n mat2 modify1 = rotate(time * 0.02);\n f += amp * noise(p);\n p = modify1 * p * 2.0;\n amp *= 0.454545;\n \n mat2 modify2 = rotate(time * 0.08);\n f += amp * noise(p);\n \n return f;\n}\n\nfloat pattern(vec2 p, out vec2 q, out vec2 r) {\n vec2 offset1 = vec2(1.0);\n vec2 offset0 = vec2(0.0);\n mat2 rot01 = rotate(0.1 * time);\n mat2 rot1 = rotate(0.1);\n \n q = vec2(fbm(p + offset1), fbm(rot01 * p + offset1));\n r = vec2(fbm(rot1 * q + offset0), fbm(q + offset0));\n return fbm(p + r);\n}\n\nfloat digit(vec2 p){\n vec2 grid = uGridMul * 15.0;\n vec2 s = floor(p * grid) / grid;\n p = p * grid;\n vec2 q, r;\n float intensity = pattern(s * 0.1, q, r) * 1.3 - 0.03;\n \n if(uUseMouse > 0.5){\n vec2 mouseWorld = uMouse * uScale;\n float distToMouse = distance(s, mouseWorld);\n float mouseInfluence = exp(-distToMouse * 8.0) * uMouseStrength * 10.0;\n intensity += mouseInfluence;\n \n float ripple = sin(distToMouse * 20.0 - iTime * 5.0) * 0.1 * mouseInfluence;\n intensity += ripple;\n }\n \n if(uUsePageLoadAnimation > 0.5){\n float cellRandom = fract(sin(dot(s, vec2(12.9898, 78.233))) * 43758.5453);\n float cellDelay = cellRandom * 0.8;\n float cellProgress = clamp((uPageLoadProgress - cellDelay) / 0.2, 0.0, 1.0);\n \n float fadeAlpha = smoothstep(0.0, 1.0, cellProgress);\n intensity *= fadeAlpha;\n }\n \n p = fract(p);\n p *= uDigitSize;\n \n float px5 = p.x * 5.0;\n float py5 = (1.0 - p.y) * 5.0;\n float x = fract(px5);\n float y = fract(py5);\n \n float i = floor(py5) - 2.0;\n float j = floor(px5) - 2.0;\n float n = i * i + j * j;\n float f = n * 0.0625;\n \n float isOn = step(0.1, intensity - f);\n float brightness = isOn * (0.2 + y * 0.8) * (0.75 + x * 0.25);\n \n return step(0.0, p.x) * step(p.x, 1.0) * step(0.0, p.y) * step(p.y, 1.0) * brightness;\n}\n\nfloat onOff(float a, float b, float c)\n{\n\treturn step(c, sin(iTime + a * cos(iTime * b))) * uFlickerAmount;\n}\n\nfloat displace(vec2 look)\n{\n float y = look.y - mod(iTime * 0.25, 1.0);\n float window = 1.0 / (1.0 + 50.0 * y * y);\n\t return sin(look.y * 20.0 + iTime) * 0.0125 * onOff(4.0, 2.0, 0.8) * (1.0 + cos(iTime * 60.0)) * window;\n}\n\nvec3 getColor(vec2 p){\n \n float bar = step(mod(p.y + time * 20.0, 1.0), 0.2) * 0.4 + 1.0;\n bar *= uScanlineIntensity;\n \n float displacement = displace(p);\n p.x += displacement;\n\n if (uGlitchAmount != 1.0) {\n float extra = displacement * (uGlitchAmount - 1.0);\n p.x += extra;\n }\n\n float middle = digit(p);\n \n const float off = 0.002;\n float sum = digit(p + vec2(-off, -off)) + digit(p + vec2(0.0, -off)) + digit(p + vec2(off, -off)) +\n digit(p + vec2(-off, 0.0)) + digit(p + vec2(0.0, 0.0)) + digit(p + vec2(off, 0.0)) +\n digit(p + vec2(-off, off)) + digit(p + vec2(0.0, off)) + digit(p + vec2(off, off));\n \n vec3 baseColor = vec3(0.9) * middle + sum * 0.1 * vec3(1.0) * bar;\n return baseColor;\n}\n\nvec2 barrel(vec2 uv){\n vec2 c = uv * 2.0 - 1.0;\n float r2 = dot(c, c);\n c *= 1.0 + uCurvature * r2;\n return c * 0.5 + 0.5;\n}\n\nvoid main() {\n time = iTime * 0.333333;\n vec2 uv = vUv;\n\n if(uCurvature != 0.0){\n uv = barrel(uv);\n }\n \n vec2 p = uv * uScale;\n vec3 col = getColor(p);\n\n if(uChromaticAberration != 0.0){\n vec2 ca = vec2(uChromaticAberration) / iResolution.xy;\n col.r = getColor(p + ca).r;\n col.b = getColor(p - ca).b;\n }\n\n col *= uTint;\n col *= uBrightness;\n\n if(uDither > 0.0){\n float rnd = hash21(gl_FragCoord.xy);\n col += (rnd - 0.5) * (uDither * 0.003922);\n }\n\n gl_FragColor = vec4(col, 1.0);\n}\n`;\n\nfunction hexToRgb(hex: string): [number, number, number] {\n let h = hex.replace('#', '').trim();\n if (h.length === 3)\n h = h\n .split('')\n .map(c => c + c)\n .join('');\n const num = parseInt(h, 16);\n return [((num >> 16) & 255) / 255, ((num >> 8) & 255) / 255, (num & 255) / 255];\n}\n\nexport default function FaultyTerminal({\n scale = 1,\n gridMul = [2, 1],\n digitSize = 1.5,\n timeScale = 0.3,\n pause = false,\n scanlineIntensity = 0.3,\n glitchAmount = 1,\n flickerAmount = 1,\n noiseAmp = 1,\n chromaticAberration = 0,\n dither = 0,\n curvature = 0.2,\n tint = '#ffffff',\n mouseReact = true,\n mouseStrength = 0.2,\n dpr = Math.min(window.devicePixelRatio || 1, 2),\n pageLoadAnimation = true,\n brightness = 1,\n className,\n style,\n ...rest\n}: FaultyTerminalProps) {\n const containerRef = useRef(null);\n const programRef = useRef(null);\n const rendererRef = useRef(null);\n const mouseRef = useRef({ x: 0.5, y: 0.5 });\n const smoothMouseRef = useRef({ x: 0.5, y: 0.5 });\n const frozenTimeRef = useRef(0);\n const rafRef = useRef(0);\n const loadAnimationStartRef = useRef(0);\n const timeOffsetRef = useRef(Math.random() * 100);\n\n const tintVec = useMemo(() => hexToRgb(tint), [tint]);\n\n const ditherValue = useMemo(() => (typeof dither === 'boolean' ? (dither ? 1 : 0) : dither), [dither]);\n\n const handleMouseMove = useCallback((e: MouseEvent) => {\n const ctn = containerRef.current;\n if (!ctn) return;\n const rect = ctn.getBoundingClientRect();\n const x = (e.clientX - rect.left) / rect.width;\n const y = 1 - (e.clientY - rect.top) / rect.height;\n mouseRef.current = { x, y };\n }, []);\n\n useEffect(() => {\n const ctn = containerRef.current;\n if (!ctn) return;\n\n const renderer = new Renderer({ dpr });\n rendererRef.current = renderer;\n const gl = renderer.gl;\n gl.clearColor(0, 0, 0, 1);\n\n const geometry = new Triangle(gl);\n\n const program = new Program(gl, {\n vertex: vertexShader,\n fragment: fragmentShader,\n uniforms: {\n iTime: { value: 0 },\n iResolution: {\n value: new Color(gl.canvas.width, gl.canvas.height, gl.canvas.width / gl.canvas.height)\n },\n uScale: { value: scale },\n\n uGridMul: { value: new Float32Array(gridMul) },\n uDigitSize: { value: digitSize },\n uScanlineIntensity: { value: scanlineIntensity },\n uGlitchAmount: { value: glitchAmount },\n uFlickerAmount: { value: flickerAmount },\n uNoiseAmp: { value: noiseAmp },\n uChromaticAberration: { value: chromaticAberration },\n uDither: { value: ditherValue },\n uCurvature: { value: curvature },\n uTint: { value: new Color(tintVec[0], tintVec[1], tintVec[2]) },\n uMouse: {\n value: new Float32Array([smoothMouseRef.current.x, smoothMouseRef.current.y])\n },\n uMouseStrength: { value: mouseStrength },\n uUseMouse: { value: mouseReact ? 1 : 0 },\n uPageLoadProgress: { value: pageLoadAnimation ? 0 : 1 },\n uUsePageLoadAnimation: { value: pageLoadAnimation ? 1 : 0 },\n uBrightness: { value: brightness }\n }\n });\n programRef.current = program;\n\n const mesh = new Mesh(gl, { geometry, program });\n\n function resize() {\n if (!ctn || !renderer) return;\n renderer.setSize(ctn.offsetWidth, ctn.offsetHeight);\n program.uniforms.iResolution.value = new Color(\n gl.canvas.width,\n gl.canvas.height,\n gl.canvas.width / gl.canvas.height\n );\n }\n\n const resizeObserver = new ResizeObserver(() => resize());\n resizeObserver.observe(ctn);\n resize();\n\n const update = (t: number) => {\n rafRef.current = requestAnimationFrame(update);\n\n if (pageLoadAnimation && loadAnimationStartRef.current === 0) {\n loadAnimationStartRef.current = t;\n }\n\n if (!pause) {\n const elapsed = (t * 0.001 + timeOffsetRef.current) * timeScale;\n program.uniforms.iTime.value = elapsed;\n frozenTimeRef.current = elapsed;\n } else {\n program.uniforms.iTime.value = frozenTimeRef.current;\n }\n\n if (pageLoadAnimation && loadAnimationStartRef.current > 0) {\n const animationDuration = 2000;\n const animationElapsed = t - loadAnimationStartRef.current;\n const progress = Math.min(animationElapsed / animationDuration, 1);\n program.uniforms.uPageLoadProgress.value = progress;\n }\n\n if (mouseReact) {\n const dampingFactor = 0.08;\n const smoothMouse = smoothMouseRef.current;\n const mouse = mouseRef.current;\n smoothMouse.x += (mouse.x - smoothMouse.x) * dampingFactor;\n smoothMouse.y += (mouse.y - smoothMouse.y) * dampingFactor;\n\n const mouseUniform = program.uniforms.uMouse.value as Float32Array;\n mouseUniform[0] = smoothMouse.x;\n mouseUniform[1] = smoothMouse.y;\n }\n\n renderer.render({ scene: mesh });\n };\n rafRef.current = requestAnimationFrame(update);\n ctn.appendChild(gl.canvas);\n\n if (mouseReact) ctn.addEventListener('mousemove', handleMouseMove);\n\n return () => {\n cancelAnimationFrame(rafRef.current);\n resizeObserver.disconnect();\n if (mouseReact) ctn.removeEventListener('mousemove', handleMouseMove);\n if (gl.canvas.parentElement === ctn) ctn.removeChild(gl.canvas);\n gl.getExtension('WEBGL_lose_context')?.loseContext();\n loadAnimationStartRef.current = 0;\n timeOffsetRef.current = Math.random() * 100;\n };\n }, [\n dpr,\n pause,\n timeScale,\n scale,\n gridMul,\n digitSize,\n scanlineIntensity,\n glitchAmount,\n flickerAmount,\n noiseAmp,\n chromaticAberration,\n ditherValue,\n curvature,\n tintVec,\n mouseReact,\n mouseStrength,\n pageLoadAnimation,\n brightness,\n handleMouseMove\n ]);\n\n return
;\n}\n" + "content": "import { Renderer, Program, Mesh, Color, Triangle } from 'ogl';\nimport React, { useEffect, useRef, useMemo, useCallback } from 'react';\nimport './FaultyTerminal.css';\n\ntype Vec2 = [number, number];\n\nexport interface FaultyTerminalProps extends React.HTMLAttributes {\n scale?: number;\n gridMul?: Vec2;\n digitSize?: number;\n timeScale?: number;\n pause?: boolean;\n scanlineIntensity?: number;\n glitchAmount?: number;\n flickerAmount?: number;\n noiseAmp?: number;\n chromaticAberration?: number;\n dither?: number | boolean;\n curvature?: number;\n tint?: string;\n mouseReact?: boolean;\n mouseStrength?: number;\n dpr?: number;\n pageLoadAnimation?: boolean;\n brightness?: number;\n}\n\nconst vertexShader = `\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 fragmentShader = `\nprecision mediump float;\n\nvarying vec2 vUv;\n\nuniform float iTime;\nuniform vec3 iResolution;\nuniform float uScale;\n\nuniform vec2 uGridMul;\nuniform float uDigitSize;\nuniform float uScanlineIntensity;\nuniform float uGlitchAmount;\nuniform float uFlickerAmount;\nuniform float uNoiseAmp;\nuniform float uChromaticAberration;\nuniform float uDither;\nuniform float uCurvature;\nuniform vec3 uTint;\nuniform vec2 uMouse;\nuniform float uMouseStrength;\nuniform float uUseMouse;\nuniform float uPageLoadProgress;\nuniform float uUsePageLoadAnimation;\nuniform float uBrightness;\n\nfloat time;\n\nfloat hash21(vec2 p){\n p = fract(p * 234.56);\n p += dot(p, p + 34.56);\n return fract(p.x * p.y);\n}\n\nfloat noise(vec2 p)\n{\n return sin(p.x * 10.0) * sin(p.y * (3.0 + sin(time * 0.090909))) + 0.2; \n}\n\nmat2 rotate(float angle)\n{\n float c = cos(angle);\n float s = sin(angle);\n return mat2(c, -s, s, c);\n}\n\nfloat fbm(vec2 p)\n{\n p *= 1.1;\n float f = 0.0;\n float amp = 0.5 * uNoiseAmp;\n \n mat2 modify0 = rotate(time * 0.02);\n f += amp * noise(p);\n p = modify0 * p * 2.0;\n amp *= 0.454545;\n \n mat2 modify1 = rotate(time * 0.02);\n f += amp * noise(p);\n p = modify1 * p * 2.0;\n amp *= 0.454545;\n \n mat2 modify2 = rotate(time * 0.08);\n f += amp * noise(p);\n \n return f;\n}\n\nfloat pattern(vec2 p, out vec2 q, out vec2 r) {\n vec2 offset1 = vec2(1.0);\n vec2 offset0 = vec2(0.0);\n mat2 rot01 = rotate(0.1 * time);\n mat2 rot1 = rotate(0.1);\n \n q = vec2(fbm(p + offset1), fbm(rot01 * p + offset1));\n r = vec2(fbm(rot1 * q + offset0), fbm(q + offset0));\n return fbm(p + r);\n}\n\nfloat digit(vec2 p){\n vec2 grid = uGridMul * 15.0;\n vec2 s = floor(p * grid) / grid;\n p = p * grid;\n vec2 q, r;\n float intensity = pattern(s * 0.1, q, r) * 1.3 - 0.03;\n \n if(uUseMouse > 0.5){\n vec2 mouseWorld = uMouse * uScale;\n float distToMouse = distance(s, mouseWorld);\n float mouseInfluence = exp(-distToMouse * 8.0) * uMouseStrength * 10.0;\n intensity += mouseInfluence;\n \n float ripple = sin(distToMouse * 20.0 - iTime * 5.0) * 0.1 * mouseInfluence;\n intensity += ripple;\n }\n \n if(uUsePageLoadAnimation > 0.5){\n float cellRandom = fract(sin(dot(s, vec2(12.9898, 78.233))) * 43758.5453);\n float cellDelay = cellRandom * 0.8;\n float cellProgress = clamp((uPageLoadProgress - cellDelay) / 0.2, 0.0, 1.0);\n \n float fadeAlpha = smoothstep(0.0, 1.0, cellProgress);\n intensity *= fadeAlpha;\n }\n \n p = fract(p);\n p *= uDigitSize;\n \n float px5 = p.x * 5.0;\n float py5 = (1.0 - p.y) * 5.0;\n float x = fract(px5);\n float y = fract(py5);\n \n float i = floor(py5) - 2.0;\n float j = floor(px5) - 2.0;\n float n = i * i + j * j;\n float f = n * 0.0625;\n \n float isOn = step(0.1, intensity - f);\n float brightness = isOn * (0.2 + y * 0.8) * (0.75 + x * 0.25);\n \n return step(0.0, p.x) * step(p.x, 1.0) * step(0.0, p.y) * step(p.y, 1.0) * brightness;\n}\n\nfloat onOff(float a, float b, float c)\n{\n\treturn step(c, sin(iTime + a * cos(iTime * b))) * uFlickerAmount;\n}\n\nfloat displace(vec2 look)\n{\n float y = look.y - mod(iTime * 0.25, 1.0);\n float window = 1.0 / (1.0 + 50.0 * y * y);\n\t return sin(look.y * 20.0 + iTime) * 0.0125 * onOff(4.0, 2.0, 0.8) * (1.0 + cos(iTime * 60.0)) * window;\n}\n\nvec3 getColor(vec2 p){\n \n float bar = step(mod(p.y + time * 20.0, 1.0), 0.2) * 0.4 + 1.0;\n bar *= uScanlineIntensity;\n \n float displacement = displace(p);\n p.x += displacement;\n\n if (uGlitchAmount != 1.0) {\n float extra = displacement * (uGlitchAmount - 1.0);\n p.x += extra;\n }\n\n float middle = digit(p);\n \n const float off = 0.002;\n float sum = digit(p + vec2(-off, -off)) + digit(p + vec2(0.0, -off)) + digit(p + vec2(off, -off)) +\n digit(p + vec2(-off, 0.0)) + digit(p + vec2(0.0, 0.0)) + digit(p + vec2(off, 0.0)) +\n digit(p + vec2(-off, off)) + digit(p + vec2(0.0, off)) + digit(p + vec2(off, off));\n \n vec3 baseColor = vec3(0.9) * middle + sum * 0.1 * vec3(1.0) * bar;\n return baseColor;\n}\n\nvec2 barrel(vec2 uv){\n vec2 c = uv * 2.0 - 1.0;\n float r2 = dot(c, c);\n c *= 1.0 + uCurvature * r2;\n return c * 0.5 + 0.5;\n}\n\nvoid main() {\n time = iTime * 0.333333;\n vec2 uv = vUv;\n\n if(uCurvature != 0.0){\n uv = barrel(uv);\n }\n \n vec2 p = uv * uScale;\n vec3 col = getColor(p);\n\n if(uChromaticAberration != 0.0){\n vec2 ca = vec2(uChromaticAberration) / iResolution.xy;\n col.r = getColor(p + ca).r;\n col.b = getColor(p - ca).b;\n }\n\n col *= uTint;\n col *= uBrightness;\n\n if(uDither > 0.0){\n float rnd = hash21(gl_FragCoord.xy);\n col += (rnd - 0.5) * (uDither * 0.003922);\n }\n\n gl_FragColor = vec4(col, 1.0);\n}\n`;\n\nfunction hexToRgb(hex: string): [number, number, number] {\n let h = hex.replace('#', '').trim();\n if (h.length === 3)\n h = h\n .split('')\n .map(c => c + c)\n .join('');\n const num = parseInt(h.slice(0, 6), 16);\n return [((num >> 16) & 255) / 255, ((num >> 8) & 255) / 255, (num & 255) / 255];\n}\n\nexport default function FaultyTerminal({\n scale = 1,\n gridMul = [2, 1],\n digitSize = 1.5,\n timeScale = 0.3,\n pause = false,\n scanlineIntensity = 0.3,\n glitchAmount = 1,\n flickerAmount = 1,\n noiseAmp = 1,\n chromaticAberration = 0,\n dither = 0,\n curvature = 0.2,\n tint = '#ffffff',\n mouseReact = true,\n mouseStrength = 0.2,\n dpr = Math.min(window.devicePixelRatio || 1, 2),\n pageLoadAnimation = true,\n brightness = 1,\n className,\n style,\n ...rest\n}: FaultyTerminalProps) {\n const containerRef = useRef(null);\n const programRef = useRef(null);\n const rendererRef = useRef(null);\n const mouseRef = useRef({ x: 0.5, y: 0.5 });\n const smoothMouseRef = useRef({ x: 0.5, y: 0.5 });\n const frozenTimeRef = useRef(0);\n const rafRef = useRef(0);\n const loadAnimationStartRef = useRef(0);\n const timeOffsetRef = useRef(Math.random() * 100);\n\n const tintVec = useMemo(() => hexToRgb(tint), [tint]);\n\n const ditherValue = useMemo(() => (typeof dither === 'boolean' ? (dither ? 1 : 0) : dither), [dither]);\n\n const handleMouseMove = useCallback((e: MouseEvent) => {\n const ctn = containerRef.current;\n if (!ctn) return;\n const rect = ctn.getBoundingClientRect();\n const x = (e.clientX - rect.left) / rect.width;\n const y = 1 - (e.clientY - rect.top) / rect.height;\n mouseRef.current = { x, y };\n }, []);\n\n useEffect(() => {\n const ctn = containerRef.current;\n if (!ctn) return;\n\n const renderer = new Renderer({ dpr });\n rendererRef.current = renderer;\n const gl = renderer.gl;\n gl.clearColor(0, 0, 0, 1);\n\n const geometry = new Triangle(gl);\n\n const program = new Program(gl, {\n vertex: vertexShader,\n fragment: fragmentShader,\n uniforms: {\n iTime: { value: 0 },\n iResolution: {\n value: new Color(gl.canvas.width, gl.canvas.height, gl.canvas.width / gl.canvas.height)\n },\n uScale: { value: scale },\n\n uGridMul: { value: new Float32Array(gridMul) },\n uDigitSize: { value: digitSize },\n uScanlineIntensity: { value: scanlineIntensity },\n uGlitchAmount: { value: glitchAmount },\n uFlickerAmount: { value: flickerAmount },\n uNoiseAmp: { value: noiseAmp },\n uChromaticAberration: { value: chromaticAberration },\n uDither: { value: ditherValue },\n uCurvature: { value: curvature },\n uTint: { value: new Color(tintVec[0], tintVec[1], tintVec[2]) },\n uMouse: {\n value: new Float32Array([smoothMouseRef.current.x, smoothMouseRef.current.y])\n },\n uMouseStrength: { value: mouseStrength },\n uUseMouse: { value: mouseReact ? 1 : 0 },\n uPageLoadProgress: { value: pageLoadAnimation ? 0 : 1 },\n uUsePageLoadAnimation: { value: pageLoadAnimation ? 1 : 0 },\n uBrightness: { value: brightness }\n }\n });\n programRef.current = program;\n\n const mesh = new Mesh(gl, { geometry, program });\n\n function resize() {\n if (!ctn || !renderer) return;\n renderer.setSize(ctn.offsetWidth, ctn.offsetHeight);\n program.uniforms.iResolution.value = new Color(\n gl.canvas.width,\n gl.canvas.height,\n gl.canvas.width / gl.canvas.height\n );\n }\n\n const resizeObserver = new ResizeObserver(() => resize());\n resizeObserver.observe(ctn);\n resize();\n\n const update = (t: number) => {\n rafRef.current = requestAnimationFrame(update);\n\n if (pageLoadAnimation && loadAnimationStartRef.current === 0) {\n loadAnimationStartRef.current = t;\n }\n\n if (!pause) {\n const elapsed = (t * 0.001 + timeOffsetRef.current) * timeScale;\n program.uniforms.iTime.value = elapsed;\n frozenTimeRef.current = elapsed;\n } else {\n program.uniforms.iTime.value = frozenTimeRef.current;\n }\n\n if (pageLoadAnimation && loadAnimationStartRef.current > 0) {\n const animationDuration = 2000;\n const animationElapsed = t - loadAnimationStartRef.current;\n const progress = Math.min(animationElapsed / animationDuration, 1);\n program.uniforms.uPageLoadProgress.value = progress;\n }\n\n if (mouseReact) {\n const dampingFactor = 0.08;\n const smoothMouse = smoothMouseRef.current;\n const mouse = mouseRef.current;\n smoothMouse.x += (mouse.x - smoothMouse.x) * dampingFactor;\n smoothMouse.y += (mouse.y - smoothMouse.y) * dampingFactor;\n\n const mouseUniform = program.uniforms.uMouse.value as Float32Array;\n mouseUniform[0] = smoothMouse.x;\n mouseUniform[1] = smoothMouse.y;\n }\n\n renderer.render({ scene: mesh });\n };\n rafRef.current = requestAnimationFrame(update);\n ctn.appendChild(gl.canvas);\n\n if (mouseReact) ctn.addEventListener('mousemove', handleMouseMove);\n\n return () => {\n cancelAnimationFrame(rafRef.current);\n resizeObserver.disconnect();\n if (mouseReact) ctn.removeEventListener('mousemove', handleMouseMove);\n if (gl.canvas.parentElement === ctn) ctn.removeChild(gl.canvas);\n gl.getExtension('WEBGL_lose_context')?.loseContext();\n loadAnimationStartRef.current = 0;\n timeOffsetRef.current = Math.random() * 100;\n };\n }, [\n dpr,\n pause,\n timeScale,\n scale,\n gridMul,\n digitSize,\n scanlineIntensity,\n glitchAmount,\n flickerAmount,\n noiseAmp,\n chromaticAberration,\n ditherValue,\n curvature,\n tintVec,\n mouseReact,\n mouseStrength,\n pageLoadAnimation,\n brightness,\n handleMouseMove\n ]);\n\n return
;\n}\n" } ], "registryDependencies": [], diff --git a/public/r/FaultyTerminal-TS-TW.json b/public/r/FaultyTerminal-TS-TW.json index 1a3374a33..fa56f32c7 100644 --- a/public/r/FaultyTerminal-TS-TW.json +++ b/public/r/FaultyTerminal-TS-TW.json @@ -8,7 +8,7 @@ { "type": "registry:component", "path": "FaultyTerminal/FaultyTerminal.tsx", - "content": "import { Renderer, Program, Mesh, Color, Triangle } from 'ogl';\nimport React, { useEffect, useRef, useMemo, useCallback } from 'react';\n\ntype Vec2 = [number, number];\n\nexport interface FaultyTerminalProps extends React.HTMLAttributes {\n scale?: number;\n gridMul?: Vec2;\n digitSize?: number;\n timeScale?: number;\n pause?: boolean;\n scanlineIntensity?: number;\n glitchAmount?: number;\n flickerAmount?: number;\n noiseAmp?: number;\n chromaticAberration?: number;\n dither?: number | boolean;\n curvature?: number;\n tint?: string;\n mouseReact?: boolean;\n mouseStrength?: number;\n dpr?: number;\n pageLoadAnimation?: boolean;\n brightness?: number;\n}\n\nconst vertexShader = `\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 fragmentShader = `\nprecision mediump float;\n\nvarying vec2 vUv;\n\nuniform float iTime;\nuniform vec3 iResolution;\nuniform float uScale;\n\nuniform vec2 uGridMul;\nuniform float uDigitSize;\nuniform float uScanlineIntensity;\nuniform float uGlitchAmount;\nuniform float uFlickerAmount;\nuniform float uNoiseAmp;\nuniform float uChromaticAberration;\nuniform float uDither;\nuniform float uCurvature;\nuniform vec3 uTint;\nuniform vec2 uMouse;\nuniform float uMouseStrength;\nuniform float uUseMouse;\nuniform float uPageLoadProgress;\nuniform float uUsePageLoadAnimation;\nuniform float uBrightness;\n\nfloat time;\n\nfloat hash21(vec2 p){\n p = fract(p * 234.56);\n p += dot(p, p + 34.56);\n return fract(p.x * p.y);\n}\n\nfloat noise(vec2 p)\n{\n return sin(p.x * 10.0) * sin(p.y * (3.0 + sin(time * 0.090909))) + 0.2; \n}\n\nmat2 rotate(float angle)\n{\n float c = cos(angle);\n float s = sin(angle);\n return mat2(c, -s, s, c);\n}\n\nfloat fbm(vec2 p)\n{\n p *= 1.1;\n float f = 0.0;\n float amp = 0.5 * uNoiseAmp;\n \n mat2 modify0 = rotate(time * 0.02);\n f += amp * noise(p);\n p = modify0 * p * 2.0;\n amp *= 0.454545;\n \n mat2 modify1 = rotate(time * 0.02);\n f += amp * noise(p);\n p = modify1 * p * 2.0;\n amp *= 0.454545;\n \n mat2 modify2 = rotate(time * 0.08);\n f += amp * noise(p);\n \n return f;\n}\n\nfloat pattern(vec2 p, out vec2 q, out vec2 r) {\n vec2 offset1 = vec2(1.0);\n vec2 offset0 = vec2(0.0);\n mat2 rot01 = rotate(0.1 * time);\n mat2 rot1 = rotate(0.1);\n \n q = vec2(fbm(p + offset1), fbm(rot01 * p + offset1));\n r = vec2(fbm(rot1 * q + offset0), fbm(q + offset0));\n return fbm(p + r);\n}\n\nfloat digit(vec2 p){\n vec2 grid = uGridMul * 15.0;\n vec2 s = floor(p * grid) / grid;\n p = p * grid;\n vec2 q, r;\n float intensity = pattern(s * 0.1, q, r) * 1.3 - 0.03;\n \n if(uUseMouse > 0.5){\n vec2 mouseWorld = uMouse * uScale;\n float distToMouse = distance(s, mouseWorld);\n float mouseInfluence = exp(-distToMouse * 8.0) * uMouseStrength * 10.0;\n intensity += mouseInfluence;\n \n float ripple = sin(distToMouse * 20.0 - iTime * 5.0) * 0.1 * mouseInfluence;\n intensity += ripple;\n }\n \n if(uUsePageLoadAnimation > 0.5){\n float cellRandom = fract(sin(dot(s, vec2(12.9898, 78.233))) * 43758.5453);\n float cellDelay = cellRandom * 0.8;\n float cellProgress = clamp((uPageLoadProgress - cellDelay) / 0.2, 0.0, 1.0);\n \n float fadeAlpha = smoothstep(0.0, 1.0, cellProgress);\n intensity *= fadeAlpha;\n }\n \n p = fract(p);\n p *= uDigitSize;\n \n float px5 = p.x * 5.0;\n float py5 = (1.0 - p.y) * 5.0;\n float x = fract(px5);\n float y = fract(py5);\n \n float i = floor(py5) - 2.0;\n float j = floor(px5) - 2.0;\n float n = i * i + j * j;\n float f = n * 0.0625;\n \n float isOn = step(0.1, intensity - f);\n float brightness = isOn * (0.2 + y * 0.8) * (0.75 + x * 0.25);\n \n return step(0.0, p.x) * step(p.x, 1.0) * step(0.0, p.y) * step(p.y, 1.0) * brightness;\n}\n\nfloat onOff(float a, float b, float c)\n{\n return step(c, sin(iTime + a * cos(iTime * b))) * uFlickerAmount;\n}\n\nfloat displace(vec2 look)\n{\n float y = look.y - mod(iTime * 0.25, 1.0);\n float window = 1.0 / (1.0 + 50.0 * y * y);\n return sin(look.y * 20.0 + iTime) * 0.0125 * onOff(4.0, 2.0, 0.8) * (1.0 + cos(iTime * 60.0)) * window;\n}\n\nvec3 getColor(vec2 p){\n \n float bar = step(mod(p.y + time * 20.0, 1.0), 0.2) * 0.4 + 1.0;\n bar *= uScanlineIntensity;\n \n float displacement = displace(p);\n p.x += displacement;\n\n if (uGlitchAmount != 1.0) {\n float extra = displacement * (uGlitchAmount - 1.0);\n p.x += extra;\n }\n\n float middle = digit(p);\n \n const float off = 0.002;\n float sum = digit(p + vec2(-off, -off)) + digit(p + vec2(0.0, -off)) + digit(p + vec2(off, -off)) +\n digit(p + vec2(-off, 0.0)) + digit(p + vec2(0.0, 0.0)) + digit(p + vec2(off, 0.0)) +\n digit(p + vec2(-off, off)) + digit(p + vec2(0.0, off)) + digit(p + vec2(off, off));\n \n vec3 baseColor = vec3(0.9) * middle + sum * 0.1 * vec3(1.0) * bar;\n return baseColor;\n}\n\nvec2 barrel(vec2 uv){\n vec2 c = uv * 2.0 - 1.0;\n float r2 = dot(c, c);\n c *= 1.0 + uCurvature * r2;\n return c * 0.5 + 0.5;\n}\n\nvoid main() {\n time = iTime * 0.333333;\n vec2 uv = vUv;\n\n if(uCurvature != 0.0){\n uv = barrel(uv);\n }\n \n vec2 p = uv * uScale;\n vec3 col = getColor(p);\n\n if(uChromaticAberration != 0.0){\n vec2 ca = vec2(uChromaticAberration) / iResolution.xy;\n col.r = getColor(p + ca).r;\n col.b = getColor(p - ca).b;\n }\n\n col *= uTint;\n col *= uBrightness;\n\n if(uDither > 0.0){\n float rnd = hash21(gl_FragCoord.xy);\n col += (rnd - 0.5) * (uDither * 0.003922);\n }\n\n gl_FragColor = vec4(col, 1.0);\n}\n`;\n\nfunction hexToRgb(hex: string): [number, number, number] {\n let h = hex.replace('#', '').trim();\n if (h.length === 3)\n h = h\n .split('')\n .map(c => c + c)\n .join('');\n const num = parseInt(h, 16);\n return [((num >> 16) & 255) / 255, ((num >> 8) & 255) / 255, (num & 255) / 255];\n}\n\nexport default function FaultyTerminal({\n scale = 1,\n gridMul = [2, 1],\n digitSize = 1.5,\n timeScale = 0.3,\n pause = false,\n scanlineIntensity = 0.3,\n glitchAmount = 1,\n flickerAmount = 1,\n noiseAmp = 1,\n chromaticAberration = 0,\n dither = 0,\n curvature = 0.2,\n tint = '#ffffff',\n mouseReact = true,\n mouseStrength = 0.2,\n dpr = Math.min(window.devicePixelRatio || 1, 2),\n pageLoadAnimation = true,\n brightness = 1,\n className,\n style,\n ...rest\n}: FaultyTerminalProps) {\n const containerRef = useRef(null);\n const programRef = useRef(null);\n const rendererRef = useRef(null);\n const mouseRef = useRef({ x: 0.5, y: 0.5 });\n const smoothMouseRef = useRef({ x: 0.5, y: 0.5 });\n const frozenTimeRef = useRef(0);\n const rafRef = useRef(0);\n const loadAnimationStartRef = useRef(0);\n const timeOffsetRef = useRef(Math.random() * 100);\n\n const tintVec = useMemo(() => hexToRgb(tint), [tint]);\n\n const ditherValue = useMemo(() => (typeof dither === 'boolean' ? (dither ? 1 : 0) : dither), [dither]);\n\n const handleMouseMove = useCallback((e: MouseEvent) => {\n const ctn = containerRef.current;\n if (!ctn) return;\n const rect = ctn.getBoundingClientRect();\n const x = (e.clientX - rect.left) / rect.width;\n const y = 1 - (e.clientY - rect.top) / rect.height;\n mouseRef.current = { x, y };\n }, []);\n\n useEffect(() => {\n const ctn = containerRef.current;\n if (!ctn) return;\n\n const renderer = new Renderer({ dpr });\n rendererRef.current = renderer;\n const gl = renderer.gl;\n gl.clearColor(0, 0, 0, 1);\n\n const geometry = new Triangle(gl);\n\n const program = new Program(gl, {\n vertex: vertexShader,\n fragment: fragmentShader,\n uniforms: {\n iTime: { value: 0 },\n iResolution: {\n value: new Color(gl.canvas.width, gl.canvas.height, gl.canvas.width / gl.canvas.height)\n },\n uScale: { value: scale },\n\n uGridMul: { value: new Float32Array(gridMul) },\n uDigitSize: { value: digitSize },\n uScanlineIntensity: { value: scanlineIntensity },\n uGlitchAmount: { value: glitchAmount },\n uFlickerAmount: { value: flickerAmount },\n uNoiseAmp: { value: noiseAmp },\n uChromaticAberration: { value: chromaticAberration },\n uDither: { value: ditherValue },\n uCurvature: { value: curvature },\n uTint: { value: new Color(tintVec[0], tintVec[1], tintVec[2]) },\n uMouse: {\n value: new Float32Array([smoothMouseRef.current.x, smoothMouseRef.current.y])\n },\n uMouseStrength: { value: mouseStrength },\n uUseMouse: { value: mouseReact ? 1 : 0 },\n uPageLoadProgress: { value: pageLoadAnimation ? 0 : 1 },\n uUsePageLoadAnimation: { value: pageLoadAnimation ? 1 : 0 },\n uBrightness: { value: brightness }\n }\n });\n programRef.current = program;\n\n const mesh = new Mesh(gl, { geometry, program });\n\n function resize() {\n if (!ctn || !renderer) return;\n renderer.setSize(ctn.offsetWidth, ctn.offsetHeight);\n program.uniforms.iResolution.value = new Color(\n gl.canvas.width,\n gl.canvas.height,\n gl.canvas.width / gl.canvas.height\n );\n }\n\n const resizeObserver = new ResizeObserver(() => resize());\n resizeObserver.observe(ctn);\n resize();\n\n const update = (t: number) => {\n rafRef.current = requestAnimationFrame(update);\n\n if (pageLoadAnimation && loadAnimationStartRef.current === 0) {\n loadAnimationStartRef.current = t;\n }\n\n if (!pause) {\n const elapsed = (t * 0.001 + timeOffsetRef.current) * timeScale;\n program.uniforms.iTime.value = elapsed;\n frozenTimeRef.current = elapsed;\n } else {\n program.uniforms.iTime.value = frozenTimeRef.current;\n }\n\n if (pageLoadAnimation && loadAnimationStartRef.current > 0) {\n const animationDuration = 2000;\n const animationElapsed = t - loadAnimationStartRef.current;\n const progress = Math.min(animationElapsed / animationDuration, 1);\n program.uniforms.uPageLoadProgress.value = progress;\n }\n\n if (mouseReact) {\n const dampingFactor = 0.08;\n const smoothMouse = smoothMouseRef.current;\n const mouse = mouseRef.current;\n smoothMouse.x += (mouse.x - smoothMouse.x) * dampingFactor;\n smoothMouse.y += (mouse.y - smoothMouse.y) * dampingFactor;\n\n const mouseUniform = program.uniforms.uMouse.value as Float32Array;\n mouseUniform[0] = smoothMouse.x;\n mouseUniform[1] = smoothMouse.y;\n }\n\n renderer.render({ scene: mesh });\n };\n rafRef.current = requestAnimationFrame(update);\n ctn.appendChild(gl.canvas);\n\n if (mouseReact) ctn.addEventListener('mousemove', handleMouseMove);\n\n return () => {\n cancelAnimationFrame(rafRef.current);\n resizeObserver.disconnect();\n if (mouseReact) ctn.removeEventListener('mousemove', handleMouseMove);\n if (gl.canvas.parentElement === ctn) ctn.removeChild(gl.canvas);\n gl.getExtension('WEBGL_lose_context')?.loseContext();\n loadAnimationStartRef.current = 0;\n timeOffsetRef.current = Math.random() * 100;\n };\n }, [\n dpr,\n pause,\n timeScale,\n scale,\n gridMul,\n digitSize,\n scanlineIntensity,\n glitchAmount,\n flickerAmount,\n noiseAmp,\n chromaticAberration,\n ditherValue,\n curvature,\n tintVec,\n mouseReact,\n mouseStrength,\n pageLoadAnimation,\n brightness,\n handleMouseMove\n ]);\n\n return (\n
\n );\n}\n" + "content": "import { Renderer, Program, Mesh, Color, Triangle } from 'ogl';\nimport React, { useEffect, useRef, useMemo, useCallback } from 'react';\n\ntype Vec2 = [number, number];\n\nexport interface FaultyTerminalProps extends React.HTMLAttributes {\n scale?: number;\n gridMul?: Vec2;\n digitSize?: number;\n timeScale?: number;\n pause?: boolean;\n scanlineIntensity?: number;\n glitchAmount?: number;\n flickerAmount?: number;\n noiseAmp?: number;\n chromaticAberration?: number;\n dither?: number | boolean;\n curvature?: number;\n tint?: string;\n mouseReact?: boolean;\n mouseStrength?: number;\n dpr?: number;\n pageLoadAnimation?: boolean;\n brightness?: number;\n}\n\nconst vertexShader = `\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 fragmentShader = `\nprecision mediump float;\n\nvarying vec2 vUv;\n\nuniform float iTime;\nuniform vec3 iResolution;\nuniform float uScale;\n\nuniform vec2 uGridMul;\nuniform float uDigitSize;\nuniform float uScanlineIntensity;\nuniform float uGlitchAmount;\nuniform float uFlickerAmount;\nuniform float uNoiseAmp;\nuniform float uChromaticAberration;\nuniform float uDither;\nuniform float uCurvature;\nuniform vec3 uTint;\nuniform vec2 uMouse;\nuniform float uMouseStrength;\nuniform float uUseMouse;\nuniform float uPageLoadProgress;\nuniform float uUsePageLoadAnimation;\nuniform float uBrightness;\n\nfloat time;\n\nfloat hash21(vec2 p){\n p = fract(p * 234.56);\n p += dot(p, p + 34.56);\n return fract(p.x * p.y);\n}\n\nfloat noise(vec2 p)\n{\n return sin(p.x * 10.0) * sin(p.y * (3.0 + sin(time * 0.090909))) + 0.2; \n}\n\nmat2 rotate(float angle)\n{\n float c = cos(angle);\n float s = sin(angle);\n return mat2(c, -s, s, c);\n}\n\nfloat fbm(vec2 p)\n{\n p *= 1.1;\n float f = 0.0;\n float amp = 0.5 * uNoiseAmp;\n \n mat2 modify0 = rotate(time * 0.02);\n f += amp * noise(p);\n p = modify0 * p * 2.0;\n amp *= 0.454545;\n \n mat2 modify1 = rotate(time * 0.02);\n f += amp * noise(p);\n p = modify1 * p * 2.0;\n amp *= 0.454545;\n \n mat2 modify2 = rotate(time * 0.08);\n f += amp * noise(p);\n \n return f;\n}\n\nfloat pattern(vec2 p, out vec2 q, out vec2 r) {\n vec2 offset1 = vec2(1.0);\n vec2 offset0 = vec2(0.0);\n mat2 rot01 = rotate(0.1 * time);\n mat2 rot1 = rotate(0.1);\n \n q = vec2(fbm(p + offset1), fbm(rot01 * p + offset1));\n r = vec2(fbm(rot1 * q + offset0), fbm(q + offset0));\n return fbm(p + r);\n}\n\nfloat digit(vec2 p){\n vec2 grid = uGridMul * 15.0;\n vec2 s = floor(p * grid) / grid;\n p = p * grid;\n vec2 q, r;\n float intensity = pattern(s * 0.1, q, r) * 1.3 - 0.03;\n \n if(uUseMouse > 0.5){\n vec2 mouseWorld = uMouse * uScale;\n float distToMouse = distance(s, mouseWorld);\n float mouseInfluence = exp(-distToMouse * 8.0) * uMouseStrength * 10.0;\n intensity += mouseInfluence;\n \n float ripple = sin(distToMouse * 20.0 - iTime * 5.0) * 0.1 * mouseInfluence;\n intensity += ripple;\n }\n \n if(uUsePageLoadAnimation > 0.5){\n float cellRandom = fract(sin(dot(s, vec2(12.9898, 78.233))) * 43758.5453);\n float cellDelay = cellRandom * 0.8;\n float cellProgress = clamp((uPageLoadProgress - cellDelay) / 0.2, 0.0, 1.0);\n \n float fadeAlpha = smoothstep(0.0, 1.0, cellProgress);\n intensity *= fadeAlpha;\n }\n \n p = fract(p);\n p *= uDigitSize;\n \n float px5 = p.x * 5.0;\n float py5 = (1.0 - p.y) * 5.0;\n float x = fract(px5);\n float y = fract(py5);\n \n float i = floor(py5) - 2.0;\n float j = floor(px5) - 2.0;\n float n = i * i + j * j;\n float f = n * 0.0625;\n \n float isOn = step(0.1, intensity - f);\n float brightness = isOn * (0.2 + y * 0.8) * (0.75 + x * 0.25);\n \n return step(0.0, p.x) * step(p.x, 1.0) * step(0.0, p.y) * step(p.y, 1.0) * brightness;\n}\n\nfloat onOff(float a, float b, float c)\n{\n return step(c, sin(iTime + a * cos(iTime * b))) * uFlickerAmount;\n}\n\nfloat displace(vec2 look)\n{\n float y = look.y - mod(iTime * 0.25, 1.0);\n float window = 1.0 / (1.0 + 50.0 * y * y);\n return sin(look.y * 20.0 + iTime) * 0.0125 * onOff(4.0, 2.0, 0.8) * (1.0 + cos(iTime * 60.0)) * window;\n}\n\nvec3 getColor(vec2 p){\n \n float bar = step(mod(p.y + time * 20.0, 1.0), 0.2) * 0.4 + 1.0;\n bar *= uScanlineIntensity;\n \n float displacement = displace(p);\n p.x += displacement;\n\n if (uGlitchAmount != 1.0) {\n float extra = displacement * (uGlitchAmount - 1.0);\n p.x += extra;\n }\n\n float middle = digit(p);\n \n const float off = 0.002;\n float sum = digit(p + vec2(-off, -off)) + digit(p + vec2(0.0, -off)) + digit(p + vec2(off, -off)) +\n digit(p + vec2(-off, 0.0)) + digit(p + vec2(0.0, 0.0)) + digit(p + vec2(off, 0.0)) +\n digit(p + vec2(-off, off)) + digit(p + vec2(0.0, off)) + digit(p + vec2(off, off));\n \n vec3 baseColor = vec3(0.9) * middle + sum * 0.1 * vec3(1.0) * bar;\n return baseColor;\n}\n\nvec2 barrel(vec2 uv){\n vec2 c = uv * 2.0 - 1.0;\n float r2 = dot(c, c);\n c *= 1.0 + uCurvature * r2;\n return c * 0.5 + 0.5;\n}\n\nvoid main() {\n time = iTime * 0.333333;\n vec2 uv = vUv;\n\n if(uCurvature != 0.0){\n uv = barrel(uv);\n }\n \n vec2 p = uv * uScale;\n vec3 col = getColor(p);\n\n if(uChromaticAberration != 0.0){\n vec2 ca = vec2(uChromaticAberration) / iResolution.xy;\n col.r = getColor(p + ca).r;\n col.b = getColor(p - ca).b;\n }\n\n col *= uTint;\n col *= uBrightness;\n\n if(uDither > 0.0){\n float rnd = hash21(gl_FragCoord.xy);\n col += (rnd - 0.5) * (uDither * 0.003922);\n }\n\n gl_FragColor = vec4(col, 1.0);\n}\n`;\n\nfunction hexToRgb(hex: string): [number, number, number] {\n let h = hex.replace('#', '').trim();\n if (h.length === 3)\n h = h\n .split('')\n .map(c => c + c)\n .join('');\n const num = parseInt(h.slice(0, 6), 16);\n return [((num >> 16) & 255) / 255, ((num >> 8) & 255) / 255, (num & 255) / 255];\n}\n\nexport default function FaultyTerminal({\n scale = 1,\n gridMul = [2, 1],\n digitSize = 1.5,\n timeScale = 0.3,\n pause = false,\n scanlineIntensity = 0.3,\n glitchAmount = 1,\n flickerAmount = 1,\n noiseAmp = 1,\n chromaticAberration = 0,\n dither = 0,\n curvature = 0.2,\n tint = '#ffffff',\n mouseReact = true,\n mouseStrength = 0.2,\n dpr = Math.min(window.devicePixelRatio || 1, 2),\n pageLoadAnimation = true,\n brightness = 1,\n className,\n style,\n ...rest\n}: FaultyTerminalProps) {\n const containerRef = useRef(null);\n const programRef = useRef(null);\n const rendererRef = useRef(null);\n const mouseRef = useRef({ x: 0.5, y: 0.5 });\n const smoothMouseRef = useRef({ x: 0.5, y: 0.5 });\n const frozenTimeRef = useRef(0);\n const rafRef = useRef(0);\n const loadAnimationStartRef = useRef(0);\n const timeOffsetRef = useRef(Math.random() * 100);\n\n const tintVec = useMemo(() => hexToRgb(tint), [tint]);\n\n const ditherValue = useMemo(() => (typeof dither === 'boolean' ? (dither ? 1 : 0) : dither), [dither]);\n\n const handleMouseMove = useCallback((e: MouseEvent) => {\n const ctn = containerRef.current;\n if (!ctn) return;\n const rect = ctn.getBoundingClientRect();\n const x = (e.clientX - rect.left) / rect.width;\n const y = 1 - (e.clientY - rect.top) / rect.height;\n mouseRef.current = { x, y };\n }, []);\n\n useEffect(() => {\n const ctn = containerRef.current;\n if (!ctn) return;\n\n const renderer = new Renderer({ dpr });\n rendererRef.current = renderer;\n const gl = renderer.gl;\n gl.clearColor(0, 0, 0, 1);\n\n const geometry = new Triangle(gl);\n\n const program = new Program(gl, {\n vertex: vertexShader,\n fragment: fragmentShader,\n uniforms: {\n iTime: { value: 0 },\n iResolution: {\n value: new Color(gl.canvas.width, gl.canvas.height, gl.canvas.width / gl.canvas.height)\n },\n uScale: { value: scale },\n\n uGridMul: { value: new Float32Array(gridMul) },\n uDigitSize: { value: digitSize },\n uScanlineIntensity: { value: scanlineIntensity },\n uGlitchAmount: { value: glitchAmount },\n uFlickerAmount: { value: flickerAmount },\n uNoiseAmp: { value: noiseAmp },\n uChromaticAberration: { value: chromaticAberration },\n uDither: { value: ditherValue },\n uCurvature: { value: curvature },\n uTint: { value: new Color(tintVec[0], tintVec[1], tintVec[2]) },\n uMouse: {\n value: new Float32Array([smoothMouseRef.current.x, smoothMouseRef.current.y])\n },\n uMouseStrength: { value: mouseStrength },\n uUseMouse: { value: mouseReact ? 1 : 0 },\n uPageLoadProgress: { value: pageLoadAnimation ? 0 : 1 },\n uUsePageLoadAnimation: { value: pageLoadAnimation ? 1 : 0 },\n uBrightness: { value: brightness }\n }\n });\n programRef.current = program;\n\n const mesh = new Mesh(gl, { geometry, program });\n\n function resize() {\n if (!ctn || !renderer) return;\n renderer.setSize(ctn.offsetWidth, ctn.offsetHeight);\n program.uniforms.iResolution.value = new Color(\n gl.canvas.width,\n gl.canvas.height,\n gl.canvas.width / gl.canvas.height\n );\n }\n\n const resizeObserver = new ResizeObserver(() => resize());\n resizeObserver.observe(ctn);\n resize();\n\n const update = (t: number) => {\n rafRef.current = requestAnimationFrame(update);\n\n if (pageLoadAnimation && loadAnimationStartRef.current === 0) {\n loadAnimationStartRef.current = t;\n }\n\n if (!pause) {\n const elapsed = (t * 0.001 + timeOffsetRef.current) * timeScale;\n program.uniforms.iTime.value = elapsed;\n frozenTimeRef.current = elapsed;\n } else {\n program.uniforms.iTime.value = frozenTimeRef.current;\n }\n\n if (pageLoadAnimation && loadAnimationStartRef.current > 0) {\n const animationDuration = 2000;\n const animationElapsed = t - loadAnimationStartRef.current;\n const progress = Math.min(animationElapsed / animationDuration, 1);\n program.uniforms.uPageLoadProgress.value = progress;\n }\n\n if (mouseReact) {\n const dampingFactor = 0.08;\n const smoothMouse = smoothMouseRef.current;\n const mouse = mouseRef.current;\n smoothMouse.x += (mouse.x - smoothMouse.x) * dampingFactor;\n smoothMouse.y += (mouse.y - smoothMouse.y) * dampingFactor;\n\n const mouseUniform = program.uniforms.uMouse.value as Float32Array;\n mouseUniform[0] = smoothMouse.x;\n mouseUniform[1] = smoothMouse.y;\n }\n\n renderer.render({ scene: mesh });\n };\n rafRef.current = requestAnimationFrame(update);\n ctn.appendChild(gl.canvas);\n\n if (mouseReact) ctn.addEventListener('mousemove', handleMouseMove);\n\n return () => {\n cancelAnimationFrame(rafRef.current);\n resizeObserver.disconnect();\n if (mouseReact) ctn.removeEventListener('mousemove', handleMouseMove);\n if (gl.canvas.parentElement === ctn) ctn.removeChild(gl.canvas);\n gl.getExtension('WEBGL_lose_context')?.loseContext();\n loadAnimationStartRef.current = 0;\n timeOffsetRef.current = Math.random() * 100;\n };\n }, [\n dpr,\n pause,\n timeScale,\n scale,\n gridMul,\n digitSize,\n scanlineIntensity,\n glitchAmount,\n flickerAmount,\n noiseAmp,\n chromaticAberration,\n ditherValue,\n curvature,\n tintVec,\n mouseReact,\n mouseStrength,\n pageLoadAnimation,\n brightness,\n handleMouseMove\n ]);\n\n return (\n
\n );\n}\n" } ], "registryDependencies": [], diff --git a/public/r/Folder-JS-CSS.json b/public/r/Folder-JS-CSS.json index 1a744dfa8..19199101b 100644 --- a/public/r/Folder-JS-CSS.json +++ b/public/r/Folder-JS-CSS.json @@ -13,7 +13,7 @@ { "type": "registry:component", "path": "Folder/Folder.jsx", - "content": "import { useState } from 'react';\nimport './Folder.css';\n\nconst darkenColor = (hex, percent) => {\n let color = hex.startsWith('#') ? hex.slice(1) : hex;\n if (color.length === 3) {\n color = color\n .split('')\n .map(c => c + c)\n .join('');\n }\n const num = parseInt(color, 16);\n let r = (num >> 16) & 0xff;\n let g = (num >> 8) & 0xff;\n let b = num & 0xff;\n r = Math.max(0, Math.min(255, Math.floor(r * (1 - percent))));\n g = Math.max(0, Math.min(255, Math.floor(g * (1 - percent))));\n b = Math.max(0, Math.min(255, Math.floor(b * (1 - percent))));\n return '#' + ((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1).toUpperCase();\n};\n\nconst Folder = ({ color = '#5227FF', size = 1, items = [], className = '' }) => {\n const maxItems = 3;\n const papers = items.slice(0, maxItems);\n while (papers.length < maxItems) {\n papers.push(null);\n }\n\n const [open, setOpen] = useState(false);\n const [paperOffsets, setPaperOffsets] = useState(Array.from({ length: maxItems }, () => ({ x: 0, y: 0 })));\n\n const folderBackColor = darkenColor(color, 0.08);\n const paper1 = darkenColor('#ffffff', 0.1);\n const paper2 = darkenColor('#ffffff', 0.05);\n const paper3 = '#ffffff';\n\n const handleClick = () => {\n setOpen(prev => !prev);\n if (open) {\n setPaperOffsets(Array.from({ length: maxItems }, () => ({ x: 0, y: 0 })));\n }\n };\n\n const handlePaperMouseMove = (e, index) => {\n if (!open) return;\n const rect = e.currentTarget.getBoundingClientRect();\n const centerX = rect.left + rect.width / 2;\n const centerY = rect.top + rect.height / 2;\n const offsetX = (e.clientX - centerX) * 0.15;\n const offsetY = (e.clientY - centerY) * 0.15;\n setPaperOffsets(prev => {\n const newOffsets = [...prev];\n newOffsets[index] = { x: offsetX, y: offsetY };\n return newOffsets;\n });\n };\n\n const handlePaperMouseLeave = (e, index) => {\n setPaperOffsets(prev => {\n const newOffsets = [...prev];\n newOffsets[index] = { x: 0, y: 0 };\n return newOffsets;\n });\n };\n\n const folderStyle = {\n '--folder-color': color,\n '--folder-back-color': folderBackColor,\n '--paper-1': paper1,\n '--paper-2': paper2,\n '--paper-3': paper3\n };\n\n const folderClassName = `folder ${open ? 'open' : ''}`.trim();\n const scaleStyle = { transform: `scale(${size})` };\n\n return (\n
\n {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n\n handleClick();\n }\n }}\n tabIndex={0}\n role=\"button\"\n aria-expanded={open}\n aria-label={open ? 'Close folder' : 'Open folder'}\n >\n
\n {papers.map((item, i) => (\n handlePaperMouseMove(e, i)}\n onMouseLeave={e => handlePaperMouseLeave(e, i)}\n style={\n open\n ? {\n '--magnet-x': `${paperOffsets[i]?.x || 0}px`,\n '--magnet-y': `${paperOffsets[i]?.y || 0}px`\n }\n : {}\n }\n >\n {item}\n
\n ))}\n
\n
\n
\n
\n
\n );\n};\n\nexport default Folder;\n" + "content": "import { useState } from 'react';\nimport './Folder.css';\n\nconst darkenColor = (hex, percent) => {\n let color = hex.startsWith('#') ? hex.slice(1) : hex;\n if (color.length === 3) {\n color = color\n .split('')\n .map(c => c + c)\n .join('');\n }\n const num = parseInt(color.slice(0, 6), 16);\n let r = (num >> 16) & 0xff;\n let g = (num >> 8) & 0xff;\n let b = num & 0xff;\n r = Math.max(0, Math.min(255, Math.floor(r * (1 - percent))));\n g = Math.max(0, Math.min(255, Math.floor(g * (1 - percent))));\n b = Math.max(0, Math.min(255, Math.floor(b * (1 - percent))));\n return '#' + ((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1).toUpperCase();\n};\n\nconst Folder = ({ color = '#5227FF', size = 1, items = [], className = '' }) => {\n const maxItems = 3;\n const papers = items.slice(0, maxItems);\n while (papers.length < maxItems) {\n papers.push(null);\n }\n\n const [open, setOpen] = useState(false);\n const [paperOffsets, setPaperOffsets] = useState(Array.from({ length: maxItems }, () => ({ x: 0, y: 0 })));\n\n const folderBackColor = darkenColor(color, 0.08);\n const paper1 = darkenColor('#ffffff', 0.1);\n const paper2 = darkenColor('#ffffff', 0.05);\n const paper3 = '#ffffff';\n\n const handleClick = () => {\n setOpen(prev => !prev);\n if (open) {\n setPaperOffsets(Array.from({ length: maxItems }, () => ({ x: 0, y: 0 })));\n }\n };\n\n const handlePaperMouseMove = (e, index) => {\n if (!open) return;\n const rect = e.currentTarget.getBoundingClientRect();\n const centerX = rect.left + rect.width / 2;\n const centerY = rect.top + rect.height / 2;\n const offsetX = (e.clientX - centerX) * 0.15;\n const offsetY = (e.clientY - centerY) * 0.15;\n setPaperOffsets(prev => {\n const newOffsets = [...prev];\n newOffsets[index] = { x: offsetX, y: offsetY };\n return newOffsets;\n });\n };\n\n const handlePaperMouseLeave = (e, index) => {\n setPaperOffsets(prev => {\n const newOffsets = [...prev];\n newOffsets[index] = { x: 0, y: 0 };\n return newOffsets;\n });\n };\n\n const folderStyle = {\n '--folder-color': color,\n '--folder-back-color': folderBackColor,\n '--paper-1': paper1,\n '--paper-2': paper2,\n '--paper-3': paper3\n };\n\n const folderClassName = `folder ${open ? 'open' : ''}`.trim();\n const scaleStyle = { transform: `scale(${size})` };\n\n return (\n
\n {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n\n handleClick();\n }\n }}\n tabIndex={0}\n role=\"button\"\n aria-expanded={open}\n aria-label={open ? 'Close folder' : 'Open folder'}\n >\n
\n {papers.map((item, i) => (\n handlePaperMouseMove(e, i)}\n onMouseLeave={e => handlePaperMouseLeave(e, i)}\n style={\n open\n ? {\n '--magnet-x': `${paperOffsets[i]?.x || 0}px`,\n '--magnet-y': `${paperOffsets[i]?.y || 0}px`\n }\n : {}\n }\n >\n {item}\n
\n ))}\n
\n
\n
\n
\n
\n );\n};\n\nexport default Folder;\n" } ], "registryDependencies": [], diff --git a/public/r/Folder-JS-TW.json b/public/r/Folder-JS-TW.json index 5d7ddee6b..f4b8483dd 100644 --- a/public/r/Folder-JS-TW.json +++ b/public/r/Folder-JS-TW.json @@ -8,7 +8,7 @@ { "type": "registry:component", "path": "Folder/Folder.jsx", - "content": "import { useState } from 'react';\n\nconst darkenColor = (hex, percent) => {\n let color = hex.startsWith('#') ? hex.slice(1) : hex;\n if (color.length === 3) {\n color = color\n .split('')\n .map(c => c + c)\n .join('');\n }\n const num = parseInt(color, 16);\n let r = (num >> 16) & 0xff;\n let g = (num >> 8) & 0xff;\n let b = num & 0xff;\n r = Math.max(0, Math.min(255, Math.floor(r * (1 - percent))));\n g = Math.max(0, Math.min(255, Math.floor(g * (1 - percent))));\n b = Math.max(0, Math.min(255, Math.floor(b * (1 - percent))));\n return '#' + ((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1).toUpperCase();\n};\n\nconst Folder = ({ color = '#5227FF', size = 1, items = [], className = '' }) => {\n const maxItems = 3;\n const papers = items.slice(0, maxItems);\n while (papers.length < maxItems) {\n papers.push(null);\n }\n\n const [open, setOpen] = useState(false);\n const [paperOffsets, setPaperOffsets] = useState(Array.from({ length: maxItems }, () => ({ x: 0, y: 0 })));\n\n const folderBackColor = darkenColor(color, 0.08);\n const paper1 = darkenColor('#ffffff', 0.1);\n const paper2 = darkenColor('#ffffff', 0.05);\n const paper3 = '#ffffff';\n\n const handleClick = () => {\n setOpen(prev => !prev);\n if (open) {\n setPaperOffsets(Array.from({ length: maxItems }, () => ({ x: 0, y: 0 })));\n }\n };\n\n const handlePaperMouseMove = (e, index) => {\n if (!open) return;\n const rect = e.currentTarget.getBoundingClientRect();\n const centerX = rect.left + rect.width / 2;\n const centerY = rect.top + rect.height / 2;\n const offsetX = (e.clientX - centerX) * 0.15;\n const offsetY = (e.clientY - centerY) * 0.15;\n setPaperOffsets(prev => {\n const newOffsets = [...prev];\n newOffsets[index] = { x: offsetX, y: offsetY };\n return newOffsets;\n });\n };\n\n const handlePaperMouseLeave = (e, index) => {\n setPaperOffsets(prev => {\n const newOffsets = [...prev];\n newOffsets[index] = { x: 0, y: 0 };\n return newOffsets;\n });\n };\n\n const folderStyle = {\n '--folder-color': color,\n '--folder-back-color': folderBackColor,\n '--paper-1': paper1,\n '--paper-2': paper2,\n '--paper-3': paper3\n };\n\n const scaleStyle = { transform: `scale(${size})` };\n\n const getOpenTransform = index => {\n if (index === 0) return 'translate(-120%, -70%) rotate(-15deg)';\n if (index === 1) return 'translate(10%, -70%) rotate(15deg)';\n if (index === 2) return 'translate(-50%, -100%) rotate(5deg)';\n return '';\n };\n\n return (\n
\n {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n\n handleClick();\n }\n }}\n tabIndex={0}\n role=\"button\"\n aria-expanded={open}\n aria-label={open ? 'Close folder' : 'Open folder'}\n >\n \n \n {papers.map((item, i) => {\n let sizeClasses = '';\n if (i === 0) sizeClasses = open ? 'w-[70%] h-[80%]' : 'w-[70%] h-[80%]';\n if (i === 1) sizeClasses = open ? 'w-[80%] h-[80%]' : 'w-[80%] h-[70%]';\n if (i === 2) sizeClasses = open ? 'w-[90%] h-[80%]' : 'w-[90%] h-[60%]';\n\n const transformStyle = open\n ? `${getOpenTransform(i)} translate(${paperOffsets[i].x}px, ${paperOffsets[i].y}px)`\n : undefined;\n\n return (\n handlePaperMouseMove(e, i)}\n onMouseLeave={e => handlePaperMouseLeave(e, i)}\n className={`absolute z-20 bottom-[10%] left-1/2 transition-all duration-300 ease-in-out ${\n !open ? 'transform -translate-x-1/2 translate-y-[10%] group-hover:translate-y-0' : 'hover:scale-110'\n } ${sizeClasses}`}\n style={{\n ...(!open ? {} : { transform: transformStyle }),\n backgroundColor: i === 0 ? paper1 : i === 1 ? paper2 : paper3,\n borderRadius: '10px'\n }}\n >\n {item}\n
\n );\n })}\n
\n
\n
\n
\n
\n );\n};\n\nexport default Folder;\n" + "content": "import { useState } from 'react';\n\nconst darkenColor = (hex, percent) => {\n let color = hex.startsWith('#') ? hex.slice(1) : hex;\n if (color.length === 3) {\n color = color\n .split('')\n .map(c => c + c)\n .join('');\n }\n const num = parseInt(color.slice(0, 6), 16);\n let r = (num >> 16) & 0xff;\n let g = (num >> 8) & 0xff;\n let b = num & 0xff;\n r = Math.max(0, Math.min(255, Math.floor(r * (1 - percent))));\n g = Math.max(0, Math.min(255, Math.floor(g * (1 - percent))));\n b = Math.max(0, Math.min(255, Math.floor(b * (1 - percent))));\n return '#' + ((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1).toUpperCase();\n};\n\nconst Folder = ({ color = '#5227FF', size = 1, items = [], className = '' }) => {\n const maxItems = 3;\n const papers = items.slice(0, maxItems);\n while (papers.length < maxItems) {\n papers.push(null);\n }\n\n const [open, setOpen] = useState(false);\n const [paperOffsets, setPaperOffsets] = useState(Array.from({ length: maxItems }, () => ({ x: 0, y: 0 })));\n\n const folderBackColor = darkenColor(color, 0.08);\n const paper1 = darkenColor('#ffffff', 0.1);\n const paper2 = darkenColor('#ffffff', 0.05);\n const paper3 = '#ffffff';\n\n const handleClick = () => {\n setOpen(prev => !prev);\n if (open) {\n setPaperOffsets(Array.from({ length: maxItems }, () => ({ x: 0, y: 0 })));\n }\n };\n\n const handlePaperMouseMove = (e, index) => {\n if (!open) return;\n const rect = e.currentTarget.getBoundingClientRect();\n const centerX = rect.left + rect.width / 2;\n const centerY = rect.top + rect.height / 2;\n const offsetX = (e.clientX - centerX) * 0.15;\n const offsetY = (e.clientY - centerY) * 0.15;\n setPaperOffsets(prev => {\n const newOffsets = [...prev];\n newOffsets[index] = { x: offsetX, y: offsetY };\n return newOffsets;\n });\n };\n\n const handlePaperMouseLeave = (e, index) => {\n setPaperOffsets(prev => {\n const newOffsets = [...prev];\n newOffsets[index] = { x: 0, y: 0 };\n return newOffsets;\n });\n };\n\n const folderStyle = {\n '--folder-color': color,\n '--folder-back-color': folderBackColor,\n '--paper-1': paper1,\n '--paper-2': paper2,\n '--paper-3': paper3\n };\n\n const scaleStyle = { transform: `scale(${size})` };\n\n const getOpenTransform = index => {\n if (index === 0) return 'translate(-120%, -70%) rotate(-15deg)';\n if (index === 1) return 'translate(10%, -70%) rotate(15deg)';\n if (index === 2) return 'translate(-50%, -100%) rotate(5deg)';\n return '';\n };\n\n return (\n
\n {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n\n handleClick();\n }\n }}\n tabIndex={0}\n role=\"button\"\n aria-expanded={open}\n aria-label={open ? 'Close folder' : 'Open folder'}\n >\n \n \n {papers.map((item, i) => {\n let sizeClasses = '';\n if (i === 0) sizeClasses = open ? 'w-[70%] h-[80%]' : 'w-[70%] h-[80%]';\n if (i === 1) sizeClasses = open ? 'w-[80%] h-[80%]' : 'w-[80%] h-[70%]';\n if (i === 2) sizeClasses = open ? 'w-[90%] h-[80%]' : 'w-[90%] h-[60%]';\n\n const transformStyle = open\n ? `${getOpenTransform(i)} translate(${paperOffsets[i].x}px, ${paperOffsets[i].y}px)`\n : undefined;\n\n return (\n handlePaperMouseMove(e, i)}\n onMouseLeave={e => handlePaperMouseLeave(e, i)}\n className={`absolute z-20 bottom-[10%] left-1/2 transition-all duration-300 ease-in-out ${\n !open ? 'transform -translate-x-1/2 translate-y-[10%] group-hover:translate-y-0' : 'hover:scale-110'\n } ${sizeClasses}`}\n style={{\n ...(!open ? {} : { transform: transformStyle }),\n backgroundColor: i === 0 ? paper1 : i === 1 ? paper2 : paper3,\n borderRadius: '10px'\n }}\n >\n {item}\n
\n );\n })}\n
\n
\n
\n
\n
\n );\n};\n\nexport default Folder;\n" } ], "registryDependencies": [], diff --git a/public/r/Folder-TS-CSS.json b/public/r/Folder-TS-CSS.json index e56a8be9f..8a95c4e80 100644 --- a/public/r/Folder-TS-CSS.json +++ b/public/r/Folder-TS-CSS.json @@ -13,7 +13,7 @@ { "type": "registry:component", "path": "Folder/Folder.tsx", - "content": "import React, { useState } from 'react';\nimport './Folder.css';\n\ninterface FolderProps {\n color?: string;\n size?: number;\n items?: React.ReactNode[];\n className?: string;\n}\n\nconst darkenColor = (hex: string, percent: number): string => {\n let color = hex.startsWith('#') ? hex.slice(1) : hex;\n if (color.length === 3) {\n color = color\n .split('')\n .map(c => c + c)\n .join('');\n }\n const num = parseInt(color, 16);\n let r = (num >> 16) & 0xff;\n let g = (num >> 8) & 0xff;\n let b = num & 0xff;\n r = Math.max(0, Math.min(255, Math.floor(r * (1 - percent))));\n g = Math.max(0, Math.min(255, Math.floor(g * (1 - percent))));\n b = Math.max(0, Math.min(255, Math.floor(b * (1 - percent))));\n return '#' + ((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1).toUpperCase();\n};\n\nconst Folder: React.FC = ({ color = '#5227FF', size = 1, items = [], className = '' }) => {\n const maxItems = 3;\n const papers = items.slice(0, maxItems);\n while (papers.length < maxItems) {\n papers.push(null);\n }\n\n const [open, setOpen] = useState(false);\n const [paperOffsets, setPaperOffsets] = useState<{ x: number; y: number }[]>(\n Array.from({ length: maxItems }, () => ({ x: 0, y: 0 }))\n );\n\n const folderBackColor = darkenColor(color, 0.08);\n const paper1 = darkenColor('#ffffff', 0.1);\n const paper2 = darkenColor('#ffffff', 0.05);\n const paper3 = '#ffffff';\n\n const handleClick = () => {\n setOpen(prev => !prev);\n if (open) {\n setPaperOffsets(Array.from({ length: maxItems }, () => ({ x: 0, y: 0 })));\n }\n };\n\n const handlePaperMouseMove = (e: React.MouseEvent, index: number) => {\n if (!open) return;\n const rect = e.currentTarget.getBoundingClientRect();\n const centerX = rect.left + rect.width / 2;\n const centerY = rect.top + rect.height / 2;\n const offsetX = (e.clientX - centerX) * 0.15;\n const offsetY = (e.clientY - centerY) * 0.15;\n setPaperOffsets(prev => {\n const newOffsets = [...prev];\n newOffsets[index] = { x: offsetX, y: offsetY };\n return newOffsets;\n });\n };\n\n const handlePaperMouseLeave = (e: React.MouseEvent, index: number) => {\n setPaperOffsets(prev => {\n const newOffsets = [...prev];\n newOffsets[index] = { x: 0, y: 0 };\n return newOffsets;\n });\n };\n\n const folderStyle: React.CSSProperties = {\n '--folder-color': color,\n '--folder-back-color': folderBackColor,\n '--paper-1': paper1,\n '--paper-2': paper2,\n '--paper-3': paper3\n } as React.CSSProperties;\n\n const folderClassName = `folder ${open ? 'open' : ''}`.trim();\n const scaleStyle = { transform: `scale(${size})` };\n\n return (\n
\n {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n handleClick();\n }\n }}\n tabIndex={0}\n role=\"button\"\n aria-expanded={open}\n aria-label={open ? 'Close folder' : 'Open folder'}\n >\n
\n {papers.map((item, i) => (\n handlePaperMouseMove(e, i)}\n onMouseLeave={e => handlePaperMouseLeave(e, i)}\n style={\n open\n ? ({\n '--magnet-x': `${paperOffsets[i]?.x || 0}px`,\n '--magnet-y': `${paperOffsets[i]?.y || 0}px`\n } as React.CSSProperties)\n : {}\n }\n >\n {item}\n
\n ))}\n
\n
\n
\n
\n
\n );\n};\n\nexport default Folder;\n" + "content": "import React, { useState } from 'react';\nimport './Folder.css';\n\ninterface FolderProps {\n color?: string;\n size?: number;\n items?: React.ReactNode[];\n className?: string;\n}\n\nconst darkenColor = (hex: string, percent: number): string => {\n let color = hex.startsWith('#') ? hex.slice(1) : hex;\n if (color.length === 3) {\n color = color\n .split('')\n .map(c => c + c)\n .join('');\n }\n const num = parseInt(color.slice(0, 6), 16);\n let r = (num >> 16) & 0xff;\n let g = (num >> 8) & 0xff;\n let b = num & 0xff;\n r = Math.max(0, Math.min(255, Math.floor(r * (1 - percent))));\n g = Math.max(0, Math.min(255, Math.floor(g * (1 - percent))));\n b = Math.max(0, Math.min(255, Math.floor(b * (1 - percent))));\n return '#' + ((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1).toUpperCase();\n};\n\nconst Folder: React.FC = ({ color = '#5227FF', size = 1, items = [], className = '' }) => {\n const maxItems = 3;\n const papers = items.slice(0, maxItems);\n while (papers.length < maxItems) {\n papers.push(null);\n }\n\n const [open, setOpen] = useState(false);\n const [paperOffsets, setPaperOffsets] = useState<{ x: number; y: number }[]>(\n Array.from({ length: maxItems }, () => ({ x: 0, y: 0 }))\n );\n\n const folderBackColor = darkenColor(color, 0.08);\n const paper1 = darkenColor('#ffffff', 0.1);\n const paper2 = darkenColor('#ffffff', 0.05);\n const paper3 = '#ffffff';\n\n const handleClick = () => {\n setOpen(prev => !prev);\n if (open) {\n setPaperOffsets(Array.from({ length: maxItems }, () => ({ x: 0, y: 0 })));\n }\n };\n\n const handlePaperMouseMove = (e: React.MouseEvent, index: number) => {\n if (!open) return;\n const rect = e.currentTarget.getBoundingClientRect();\n const centerX = rect.left + rect.width / 2;\n const centerY = rect.top + rect.height / 2;\n const offsetX = (e.clientX - centerX) * 0.15;\n const offsetY = (e.clientY - centerY) * 0.15;\n setPaperOffsets(prev => {\n const newOffsets = [...prev];\n newOffsets[index] = { x: offsetX, y: offsetY };\n return newOffsets;\n });\n };\n\n const handlePaperMouseLeave = (e: React.MouseEvent, index: number) => {\n setPaperOffsets(prev => {\n const newOffsets = [...prev];\n newOffsets[index] = { x: 0, y: 0 };\n return newOffsets;\n });\n };\n\n const folderStyle: React.CSSProperties = {\n '--folder-color': color,\n '--folder-back-color': folderBackColor,\n '--paper-1': paper1,\n '--paper-2': paper2,\n '--paper-3': paper3\n } as React.CSSProperties;\n\n const folderClassName = `folder ${open ? 'open' : ''}`.trim();\n const scaleStyle = { transform: `scale(${size})` };\n\n return (\n
\n {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n handleClick();\n }\n }}\n tabIndex={0}\n role=\"button\"\n aria-expanded={open}\n aria-label={open ? 'Close folder' : 'Open folder'}\n >\n
\n {papers.map((item, i) => (\n handlePaperMouseMove(e, i)}\n onMouseLeave={e => handlePaperMouseLeave(e, i)}\n style={\n open\n ? ({\n '--magnet-x': `${paperOffsets[i]?.x || 0}px`,\n '--magnet-y': `${paperOffsets[i]?.y || 0}px`\n } as React.CSSProperties)\n : {}\n }\n >\n {item}\n
\n ))}\n
\n
\n
\n
\n
\n );\n};\n\nexport default Folder;\n" } ], "registryDependencies": [], diff --git a/public/r/Folder-TS-TW.json b/public/r/Folder-TS-TW.json index ae4d8feb2..d2292a6a6 100644 --- a/public/r/Folder-TS-TW.json +++ b/public/r/Folder-TS-TW.json @@ -8,7 +8,7 @@ { "type": "registry:component", "path": "Folder/Folder.tsx", - "content": "import React, { useState } from 'react';\n\ninterface FolderProps {\n color?: string;\n size?: number;\n items?: React.ReactNode[];\n className?: string;\n}\n\nconst darkenColor = (hex: string, percent: number): string => {\n let color = hex.startsWith('#') ? hex.slice(1) : hex;\n if (color.length === 3) {\n color = color\n .split('')\n .map(c => c + c)\n .join('');\n }\n const num = parseInt(color, 16);\n let r = (num >> 16) & 0xff;\n let g = (num >> 8) & 0xff;\n let b = num & 0xff;\n r = Math.max(0, Math.min(255, Math.floor(r * (1 - percent))));\n g = Math.max(0, Math.min(255, Math.floor(g * (1 - percent))));\n b = Math.max(0, Math.min(255, Math.floor(b * (1 - percent))));\n return '#' + ((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1).toUpperCase();\n};\n\nconst Folder: React.FC = ({ color = '#5227FF', size = 1, items = [], className = '' }) => {\n const maxItems = 3;\n const papers = items.slice(0, maxItems);\n while (papers.length < maxItems) {\n papers.push(null);\n }\n\n const [open, setOpen] = useState(false);\n const [paperOffsets, setPaperOffsets] = useState<{ x: number; y: number }[]>(\n Array.from({ length: maxItems }, () => ({ x: 0, y: 0 }))\n );\n\n const folderBackColor = darkenColor(color, 0.08);\n const paper1 = darkenColor('#ffffff', 0.1);\n const paper2 = darkenColor('#ffffff', 0.05);\n const paper3 = '#ffffff';\n\n const handleClick = () => {\n setOpen(prev => !prev);\n if (open) {\n setPaperOffsets(Array.from({ length: maxItems }, () => ({ x: 0, y: 0 })));\n }\n };\n\n const handlePaperMouseMove = (e: React.MouseEvent, index: number) => {\n if (!open) return;\n const rect = e.currentTarget.getBoundingClientRect();\n const centerX = rect.left + rect.width / 2;\n const centerY = rect.top + rect.height / 2;\n const offsetX = (e.clientX - centerX) * 0.15;\n const offsetY = (e.clientY - centerY) * 0.15;\n setPaperOffsets(prev => {\n const newOffsets = [...prev];\n newOffsets[index] = { x: offsetX, y: offsetY };\n return newOffsets;\n });\n };\n\n const handlePaperMouseLeave = (e: React.MouseEvent, index: number) => {\n setPaperOffsets(prev => {\n const newOffsets = [...prev];\n newOffsets[index] = { x: 0, y: 0 };\n return newOffsets;\n });\n };\n\n const folderStyle: React.CSSProperties = {\n '--folder-color': color,\n '--folder-back-color': folderBackColor,\n '--paper-1': paper1,\n '--paper-2': paper2,\n '--paper-3': paper3\n } as React.CSSProperties;\n\n const scaleStyle = { transform: `scale(${size})` };\n\n const getOpenTransform = (index: number) => {\n if (index === 0) return 'translate(-120%, -70%) rotate(-15deg)';\n if (index === 1) return 'translate(10%, -70%) rotate(15deg)';\n if (index === 2) return 'translate(-50%, -100%) rotate(5deg)';\n return '';\n };\n\n return (\n
\n {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n handleClick();\n }\n }}\n tabIndex={0}\n role=\"button\"\n aria-expanded={open}\n aria-label={open ? 'Close folder' : 'Open folder'}\n >\n \n \n {papers.map((item, i) => {\n let sizeClasses = '';\n if (i === 0) sizeClasses = open ? 'w-[70%] h-[80%]' : 'w-[70%] h-[80%]';\n if (i === 1) sizeClasses = open ? 'w-[80%] h-[80%]' : 'w-[80%] h-[70%]';\n if (i === 2) sizeClasses = open ? 'w-[90%] h-[80%]' : 'w-[90%] h-[60%]';\n\n const transformStyle = open\n ? `${getOpenTransform(i)} translate(${paperOffsets[i].x}px, ${paperOffsets[i].y}px)`\n : undefined;\n\n return (\n handlePaperMouseMove(e, i)}\n onMouseLeave={e => handlePaperMouseLeave(e, i)}\n className={`absolute z-20 bottom-[10%] left-1/2 transition-all duration-300 ease-in-out ${\n !open ? 'transform -translate-x-1/2 translate-y-[10%] group-hover:translate-y-0' : 'hover:scale-110'\n } ${sizeClasses}`}\n style={{\n ...(!open ? {} : { transform: transformStyle }),\n backgroundColor: i === 0 ? paper1 : i === 1 ? paper2 : paper3,\n borderRadius: '10px'\n }}\n >\n {item}\n
\n );\n })}\n
\n
\n
\n
\n
\n );\n};\n\nexport default Folder;\n" + "content": "import React, { useState } from 'react';\n\ninterface FolderProps {\n color?: string;\n size?: number;\n items?: React.ReactNode[];\n className?: string;\n}\n\nconst darkenColor = (hex: string, percent: number): string => {\n let color = hex.startsWith('#') ? hex.slice(1) : hex;\n if (color.length === 3) {\n color = color\n .split('')\n .map(c => c + c)\n .join('');\n }\n const num = parseInt(color.slice(0, 6), 16);\n let r = (num >> 16) & 0xff;\n let g = (num >> 8) & 0xff;\n let b = num & 0xff;\n r = Math.max(0, Math.min(255, Math.floor(r * (1 - percent))));\n g = Math.max(0, Math.min(255, Math.floor(g * (1 - percent))));\n b = Math.max(0, Math.min(255, Math.floor(b * (1 - percent))));\n return '#' + ((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1).toUpperCase();\n};\n\nconst Folder: React.FC = ({ color = '#5227FF', size = 1, items = [], className = '' }) => {\n const maxItems = 3;\n const papers = items.slice(0, maxItems);\n while (papers.length < maxItems) {\n papers.push(null);\n }\n\n const [open, setOpen] = useState(false);\n const [paperOffsets, setPaperOffsets] = useState<{ x: number; y: number }[]>(\n Array.from({ length: maxItems }, () => ({ x: 0, y: 0 }))\n );\n\n const folderBackColor = darkenColor(color, 0.08);\n const paper1 = darkenColor('#ffffff', 0.1);\n const paper2 = darkenColor('#ffffff', 0.05);\n const paper3 = '#ffffff';\n\n const handleClick = () => {\n setOpen(prev => !prev);\n if (open) {\n setPaperOffsets(Array.from({ length: maxItems }, () => ({ x: 0, y: 0 })));\n }\n };\n\n const handlePaperMouseMove = (e: React.MouseEvent, index: number) => {\n if (!open) return;\n const rect = e.currentTarget.getBoundingClientRect();\n const centerX = rect.left + rect.width / 2;\n const centerY = rect.top + rect.height / 2;\n const offsetX = (e.clientX - centerX) * 0.15;\n const offsetY = (e.clientY - centerY) * 0.15;\n setPaperOffsets(prev => {\n const newOffsets = [...prev];\n newOffsets[index] = { x: offsetX, y: offsetY };\n return newOffsets;\n });\n };\n\n const handlePaperMouseLeave = (e: React.MouseEvent, index: number) => {\n setPaperOffsets(prev => {\n const newOffsets = [...prev];\n newOffsets[index] = { x: 0, y: 0 };\n return newOffsets;\n });\n };\n\n const folderStyle: React.CSSProperties = {\n '--folder-color': color,\n '--folder-back-color': folderBackColor,\n '--paper-1': paper1,\n '--paper-2': paper2,\n '--paper-3': paper3\n } as React.CSSProperties;\n\n const scaleStyle = { transform: `scale(${size})` };\n\n const getOpenTransform = (index: number) => {\n if (index === 0) return 'translate(-120%, -70%) rotate(-15deg)';\n if (index === 1) return 'translate(10%, -70%) rotate(15deg)';\n if (index === 2) return 'translate(-50%, -100%) rotate(5deg)';\n return '';\n };\n\n return (\n
\n {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n handleClick();\n }\n }}\n tabIndex={0}\n role=\"button\"\n aria-expanded={open}\n aria-label={open ? 'Close folder' : 'Open folder'}\n >\n \n \n {papers.map((item, i) => {\n let sizeClasses = '';\n if (i === 0) sizeClasses = open ? 'w-[70%] h-[80%]' : 'w-[70%] h-[80%]';\n if (i === 1) sizeClasses = open ? 'w-[80%] h-[80%]' : 'w-[80%] h-[70%]';\n if (i === 2) sizeClasses = open ? 'w-[90%] h-[80%]' : 'w-[90%] h-[60%]';\n\n const transformStyle = open\n ? `${getOpenTransform(i)} translate(${paperOffsets[i].x}px, ${paperOffsets[i].y}px)`\n : undefined;\n\n return (\n handlePaperMouseMove(e, i)}\n onMouseLeave={e => handlePaperMouseLeave(e, i)}\n className={`absolute z-20 bottom-[10%] left-1/2 transition-all duration-300 ease-in-out ${\n !open ? 'transform -translate-x-1/2 translate-y-[10%] group-hover:translate-y-0' : 'hover:scale-110'\n } ${sizeClasses}`}\n style={{\n ...(!open ? {} : { transform: transformStyle }),\n backgroundColor: i === 0 ? paper1 : i === 1 ? paper2 : paper3,\n borderRadius: '10px'\n }}\n >\n {item}\n
\n );\n })}\n \n \n \n \n \n );\n};\n\nexport default Folder;\n" } ], "registryDependencies": [], diff --git a/public/r/LaserFlow-JS-CSS.json b/public/r/LaserFlow-JS-CSS.json index ca9f50458..a9b1f7b17 100644 --- a/public/r/LaserFlow-JS-CSS.json +++ b/public/r/LaserFlow-JS-CSS.json @@ -13,7 +13,7 @@ { "type": "registry:component", "path": "LaserFlow/LaserFlow.jsx", - "content": "import { useEffect, useRef } from 'react';\nimport * as THREE from 'three';\nimport './LaserFlow.css';\n\nconst VERT = `\nprecision highp float;\nattribute vec3 position;\nvoid main(){\n gl_Position = vec4(position, 1.0);\n}\n`;\n\nconst FRAG = `\n#ifdef GL_ES\n#extension GL_OES_standard_derivatives : enable\n#endif\nprecision highp float;\nprecision mediump int;\n\nuniform float iTime;\nuniform vec3 iResolution;\nuniform vec4 iMouse;\nuniform float uWispDensity;\nuniform float uTiltScale;\nuniform float uFlowTime;\nuniform float uFogTime;\nuniform float uBeamXFrac;\nuniform float uBeamYFrac;\nuniform float uFlowSpeed;\nuniform float uVLenFactor;\nuniform float uHLenFactor;\nuniform float uFogIntensity;\nuniform float uFogScale;\nuniform float uWSpeed;\nuniform float uWIntensity;\nuniform float uFlowStrength;\nuniform float uDecay;\nuniform float uFalloffStart;\nuniform float uFogFallSpeed;\nuniform vec3 uColor;\nuniform float uFade;\n\n// Core beam/flare shaping and dynamics\n#define PI 3.14159265359\n#define TWO_PI 6.28318530718\n#define EPS 1e-6\n#define EDGE_SOFT (DT_LOCAL*4.0)\n#define DT_LOCAL 0.0038\n#define TAP_RADIUS 6\n#define R_H 150.0\n#define R_V 150.0\n#define FLARE_HEIGHT 16.0\n#define FLARE_AMOUNT 8.0\n#define FLARE_EXP 2.0\n#define TOP_FADE_START 0.1\n#define TOP_FADE_EXP 1.0\n#define FLOW_PERIOD 0.5\n#define FLOW_SHARPNESS 1.5\n\n// Wisps (animated micro-streaks) that travel along the beam\n#define W_BASE_X 1.5\n#define W_LAYER_GAP 0.25\n#define W_LANES 10\n#define W_SIDE_DECAY 0.5\n#define W_HALF 0.01\n#define W_AA 0.15\n#define W_CELL 20.0\n#define W_SEG_MIN 0.01\n#define W_SEG_MAX 0.55\n#define W_CURVE_AMOUNT 15.0\n#define W_CURVE_RANGE (FLARE_HEIGHT - 3.0)\n#define W_BOTTOM_EXP 10.0\n\n// Volumetric fog controls\n#define FOG_ON 1\n#define FOG_CONTRAST 1.2\n#define FOG_SPEED_U 0.1\n#define FOG_SPEED_V -0.1\n#define FOG_OCTAVES 5\n#define FOG_BOTTOM_BIAS 0.8\n#define FOG_TILT_TO_MOUSE 0.05\n#define FOG_TILT_DEADZONE 0.01\n#define FOG_TILT_MAX_X 0.35\n#define FOG_TILT_SHAPE 1.5\n#define FOG_BEAM_MIN 0.0\n#define FOG_BEAM_MAX 0.75\n#define FOG_MASK_GAMMA 0.5\n#define FOG_EXPAND_SHAPE 12.2\n#define FOG_EDGE_MIX 0.5\n\n// Horizontal vignette for the fog volume\n#define HFOG_EDGE_START 0.20\n#define HFOG_EDGE_END 0.98\n#define HFOG_EDGE_GAMMA 1.4\n#define HFOG_Y_RADIUS 25.0\n#define HFOG_Y_SOFT 60.0\n\n// Beam extents and edge masking\n#define EDGE_X0 0.22\n#define EDGE_X1 0.995\n#define EDGE_X_GAMMA 1.25\n#define EDGE_LUMA_T0 0.0\n#define EDGE_LUMA_T1 2.0\n#define DITHER_STRENGTH 1.0\n\n float g(float x){return x<=0.00031308?12.92*x:1.055*pow(x,1.0/2.4)-0.055;}\n float bs(vec2 p,vec2 q,float powr){\n float d=distance(p,q),f=powr*uFalloffStart,r=(f*f)/(d*d+EPS);\n return powr*min(1.0,r);\n }\n float bsa(vec2 p,vec2 q,float powr,vec2 s){\n vec2 d=p-q; float dd=(d.x*d.x)/(s.x*s.x)+(d.y*d.y)/(s.y*s.y),f=powr*uFalloffStart,r=(f*f)/(dd+EPS);\n return powr*min(1.0,r);\n }\n float tri01(float x){float f=fract(x);return 1.0-abs(f*2.0-1.0);}\n float tauWf(float t,float tmin,float tmax){float a=smoothstep(tmin,tmin+EDGE_SOFT,t),b=1.0-smoothstep(tmax-EDGE_SOFT,tmax,t);return max(0.0,a*b);} \n float h21(vec2 p){p=fract(p*vec2(123.34,456.21));p+=dot(p,p+34.123);return fract(p.x*p.y);}\n float vnoise(vec2 p){\n vec2 i=floor(p),f=fract(p);\n float a=h21(i),b=h21(i+vec2(1,0)),c=h21(i+vec2(0,1)),d=h21(i+vec2(1,1));\n vec2 u=f*f*(3.0-2.0*f);\n return mix(mix(a,b,u.x),mix(c,d,u.x),u.y);\n }\n float fbm2(vec2 p){\n float v=0.0,amp=0.6; mat2 m=mat2(0.86,0.5,-0.5,0.86);\n for(int i=0;i=lanes) break;\n float off=W_BASE_X+float(i)*W_LAYER_GAP,xc=sgn*(off*xS);\n float dx=abs(uv.x-xc),lat=1.0-smoothstep(W_HALF,W_HALF+W_AA,dx),amp=exp(-off*W_SIDE_DECAY);\n float seed=h21(vec2(off,sgn*17.0)),yf2=yf+seed*7.0,ci=floor(yf2),fy=fract(yf2);\n float seg=mix(W_SEG_MIN,W_SEG_MAX,h21(vec2(ci,off*2.3)));\n float spR=h21(vec2(ci,off+sgn*31.0)),seg1=rGate(fy,seg)*step(spR,sp);\n if(ep>0.0){float spR2=h21(vec2(ci*3.1+7.0,off*5.3+sgn*13.0)); float f2=fract(fy+0.5); seg1+=rGate(f2,seg*0.9)*step(spR2,ep);}\n sum+=amp*lat*seg1;\n }\n }\n float span=smoothstep(-3.0,0.0,y)*(1.0-smoothstep(R_V-6.0,R_V,y));\n return uWIntensity*sum*topF*bGain*span;\n}\n\nvoid mainImage(out vec4 fc,in vec2 frag){\n vec2 C=iResolution.xy*.5; float invW=1.0/max(C.x,1.0);\n vec2 sc=(512.0/iResolution.xy)*.4;\n vec2 uv=(frag-C)*sc,off=vec2(uBeamXFrac*iResolution.x*sc.x,uBeamYFrac*iResolution.y*sc.y);\n vec2 uvc = uv - off;\n float a=0.0,b=0.0;\n float basePhase=1.5*PI+uDecay*.5; float tauMin=basePhase-uDecay; float tauMax=basePhase;\n float cx=clamp(uvc.x/(R_H*uHLenFactor),-1.0,1.0),tH=clamp(TWO_PI-acos(cx),tauMin,tauMax);\n for(int k=-TAP_RADIUS;k<=TAP_RADIUS;++k){\n float tu=tH+float(k)*DT_LOCAL,wt=tauWf(tu,tauMin,tauMax); if(wt<=0.0) continue;\n float spd=max(abs(sin(tu)),0.02),u=clamp((basePhase-tu)/max(uDecay,EPS),0.0,1.0),env=pow(1.0-abs(u*2.0-1.0),0.8);\n vec2 p=vec2((R_H*uHLenFactor)*cos(tu),0.0);\n a+=wt*bs(uvc,p,env*spd);\n }\n float yPix=uvc.y,cy=clamp(-yPix/(R_V*uVLenFactor),-1.0,1.0),tV=clamp(TWO_PI-acos(cy),tauMin,tauMax);\n for(int k=-TAP_RADIUS;k<=TAP_RADIUS;++k){\n float tu=tV+float(k)*DT_LOCAL,wt=tauWf(tu,tauMin,tauMax); if(wt<=0.0) continue;\n float yb=(-R_V)*cos(tu),s=clamp(yb/R_V,0.0,1.0),spd=max(abs(sin(tu)),0.02);\n float env=pow(1.0-s,0.6)*spd;\n float cap=1.0-smoothstep(TOP_FADE_START,1.0,s); cap=pow(cap,TOP_FADE_EXP); env*=cap;\n float ph=s/max(FLOW_PERIOD,EPS)+uFlowTime*uFlowSpeed;\n float fl=pow(tri01(ph),FLOW_SHARPNESS);\n env*=mix(1.0-uFlowStrength,1.0,fl);\n float yp=(-R_V*uVLenFactor)*cos(tu),m=pow(smoothstep(FLARE_HEIGHT,0.0,yp),FLARE_EXP),wx=1.0+FLARE_AMOUNT*m;\n vec2 sig=vec2(wx,1.0),p=vec2(0.0,yp);\n float mask=step(0.0,yp);\n b+=wt*bsa(uvc,p,mask*env,sig);\n }\n float sPix=clamp(yPix/R_V,0.0,1.0),topA=pow(1.0-smoothstep(TOP_FADE_START,1.0,sPix),TOP_FADE_EXP);\n float L=a+b*topA;\n float w=vWisps(vec2(uvc.x,yPix),topA);\n float fog=0.0;\n#if FOG_ON\n vec2 fuv=uvc*uFogScale;\n float mAct=step(1.0,length(iMouse.xy)),nx=((iMouse.x-C.x)*invW)*mAct;\n float ax = abs(nx);\n float stMag = mix(ax, pow(ax, FOG_TILT_SHAPE), 0.35);\n float st = sign(nx) * stMag * uTiltScale;\n st = clamp(st, -FOG_TILT_MAX_X, FOG_TILT_MAX_X);\n vec2 dir=normalize(vec2(st,1.0));\n fuv+=uFogTime*uFogFallSpeed*dir;\n vec2 prp=vec2(-dir.y,dir.x);\n fuv+=prp*(0.08*sin(dot(uvc,prp)*0.08+uFogTime*0.9));\n float n=fbm2(fuv+vec2(fbm2(fuv+vec2(7.3,2.1)),fbm2(fuv+vec2(-3.7,5.9)))*0.6);\n n=pow(clamp(n,0.0,1.0),FOG_CONTRAST);\n float pixW = 1.0 / max(iResolution.y, 1.0);\n#ifdef GL_OES_standard_derivatives\n float wL = max(fwidth(L), pixW);\n#else\n float wL = pixW;\n#endif\n float m0=pow(smoothstep(FOG_BEAM_MIN - wL, FOG_BEAM_MAX + wL, L),FOG_MASK_GAMMA);\n float bm=1.0-pow(1.0-m0,FOG_EXPAND_SHAPE); bm=mix(bm*m0,bm,FOG_EDGE_MIX);\n float yP=1.0-smoothstep(HFOG_Y_RADIUS,HFOG_Y_RADIUS+HFOG_Y_SOFT,abs(yPix));\n float nxF=abs((frag.x-C.x)*invW),hE=1.0-smoothstep(HFOG_EDGE_START,HFOG_EDGE_END,nxF); hE=pow(clamp(hE,0.0,1.0),HFOG_EDGE_GAMMA);\n float hW=mix(1.0,hE,clamp(yP,0.0,1.0));\n float bBias=mix(1.0,1.0-sPix,FOG_BOTTOM_BIAS);\n float browserFogIntensity = uFogIntensity;\n browserFogIntensity *= 1.8;\n float radialFade = 1.0 - smoothstep(0.0, 0.7, length(uvc) / 120.0);\n float safariFog = n * browserFogIntensity * bBias * bm * hW * radialFade;\n fog = safariFog;\n#endif\n float LF=L+fog;\n float dith=(h21(frag)-0.5)*(DITHER_STRENGTH/255.0);\n float tone=g(LF+w);\n vec3 col=tone*uColor+dith;\n float alpha=clamp(g(L+w*0.6)+dith*0.6,0.0,1.0);\n float nxE=abs((frag.x-C.x)*invW),xF=pow(clamp(1.0-smoothstep(EDGE_X0,EDGE_X1,nxE),0.0,1.0),EDGE_X_GAMMA);\n float scene=LF+max(0.0,w)*0.5,hi=smoothstep(EDGE_LUMA_T0,EDGE_LUMA_T1,scene);\n float eM=mix(xF,1.0,hi);\n col*=eM; alpha*=eM;\n col*=uFade; alpha*=uFade;\n fc=vec4(col,alpha);\n}\n\nvoid main(){\n vec4 fc;\n mainImage(fc, gl_FragCoord.xy);\n gl_FragColor = fc;\n}\n`;\n\nexport const LaserFlow = ({\n className,\n style,\n wispDensity = 1,\n dpr,\n mouseSmoothTime = 0.0,\n mouseTiltStrength = 0.01,\n horizontalBeamOffset = 0.1,\n verticalBeamOffset = 0.0,\n flowSpeed = 0.35,\n verticalSizing = 2.0,\n horizontalSizing = 0.5,\n fogIntensity = 0.45,\n fogScale = 0.3,\n wispSpeed = 15.0,\n wispIntensity = 5.0,\n flowStrength = 0.25,\n decay = 1.1,\n falloffStart = 1.2,\n fogFallSpeed = 0.6,\n color = '#FF79C6'\n}) => {\n const mountRef = useRef(null);\n const rendererRef = useRef(null);\n const uniformsRef = useRef(null);\n const hasFadedRef = useRef(false);\n const rectRef = useRef(null);\n const baseDprRef = useRef(1);\n const currentDprRef = useRef(1);\n const lastSizeRef = useRef({ width: 0, height: 0, dpr: 0 });\n const fpsSamplesRef = useRef([]);\n const lastFpsCheckRef = useRef(performance.now());\n const emaDtRef = useRef(16.7);\n const pausedRef = useRef(false);\n const inViewRef = useRef(true);\n\n const hexToRGB = hex => {\n let c = hex.trim();\n if (c[0] === '#') c = c.slice(1);\n if (c.length === 3)\n c = c\n .split('')\n .map(x => x + x)\n .join('');\n const n = parseInt(c, 16) || 0xffffff;\n return { r: ((n >> 16) & 255) / 255, g: ((n >> 8) & 255) / 255, b: (n & 255) / 255 };\n };\n\n useEffect(() => {\n const mount = mountRef.current;\n const renderer = new THREE.WebGLRenderer({\n antialias: false,\n alpha: false,\n depth: false,\n stencil: false,\n powerPreference: 'high-performance',\n premultipliedAlpha: false,\n preserveDrawingBuffer: false,\n failIfMajorPerformanceCaveat: false,\n logarithmicDepthBuffer: false\n });\n rendererRef.current = renderer;\n\n baseDprRef.current = Math.min(dpr ?? (window.devicePixelRatio || 1), 2);\n currentDprRef.current = baseDprRef.current;\n\n renderer.setPixelRatio(currentDprRef.current);\n renderer.shadowMap.enabled = false;\n renderer.outputColorSpace = THREE.SRGBColorSpace;\n renderer.setClearColor(0x000000, 1);\n const canvas = renderer.domElement;\n canvas.style.width = '100%';\n canvas.style.height = '100%';\n canvas.style.display = 'block';\n mount.appendChild(canvas);\n\n const scene = new THREE.Scene();\n const camera = new THREE.OrthographicCamera(-1, 1, 1, -1, 0, 1);\n\n const geometry = new THREE.BufferGeometry();\n geometry.setAttribute('position', new THREE.BufferAttribute(new Float32Array([-1, -1, 0, 3, -1, 0, -1, 3, 0]), 3));\n\n const uniforms = {\n iTime: { value: 0 },\n iResolution: { value: new THREE.Vector3(1, 1, 1) },\n iMouse: { value: new THREE.Vector4(0, 0, 0, 0) },\n uWispDensity: { value: wispDensity },\n uTiltScale: { value: mouseTiltStrength },\n uFlowTime: { value: 0 },\n uFogTime: { value: 0 },\n uBeamXFrac: { value: horizontalBeamOffset },\n uBeamYFrac: { value: verticalBeamOffset },\n uFlowSpeed: { value: flowSpeed },\n uVLenFactor: { value: verticalSizing },\n uHLenFactor: { value: horizontalSizing },\n uFogIntensity: { value: fogIntensity },\n uFogScale: { value: fogScale },\n uWSpeed: { value: wispSpeed },\n uWIntensity: { value: wispIntensity },\n uFlowStrength: { value: flowStrength },\n uDecay: { value: decay },\n uFalloffStart: { value: falloffStart },\n uFogFallSpeed: { value: fogFallSpeed },\n uColor: { value: new THREE.Vector3(1, 1, 1) },\n uFade: { value: hasFadedRef.current ? 1 : 0 }\n };\n uniformsRef.current = uniforms;\n\n const material = new THREE.RawShaderMaterial({\n vertexShader: VERT,\n fragmentShader: FRAG,\n uniforms,\n transparent: false,\n depthTest: false,\n depthWrite: false,\n blending: THREE.NormalBlending\n });\n\n const mesh = new THREE.Mesh(geometry, material);\n mesh.frustumCulled = false;\n scene.add(mesh);\n\n const clock = new THREE.Clock();\n let prevTime = 0;\n let fade = hasFadedRef.current ? 1 : 0;\n\n const mouseTarget = new THREE.Vector2(0, 0);\n const mouseSmooth = new THREE.Vector2(0, 0);\n\n const setSizeNow = () => {\n const w = mount.clientWidth || 1;\n const h = mount.clientHeight || 1;\n const pr = currentDprRef.current;\n\n const last = lastSizeRef.current;\n const sizeChanged = Math.abs(w - last.width) > 0.5 || Math.abs(h - last.height) > 0.5;\n const dprChanged = Math.abs(pr - last.dpr) > 0.01;\n if (!sizeChanged && !dprChanged) {\n return;\n }\n\n lastSizeRef.current = { width: w, height: h, dpr: pr };\n renderer.setPixelRatio(pr);\n renderer.setSize(w, h, false);\n uniforms.iResolution.value.set(w * pr, h * pr, pr);\n rectRef.current = canvas.getBoundingClientRect();\n\n if (!pausedRef.current) {\n renderer.render(scene, camera);\n }\n };\n\n let resizeRaf = 0;\n const scheduleResize = () => {\n if (resizeRaf) cancelAnimationFrame(resizeRaf);\n resizeRaf = requestAnimationFrame(setSizeNow);\n };\n\n setSizeNow();\n const ro = new ResizeObserver(scheduleResize);\n ro.observe(mount);\n\n const io = new IntersectionObserver(\n entries => {\n inViewRef.current = entries[0]?.isIntersecting ?? true;\n },\n { root: null, threshold: 0 }\n );\n io.observe(mount);\n\n const onVis = () => {\n pausedRef.current = document.hidden;\n };\n document.addEventListener('visibilitychange', onVis, { passive: true });\n\n const updateMouse = (clientX, clientY) => {\n const rect = rectRef.current;\n if (!rect) return;\n const x = clientX - rect.left;\n const y = clientY - rect.top;\n const ratio = currentDprRef.current;\n const hb = rect.height * ratio;\n mouseTarget.set(x * ratio, hb - y * ratio);\n };\n const onMove = ev => updateMouse(ev.clientX, ev.clientY);\n const onLeave = () => mouseTarget.set(0, 0);\n canvas.addEventListener('pointermove', onMove, { passive: true });\n canvas.addEventListener('pointerdown', onMove, { passive: true });\n canvas.addEventListener('pointerenter', onMove, { passive: true });\n canvas.addEventListener('pointerleave', onLeave, { passive: true });\n\n const onCtxLost = e => {\n e.preventDefault();\n pausedRef.current = true;\n };\n const onCtxRestored = () => {\n pausedRef.current = false;\n scheduleResize();\n };\n canvas.addEventListener('webglcontextlost', onCtxLost, false);\n canvas.addEventListener('webglcontextrestored', onCtxRestored, false);\n\n let raf = 0;\n\n const clamp = (v, lo, hi) => Math.max(lo, Math.min(hi, v));\n const dprFloor = 0.6;\n const lowerThresh = 50;\n const upperThresh = 58;\n let lastDprChangeRef = 0;\n const dprChangeCooldown = 2000;\n\n const adjustDprIfNeeded = now => {\n const elapsed = now - lastFpsCheckRef.current;\n if (elapsed < 750) return;\n\n const samples = fpsSamplesRef.current;\n if (samples.length === 0) {\n lastFpsCheckRef.current = now;\n return;\n }\n const avgFps = samples.reduce((a, b) => a + b, 0) / samples.length;\n\n let next = currentDprRef.current;\n const base = baseDprRef.current;\n\n if (avgFps < lowerThresh) {\n next = clamp(currentDprRef.current * 0.85, dprFloor, base);\n } else if (avgFps > upperThresh && currentDprRef.current < base) {\n next = clamp(currentDprRef.current * 1.1, dprFloor, base);\n }\n\n if (Math.abs(next - currentDprRef.current) > 0.01 && now - lastDprChangeRef > dprChangeCooldown) {\n currentDprRef.current = next;\n lastDprChangeRef = now;\n setSizeNow();\n }\n\n fpsSamplesRef.current = [];\n lastFpsCheckRef.current = now;\n };\n\n const animate = () => {\n raf = requestAnimationFrame(animate);\n if (pausedRef.current || !inViewRef.current) return;\n\n const t = clock.getElapsedTime();\n const dt = Math.max(0, t - prevTime);\n prevTime = t;\n\n const dtMs = dt * 1000;\n emaDtRef.current = emaDtRef.current * 0.9 + dtMs * 0.1;\n const instFps = 1000 / Math.max(1, emaDtRef.current);\n fpsSamplesRef.current.push(instFps);\n\n uniforms.iTime.value = t;\n\n const cdt = Math.min(0.033, Math.max(0.001, dt));\n uniforms.uFlowTime.value += cdt;\n uniforms.uFogTime.value += cdt;\n\n if (!hasFadedRef.current) {\n const fadeDur = 1.0;\n fade = Math.min(1, fade + cdt / fadeDur);\n uniforms.uFade.value = fade;\n if (fade >= 1) hasFadedRef.current = true;\n }\n\n const tau = Math.max(1e-3, mouseSmoothTime);\n const alpha = 1 - Math.exp(-cdt / tau);\n mouseSmooth.lerp(mouseTarget, alpha);\n uniforms.iMouse.value.set(mouseSmooth.x, mouseSmooth.y, 0, 0);\n\n renderer.render(scene, camera);\n\n adjustDprIfNeeded(performance.now());\n };\n\n animate();\n\n return () => {\n cancelAnimationFrame(raf);\n ro.disconnect();\n io.disconnect();\n document.removeEventListener('visibilitychange', onVis);\n canvas.removeEventListener('pointermove', onMove);\n canvas.removeEventListener('pointerdown', onMove);\n canvas.removeEventListener('pointerenter', onMove);\n canvas.removeEventListener('pointerleave', onLeave);\n canvas.removeEventListener('webglcontextlost', onCtxLost);\n canvas.removeEventListener('webglcontextrestored', onCtxRestored);\n geometry.dispose();\n material.dispose();\n renderer.dispose();\n renderer.forceContextLoss();\n if (mount.contains(canvas)) mount.removeChild(canvas);\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [dpr]);\n\n useEffect(() => {\n const uniforms = uniformsRef.current;\n if (!uniforms) return;\n\n uniforms.uWispDensity.value = wispDensity;\n uniforms.uTiltScale.value = mouseTiltStrength;\n uniforms.uBeamXFrac.value = horizontalBeamOffset;\n uniforms.uBeamYFrac.value = verticalBeamOffset;\n uniforms.uFlowSpeed.value = flowSpeed;\n uniforms.uVLenFactor.value = verticalSizing;\n uniforms.uHLenFactor.value = horizontalSizing;\n uniforms.uFogIntensity.value = fogIntensity;\n uniforms.uFogScale.value = fogScale;\n uniforms.uWSpeed.value = wispSpeed;\n uniforms.uWIntensity.value = wispIntensity;\n uniforms.uFlowStrength.value = flowStrength;\n uniforms.uDecay.value = decay;\n uniforms.uFalloffStart.value = falloffStart;\n uniforms.uFogFallSpeed.value = fogFallSpeed;\n\n const { r, g, b } = hexToRGB(color || '#FFFFFF');\n uniforms.uColor.value.set(r, g, b);\n }, [\n wispDensity,\n mouseTiltStrength,\n horizontalBeamOffset,\n verticalBeamOffset,\n flowSpeed,\n verticalSizing,\n horizontalSizing,\n fogIntensity,\n fogScale,\n wispSpeed,\n wispIntensity,\n flowStrength,\n decay,\n falloffStart,\n fogFallSpeed,\n color\n ]);\n\n return
;\n};\n\nexport default LaserFlow;\n" + "content": "import { useEffect, useRef } from 'react';\nimport * as THREE from 'three';\nimport './LaserFlow.css';\n\nconst VERT = `\nprecision highp float;\nattribute vec3 position;\nvoid main(){\n gl_Position = vec4(position, 1.0);\n}\n`;\n\nconst FRAG = `\n#ifdef GL_ES\n#extension GL_OES_standard_derivatives : enable\n#endif\nprecision highp float;\nprecision mediump int;\n\nuniform float iTime;\nuniform vec3 iResolution;\nuniform vec4 iMouse;\nuniform float uWispDensity;\nuniform float uTiltScale;\nuniform float uFlowTime;\nuniform float uFogTime;\nuniform float uBeamXFrac;\nuniform float uBeamYFrac;\nuniform float uFlowSpeed;\nuniform float uVLenFactor;\nuniform float uHLenFactor;\nuniform float uFogIntensity;\nuniform float uFogScale;\nuniform float uWSpeed;\nuniform float uWIntensity;\nuniform float uFlowStrength;\nuniform float uDecay;\nuniform float uFalloffStart;\nuniform float uFogFallSpeed;\nuniform vec3 uColor;\nuniform float uFade;\n\n// Core beam/flare shaping and dynamics\n#define PI 3.14159265359\n#define TWO_PI 6.28318530718\n#define EPS 1e-6\n#define EDGE_SOFT (DT_LOCAL*4.0)\n#define DT_LOCAL 0.0038\n#define TAP_RADIUS 6\n#define R_H 150.0\n#define R_V 150.0\n#define FLARE_HEIGHT 16.0\n#define FLARE_AMOUNT 8.0\n#define FLARE_EXP 2.0\n#define TOP_FADE_START 0.1\n#define TOP_FADE_EXP 1.0\n#define FLOW_PERIOD 0.5\n#define FLOW_SHARPNESS 1.5\n\n// Wisps (animated micro-streaks) that travel along the beam\n#define W_BASE_X 1.5\n#define W_LAYER_GAP 0.25\n#define W_LANES 10\n#define W_SIDE_DECAY 0.5\n#define W_HALF 0.01\n#define W_AA 0.15\n#define W_CELL 20.0\n#define W_SEG_MIN 0.01\n#define W_SEG_MAX 0.55\n#define W_CURVE_AMOUNT 15.0\n#define W_CURVE_RANGE (FLARE_HEIGHT - 3.0)\n#define W_BOTTOM_EXP 10.0\n\n// Volumetric fog controls\n#define FOG_ON 1\n#define FOG_CONTRAST 1.2\n#define FOG_SPEED_U 0.1\n#define FOG_SPEED_V -0.1\n#define FOG_OCTAVES 5\n#define FOG_BOTTOM_BIAS 0.8\n#define FOG_TILT_TO_MOUSE 0.05\n#define FOG_TILT_DEADZONE 0.01\n#define FOG_TILT_MAX_X 0.35\n#define FOG_TILT_SHAPE 1.5\n#define FOG_BEAM_MIN 0.0\n#define FOG_BEAM_MAX 0.75\n#define FOG_MASK_GAMMA 0.5\n#define FOG_EXPAND_SHAPE 12.2\n#define FOG_EDGE_MIX 0.5\n\n// Horizontal vignette for the fog volume\n#define HFOG_EDGE_START 0.20\n#define HFOG_EDGE_END 0.98\n#define HFOG_EDGE_GAMMA 1.4\n#define HFOG_Y_RADIUS 25.0\n#define HFOG_Y_SOFT 60.0\n\n// Beam extents and edge masking\n#define EDGE_X0 0.22\n#define EDGE_X1 0.995\n#define EDGE_X_GAMMA 1.25\n#define EDGE_LUMA_T0 0.0\n#define EDGE_LUMA_T1 2.0\n#define DITHER_STRENGTH 1.0\n\n float g(float x){return x<=0.00031308?12.92*x:1.055*pow(x,1.0/2.4)-0.055;}\n float bs(vec2 p,vec2 q,float powr){\n float d=distance(p,q),f=powr*uFalloffStart,r=(f*f)/(d*d+EPS);\n return powr*min(1.0,r);\n }\n float bsa(vec2 p,vec2 q,float powr,vec2 s){\n vec2 d=p-q; float dd=(d.x*d.x)/(s.x*s.x)+(d.y*d.y)/(s.y*s.y),f=powr*uFalloffStart,r=(f*f)/(dd+EPS);\n return powr*min(1.0,r);\n }\n float tri01(float x){float f=fract(x);return 1.0-abs(f*2.0-1.0);}\n float tauWf(float t,float tmin,float tmax){float a=smoothstep(tmin,tmin+EDGE_SOFT,t),b=1.0-smoothstep(tmax-EDGE_SOFT,tmax,t);return max(0.0,a*b);} \n float h21(vec2 p){p=fract(p*vec2(123.34,456.21));p+=dot(p,p+34.123);return fract(p.x*p.y);}\n float vnoise(vec2 p){\n vec2 i=floor(p),f=fract(p);\n float a=h21(i),b=h21(i+vec2(1,0)),c=h21(i+vec2(0,1)),d=h21(i+vec2(1,1));\n vec2 u=f*f*(3.0-2.0*f);\n return mix(mix(a,b,u.x),mix(c,d,u.x),u.y);\n }\n float fbm2(vec2 p){\n float v=0.0,amp=0.6; mat2 m=mat2(0.86,0.5,-0.5,0.86);\n for(int i=0;i=lanes) break;\n float off=W_BASE_X+float(i)*W_LAYER_GAP,xc=sgn*(off*xS);\n float dx=abs(uv.x-xc),lat=1.0-smoothstep(W_HALF,W_HALF+W_AA,dx),amp=exp(-off*W_SIDE_DECAY);\n float seed=h21(vec2(off,sgn*17.0)),yf2=yf+seed*7.0,ci=floor(yf2),fy=fract(yf2);\n float seg=mix(W_SEG_MIN,W_SEG_MAX,h21(vec2(ci,off*2.3)));\n float spR=h21(vec2(ci,off+sgn*31.0)),seg1=rGate(fy,seg)*step(spR,sp);\n if(ep>0.0){float spR2=h21(vec2(ci*3.1+7.0,off*5.3+sgn*13.0)); float f2=fract(fy+0.5); seg1+=rGate(f2,seg*0.9)*step(spR2,ep);}\n sum+=amp*lat*seg1;\n }\n }\n float span=smoothstep(-3.0,0.0,y)*(1.0-smoothstep(R_V-6.0,R_V,y));\n return uWIntensity*sum*topF*bGain*span;\n}\n\nvoid mainImage(out vec4 fc,in vec2 frag){\n vec2 C=iResolution.xy*.5; float invW=1.0/max(C.x,1.0);\n vec2 sc=(512.0/iResolution.xy)*.4;\n vec2 uv=(frag-C)*sc,off=vec2(uBeamXFrac*iResolution.x*sc.x,uBeamYFrac*iResolution.y*sc.y);\n vec2 uvc = uv - off;\n float a=0.0,b=0.0;\n float basePhase=1.5*PI+uDecay*.5; float tauMin=basePhase-uDecay; float tauMax=basePhase;\n float cx=clamp(uvc.x/(R_H*uHLenFactor),-1.0,1.0),tH=clamp(TWO_PI-acos(cx),tauMin,tauMax);\n for(int k=-TAP_RADIUS;k<=TAP_RADIUS;++k){\n float tu=tH+float(k)*DT_LOCAL,wt=tauWf(tu,tauMin,tauMax); if(wt<=0.0) continue;\n float spd=max(abs(sin(tu)),0.02),u=clamp((basePhase-tu)/max(uDecay,EPS),0.0,1.0),env=pow(1.0-abs(u*2.0-1.0),0.8);\n vec2 p=vec2((R_H*uHLenFactor)*cos(tu),0.0);\n a+=wt*bs(uvc,p,env*spd);\n }\n float yPix=uvc.y,cy=clamp(-yPix/(R_V*uVLenFactor),-1.0,1.0),tV=clamp(TWO_PI-acos(cy),tauMin,tauMax);\n for(int k=-TAP_RADIUS;k<=TAP_RADIUS;++k){\n float tu=tV+float(k)*DT_LOCAL,wt=tauWf(tu,tauMin,tauMax); if(wt<=0.0) continue;\n float yb=(-R_V)*cos(tu),s=clamp(yb/R_V,0.0,1.0),spd=max(abs(sin(tu)),0.02);\n float env=pow(1.0-s,0.6)*spd;\n float cap=1.0-smoothstep(TOP_FADE_START,1.0,s); cap=pow(cap,TOP_FADE_EXP); env*=cap;\n float ph=s/max(FLOW_PERIOD,EPS)+uFlowTime*uFlowSpeed;\n float fl=pow(tri01(ph),FLOW_SHARPNESS);\n env*=mix(1.0-uFlowStrength,1.0,fl);\n float yp=(-R_V*uVLenFactor)*cos(tu),m=pow(smoothstep(FLARE_HEIGHT,0.0,yp),FLARE_EXP),wx=1.0+FLARE_AMOUNT*m;\n vec2 sig=vec2(wx,1.0),p=vec2(0.0,yp);\n float mask=step(0.0,yp);\n b+=wt*bsa(uvc,p,mask*env,sig);\n }\n float sPix=clamp(yPix/R_V,0.0,1.0),topA=pow(1.0-smoothstep(TOP_FADE_START,1.0,sPix),TOP_FADE_EXP);\n float L=a+b*topA;\n float w=vWisps(vec2(uvc.x,yPix),topA);\n float fog=0.0;\n#if FOG_ON\n vec2 fuv=uvc*uFogScale;\n float mAct=step(1.0,length(iMouse.xy)),nx=((iMouse.x-C.x)*invW)*mAct;\n float ax = abs(nx);\n float stMag = mix(ax, pow(ax, FOG_TILT_SHAPE), 0.35);\n float st = sign(nx) * stMag * uTiltScale;\n st = clamp(st, -FOG_TILT_MAX_X, FOG_TILT_MAX_X);\n vec2 dir=normalize(vec2(st,1.0));\n fuv+=uFogTime*uFogFallSpeed*dir;\n vec2 prp=vec2(-dir.y,dir.x);\n fuv+=prp*(0.08*sin(dot(uvc,prp)*0.08+uFogTime*0.9));\n float n=fbm2(fuv+vec2(fbm2(fuv+vec2(7.3,2.1)),fbm2(fuv+vec2(-3.7,5.9)))*0.6);\n n=pow(clamp(n,0.0,1.0),FOG_CONTRAST);\n float pixW = 1.0 / max(iResolution.y, 1.0);\n#ifdef GL_OES_standard_derivatives\n float wL = max(fwidth(L), pixW);\n#else\n float wL = pixW;\n#endif\n float m0=pow(smoothstep(FOG_BEAM_MIN - wL, FOG_BEAM_MAX + wL, L),FOG_MASK_GAMMA);\n float bm=1.0-pow(1.0-m0,FOG_EXPAND_SHAPE); bm=mix(bm*m0,bm,FOG_EDGE_MIX);\n float yP=1.0-smoothstep(HFOG_Y_RADIUS,HFOG_Y_RADIUS+HFOG_Y_SOFT,abs(yPix));\n float nxF=abs((frag.x-C.x)*invW),hE=1.0-smoothstep(HFOG_EDGE_START,HFOG_EDGE_END,nxF); hE=pow(clamp(hE,0.0,1.0),HFOG_EDGE_GAMMA);\n float hW=mix(1.0,hE,clamp(yP,0.0,1.0));\n float bBias=mix(1.0,1.0-sPix,FOG_BOTTOM_BIAS);\n float browserFogIntensity = uFogIntensity;\n browserFogIntensity *= 1.8;\n float radialFade = 1.0 - smoothstep(0.0, 0.7, length(uvc) / 120.0);\n float safariFog = n * browserFogIntensity * bBias * bm * hW * radialFade;\n fog = safariFog;\n#endif\n float LF=L+fog;\n float dith=(h21(frag)-0.5)*(DITHER_STRENGTH/255.0);\n float tone=g(LF+w);\n vec3 col=tone*uColor+dith;\n float alpha=clamp(g(L+w*0.6)+dith*0.6,0.0,1.0);\n float nxE=abs((frag.x-C.x)*invW),xF=pow(clamp(1.0-smoothstep(EDGE_X0,EDGE_X1,nxE),0.0,1.0),EDGE_X_GAMMA);\n float scene=LF+max(0.0,w)*0.5,hi=smoothstep(EDGE_LUMA_T0,EDGE_LUMA_T1,scene);\n float eM=mix(xF,1.0,hi);\n col*=eM; alpha*=eM;\n col*=uFade; alpha*=uFade;\n fc=vec4(col,alpha);\n}\n\nvoid main(){\n vec4 fc;\n mainImage(fc, gl_FragCoord.xy);\n gl_FragColor = fc;\n}\n`;\n\nexport const LaserFlow = ({\n className,\n style,\n wispDensity = 1,\n dpr,\n mouseSmoothTime = 0.0,\n mouseTiltStrength = 0.01,\n horizontalBeamOffset = 0.1,\n verticalBeamOffset = 0.0,\n flowSpeed = 0.35,\n verticalSizing = 2.0,\n horizontalSizing = 0.5,\n fogIntensity = 0.45,\n fogScale = 0.3,\n wispSpeed = 15.0,\n wispIntensity = 5.0,\n flowStrength = 0.25,\n decay = 1.1,\n falloffStart = 1.2,\n fogFallSpeed = 0.6,\n color = '#FF79C6'\n}) => {\n const mountRef = useRef(null);\n const rendererRef = useRef(null);\n const uniformsRef = useRef(null);\n const hasFadedRef = useRef(false);\n const rectRef = useRef(null);\n const baseDprRef = useRef(1);\n const currentDprRef = useRef(1);\n const lastSizeRef = useRef({ width: 0, height: 0, dpr: 0 });\n const fpsSamplesRef = useRef([]);\n const lastFpsCheckRef = useRef(performance.now());\n const emaDtRef = useRef(16.7);\n const pausedRef = useRef(false);\n const inViewRef = useRef(true);\n\n const hexToRGB = hex => {\n let c = hex.trim();\n if (c[0] === '#') c = c.slice(1);\n if (c.length === 3)\n c = c\n .split('')\n .map(x => x + x)\n .join('');\n const n = parseInt(c.slice(0, 6), 16) || 0xffffff;\n return { r: ((n >> 16) & 255) / 255, g: ((n >> 8) & 255) / 255, b: (n & 255) / 255 };\n };\n\n useEffect(() => {\n const mount = mountRef.current;\n const renderer = new THREE.WebGLRenderer({\n antialias: false,\n alpha: false,\n depth: false,\n stencil: false,\n powerPreference: 'high-performance',\n premultipliedAlpha: false,\n preserveDrawingBuffer: false,\n failIfMajorPerformanceCaveat: false,\n logarithmicDepthBuffer: false\n });\n rendererRef.current = renderer;\n\n baseDprRef.current = Math.min(dpr ?? (window.devicePixelRatio || 1), 2);\n currentDprRef.current = baseDprRef.current;\n\n renderer.setPixelRatio(currentDprRef.current);\n renderer.shadowMap.enabled = false;\n renderer.outputColorSpace = THREE.SRGBColorSpace;\n renderer.setClearColor(0x000000, 1);\n const canvas = renderer.domElement;\n canvas.style.width = '100%';\n canvas.style.height = '100%';\n canvas.style.display = 'block';\n mount.appendChild(canvas);\n\n const scene = new THREE.Scene();\n const camera = new THREE.OrthographicCamera(-1, 1, 1, -1, 0, 1);\n\n const geometry = new THREE.BufferGeometry();\n geometry.setAttribute('position', new THREE.BufferAttribute(new Float32Array([-1, -1, 0, 3, -1, 0, -1, 3, 0]), 3));\n\n const uniforms = {\n iTime: { value: 0 },\n iResolution: { value: new THREE.Vector3(1, 1, 1) },\n iMouse: { value: new THREE.Vector4(0, 0, 0, 0) },\n uWispDensity: { value: wispDensity },\n uTiltScale: { value: mouseTiltStrength },\n uFlowTime: { value: 0 },\n uFogTime: { value: 0 },\n uBeamXFrac: { value: horizontalBeamOffset },\n uBeamYFrac: { value: verticalBeamOffset },\n uFlowSpeed: { value: flowSpeed },\n uVLenFactor: { value: verticalSizing },\n uHLenFactor: { value: horizontalSizing },\n uFogIntensity: { value: fogIntensity },\n uFogScale: { value: fogScale },\n uWSpeed: { value: wispSpeed },\n uWIntensity: { value: wispIntensity },\n uFlowStrength: { value: flowStrength },\n uDecay: { value: decay },\n uFalloffStart: { value: falloffStart },\n uFogFallSpeed: { value: fogFallSpeed },\n uColor: { value: new THREE.Vector3(1, 1, 1) },\n uFade: { value: hasFadedRef.current ? 1 : 0 }\n };\n uniformsRef.current = uniforms;\n\n const material = new THREE.RawShaderMaterial({\n vertexShader: VERT,\n fragmentShader: FRAG,\n uniforms,\n transparent: false,\n depthTest: false,\n depthWrite: false,\n blending: THREE.NormalBlending\n });\n\n const mesh = new THREE.Mesh(geometry, material);\n mesh.frustumCulled = false;\n scene.add(mesh);\n\n const clock = new THREE.Clock();\n let prevTime = 0;\n let fade = hasFadedRef.current ? 1 : 0;\n\n const mouseTarget = new THREE.Vector2(0, 0);\n const mouseSmooth = new THREE.Vector2(0, 0);\n\n const setSizeNow = () => {\n const w = mount.clientWidth || 1;\n const h = mount.clientHeight || 1;\n const pr = currentDprRef.current;\n\n const last = lastSizeRef.current;\n const sizeChanged = Math.abs(w - last.width) > 0.5 || Math.abs(h - last.height) > 0.5;\n const dprChanged = Math.abs(pr - last.dpr) > 0.01;\n if (!sizeChanged && !dprChanged) {\n return;\n }\n\n lastSizeRef.current = { width: w, height: h, dpr: pr };\n renderer.setPixelRatio(pr);\n renderer.setSize(w, h, false);\n uniforms.iResolution.value.set(w * pr, h * pr, pr);\n rectRef.current = canvas.getBoundingClientRect();\n\n if (!pausedRef.current) {\n renderer.render(scene, camera);\n }\n };\n\n let resizeRaf = 0;\n const scheduleResize = () => {\n if (resizeRaf) cancelAnimationFrame(resizeRaf);\n resizeRaf = requestAnimationFrame(setSizeNow);\n };\n\n setSizeNow();\n const ro = new ResizeObserver(scheduleResize);\n ro.observe(mount);\n\n const io = new IntersectionObserver(\n entries => {\n inViewRef.current = entries[0]?.isIntersecting ?? true;\n },\n { root: null, threshold: 0 }\n );\n io.observe(mount);\n\n const onVis = () => {\n pausedRef.current = document.hidden;\n };\n document.addEventListener('visibilitychange', onVis, { passive: true });\n\n const updateMouse = (clientX, clientY) => {\n const rect = rectRef.current;\n if (!rect) return;\n const x = clientX - rect.left;\n const y = clientY - rect.top;\n const ratio = currentDprRef.current;\n const hb = rect.height * ratio;\n mouseTarget.set(x * ratio, hb - y * ratio);\n };\n const onMove = ev => updateMouse(ev.clientX, ev.clientY);\n const onLeave = () => mouseTarget.set(0, 0);\n canvas.addEventListener('pointermove', onMove, { passive: true });\n canvas.addEventListener('pointerdown', onMove, { passive: true });\n canvas.addEventListener('pointerenter', onMove, { passive: true });\n canvas.addEventListener('pointerleave', onLeave, { passive: true });\n\n const onCtxLost = e => {\n e.preventDefault();\n pausedRef.current = true;\n };\n const onCtxRestored = () => {\n pausedRef.current = false;\n scheduleResize();\n };\n canvas.addEventListener('webglcontextlost', onCtxLost, false);\n canvas.addEventListener('webglcontextrestored', onCtxRestored, false);\n\n let raf = 0;\n\n const clamp = (v, lo, hi) => Math.max(lo, Math.min(hi, v));\n const dprFloor = 0.6;\n const lowerThresh = 50;\n const upperThresh = 58;\n let lastDprChangeRef = 0;\n const dprChangeCooldown = 2000;\n\n const adjustDprIfNeeded = now => {\n const elapsed = now - lastFpsCheckRef.current;\n if (elapsed < 750) return;\n\n const samples = fpsSamplesRef.current;\n if (samples.length === 0) {\n lastFpsCheckRef.current = now;\n return;\n }\n const avgFps = samples.reduce((a, b) => a + b, 0) / samples.length;\n\n let next = currentDprRef.current;\n const base = baseDprRef.current;\n\n if (avgFps < lowerThresh) {\n next = clamp(currentDprRef.current * 0.85, dprFloor, base);\n } else if (avgFps > upperThresh && currentDprRef.current < base) {\n next = clamp(currentDprRef.current * 1.1, dprFloor, base);\n }\n\n if (Math.abs(next - currentDprRef.current) > 0.01 && now - lastDprChangeRef > dprChangeCooldown) {\n currentDprRef.current = next;\n lastDprChangeRef = now;\n setSizeNow();\n }\n\n fpsSamplesRef.current = [];\n lastFpsCheckRef.current = now;\n };\n\n const animate = () => {\n raf = requestAnimationFrame(animate);\n if (pausedRef.current || !inViewRef.current) return;\n\n const t = clock.getElapsedTime();\n const dt = Math.max(0, t - prevTime);\n prevTime = t;\n\n const dtMs = dt * 1000;\n emaDtRef.current = emaDtRef.current * 0.9 + dtMs * 0.1;\n const instFps = 1000 / Math.max(1, emaDtRef.current);\n fpsSamplesRef.current.push(instFps);\n\n uniforms.iTime.value = t;\n\n const cdt = Math.min(0.033, Math.max(0.001, dt));\n uniforms.uFlowTime.value += cdt;\n uniforms.uFogTime.value += cdt;\n\n if (!hasFadedRef.current) {\n const fadeDur = 1.0;\n fade = Math.min(1, fade + cdt / fadeDur);\n uniforms.uFade.value = fade;\n if (fade >= 1) hasFadedRef.current = true;\n }\n\n const tau = Math.max(1e-3, mouseSmoothTime);\n const alpha = 1 - Math.exp(-cdt / tau);\n mouseSmooth.lerp(mouseTarget, alpha);\n uniforms.iMouse.value.set(mouseSmooth.x, mouseSmooth.y, 0, 0);\n\n renderer.render(scene, camera);\n\n adjustDprIfNeeded(performance.now());\n };\n\n animate();\n\n return () => {\n cancelAnimationFrame(raf);\n ro.disconnect();\n io.disconnect();\n document.removeEventListener('visibilitychange', onVis);\n canvas.removeEventListener('pointermove', onMove);\n canvas.removeEventListener('pointerdown', onMove);\n canvas.removeEventListener('pointerenter', onMove);\n canvas.removeEventListener('pointerleave', onLeave);\n canvas.removeEventListener('webglcontextlost', onCtxLost);\n canvas.removeEventListener('webglcontextrestored', onCtxRestored);\n geometry.dispose();\n material.dispose();\n renderer.dispose();\n renderer.forceContextLoss();\n if (mount.contains(canvas)) mount.removeChild(canvas);\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [dpr]);\n\n useEffect(() => {\n const uniforms = uniformsRef.current;\n if (!uniforms) return;\n\n uniforms.uWispDensity.value = wispDensity;\n uniforms.uTiltScale.value = mouseTiltStrength;\n uniforms.uBeamXFrac.value = horizontalBeamOffset;\n uniforms.uBeamYFrac.value = verticalBeamOffset;\n uniforms.uFlowSpeed.value = flowSpeed;\n uniforms.uVLenFactor.value = verticalSizing;\n uniforms.uHLenFactor.value = horizontalSizing;\n uniforms.uFogIntensity.value = fogIntensity;\n uniforms.uFogScale.value = fogScale;\n uniforms.uWSpeed.value = wispSpeed;\n uniforms.uWIntensity.value = wispIntensity;\n uniforms.uFlowStrength.value = flowStrength;\n uniforms.uDecay.value = decay;\n uniforms.uFalloffStart.value = falloffStart;\n uniforms.uFogFallSpeed.value = fogFallSpeed;\n\n const { r, g, b } = hexToRGB(color || '#FFFFFF');\n uniforms.uColor.value.set(r, g, b);\n }, [\n wispDensity,\n mouseTiltStrength,\n horizontalBeamOffset,\n verticalBeamOffset,\n flowSpeed,\n verticalSizing,\n horizontalSizing,\n fogIntensity,\n fogScale,\n wispSpeed,\n wispIntensity,\n flowStrength,\n decay,\n falloffStart,\n fogFallSpeed,\n color\n ]);\n\n return
;\n};\n\nexport default LaserFlow;\n" } ], "registryDependencies": [], diff --git a/public/r/LaserFlow-JS-TW.json b/public/r/LaserFlow-JS-TW.json index 0a65ac1e9..b7d48d8d8 100644 --- a/public/r/LaserFlow-JS-TW.json +++ b/public/r/LaserFlow-JS-TW.json @@ -8,7 +8,7 @@ { "type": "registry:component", "path": "LaserFlow/LaserFlow.jsx", - "content": "import { useEffect, useRef } from 'react';\nimport * as THREE from 'three';\n\nconst VERT = `\nprecision highp float;\nattribute vec3 position;\nvoid main(){\n gl_Position = vec4(position, 1.0);\n}\n`;\n\nconst FRAG = `\n#ifdef GL_ES\n#extension GL_OES_standard_derivatives : enable\n#endif\nprecision highp float;\nprecision mediump int;\n\nuniform float iTime;\nuniform vec3 iResolution;\nuniform vec4 iMouse;\nuniform float uWispDensity;\nuniform float uTiltScale;\nuniform float uFlowTime;\nuniform float uFogTime;\nuniform float uBeamXFrac;\nuniform float uBeamYFrac;\nuniform float uFlowSpeed;\nuniform float uVLenFactor;\nuniform float uHLenFactor;\nuniform float uFogIntensity;\nuniform float uFogScale;\nuniform float uWSpeed;\nuniform float uWIntensity;\nuniform float uFlowStrength;\nuniform float uDecay;\nuniform float uFalloffStart;\nuniform float uFogFallSpeed;\nuniform vec3 uColor;\nuniform float uFade;\n\n// Core beam/flare shaping and dynamics\n#define PI 3.14159265359\n#define TWO_PI 6.28318530718\n#define EPS 1e-6\n#define EDGE_SOFT (DT_LOCAL*4.0)\n#define DT_LOCAL 0.0038\n#define TAP_RADIUS 6\n#define R_H 150.0\n#define R_V 150.0\n#define FLARE_HEIGHT 16.0\n#define FLARE_AMOUNT 8.0\n#define FLARE_EXP 2.0\n#define TOP_FADE_START 0.1\n#define TOP_FADE_EXP 1.0\n#define FLOW_PERIOD 0.5\n#define FLOW_SHARPNESS 1.5\n\n// Wisps (animated micro-streaks) that travel along the beam\n#define W_BASE_X 1.5\n#define W_LAYER_GAP 0.25\n#define W_LANES 10\n#define W_SIDE_DECAY 0.5\n#define W_HALF 0.01\n#define W_AA 0.15\n#define W_CELL 20.0\n#define W_SEG_MIN 0.01\n#define W_SEG_MAX 0.55\n#define W_CURVE_AMOUNT 15.0\n#define W_CURVE_RANGE (FLARE_HEIGHT - 3.0)\n#define W_BOTTOM_EXP 10.0\n\n// Volumetric fog controls\n#define FOG_ON 1\n#define FOG_CONTRAST 1.2\n#define FOG_SPEED_U 0.1\n#define FOG_SPEED_V -0.1\n#define FOG_OCTAVES 5\n#define FOG_BOTTOM_BIAS 0.8\n#define FOG_TILT_TO_MOUSE 0.05\n#define FOG_TILT_DEADZONE 0.01\n#define FOG_TILT_MAX_X 0.35\n#define FOG_TILT_SHAPE 1.5\n#define FOG_BEAM_MIN 0.0\n#define FOG_BEAM_MAX 0.75\n#define FOG_MASK_GAMMA 0.5\n#define FOG_EXPAND_SHAPE 12.2\n#define FOG_EDGE_MIX 0.5\n\n// Horizontal vignette for the fog volume\n#define HFOG_EDGE_START 0.20\n#define HFOG_EDGE_END 0.98\n#define HFOG_EDGE_GAMMA 1.4\n#define HFOG_Y_RADIUS 25.0\n#define HFOG_Y_SOFT 60.0\n\n// Beam extents and edge masking\n#define EDGE_X0 0.22\n#define EDGE_X1 0.995\n#define EDGE_X_GAMMA 1.25\n#define EDGE_LUMA_T0 0.0\n#define EDGE_LUMA_T1 2.0\n#define DITHER_STRENGTH 1.0\n\n float g(float x){return x<=0.00031308?12.92*x:1.055*pow(x,1.0/2.4)-0.055;}\n float bs(vec2 p,vec2 q,float powr){\n float d=distance(p,q),f=powr*uFalloffStart,r=(f*f)/(d*d+EPS);\n return powr*min(1.0,r);\n }\n float bsa(vec2 p,vec2 q,float powr,vec2 s){\n vec2 d=p-q; float dd=(d.x*d.x)/(s.x*s.x)+(d.y*d.y)/(s.y*s.y),f=powr*uFalloffStart,r=(f*f)/(dd+EPS);\n return powr*min(1.0,r);\n }\n float tri01(float x){float f=fract(x);return 1.0-abs(f*2.0-1.0);}\n float tauWf(float t,float tmin,float tmax){float a=smoothstep(tmin,tmin+EDGE_SOFT,t),b=1.0-smoothstep(tmax-EDGE_SOFT,tmax,t);return max(0.0,a*b);} \n float h21(vec2 p){p=fract(p*vec2(123.34,456.21));p+=dot(p,p+34.123);return fract(p.x*p.y);}\n float vnoise(vec2 p){\n vec2 i=floor(p),f=fract(p);\n float a=h21(i),b=h21(i+vec2(1,0)),c=h21(i+vec2(0,1)),d=h21(i+vec2(1,1));\n vec2 u=f*f*(3.0-2.0*f);\n return mix(mix(a,b,u.x),mix(c,d,u.x),u.y);\n }\n float fbm2(vec2 p){\n float v=0.0,amp=0.6; mat2 m=mat2(0.86,0.5,-0.5,0.86);\n for(int i=0;i=lanes) break;\n float off=W_BASE_X+float(i)*W_LAYER_GAP,xc=sgn*(off*xS);\n float dx=abs(uv.x-xc),lat=1.0-smoothstep(W_HALF,W_HALF+W_AA,dx),amp=exp(-off*W_SIDE_DECAY);\n float seed=h21(vec2(off,sgn*17.0)),yf2=yf+seed*7.0,ci=floor(yf2),fy=fract(yf2);\n float seg=mix(W_SEG_MIN,W_SEG_MAX,h21(vec2(ci,off*2.3)));\n float spR=h21(vec2(ci,off+sgn*31.0)),seg1=rGate(fy,seg)*step(spR,sp);\n if(ep>0.0){float spR2=h21(vec2(ci*3.1+7.0,off*5.3+sgn*13.0)); float f2=fract(fy+0.5); seg1+=rGate(f2,seg*0.9)*step(spR2,ep);}\n sum+=amp*lat*seg1;\n }\n }\n float span=smoothstep(-3.0,0.0,y)*(1.0-smoothstep(R_V-6.0,R_V,y));\n return uWIntensity*sum*topF*bGain*span;\n}\n\nvoid mainImage(out vec4 fc,in vec2 frag){\n vec2 C=iResolution.xy*.5; float invW=1.0/max(C.x,1.0);\n vec2 sc=(512.0/iResolution.xy)*.4;\n vec2 uv=(frag-C)*sc,off=vec2(uBeamXFrac*iResolution.x*sc.x,uBeamYFrac*iResolution.y*sc.y);\n vec2 uvc = uv - off;\n float a=0.0,b=0.0;\n float basePhase=1.5*PI+uDecay*.5; float tauMin=basePhase-uDecay; float tauMax=basePhase;\n float cx=clamp(uvc.x/(R_H*uHLenFactor),-1.0,1.0),tH=clamp(TWO_PI-acos(cx),tauMin,tauMax);\n for(int k=-TAP_RADIUS;k<=TAP_RADIUS;++k){\n float tu=tH+float(k)*DT_LOCAL,wt=tauWf(tu,tauMin,tauMax); if(wt<=0.0) continue;\n float spd=max(abs(sin(tu)),0.02),u=clamp((basePhase-tu)/max(uDecay,EPS),0.0,1.0),env=pow(1.0-abs(u*2.0-1.0),0.8);\n vec2 p=vec2((R_H*uHLenFactor)*cos(tu),0.0);\n a+=wt*bs(uvc,p,env*spd);\n }\n float yPix=uvc.y,cy=clamp(-yPix/(R_V*uVLenFactor),-1.0,1.0),tV=clamp(TWO_PI-acos(cy),tauMin,tauMax);\n for(int k=-TAP_RADIUS;k<=TAP_RADIUS;++k){\n float tu=tV+float(k)*DT_LOCAL,wt=tauWf(tu,tauMin,tauMax); if(wt<=0.0) continue;\n float yb=(-R_V)*cos(tu),s=clamp(yb/R_V,0.0,1.0),spd=max(abs(sin(tu)),0.02);\n float env=pow(1.0-s,0.6)*spd;\n float cap=1.0-smoothstep(TOP_FADE_START,1.0,s); cap=pow(cap,TOP_FADE_EXP); env*=cap;\n float ph=s/max(FLOW_PERIOD,EPS)+uFlowTime*uFlowSpeed;\n float fl=pow(tri01(ph),FLOW_SHARPNESS);\n env*=mix(1.0-uFlowStrength,1.0,fl);\n float yp=(-R_V*uVLenFactor)*cos(tu),m=pow(smoothstep(FLARE_HEIGHT,0.0,yp),FLARE_EXP),wx=1.0+FLARE_AMOUNT*m;\n vec2 sig=vec2(wx,1.0),p=vec2(0.0,yp);\n float mask=step(0.0,yp);\n b+=wt*bsa(uvc,p,mask*env,sig);\n }\n float sPix=clamp(yPix/R_V,0.0,1.0),topA=pow(1.0-smoothstep(TOP_FADE_START,1.0,sPix),TOP_FADE_EXP);\n float L=a+b*topA;\n float w=vWisps(vec2(uvc.x,yPix),topA);\n float fog=0.0;\n#if FOG_ON\n vec2 fuv=uvc*uFogScale;\n float mAct=step(1.0,length(iMouse.xy)),nx=((iMouse.x-C.x)*invW)*mAct;\n float ax = abs(nx);\n float stMag = mix(ax, pow(ax, FOG_TILT_SHAPE), 0.35);\n float st = sign(nx) * stMag * uTiltScale;\n st = clamp(st, -FOG_TILT_MAX_X, FOG_TILT_MAX_X);\n vec2 dir=normalize(vec2(st,1.0));\n fuv+=uFogTime*uFogFallSpeed*dir;\n vec2 prp=vec2(-dir.y,dir.x);\n fuv+=prp*(0.08*sin(dot(uvc,prp)*0.08+uFogTime*0.9));\n float n=fbm2(fuv+vec2(fbm2(fuv+vec2(7.3,2.1)),fbm2(fuv+vec2(-3.7,5.9)))*0.6);\n n=pow(clamp(n,0.0,1.0),FOG_CONTRAST);\n float pixW = 1.0 / max(iResolution.y, 1.0);\n#ifdef GL_OES_standard_derivatives\n float wL = max(fwidth(L), pixW);\n#else\n float wL = pixW;\n#endif\n float m0=pow(smoothstep(FOG_BEAM_MIN - wL, FOG_BEAM_MAX + wL, L),FOG_MASK_GAMMA);\n float bm=1.0-pow(1.0-m0,FOG_EXPAND_SHAPE); bm=mix(bm*m0,bm,FOG_EDGE_MIX);\n float yP=1.0-smoothstep(HFOG_Y_RADIUS,HFOG_Y_RADIUS+HFOG_Y_SOFT,abs(yPix));\n float nxF=abs((frag.x-C.x)*invW),hE=1.0-smoothstep(HFOG_EDGE_START,HFOG_EDGE_END,nxF); hE=pow(clamp(hE,0.0,1.0),HFOG_EDGE_GAMMA);\n float hW=mix(1.0,hE,clamp(yP,0.0,1.0));\n float bBias=mix(1.0,1.0-sPix,FOG_BOTTOM_BIAS);\n float browserFogIntensity = uFogIntensity;\n browserFogIntensity *= 1.8;\n float radialFade = 1.0 - smoothstep(0.0, 0.7, length(uvc) / 120.0);\n float safariFog = n * browserFogIntensity * bBias * bm * hW * radialFade;\n fog = safariFog;\n#endif\n float LF=L+fog;\n float dith=(h21(frag)-0.5)*(DITHER_STRENGTH/255.0);\n float tone=g(LF+w);\n vec3 col=tone*uColor+dith;\n float alpha=clamp(g(L+w*0.6)+dith*0.6,0.0,1.0);\n float nxE=abs((frag.x-C.x)*invW),xF=pow(clamp(1.0-smoothstep(EDGE_X0,EDGE_X1,nxE),0.0,1.0),EDGE_X_GAMMA);\n float scene=LF+max(0.0,w)*0.5,hi=smoothstep(EDGE_LUMA_T0,EDGE_LUMA_T1,scene);\n float eM=mix(xF,1.0,hi);\n col*=eM; alpha*=eM;\n col*=uFade; alpha*=uFade;\n fc=vec4(col,alpha);\n}\n\nvoid main(){\n vec4 fc;\n mainImage(fc, gl_FragCoord.xy);\n gl_FragColor = fc;\n}\n`;\n\nexport const LaserFlow = ({\n className,\n style,\n wispDensity = 1,\n dpr,\n mouseSmoothTime = 0.0,\n mouseTiltStrength = 0.01,\n horizontalBeamOffset = 0.1,\n verticalBeamOffset = 0.0,\n flowSpeed = 0.35,\n verticalSizing = 2.0,\n horizontalSizing = 0.5,\n fogIntensity = 0.45,\n fogScale = 0.3,\n wispSpeed = 15.0,\n wispIntensity = 5.0,\n flowStrength = 0.25,\n decay = 1.1,\n falloffStart = 1.2,\n fogFallSpeed = 0.6,\n color = '#FF79C6'\n}) => {\n const mountRef = useRef(null);\n const rendererRef = useRef(null);\n const uniformsRef = useRef(null);\n const hasFadedRef = useRef(false);\n const rectRef = useRef(null);\n const baseDprRef = useRef(1);\n const currentDprRef = useRef(1);\n const lastSizeRef = useRef({ width: 0, height: 0, dpr: 0 });\n const fpsSamplesRef = useRef([]);\n const lastFpsCheckRef = useRef(performance.now());\n const emaDtRef = useRef(16.7);\n const pausedRef = useRef(false);\n const inViewRef = useRef(true);\n\n const hexToRGB = hex => {\n let c = hex.trim();\n if (c[0] === '#') c = c.slice(1);\n if (c.length === 3)\n c = c\n .split('')\n .map(x => x + x)\n .join('');\n const n = parseInt(c, 16) || 0xffffff;\n return { r: ((n >> 16) & 255) / 255, g: ((n >> 8) & 255) / 255, b: (n & 255) / 255 };\n };\n\n useEffect(() => {\n const mount = mountRef.current;\n const renderer = new THREE.WebGLRenderer({\n antialias: false,\n alpha: false,\n depth: false,\n stencil: false,\n powerPreference: 'high-performance',\n premultipliedAlpha: false,\n preserveDrawingBuffer: false,\n failIfMajorPerformanceCaveat: false,\n logarithmicDepthBuffer: false\n });\n rendererRef.current = renderer;\n\n baseDprRef.current = Math.min(dpr ?? (window.devicePixelRatio || 1), 2);\n currentDprRef.current = baseDprRef.current;\n\n renderer.setPixelRatio(currentDprRef.current);\n renderer.shadowMap.enabled = false;\n renderer.outputColorSpace = THREE.SRGBColorSpace;\n renderer.setClearColor(0x000000, 1);\n const canvas = renderer.domElement;\n canvas.style.width = '100%';\n canvas.style.height = '100%';\n canvas.style.display = 'block';\n mount.appendChild(canvas);\n\n const scene = new THREE.Scene();\n const camera = new THREE.OrthographicCamera(-1, 1, 1, -1, 0, 1);\n\n const geometry = new THREE.BufferGeometry();\n geometry.setAttribute('position', new THREE.BufferAttribute(new Float32Array([-1, -1, 0, 3, -1, 0, -1, 3, 0]), 3));\n\n const uniforms = {\n iTime: { value: 0 },\n iResolution: { value: new THREE.Vector3(1, 1, 1) },\n iMouse: { value: new THREE.Vector4(0, 0, 0, 0) },\n uWispDensity: { value: wispDensity },\n uTiltScale: { value: mouseTiltStrength },\n uFlowTime: { value: 0 },\n uFogTime: { value: 0 },\n uBeamXFrac: { value: horizontalBeamOffset },\n uBeamYFrac: { value: verticalBeamOffset },\n uFlowSpeed: { value: flowSpeed },\n uVLenFactor: { value: verticalSizing },\n uHLenFactor: { value: horizontalSizing },\n uFogIntensity: { value: fogIntensity },\n uFogScale: { value: fogScale },\n uWSpeed: { value: wispSpeed },\n uWIntensity: { value: wispIntensity },\n uFlowStrength: { value: flowStrength },\n uDecay: { value: decay },\n uFalloffStart: { value: falloffStart },\n uFogFallSpeed: { value: fogFallSpeed },\n uColor: { value: new THREE.Vector3(1, 1, 1) },\n uFade: { value: hasFadedRef.current ? 1 : 0 }\n };\n uniformsRef.current = uniforms;\n\n const material = new THREE.RawShaderMaterial({\n vertexShader: VERT,\n fragmentShader: FRAG,\n uniforms,\n transparent: false,\n depthTest: false,\n depthWrite: false,\n blending: THREE.NormalBlending\n });\n\n const mesh = new THREE.Mesh(geometry, material);\n mesh.frustumCulled = false;\n scene.add(mesh);\n\n const clock = new THREE.Clock();\n let prevTime = 0;\n let fade = hasFadedRef.current ? 1 : 0;\n\n const mouseTarget = new THREE.Vector2(0, 0);\n const mouseSmooth = new THREE.Vector2(0, 0);\n\n const setSizeNow = () => {\n const w = mount.clientWidth || 1;\n const h = mount.clientHeight || 1;\n const pr = currentDprRef.current;\n\n const last = lastSizeRef.current;\n const sizeChanged = Math.abs(w - last.width) > 0.5 || Math.abs(h - last.height) > 0.5;\n const dprChanged = Math.abs(pr - last.dpr) > 0.01;\n if (!sizeChanged && !dprChanged) {\n return;\n }\n\n lastSizeRef.current = { width: w, height: h, dpr: pr };\n renderer.setPixelRatio(pr);\n renderer.setSize(w, h, false);\n uniforms.iResolution.value.set(w * pr, h * pr, pr);\n rectRef.current = canvas.getBoundingClientRect();\n\n if (!pausedRef.current) {\n renderer.render(scene, camera);\n }\n };\n\n let resizeRaf = 0;\n const scheduleResize = () => {\n if (resizeRaf) cancelAnimationFrame(resizeRaf);\n resizeRaf = requestAnimationFrame(setSizeNow);\n };\n\n setSizeNow();\n const ro = new ResizeObserver(scheduleResize);\n ro.observe(mount);\n\n const io = new IntersectionObserver(\n entries => {\n inViewRef.current = entries[0]?.isIntersecting ?? true;\n },\n { root: null, threshold: 0 }\n );\n io.observe(mount);\n\n const onVis = () => {\n pausedRef.current = document.hidden;\n };\n document.addEventListener('visibilitychange', onVis, { passive: true });\n\n const updateMouse = (clientX, clientY) => {\n const rect = rectRef.current;\n if (!rect) return;\n const x = clientX - rect.left;\n const y = clientY - rect.top;\n const ratio = currentDprRef.current;\n const hb = rect.height * ratio;\n mouseTarget.set(x * ratio, hb - y * ratio);\n };\n const onMove = ev => updateMouse(ev.clientX, ev.clientY);\n const onLeave = () => mouseTarget.set(0, 0);\n canvas.addEventListener('pointermove', onMove, { passive: true });\n canvas.addEventListener('pointerdown', onMove, { passive: true });\n canvas.addEventListener('pointerenter', onMove, { passive: true });\n canvas.addEventListener('pointerleave', onLeave, { passive: true });\n\n const onCtxLost = e => {\n e.preventDefault();\n pausedRef.current = true;\n };\n const onCtxRestored = () => {\n pausedRef.current = false;\n scheduleResize();\n };\n canvas.addEventListener('webglcontextlost', onCtxLost, false);\n canvas.addEventListener('webglcontextrestored', onCtxRestored, false);\n\n let raf = 0;\n\n const clamp = (v, lo, hi) => Math.max(lo, Math.min(hi, v));\n const dprFloor = 0.6;\n const lowerThresh = 50;\n const upperThresh = 58;\n let lastDprChangeRef = 0;\n const dprChangeCooldown = 2000;\n\n const adjustDprIfNeeded = now => {\n const elapsed = now - lastFpsCheckRef.current;\n if (elapsed < 750) return;\n\n const samples = fpsSamplesRef.current;\n if (samples.length === 0) {\n lastFpsCheckRef.current = now;\n return;\n }\n const avgFps = samples.reduce((a, b) => a + b, 0) / samples.length;\n\n let next = currentDprRef.current;\n const base = baseDprRef.current;\n\n if (avgFps < lowerThresh) {\n next = clamp(currentDprRef.current * 0.85, dprFloor, base);\n } else if (avgFps > upperThresh && currentDprRef.current < base) {\n next = clamp(currentDprRef.current * 1.1, dprFloor, base);\n }\n\n if (Math.abs(next - currentDprRef.current) > 0.01 && now - lastDprChangeRef > dprChangeCooldown) {\n currentDprRef.current = next;\n lastDprChangeRef = now;\n setSizeNow();\n }\n\n fpsSamplesRef.current = [];\n lastFpsCheckRef.current = now;\n };\n\n const animate = () => {\n raf = requestAnimationFrame(animate);\n if (pausedRef.current || !inViewRef.current) return;\n\n const t = clock.getElapsedTime();\n const dt = Math.max(0, t - prevTime);\n prevTime = t;\n\n const dtMs = dt * 1000;\n emaDtRef.current = emaDtRef.current * 0.9 + dtMs * 0.1;\n const instFps = 1000 / Math.max(1, emaDtRef.current);\n fpsSamplesRef.current.push(instFps);\n\n uniforms.iTime.value = t;\n\n const cdt = Math.min(0.033, Math.max(0.001, dt));\n uniforms.uFlowTime.value += cdt;\n uniforms.uFogTime.value += cdt;\n\n if (!hasFadedRef.current) {\n const fadeDur = 1.0;\n fade = Math.min(1, fade + cdt / fadeDur);\n uniforms.uFade.value = fade;\n if (fade >= 1) hasFadedRef.current = true;\n }\n\n const tau = Math.max(1e-3, mouseSmoothTime);\n const alpha = 1 - Math.exp(-cdt / tau);\n mouseSmooth.lerp(mouseTarget, alpha);\n uniforms.iMouse.value.set(mouseSmooth.x, mouseSmooth.y, 0, 0);\n\n renderer.render(scene, camera);\n\n adjustDprIfNeeded(performance.now());\n };\n\n animate();\n\n return () => {\n cancelAnimationFrame(raf);\n ro.disconnect();\n io.disconnect();\n document.removeEventListener('visibilitychange', onVis);\n canvas.removeEventListener('pointermove', onMove);\n canvas.removeEventListener('pointerdown', onMove);\n canvas.removeEventListener('pointerenter', onMove);\n canvas.removeEventListener('pointerleave', onLeave);\n canvas.removeEventListener('webglcontextlost', onCtxLost);\n canvas.removeEventListener('webglcontextrestored', onCtxRestored);\n geometry.dispose();\n material.dispose();\n renderer.dispose();\n renderer.forceContextLoss();\n if (mount.contains(canvas)) mount.removeChild(canvas);\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [dpr]);\n\n useEffect(() => {\n const uniforms = uniformsRef.current;\n if (!uniforms) return;\n\n uniforms.uWispDensity.value = wispDensity;\n uniforms.uTiltScale.value = mouseTiltStrength;\n uniforms.uBeamXFrac.value = horizontalBeamOffset;\n uniforms.uBeamYFrac.value = verticalBeamOffset;\n uniforms.uFlowSpeed.value = flowSpeed;\n uniforms.uVLenFactor.value = verticalSizing;\n uniforms.uHLenFactor.value = horizontalSizing;\n uniforms.uFogIntensity.value = fogIntensity;\n uniforms.uFogScale.value = fogScale;\n uniforms.uWSpeed.value = wispSpeed;\n uniforms.uWIntensity.value = wispIntensity;\n uniforms.uFlowStrength.value = flowStrength;\n uniforms.uDecay.value = decay;\n uniforms.uFalloffStart.value = falloffStart;\n uniforms.uFogFallSpeed.value = fogFallSpeed;\n\n const { r, g, b } = hexToRGB(color || '#FFFFFF');\n uniforms.uColor.value.set(r, g, b);\n }, [\n wispDensity,\n mouseTiltStrength,\n horizontalBeamOffset,\n verticalBeamOffset,\n flowSpeed,\n verticalSizing,\n horizontalSizing,\n fogIntensity,\n fogScale,\n wispSpeed,\n wispIntensity,\n flowStrength,\n decay,\n falloffStart,\n fogFallSpeed,\n color\n ]);\n\n return
;\n};\n\nexport default LaserFlow;\n" + "content": "import { useEffect, useRef } from 'react';\nimport * as THREE from 'three';\n\nconst VERT = `\nprecision highp float;\nattribute vec3 position;\nvoid main(){\n gl_Position = vec4(position, 1.0);\n}\n`;\n\nconst FRAG = `\n#ifdef GL_ES\n#extension GL_OES_standard_derivatives : enable\n#endif\nprecision highp float;\nprecision mediump int;\n\nuniform float iTime;\nuniform vec3 iResolution;\nuniform vec4 iMouse;\nuniform float uWispDensity;\nuniform float uTiltScale;\nuniform float uFlowTime;\nuniform float uFogTime;\nuniform float uBeamXFrac;\nuniform float uBeamYFrac;\nuniform float uFlowSpeed;\nuniform float uVLenFactor;\nuniform float uHLenFactor;\nuniform float uFogIntensity;\nuniform float uFogScale;\nuniform float uWSpeed;\nuniform float uWIntensity;\nuniform float uFlowStrength;\nuniform float uDecay;\nuniform float uFalloffStart;\nuniform float uFogFallSpeed;\nuniform vec3 uColor;\nuniform float uFade;\n\n// Core beam/flare shaping and dynamics\n#define PI 3.14159265359\n#define TWO_PI 6.28318530718\n#define EPS 1e-6\n#define EDGE_SOFT (DT_LOCAL*4.0)\n#define DT_LOCAL 0.0038\n#define TAP_RADIUS 6\n#define R_H 150.0\n#define R_V 150.0\n#define FLARE_HEIGHT 16.0\n#define FLARE_AMOUNT 8.0\n#define FLARE_EXP 2.0\n#define TOP_FADE_START 0.1\n#define TOP_FADE_EXP 1.0\n#define FLOW_PERIOD 0.5\n#define FLOW_SHARPNESS 1.5\n\n// Wisps (animated micro-streaks) that travel along the beam\n#define W_BASE_X 1.5\n#define W_LAYER_GAP 0.25\n#define W_LANES 10\n#define W_SIDE_DECAY 0.5\n#define W_HALF 0.01\n#define W_AA 0.15\n#define W_CELL 20.0\n#define W_SEG_MIN 0.01\n#define W_SEG_MAX 0.55\n#define W_CURVE_AMOUNT 15.0\n#define W_CURVE_RANGE (FLARE_HEIGHT - 3.0)\n#define W_BOTTOM_EXP 10.0\n\n// Volumetric fog controls\n#define FOG_ON 1\n#define FOG_CONTRAST 1.2\n#define FOG_SPEED_U 0.1\n#define FOG_SPEED_V -0.1\n#define FOG_OCTAVES 5\n#define FOG_BOTTOM_BIAS 0.8\n#define FOG_TILT_TO_MOUSE 0.05\n#define FOG_TILT_DEADZONE 0.01\n#define FOG_TILT_MAX_X 0.35\n#define FOG_TILT_SHAPE 1.5\n#define FOG_BEAM_MIN 0.0\n#define FOG_BEAM_MAX 0.75\n#define FOG_MASK_GAMMA 0.5\n#define FOG_EXPAND_SHAPE 12.2\n#define FOG_EDGE_MIX 0.5\n\n// Horizontal vignette for the fog volume\n#define HFOG_EDGE_START 0.20\n#define HFOG_EDGE_END 0.98\n#define HFOG_EDGE_GAMMA 1.4\n#define HFOG_Y_RADIUS 25.0\n#define HFOG_Y_SOFT 60.0\n\n// Beam extents and edge masking\n#define EDGE_X0 0.22\n#define EDGE_X1 0.995\n#define EDGE_X_GAMMA 1.25\n#define EDGE_LUMA_T0 0.0\n#define EDGE_LUMA_T1 2.0\n#define DITHER_STRENGTH 1.0\n\n float g(float x){return x<=0.00031308?12.92*x:1.055*pow(x,1.0/2.4)-0.055;}\n float bs(vec2 p,vec2 q,float powr){\n float d=distance(p,q),f=powr*uFalloffStart,r=(f*f)/(d*d+EPS);\n return powr*min(1.0,r);\n }\n float bsa(vec2 p,vec2 q,float powr,vec2 s){\n vec2 d=p-q; float dd=(d.x*d.x)/(s.x*s.x)+(d.y*d.y)/(s.y*s.y),f=powr*uFalloffStart,r=(f*f)/(dd+EPS);\n return powr*min(1.0,r);\n }\n float tri01(float x){float f=fract(x);return 1.0-abs(f*2.0-1.0);}\n float tauWf(float t,float tmin,float tmax){float a=smoothstep(tmin,tmin+EDGE_SOFT,t),b=1.0-smoothstep(tmax-EDGE_SOFT,tmax,t);return max(0.0,a*b);} \n float h21(vec2 p){p=fract(p*vec2(123.34,456.21));p+=dot(p,p+34.123);return fract(p.x*p.y);}\n float vnoise(vec2 p){\n vec2 i=floor(p),f=fract(p);\n float a=h21(i),b=h21(i+vec2(1,0)),c=h21(i+vec2(0,1)),d=h21(i+vec2(1,1));\n vec2 u=f*f*(3.0-2.0*f);\n return mix(mix(a,b,u.x),mix(c,d,u.x),u.y);\n }\n float fbm2(vec2 p){\n float v=0.0,amp=0.6; mat2 m=mat2(0.86,0.5,-0.5,0.86);\n for(int i=0;i=lanes) break;\n float off=W_BASE_X+float(i)*W_LAYER_GAP,xc=sgn*(off*xS);\n float dx=abs(uv.x-xc),lat=1.0-smoothstep(W_HALF,W_HALF+W_AA,dx),amp=exp(-off*W_SIDE_DECAY);\n float seed=h21(vec2(off,sgn*17.0)),yf2=yf+seed*7.0,ci=floor(yf2),fy=fract(yf2);\n float seg=mix(W_SEG_MIN,W_SEG_MAX,h21(vec2(ci,off*2.3)));\n float spR=h21(vec2(ci,off+sgn*31.0)),seg1=rGate(fy,seg)*step(spR,sp);\n if(ep>0.0){float spR2=h21(vec2(ci*3.1+7.0,off*5.3+sgn*13.0)); float f2=fract(fy+0.5); seg1+=rGate(f2,seg*0.9)*step(spR2,ep);}\n sum+=amp*lat*seg1;\n }\n }\n float span=smoothstep(-3.0,0.0,y)*(1.0-smoothstep(R_V-6.0,R_V,y));\n return uWIntensity*sum*topF*bGain*span;\n}\n\nvoid mainImage(out vec4 fc,in vec2 frag){\n vec2 C=iResolution.xy*.5; float invW=1.0/max(C.x,1.0);\n vec2 sc=(512.0/iResolution.xy)*.4;\n vec2 uv=(frag-C)*sc,off=vec2(uBeamXFrac*iResolution.x*sc.x,uBeamYFrac*iResolution.y*sc.y);\n vec2 uvc = uv - off;\n float a=0.0,b=0.0;\n float basePhase=1.5*PI+uDecay*.5; float tauMin=basePhase-uDecay; float tauMax=basePhase;\n float cx=clamp(uvc.x/(R_H*uHLenFactor),-1.0,1.0),tH=clamp(TWO_PI-acos(cx),tauMin,tauMax);\n for(int k=-TAP_RADIUS;k<=TAP_RADIUS;++k){\n float tu=tH+float(k)*DT_LOCAL,wt=tauWf(tu,tauMin,tauMax); if(wt<=0.0) continue;\n float spd=max(abs(sin(tu)),0.02),u=clamp((basePhase-tu)/max(uDecay,EPS),0.0,1.0),env=pow(1.0-abs(u*2.0-1.0),0.8);\n vec2 p=vec2((R_H*uHLenFactor)*cos(tu),0.0);\n a+=wt*bs(uvc,p,env*spd);\n }\n float yPix=uvc.y,cy=clamp(-yPix/(R_V*uVLenFactor),-1.0,1.0),tV=clamp(TWO_PI-acos(cy),tauMin,tauMax);\n for(int k=-TAP_RADIUS;k<=TAP_RADIUS;++k){\n float tu=tV+float(k)*DT_LOCAL,wt=tauWf(tu,tauMin,tauMax); if(wt<=0.0) continue;\n float yb=(-R_V)*cos(tu),s=clamp(yb/R_V,0.0,1.0),spd=max(abs(sin(tu)),0.02);\n float env=pow(1.0-s,0.6)*spd;\n float cap=1.0-smoothstep(TOP_FADE_START,1.0,s); cap=pow(cap,TOP_FADE_EXP); env*=cap;\n float ph=s/max(FLOW_PERIOD,EPS)+uFlowTime*uFlowSpeed;\n float fl=pow(tri01(ph),FLOW_SHARPNESS);\n env*=mix(1.0-uFlowStrength,1.0,fl);\n float yp=(-R_V*uVLenFactor)*cos(tu),m=pow(smoothstep(FLARE_HEIGHT,0.0,yp),FLARE_EXP),wx=1.0+FLARE_AMOUNT*m;\n vec2 sig=vec2(wx,1.0),p=vec2(0.0,yp);\n float mask=step(0.0,yp);\n b+=wt*bsa(uvc,p,mask*env,sig);\n }\n float sPix=clamp(yPix/R_V,0.0,1.0),topA=pow(1.0-smoothstep(TOP_FADE_START,1.0,sPix),TOP_FADE_EXP);\n float L=a+b*topA;\n float w=vWisps(vec2(uvc.x,yPix),topA);\n float fog=0.0;\n#if FOG_ON\n vec2 fuv=uvc*uFogScale;\n float mAct=step(1.0,length(iMouse.xy)),nx=((iMouse.x-C.x)*invW)*mAct;\n float ax = abs(nx);\n float stMag = mix(ax, pow(ax, FOG_TILT_SHAPE), 0.35);\n float st = sign(nx) * stMag * uTiltScale;\n st = clamp(st, -FOG_TILT_MAX_X, FOG_TILT_MAX_X);\n vec2 dir=normalize(vec2(st,1.0));\n fuv+=uFogTime*uFogFallSpeed*dir;\n vec2 prp=vec2(-dir.y,dir.x);\n fuv+=prp*(0.08*sin(dot(uvc,prp)*0.08+uFogTime*0.9));\n float n=fbm2(fuv+vec2(fbm2(fuv+vec2(7.3,2.1)),fbm2(fuv+vec2(-3.7,5.9)))*0.6);\n n=pow(clamp(n,0.0,1.0),FOG_CONTRAST);\n float pixW = 1.0 / max(iResolution.y, 1.0);\n#ifdef GL_OES_standard_derivatives\n float wL = max(fwidth(L), pixW);\n#else\n float wL = pixW;\n#endif\n float m0=pow(smoothstep(FOG_BEAM_MIN - wL, FOG_BEAM_MAX + wL, L),FOG_MASK_GAMMA);\n float bm=1.0-pow(1.0-m0,FOG_EXPAND_SHAPE); bm=mix(bm*m0,bm,FOG_EDGE_MIX);\n float yP=1.0-smoothstep(HFOG_Y_RADIUS,HFOG_Y_RADIUS+HFOG_Y_SOFT,abs(yPix));\n float nxF=abs((frag.x-C.x)*invW),hE=1.0-smoothstep(HFOG_EDGE_START,HFOG_EDGE_END,nxF); hE=pow(clamp(hE,0.0,1.0),HFOG_EDGE_GAMMA);\n float hW=mix(1.0,hE,clamp(yP,0.0,1.0));\n float bBias=mix(1.0,1.0-sPix,FOG_BOTTOM_BIAS);\n float browserFogIntensity = uFogIntensity;\n browserFogIntensity *= 1.8;\n float radialFade = 1.0 - smoothstep(0.0, 0.7, length(uvc) / 120.0);\n float safariFog = n * browserFogIntensity * bBias * bm * hW * radialFade;\n fog = safariFog;\n#endif\n float LF=L+fog;\n float dith=(h21(frag)-0.5)*(DITHER_STRENGTH/255.0);\n float tone=g(LF+w);\n vec3 col=tone*uColor+dith;\n float alpha=clamp(g(L+w*0.6)+dith*0.6,0.0,1.0);\n float nxE=abs((frag.x-C.x)*invW),xF=pow(clamp(1.0-smoothstep(EDGE_X0,EDGE_X1,nxE),0.0,1.0),EDGE_X_GAMMA);\n float scene=LF+max(0.0,w)*0.5,hi=smoothstep(EDGE_LUMA_T0,EDGE_LUMA_T1,scene);\n float eM=mix(xF,1.0,hi);\n col*=eM; alpha*=eM;\n col*=uFade; alpha*=uFade;\n fc=vec4(col,alpha);\n}\n\nvoid main(){\n vec4 fc;\n mainImage(fc, gl_FragCoord.xy);\n gl_FragColor = fc;\n}\n`;\n\nexport const LaserFlow = ({\n className,\n style,\n wispDensity = 1,\n dpr,\n mouseSmoothTime = 0.0,\n mouseTiltStrength = 0.01,\n horizontalBeamOffset = 0.1,\n verticalBeamOffset = 0.0,\n flowSpeed = 0.35,\n verticalSizing = 2.0,\n horizontalSizing = 0.5,\n fogIntensity = 0.45,\n fogScale = 0.3,\n wispSpeed = 15.0,\n wispIntensity = 5.0,\n flowStrength = 0.25,\n decay = 1.1,\n falloffStart = 1.2,\n fogFallSpeed = 0.6,\n color = '#FF79C6'\n}) => {\n const mountRef = useRef(null);\n const rendererRef = useRef(null);\n const uniformsRef = useRef(null);\n const hasFadedRef = useRef(false);\n const rectRef = useRef(null);\n const baseDprRef = useRef(1);\n const currentDprRef = useRef(1);\n const lastSizeRef = useRef({ width: 0, height: 0, dpr: 0 });\n const fpsSamplesRef = useRef([]);\n const lastFpsCheckRef = useRef(performance.now());\n const emaDtRef = useRef(16.7);\n const pausedRef = useRef(false);\n const inViewRef = useRef(true);\n\n const hexToRGB = hex => {\n let c = hex.trim();\n if (c[0] === '#') c = c.slice(1);\n if (c.length === 3)\n c = c\n .split('')\n .map(x => x + x)\n .join('');\n const n = parseInt(c.slice(0, 6), 16) || 0xffffff;\n return { r: ((n >> 16) & 255) / 255, g: ((n >> 8) & 255) / 255, b: (n & 255) / 255 };\n };\n\n useEffect(() => {\n const mount = mountRef.current;\n const renderer = new THREE.WebGLRenderer({\n antialias: false,\n alpha: false,\n depth: false,\n stencil: false,\n powerPreference: 'high-performance',\n premultipliedAlpha: false,\n preserveDrawingBuffer: false,\n failIfMajorPerformanceCaveat: false,\n logarithmicDepthBuffer: false\n });\n rendererRef.current = renderer;\n\n baseDprRef.current = Math.min(dpr ?? (window.devicePixelRatio || 1), 2);\n currentDprRef.current = baseDprRef.current;\n\n renderer.setPixelRatio(currentDprRef.current);\n renderer.shadowMap.enabled = false;\n renderer.outputColorSpace = THREE.SRGBColorSpace;\n renderer.setClearColor(0x000000, 1);\n const canvas = renderer.domElement;\n canvas.style.width = '100%';\n canvas.style.height = '100%';\n canvas.style.display = 'block';\n mount.appendChild(canvas);\n\n const scene = new THREE.Scene();\n const camera = new THREE.OrthographicCamera(-1, 1, 1, -1, 0, 1);\n\n const geometry = new THREE.BufferGeometry();\n geometry.setAttribute('position', new THREE.BufferAttribute(new Float32Array([-1, -1, 0, 3, -1, 0, -1, 3, 0]), 3));\n\n const uniforms = {\n iTime: { value: 0 },\n iResolution: { value: new THREE.Vector3(1, 1, 1) },\n iMouse: { value: new THREE.Vector4(0, 0, 0, 0) },\n uWispDensity: { value: wispDensity },\n uTiltScale: { value: mouseTiltStrength },\n uFlowTime: { value: 0 },\n uFogTime: { value: 0 },\n uBeamXFrac: { value: horizontalBeamOffset },\n uBeamYFrac: { value: verticalBeamOffset },\n uFlowSpeed: { value: flowSpeed },\n uVLenFactor: { value: verticalSizing },\n uHLenFactor: { value: horizontalSizing },\n uFogIntensity: { value: fogIntensity },\n uFogScale: { value: fogScale },\n uWSpeed: { value: wispSpeed },\n uWIntensity: { value: wispIntensity },\n uFlowStrength: { value: flowStrength },\n uDecay: { value: decay },\n uFalloffStart: { value: falloffStart },\n uFogFallSpeed: { value: fogFallSpeed },\n uColor: { value: new THREE.Vector3(1, 1, 1) },\n uFade: { value: hasFadedRef.current ? 1 : 0 }\n };\n uniformsRef.current = uniforms;\n\n const material = new THREE.RawShaderMaterial({\n vertexShader: VERT,\n fragmentShader: FRAG,\n uniforms,\n transparent: false,\n depthTest: false,\n depthWrite: false,\n blending: THREE.NormalBlending\n });\n\n const mesh = new THREE.Mesh(geometry, material);\n mesh.frustumCulled = false;\n scene.add(mesh);\n\n const clock = new THREE.Clock();\n let prevTime = 0;\n let fade = hasFadedRef.current ? 1 : 0;\n\n const mouseTarget = new THREE.Vector2(0, 0);\n const mouseSmooth = new THREE.Vector2(0, 0);\n\n const setSizeNow = () => {\n const w = mount.clientWidth || 1;\n const h = mount.clientHeight || 1;\n const pr = currentDprRef.current;\n\n const last = lastSizeRef.current;\n const sizeChanged = Math.abs(w - last.width) > 0.5 || Math.abs(h - last.height) > 0.5;\n const dprChanged = Math.abs(pr - last.dpr) > 0.01;\n if (!sizeChanged && !dprChanged) {\n return;\n }\n\n lastSizeRef.current = { width: w, height: h, dpr: pr };\n renderer.setPixelRatio(pr);\n renderer.setSize(w, h, false);\n uniforms.iResolution.value.set(w * pr, h * pr, pr);\n rectRef.current = canvas.getBoundingClientRect();\n\n if (!pausedRef.current) {\n renderer.render(scene, camera);\n }\n };\n\n let resizeRaf = 0;\n const scheduleResize = () => {\n if (resizeRaf) cancelAnimationFrame(resizeRaf);\n resizeRaf = requestAnimationFrame(setSizeNow);\n };\n\n setSizeNow();\n const ro = new ResizeObserver(scheduleResize);\n ro.observe(mount);\n\n const io = new IntersectionObserver(\n entries => {\n inViewRef.current = entries[0]?.isIntersecting ?? true;\n },\n { root: null, threshold: 0 }\n );\n io.observe(mount);\n\n const onVis = () => {\n pausedRef.current = document.hidden;\n };\n document.addEventListener('visibilitychange', onVis, { passive: true });\n\n const updateMouse = (clientX, clientY) => {\n const rect = rectRef.current;\n if (!rect) return;\n const x = clientX - rect.left;\n const y = clientY - rect.top;\n const ratio = currentDprRef.current;\n const hb = rect.height * ratio;\n mouseTarget.set(x * ratio, hb - y * ratio);\n };\n const onMove = ev => updateMouse(ev.clientX, ev.clientY);\n const onLeave = () => mouseTarget.set(0, 0);\n canvas.addEventListener('pointermove', onMove, { passive: true });\n canvas.addEventListener('pointerdown', onMove, { passive: true });\n canvas.addEventListener('pointerenter', onMove, { passive: true });\n canvas.addEventListener('pointerleave', onLeave, { passive: true });\n\n const onCtxLost = e => {\n e.preventDefault();\n pausedRef.current = true;\n };\n const onCtxRestored = () => {\n pausedRef.current = false;\n scheduleResize();\n };\n canvas.addEventListener('webglcontextlost', onCtxLost, false);\n canvas.addEventListener('webglcontextrestored', onCtxRestored, false);\n\n let raf = 0;\n\n const clamp = (v, lo, hi) => Math.max(lo, Math.min(hi, v));\n const dprFloor = 0.6;\n const lowerThresh = 50;\n const upperThresh = 58;\n let lastDprChangeRef = 0;\n const dprChangeCooldown = 2000;\n\n const adjustDprIfNeeded = now => {\n const elapsed = now - lastFpsCheckRef.current;\n if (elapsed < 750) return;\n\n const samples = fpsSamplesRef.current;\n if (samples.length === 0) {\n lastFpsCheckRef.current = now;\n return;\n }\n const avgFps = samples.reduce((a, b) => a + b, 0) / samples.length;\n\n let next = currentDprRef.current;\n const base = baseDprRef.current;\n\n if (avgFps < lowerThresh) {\n next = clamp(currentDprRef.current * 0.85, dprFloor, base);\n } else if (avgFps > upperThresh && currentDprRef.current < base) {\n next = clamp(currentDprRef.current * 1.1, dprFloor, base);\n }\n\n if (Math.abs(next - currentDprRef.current) > 0.01 && now - lastDprChangeRef > dprChangeCooldown) {\n currentDprRef.current = next;\n lastDprChangeRef = now;\n setSizeNow();\n }\n\n fpsSamplesRef.current = [];\n lastFpsCheckRef.current = now;\n };\n\n const animate = () => {\n raf = requestAnimationFrame(animate);\n if (pausedRef.current || !inViewRef.current) return;\n\n const t = clock.getElapsedTime();\n const dt = Math.max(0, t - prevTime);\n prevTime = t;\n\n const dtMs = dt * 1000;\n emaDtRef.current = emaDtRef.current * 0.9 + dtMs * 0.1;\n const instFps = 1000 / Math.max(1, emaDtRef.current);\n fpsSamplesRef.current.push(instFps);\n\n uniforms.iTime.value = t;\n\n const cdt = Math.min(0.033, Math.max(0.001, dt));\n uniforms.uFlowTime.value += cdt;\n uniforms.uFogTime.value += cdt;\n\n if (!hasFadedRef.current) {\n const fadeDur = 1.0;\n fade = Math.min(1, fade + cdt / fadeDur);\n uniforms.uFade.value = fade;\n if (fade >= 1) hasFadedRef.current = true;\n }\n\n const tau = Math.max(1e-3, mouseSmoothTime);\n const alpha = 1 - Math.exp(-cdt / tau);\n mouseSmooth.lerp(mouseTarget, alpha);\n uniforms.iMouse.value.set(mouseSmooth.x, mouseSmooth.y, 0, 0);\n\n renderer.render(scene, camera);\n\n adjustDprIfNeeded(performance.now());\n };\n\n animate();\n\n return () => {\n cancelAnimationFrame(raf);\n ro.disconnect();\n io.disconnect();\n document.removeEventListener('visibilitychange', onVis);\n canvas.removeEventListener('pointermove', onMove);\n canvas.removeEventListener('pointerdown', onMove);\n canvas.removeEventListener('pointerenter', onMove);\n canvas.removeEventListener('pointerleave', onLeave);\n canvas.removeEventListener('webglcontextlost', onCtxLost);\n canvas.removeEventListener('webglcontextrestored', onCtxRestored);\n geometry.dispose();\n material.dispose();\n renderer.dispose();\n renderer.forceContextLoss();\n if (mount.contains(canvas)) mount.removeChild(canvas);\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [dpr]);\n\n useEffect(() => {\n const uniforms = uniformsRef.current;\n if (!uniforms) return;\n\n uniforms.uWispDensity.value = wispDensity;\n uniforms.uTiltScale.value = mouseTiltStrength;\n uniforms.uBeamXFrac.value = horizontalBeamOffset;\n uniforms.uBeamYFrac.value = verticalBeamOffset;\n uniforms.uFlowSpeed.value = flowSpeed;\n uniforms.uVLenFactor.value = verticalSizing;\n uniforms.uHLenFactor.value = horizontalSizing;\n uniforms.uFogIntensity.value = fogIntensity;\n uniforms.uFogScale.value = fogScale;\n uniforms.uWSpeed.value = wispSpeed;\n uniforms.uWIntensity.value = wispIntensity;\n uniforms.uFlowStrength.value = flowStrength;\n uniforms.uDecay.value = decay;\n uniforms.uFalloffStart.value = falloffStart;\n uniforms.uFogFallSpeed.value = fogFallSpeed;\n\n const { r, g, b } = hexToRGB(color || '#FFFFFF');\n uniforms.uColor.value.set(r, g, b);\n }, [\n wispDensity,\n mouseTiltStrength,\n horizontalBeamOffset,\n verticalBeamOffset,\n flowSpeed,\n verticalSizing,\n horizontalSizing,\n fogIntensity,\n fogScale,\n wispSpeed,\n wispIntensity,\n flowStrength,\n decay,\n falloffStart,\n fogFallSpeed,\n color\n ]);\n\n return
;\n};\n\nexport default LaserFlow;\n" } ], "registryDependencies": [], diff --git a/public/r/LaserFlow-TS-CSS.json b/public/r/LaserFlow-TS-CSS.json index f2898c6f4..db45d1339 100644 --- a/public/r/LaserFlow-TS-CSS.json +++ b/public/r/LaserFlow-TS-CSS.json @@ -13,7 +13,7 @@ { "type": "registry:component", "path": "LaserFlow/LaserFlow.tsx", - "content": "import React, { useEffect, useRef } from 'react';\nimport * as THREE from 'three';\nimport './LaserFlow.css';\n\ntype Props = {\n className?: string;\n style?: React.CSSProperties;\n wispDensity?: number;\n dpr?: number;\n mouseSmoothTime?: number;\n mouseTiltStrength?: number;\n horizontalBeamOffset?: number;\n verticalBeamOffset?: number;\n flowSpeed?: number;\n verticalSizing?: number;\n horizontalSizing?: number;\n fogIntensity?: number;\n fogScale?: number;\n wispSpeed?: number;\n wispIntensity?: number;\n flowStrength?: number;\n decay?: number;\n falloffStart?: number;\n fogFallSpeed?: number;\n color?: string;\n};\n\nconst VERT = `\nprecision highp float;\nattribute vec3 position;\nvoid main(){\n gl_Position = vec4(position, 1.0);\n}\n`;\n\nconst FRAG = `\n#ifdef GL_ES\n#extension GL_OES_standard_derivatives : enable\n#endif\nprecision highp float;\nprecision mediump int;\n\nuniform float iTime;\nuniform vec3 iResolution;\nuniform vec4 iMouse;\nuniform float uWispDensity;\nuniform float uTiltScale;\nuniform float uFlowTime;\nuniform float uFogTime;\nuniform float uBeamXFrac;\nuniform float uBeamYFrac;\nuniform float uFlowSpeed;\nuniform float uVLenFactor;\nuniform float uHLenFactor;\nuniform float uFogIntensity;\nuniform float uFogScale;\nuniform float uWSpeed;\nuniform float uWIntensity;\nuniform float uFlowStrength;\nuniform float uDecay;\nuniform float uFalloffStart;\nuniform float uFogFallSpeed;\nuniform vec3 uColor;\nuniform float uFade;\n\n// Core beam/flare shaping and dynamics\n#define PI 3.14159265359\n#define TWO_PI 6.28318530718\n#define EPS 1e-6\n#define EDGE_SOFT (DT_LOCAL*4.0)\n#define DT_LOCAL 0.0038\n#define TAP_RADIUS 6\n#define R_H 150.0\n#define R_V 150.0\n#define FLARE_HEIGHT 16.0\n#define FLARE_AMOUNT 8.0\n#define FLARE_EXP 2.0\n#define TOP_FADE_START 0.1\n#define TOP_FADE_EXP 1.0\n#define FLOW_PERIOD 0.5\n#define FLOW_SHARPNESS 1.5\n\n// Wisps (animated micro-streaks) that travel along the beam\n#define W_BASE_X 1.5\n#define W_LAYER_GAP 0.25\n#define W_LANES 10\n#define W_SIDE_DECAY 0.5\n#define W_HALF 0.01\n#define W_AA 0.15\n#define W_CELL 20.0\n#define W_SEG_MIN 0.01\n#define W_SEG_MAX 0.55\n#define W_CURVE_AMOUNT 15.0\n#define W_CURVE_RANGE (FLARE_HEIGHT - 3.0)\n#define W_BOTTOM_EXP 10.0\n\n// Volumetric fog controls\n#define FOG_ON 1\n#define FOG_CONTRAST 1.2\n#define FOG_SPEED_U 0.1\n#define FOG_SPEED_V -0.1\n#define FOG_OCTAVES 5\n#define FOG_BOTTOM_BIAS 0.8\n#define FOG_TILT_TO_MOUSE 0.05\n#define FOG_TILT_DEADZONE 0.01\n#define FOG_TILT_MAX_X 0.35\n#define FOG_TILT_SHAPE 1.5\n#define FOG_BEAM_MIN 0.0\n#define FOG_BEAM_MAX 0.75\n#define FOG_MASK_GAMMA 0.5\n#define FOG_EXPAND_SHAPE 12.2\n#define FOG_EDGE_MIX 0.5\n\n// Horizontal vignette for the fog volume\n#define HFOG_EDGE_START 0.20\n#define HFOG_EDGE_END 0.98\n#define HFOG_EDGE_GAMMA 1.4\n#define HFOG_Y_RADIUS 25.0\n#define HFOG_Y_SOFT 60.0\n\n// Beam extents and edge masking\n#define EDGE_X0 0.22\n#define EDGE_X1 0.995\n#define EDGE_X_GAMMA 1.25\n#define EDGE_LUMA_T0 0.0\n#define EDGE_LUMA_T1 2.0\n#define DITHER_STRENGTH 1.0\n\n float g(float x){return x<=0.00031308?12.92*x:1.055*pow(x,1.0/2.4)-0.055;}\n float bs(vec2 p,vec2 q,float powr){\n float d=distance(p,q),f=powr*uFalloffStart,r=(f*f)/(d*d+EPS);\n return powr*min(1.0,r);\n }\n float bsa(vec2 p,vec2 q,float powr,vec2 s){\n vec2 d=p-q; float dd=(d.x*d.x)/(s.x*s.x)+(d.y*d.y)/(s.y*s.y),f=powr*uFalloffStart,r=(f*f)/(dd+EPS);\n return powr*min(1.0,r);\n }\n float tri01(float x){float f=fract(x);return 1.0-abs(f*2.0-1.0);}\n float tauWf(float t,float tmin,float tmax){float a=smoothstep(tmin,tmin+EDGE_SOFT,t),b=1.0-smoothstep(tmax-EDGE_SOFT,tmax,t);return max(0.0,a*b);} \n float h21(vec2 p){p=fract(p*vec2(123.34,456.21));p+=dot(p,p+34.123);return fract(p.x*p.y);}\n float vnoise(vec2 p){\n vec2 i=floor(p),f=fract(p);\n float a=h21(i),b=h21(i+vec2(1,0)),c=h21(i+vec2(0,1)),d=h21(i+vec2(1,1));\n vec2 u=f*f*(3.0-2.0*f);\n return mix(mix(a,b,u.x),mix(c,d,u.x),u.y);\n }\n float fbm2(vec2 p){\n float v=0.0,amp=0.6; mat2 m=mat2(0.86,0.5,-0.5,0.86);\n for(int i=0;i=lanes) break;\n float off=W_BASE_X+float(i)*W_LAYER_GAP,xc=sgn*(off*xS);\n float dx=abs(uv.x-xc),lat=1.0-smoothstep(W_HALF,W_HALF+W_AA,dx),amp=exp(-off*W_SIDE_DECAY);\n float seed=h21(vec2(off,sgn*17.0)),yf2=yf+seed*7.0,ci=floor(yf2),fy=fract(yf2);\n float seg=mix(W_SEG_MIN,W_SEG_MAX,h21(vec2(ci,off*2.3)));\n float spR=h21(vec2(ci,off+sgn*31.0)),seg1=rGate(fy,seg)*step(spR,sp);\n if(ep>0.0){float spR2=h21(vec2(ci*3.1+7.0,off*5.3+sgn*13.0)); float f2=fract(fy+0.5); seg1+=rGate(f2,seg*0.9)*step(spR2,ep);}\n sum+=amp*lat*seg1;\n }\n }\n float span=smoothstep(-3.0,0.0,y)*(1.0-smoothstep(R_V-6.0,R_V,y));\n return uWIntensity*sum*topF*bGain*span;\n}\n\nvoid mainImage(out vec4 fc,in vec2 frag){\n vec2 C=iResolution.xy*.5; float invW=1.0/max(C.x,1.0);\n vec2 sc=(512.0/iResolution.xy)*.4;\n vec2 uv=(frag-C)*sc,off=vec2(uBeamXFrac*iResolution.x*sc.x,uBeamYFrac*iResolution.y*sc.y);\n vec2 uvc = uv - off;\n float a=0.0,b=0.0;\n float basePhase=1.5*PI+uDecay*.5; float tauMin=basePhase-uDecay; float tauMax=basePhase;\n float cx=clamp(uvc.x/(R_H*uHLenFactor),-1.0,1.0),tH=clamp(TWO_PI-acos(cx),tauMin,tauMax);\n for(int k=-TAP_RADIUS;k<=TAP_RADIUS;++k){\n float tu=tH+float(k)*DT_LOCAL,wt=tauWf(tu,tauMin,tauMax); if(wt<=0.0) continue;\n float spd=max(abs(sin(tu)),0.02),u=clamp((basePhase-tu)/max(uDecay,EPS),0.0,1.0),env=pow(1.0-abs(u*2.0-1.0),0.8);\n vec2 p=vec2((R_H*uHLenFactor)*cos(tu),0.0);\n a+=wt*bs(uvc,p,env*spd);\n }\n float yPix=uvc.y,cy=clamp(-yPix/(R_V*uVLenFactor),-1.0,1.0),tV=clamp(TWO_PI-acos(cy),tauMin,tauMax);\n for(int k=-TAP_RADIUS;k<=TAP_RADIUS;++k){\n float tu=tV+float(k)*DT_LOCAL,wt=tauWf(tu,tauMin,tauMax); if(wt<=0.0) continue;\n float yb=(-R_V)*cos(tu),s=clamp(yb/R_V,0.0,1.0),spd=max(abs(sin(tu)),0.02);\n float env=pow(1.0-s,0.6)*spd;\n float cap=1.0-smoothstep(TOP_FADE_START,1.0,s); cap=pow(cap,TOP_FADE_EXP); env*=cap;\n float ph=s/max(FLOW_PERIOD,EPS)+uFlowTime*uFlowSpeed;\n float fl=pow(tri01(ph),FLOW_SHARPNESS);\n env*=mix(1.0-uFlowStrength,1.0,fl);\n float yp=(-R_V*uVLenFactor)*cos(tu),m=pow(smoothstep(FLARE_HEIGHT,0.0,yp),FLARE_EXP),wx=1.0+FLARE_AMOUNT*m;\n vec2 sig=vec2(wx,1.0),p=vec2(0.0,yp);\n float mask=step(0.0,yp);\n b+=wt*bsa(uvc,p,mask*env,sig);\n }\n float sPix=clamp(yPix/R_V,0.0,1.0),topA=pow(1.0-smoothstep(TOP_FADE_START,1.0,sPix),TOP_FADE_EXP);\n float L=a+b*topA;\n float w=vWisps(vec2(uvc.x,yPix),topA);\n float fog=0.0;\n#if FOG_ON\n vec2 fuv=uvc*uFogScale;\n float mAct=step(1.0,length(iMouse.xy)),nx=((iMouse.x-C.x)*invW)*mAct;\n float ax = abs(nx);\n float stMag = mix(ax, pow(ax, FOG_TILT_SHAPE), 0.35);\n float st = sign(nx) * stMag * uTiltScale;\n st = clamp(st, -FOG_TILT_MAX_X, FOG_TILT_MAX_X);\n vec2 dir=normalize(vec2(st,1.0));\n fuv+=uFogTime*uFogFallSpeed*dir;\n vec2 prp=vec2(-dir.y,dir.x);\n fuv+=prp*(0.08*sin(dot(uvc,prp)*0.08+uFogTime*0.9));\n float n=fbm2(fuv+vec2(fbm2(fuv+vec2(7.3,2.1)),fbm2(fuv+vec2(-3.7,5.9)))*0.6);\n n=pow(clamp(n,0.0,1.0),FOG_CONTRAST);\n float pixW = 1.0 / max(iResolution.y, 1.0);\n#ifdef GL_OES_standard_derivatives\n float wL = max(fwidth(L), pixW);\n#else\n float wL = pixW;\n#endif\n float m0=pow(smoothstep(FOG_BEAM_MIN - wL, FOG_BEAM_MAX + wL, L),FOG_MASK_GAMMA);\n float bm=1.0-pow(1.0-m0,FOG_EXPAND_SHAPE); bm=mix(bm*m0,bm,FOG_EDGE_MIX);\n float yP=1.0-smoothstep(HFOG_Y_RADIUS,HFOG_Y_RADIUS+HFOG_Y_SOFT,abs(yPix));\n float nxF=abs((frag.x-C.x)*invW),hE=1.0-smoothstep(HFOG_EDGE_START,HFOG_EDGE_END,nxF); hE=pow(clamp(hE,0.0,1.0),HFOG_EDGE_GAMMA);\n float hW=mix(1.0,hE,clamp(yP,0.0,1.0));\n float bBias=mix(1.0,1.0-sPix,FOG_BOTTOM_BIAS);\n float browserFogIntensity = uFogIntensity;\n browserFogIntensity *= 1.8;\n float radialFade = 1.0 - smoothstep(0.0, 0.7, length(uvc) / 120.0);\n float safariFog = n * browserFogIntensity * bBias * bm * hW * radialFade;\n fog = safariFog;\n#endif\n float LF=L+fog;\n float dith=(h21(frag)-0.5)*(DITHER_STRENGTH/255.0);\n float tone=g(LF+w);\n vec3 col=tone*uColor+dith;\n float alpha=clamp(g(L+w*0.6)+dith*0.6,0.0,1.0);\n float nxE=abs((frag.x-C.x)*invW),xF=pow(clamp(1.0-smoothstep(EDGE_X0,EDGE_X1,nxE),0.0,1.0),EDGE_X_GAMMA);\n float scene=LF+max(0.0,w)*0.5,hi=smoothstep(EDGE_LUMA_T0,EDGE_LUMA_T1,scene);\n float eM=mix(xF,1.0,hi);\n col*=eM; alpha*=eM;\n col*=uFade; alpha*=uFade;\n fc=vec4(col,alpha);\n}\n\nvoid main(){\n vec4 fc;\n mainImage(fc, gl_FragCoord.xy);\n gl_FragColor = fc;\n}\n`;\n\nexport const LaserFlow: React.FC = ({\n className,\n style,\n wispDensity = 1,\n dpr,\n mouseSmoothTime = 0.0,\n mouseTiltStrength = 0.01,\n horizontalBeamOffset = 0.1,\n verticalBeamOffset = 0.0,\n flowSpeed = 0.35,\n verticalSizing = 2.0,\n horizontalSizing = 0.5,\n fogIntensity = 0.45,\n fogScale = 0.3,\n wispSpeed = 15.0,\n wispIntensity = 5.0,\n flowStrength = 0.25,\n decay = 1.1,\n falloffStart = 1.2,\n fogFallSpeed = 0.6,\n color = '#FF79C6'\n}) => {\n const mountRef = useRef(null);\n const rendererRef = useRef(null);\n const uniformsRef = useRef(null);\n const hasFadedRef = useRef(false);\n const rectRef = useRef(null);\n const baseDprRef = useRef(1);\n const currentDprRef = useRef(1);\n const lastSizeRef = useRef({ width: 0, height: 0, dpr: 0 });\n const fpsSamplesRef = useRef([]);\n const lastFpsCheckRef = useRef(performance.now());\n const emaDtRef = useRef(16.7); // ms\n const pausedRef = useRef(false);\n const inViewRef = useRef(true);\n\n const hexToRGB = (hex: string) => {\n let c = hex.trim();\n if (c[0] === '#') c = c.slice(1);\n if (c.length === 3)\n c = c\n .split('')\n .map(x => x + x)\n .join('');\n const n = parseInt(c, 16) || 0xffffff;\n return { r: ((n >> 16) & 255) / 255, g: ((n >> 8) & 255) / 255, b: (n & 255) / 255 };\n };\n\n useEffect(() => {\n const mount = mountRef.current!;\n const renderer = new THREE.WebGLRenderer({\n antialias: false,\n alpha: false,\n depth: false,\n stencil: false,\n powerPreference: 'high-performance',\n premultipliedAlpha: false,\n preserveDrawingBuffer: false,\n failIfMajorPerformanceCaveat: false,\n logarithmicDepthBuffer: false\n });\n rendererRef.current = renderer;\n\n baseDprRef.current = Math.min(dpr ?? (window.devicePixelRatio || 1), 2);\n currentDprRef.current = baseDprRef.current;\n\n renderer.setPixelRatio(currentDprRef.current);\n renderer.shadowMap.enabled = false;\n renderer.outputColorSpace = THREE.SRGBColorSpace;\n renderer.setClearColor(0x000000, 1);\n const canvas = renderer.domElement;\n canvas.style.width = '100%';\n canvas.style.height = '100%';\n canvas.style.display = 'block';\n mount.appendChild(canvas);\n\n const scene = new THREE.Scene();\n const camera = new THREE.OrthographicCamera(-1, 1, 1, -1, 0, 1);\n\n const geometry = new THREE.BufferGeometry();\n geometry.setAttribute('position', new THREE.BufferAttribute(new Float32Array([-1, -1, 0, 3, -1, 0, -1, 3, 0]), 3));\n\n const uniforms = {\n iTime: { value: 0 },\n iResolution: { value: new THREE.Vector3(1, 1, 1) },\n iMouse: { value: new THREE.Vector4(0, 0, 0, 0) },\n uWispDensity: { value: wispDensity },\n uTiltScale: { value: mouseTiltStrength },\n uFlowTime: { value: 0 },\n uFogTime: { value: 0 },\n uBeamXFrac: { value: horizontalBeamOffset },\n uBeamYFrac: { value: verticalBeamOffset },\n uFlowSpeed: { value: flowSpeed },\n uVLenFactor: { value: verticalSizing },\n uHLenFactor: { value: horizontalSizing },\n uFogIntensity: { value: fogIntensity },\n uFogScale: { value: fogScale },\n uWSpeed: { value: wispSpeed },\n uWIntensity: { value: wispIntensity },\n uFlowStrength: { value: flowStrength },\n uDecay: { value: decay },\n uFalloffStart: { value: falloffStart },\n uFogFallSpeed: { value: fogFallSpeed },\n uColor: { value: new THREE.Vector3(1, 1, 1) },\n uFade: { value: hasFadedRef.current ? 1 : 0 }\n };\n uniformsRef.current = uniforms;\n\n const material = new THREE.RawShaderMaterial({\n vertexShader: VERT,\n fragmentShader: FRAG,\n uniforms,\n transparent: false,\n depthTest: false,\n depthWrite: false,\n blending: THREE.NormalBlending\n });\n\n const mesh = new THREE.Mesh(geometry, material);\n mesh.frustumCulled = false;\n scene.add(mesh);\n\n const clock = new THREE.Clock();\n let prevTime = 0;\n let fade = hasFadedRef.current ? 1 : 0;\n\n const mouseTarget = new THREE.Vector2(0, 0);\n const mouseSmooth = new THREE.Vector2(0, 0);\n\n const setSizeNow = () => {\n const w = mount.clientWidth || 1;\n const h = mount.clientHeight || 1;\n const pr = currentDprRef.current;\n\n const last = lastSizeRef.current;\n const sizeChanged = Math.abs(w - last.width) > 0.5 || Math.abs(h - last.height) > 0.5;\n const dprChanged = Math.abs(pr - last.dpr) > 0.01;\n if (!sizeChanged && !dprChanged) {\n return;\n }\n\n lastSizeRef.current = { width: w, height: h, dpr: pr };\n renderer.setPixelRatio(pr);\n renderer.setSize(w, h, false);\n uniforms.iResolution.value.set(w * pr, h * pr, pr);\n rectRef.current = canvas.getBoundingClientRect();\n\n if (!pausedRef.current) {\n renderer.render(scene, camera);\n }\n };\n\n let resizeRaf = 0;\n const scheduleResize = () => {\n if (resizeRaf) cancelAnimationFrame(resizeRaf);\n resizeRaf = requestAnimationFrame(setSizeNow);\n };\n\n setSizeNow();\n const ro = new ResizeObserver(scheduleResize);\n ro.observe(mount);\n\n const io = new IntersectionObserver(\n entries => {\n inViewRef.current = entries[0]?.isIntersecting ?? true;\n },\n { root: null, threshold: 0 }\n );\n io.observe(mount);\n\n const onVis = () => {\n pausedRef.current = document.hidden;\n };\n document.addEventListener('visibilitychange', onVis, { passive: true });\n\n const updateMouse = (clientX: number, clientY: number) => {\n const rect = rectRef.current;\n if (!rect) return;\n const x = clientX - rect.left;\n const y = clientY - rect.top;\n const ratio = currentDprRef.current;\n const hb = rect.height * ratio;\n mouseTarget.set(x * ratio, hb - y * ratio);\n };\n const onMove = (ev: PointerEvent | MouseEvent) => updateMouse(ev.clientX, ev.clientY);\n const onLeave = () => mouseTarget.set(0, 0);\n canvas.addEventListener('pointermove', onMove as any, { passive: true });\n canvas.addEventListener('pointerdown', onMove as any, { passive: true });\n canvas.addEventListener('pointerenter', onMove as any, { passive: true });\n canvas.addEventListener('pointerleave', onLeave as any, { passive: true });\n\n const onCtxLost = (e: Event) => {\n e.preventDefault();\n pausedRef.current = true;\n };\n const onCtxRestored = () => {\n pausedRef.current = false;\n scheduleResize();\n };\n canvas.addEventListener('webglcontextlost', onCtxLost, false);\n canvas.addEventListener('webglcontextrestored', onCtxRestored, false);\n\n let raf = 0;\n\n const clamp = (v: number, lo: number, hi: number) => Math.max(lo, Math.min(hi, v));\n const dprFloor = 0.6;\n const lowerThresh = 50;\n const upperThresh = 58;\n let lastDprChangeRef = 0;\n const dprChangeCooldown = 2000;\n\n const adjustDprIfNeeded = (now: number) => {\n const elapsed = now - lastFpsCheckRef.current;\n if (elapsed < 750) return;\n\n const samples = fpsSamplesRef.current;\n if (samples.length === 0) {\n lastFpsCheckRef.current = now;\n return;\n }\n const avgFps = samples.reduce((a, b) => a + b, 0) / samples.length;\n\n let next = currentDprRef.current;\n const base = baseDprRef.current;\n\n if (avgFps < lowerThresh) {\n next = clamp(currentDprRef.current * 0.85, dprFloor, base);\n } else if (avgFps > upperThresh && currentDprRef.current < base) {\n next = clamp(currentDprRef.current * 1.1, dprFloor, base);\n }\n\n if (Math.abs(next - currentDprRef.current) > 0.01 && now - lastDprChangeRef > dprChangeCooldown) {\n currentDprRef.current = next;\n lastDprChangeRef = now;\n setSizeNow();\n }\n\n fpsSamplesRef.current = [];\n lastFpsCheckRef.current = now;\n };\n\n const animate = () => {\n raf = requestAnimationFrame(animate);\n if (pausedRef.current || !inViewRef.current) return;\n\n const t = clock.getElapsedTime();\n const dt = Math.max(0, t - prevTime);\n prevTime = t;\n\n const dtMs = dt * 1000;\n emaDtRef.current = emaDtRef.current * 0.9 + dtMs * 0.1;\n const instFps = 1000 / Math.max(1, emaDtRef.current);\n fpsSamplesRef.current.push(instFps);\n\n uniforms.iTime.value = t;\n\n const cdt = Math.min(0.033, Math.max(0.001, dt));\n (uniforms.uFlowTime.value as number) += cdt;\n (uniforms.uFogTime.value as number) += cdt;\n\n if (!hasFadedRef.current) {\n const fadeDur = 1.0;\n fade = Math.min(1, fade + cdt / fadeDur);\n uniforms.uFade.value = fade;\n if (fade >= 1) hasFadedRef.current = true;\n }\n\n const tau = Math.max(1e-3, mouseSmoothTime);\n const alpha = 1 - Math.exp(-cdt / tau);\n mouseSmooth.lerp(mouseTarget, alpha);\n uniforms.iMouse.value.set(mouseSmooth.x, mouseSmooth.y, 0, 0);\n\n renderer.render(scene, camera);\n\n adjustDprIfNeeded(performance.now());\n };\n\n animate();\n\n return () => {\n cancelAnimationFrame(raf);\n ro.disconnect();\n io.disconnect();\n document.removeEventListener('visibilitychange', onVis);\n canvas.removeEventListener('pointermove', onMove as any);\n canvas.removeEventListener('pointerdown', onMove as any);\n canvas.removeEventListener('pointerenter', onMove as any);\n canvas.removeEventListener('pointerleave', onLeave as any);\n canvas.removeEventListener('webglcontextlost', onCtxLost);\n canvas.removeEventListener('webglcontextrestored', onCtxRestored);\n geometry.dispose();\n material.dispose();\n renderer.dispose();\n renderer.forceContextLoss();\n if (mount.contains(canvas)) mount.removeChild(canvas);\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [dpr]);\n\n useEffect(() => {\n const uniforms = uniformsRef.current;\n if (!uniforms) return;\n\n uniforms.uWispDensity.value = wispDensity;\n uniforms.uTiltScale.value = mouseTiltStrength;\n uniforms.uBeamXFrac.value = horizontalBeamOffset;\n uniforms.uBeamYFrac.value = verticalBeamOffset;\n uniforms.uFlowSpeed.value = flowSpeed;\n uniforms.uVLenFactor.value = verticalSizing;\n uniforms.uHLenFactor.value = horizontalSizing;\n uniforms.uFogIntensity.value = fogIntensity;\n uniforms.uFogScale.value = fogScale;\n uniforms.uWSpeed.value = wispSpeed;\n uniforms.uWIntensity.value = wispIntensity;\n uniforms.uFlowStrength.value = flowStrength;\n uniforms.uDecay.value = decay;\n uniforms.uFalloffStart.value = falloffStart;\n uniforms.uFogFallSpeed.value = fogFallSpeed;\n\n const { r, g, b } = hexToRGB(color || '#FFFFFF');\n uniforms.uColor.value.set(r, g, b);\n }, [\n wispDensity,\n mouseTiltStrength,\n horizontalBeamOffset,\n verticalBeamOffset,\n flowSpeed,\n verticalSizing,\n horizontalSizing,\n fogIntensity,\n fogScale,\n wispSpeed,\n wispIntensity,\n flowStrength,\n decay,\n falloffStart,\n fogFallSpeed,\n color\n ]);\n\n return
;\n};\n\nexport default LaserFlow;\n" + "content": "import React, { useEffect, useRef } from 'react';\nimport * as THREE from 'three';\nimport './LaserFlow.css';\n\ntype Props = {\n className?: string;\n style?: React.CSSProperties;\n wispDensity?: number;\n dpr?: number;\n mouseSmoothTime?: number;\n mouseTiltStrength?: number;\n horizontalBeamOffset?: number;\n verticalBeamOffset?: number;\n flowSpeed?: number;\n verticalSizing?: number;\n horizontalSizing?: number;\n fogIntensity?: number;\n fogScale?: number;\n wispSpeed?: number;\n wispIntensity?: number;\n flowStrength?: number;\n decay?: number;\n falloffStart?: number;\n fogFallSpeed?: number;\n color?: string;\n};\n\nconst VERT = `\nprecision highp float;\nattribute vec3 position;\nvoid main(){\n gl_Position = vec4(position, 1.0);\n}\n`;\n\nconst FRAG = `\n#ifdef GL_ES\n#extension GL_OES_standard_derivatives : enable\n#endif\nprecision highp float;\nprecision mediump int;\n\nuniform float iTime;\nuniform vec3 iResolution;\nuniform vec4 iMouse;\nuniform float uWispDensity;\nuniform float uTiltScale;\nuniform float uFlowTime;\nuniform float uFogTime;\nuniform float uBeamXFrac;\nuniform float uBeamYFrac;\nuniform float uFlowSpeed;\nuniform float uVLenFactor;\nuniform float uHLenFactor;\nuniform float uFogIntensity;\nuniform float uFogScale;\nuniform float uWSpeed;\nuniform float uWIntensity;\nuniform float uFlowStrength;\nuniform float uDecay;\nuniform float uFalloffStart;\nuniform float uFogFallSpeed;\nuniform vec3 uColor;\nuniform float uFade;\n\n// Core beam/flare shaping and dynamics\n#define PI 3.14159265359\n#define TWO_PI 6.28318530718\n#define EPS 1e-6\n#define EDGE_SOFT (DT_LOCAL*4.0)\n#define DT_LOCAL 0.0038\n#define TAP_RADIUS 6\n#define R_H 150.0\n#define R_V 150.0\n#define FLARE_HEIGHT 16.0\n#define FLARE_AMOUNT 8.0\n#define FLARE_EXP 2.0\n#define TOP_FADE_START 0.1\n#define TOP_FADE_EXP 1.0\n#define FLOW_PERIOD 0.5\n#define FLOW_SHARPNESS 1.5\n\n// Wisps (animated micro-streaks) that travel along the beam\n#define W_BASE_X 1.5\n#define W_LAYER_GAP 0.25\n#define W_LANES 10\n#define W_SIDE_DECAY 0.5\n#define W_HALF 0.01\n#define W_AA 0.15\n#define W_CELL 20.0\n#define W_SEG_MIN 0.01\n#define W_SEG_MAX 0.55\n#define W_CURVE_AMOUNT 15.0\n#define W_CURVE_RANGE (FLARE_HEIGHT - 3.0)\n#define W_BOTTOM_EXP 10.0\n\n// Volumetric fog controls\n#define FOG_ON 1\n#define FOG_CONTRAST 1.2\n#define FOG_SPEED_U 0.1\n#define FOG_SPEED_V -0.1\n#define FOG_OCTAVES 5\n#define FOG_BOTTOM_BIAS 0.8\n#define FOG_TILT_TO_MOUSE 0.05\n#define FOG_TILT_DEADZONE 0.01\n#define FOG_TILT_MAX_X 0.35\n#define FOG_TILT_SHAPE 1.5\n#define FOG_BEAM_MIN 0.0\n#define FOG_BEAM_MAX 0.75\n#define FOG_MASK_GAMMA 0.5\n#define FOG_EXPAND_SHAPE 12.2\n#define FOG_EDGE_MIX 0.5\n\n// Horizontal vignette for the fog volume\n#define HFOG_EDGE_START 0.20\n#define HFOG_EDGE_END 0.98\n#define HFOG_EDGE_GAMMA 1.4\n#define HFOG_Y_RADIUS 25.0\n#define HFOG_Y_SOFT 60.0\n\n// Beam extents and edge masking\n#define EDGE_X0 0.22\n#define EDGE_X1 0.995\n#define EDGE_X_GAMMA 1.25\n#define EDGE_LUMA_T0 0.0\n#define EDGE_LUMA_T1 2.0\n#define DITHER_STRENGTH 1.0\n\n float g(float x){return x<=0.00031308?12.92*x:1.055*pow(x,1.0/2.4)-0.055;}\n float bs(vec2 p,vec2 q,float powr){\n float d=distance(p,q),f=powr*uFalloffStart,r=(f*f)/(d*d+EPS);\n return powr*min(1.0,r);\n }\n float bsa(vec2 p,vec2 q,float powr,vec2 s){\n vec2 d=p-q; float dd=(d.x*d.x)/(s.x*s.x)+(d.y*d.y)/(s.y*s.y),f=powr*uFalloffStart,r=(f*f)/(dd+EPS);\n return powr*min(1.0,r);\n }\n float tri01(float x){float f=fract(x);return 1.0-abs(f*2.0-1.0);}\n float tauWf(float t,float tmin,float tmax){float a=smoothstep(tmin,tmin+EDGE_SOFT,t),b=1.0-smoothstep(tmax-EDGE_SOFT,tmax,t);return max(0.0,a*b);} \n float h21(vec2 p){p=fract(p*vec2(123.34,456.21));p+=dot(p,p+34.123);return fract(p.x*p.y);}\n float vnoise(vec2 p){\n vec2 i=floor(p),f=fract(p);\n float a=h21(i),b=h21(i+vec2(1,0)),c=h21(i+vec2(0,1)),d=h21(i+vec2(1,1));\n vec2 u=f*f*(3.0-2.0*f);\n return mix(mix(a,b,u.x),mix(c,d,u.x),u.y);\n }\n float fbm2(vec2 p){\n float v=0.0,amp=0.6; mat2 m=mat2(0.86,0.5,-0.5,0.86);\n for(int i=0;i=lanes) break;\n float off=W_BASE_X+float(i)*W_LAYER_GAP,xc=sgn*(off*xS);\n float dx=abs(uv.x-xc),lat=1.0-smoothstep(W_HALF,W_HALF+W_AA,dx),amp=exp(-off*W_SIDE_DECAY);\n float seed=h21(vec2(off,sgn*17.0)),yf2=yf+seed*7.0,ci=floor(yf2),fy=fract(yf2);\n float seg=mix(W_SEG_MIN,W_SEG_MAX,h21(vec2(ci,off*2.3)));\n float spR=h21(vec2(ci,off+sgn*31.0)),seg1=rGate(fy,seg)*step(spR,sp);\n if(ep>0.0){float spR2=h21(vec2(ci*3.1+7.0,off*5.3+sgn*13.0)); float f2=fract(fy+0.5); seg1+=rGate(f2,seg*0.9)*step(spR2,ep);}\n sum+=amp*lat*seg1;\n }\n }\n float span=smoothstep(-3.0,0.0,y)*(1.0-smoothstep(R_V-6.0,R_V,y));\n return uWIntensity*sum*topF*bGain*span;\n}\n\nvoid mainImage(out vec4 fc,in vec2 frag){\n vec2 C=iResolution.xy*.5; float invW=1.0/max(C.x,1.0);\n vec2 sc=(512.0/iResolution.xy)*.4;\n vec2 uv=(frag-C)*sc,off=vec2(uBeamXFrac*iResolution.x*sc.x,uBeamYFrac*iResolution.y*sc.y);\n vec2 uvc = uv - off;\n float a=0.0,b=0.0;\n float basePhase=1.5*PI+uDecay*.5; float tauMin=basePhase-uDecay; float tauMax=basePhase;\n float cx=clamp(uvc.x/(R_H*uHLenFactor),-1.0,1.0),tH=clamp(TWO_PI-acos(cx),tauMin,tauMax);\n for(int k=-TAP_RADIUS;k<=TAP_RADIUS;++k){\n float tu=tH+float(k)*DT_LOCAL,wt=tauWf(tu,tauMin,tauMax); if(wt<=0.0) continue;\n float spd=max(abs(sin(tu)),0.02),u=clamp((basePhase-tu)/max(uDecay,EPS),0.0,1.0),env=pow(1.0-abs(u*2.0-1.0),0.8);\n vec2 p=vec2((R_H*uHLenFactor)*cos(tu),0.0);\n a+=wt*bs(uvc,p,env*spd);\n }\n float yPix=uvc.y,cy=clamp(-yPix/(R_V*uVLenFactor),-1.0,1.0),tV=clamp(TWO_PI-acos(cy),tauMin,tauMax);\n for(int k=-TAP_RADIUS;k<=TAP_RADIUS;++k){\n float tu=tV+float(k)*DT_LOCAL,wt=tauWf(tu,tauMin,tauMax); if(wt<=0.0) continue;\n float yb=(-R_V)*cos(tu),s=clamp(yb/R_V,0.0,1.0),spd=max(abs(sin(tu)),0.02);\n float env=pow(1.0-s,0.6)*spd;\n float cap=1.0-smoothstep(TOP_FADE_START,1.0,s); cap=pow(cap,TOP_FADE_EXP); env*=cap;\n float ph=s/max(FLOW_PERIOD,EPS)+uFlowTime*uFlowSpeed;\n float fl=pow(tri01(ph),FLOW_SHARPNESS);\n env*=mix(1.0-uFlowStrength,1.0,fl);\n float yp=(-R_V*uVLenFactor)*cos(tu),m=pow(smoothstep(FLARE_HEIGHT,0.0,yp),FLARE_EXP),wx=1.0+FLARE_AMOUNT*m;\n vec2 sig=vec2(wx,1.0),p=vec2(0.0,yp);\n float mask=step(0.0,yp);\n b+=wt*bsa(uvc,p,mask*env,sig);\n }\n float sPix=clamp(yPix/R_V,0.0,1.0),topA=pow(1.0-smoothstep(TOP_FADE_START,1.0,sPix),TOP_FADE_EXP);\n float L=a+b*topA;\n float w=vWisps(vec2(uvc.x,yPix),topA);\n float fog=0.0;\n#if FOG_ON\n vec2 fuv=uvc*uFogScale;\n float mAct=step(1.0,length(iMouse.xy)),nx=((iMouse.x-C.x)*invW)*mAct;\n float ax = abs(nx);\n float stMag = mix(ax, pow(ax, FOG_TILT_SHAPE), 0.35);\n float st = sign(nx) * stMag * uTiltScale;\n st = clamp(st, -FOG_TILT_MAX_X, FOG_TILT_MAX_X);\n vec2 dir=normalize(vec2(st,1.0));\n fuv+=uFogTime*uFogFallSpeed*dir;\n vec2 prp=vec2(-dir.y,dir.x);\n fuv+=prp*(0.08*sin(dot(uvc,prp)*0.08+uFogTime*0.9));\n float n=fbm2(fuv+vec2(fbm2(fuv+vec2(7.3,2.1)),fbm2(fuv+vec2(-3.7,5.9)))*0.6);\n n=pow(clamp(n,0.0,1.0),FOG_CONTRAST);\n float pixW = 1.0 / max(iResolution.y, 1.0);\n#ifdef GL_OES_standard_derivatives\n float wL = max(fwidth(L), pixW);\n#else\n float wL = pixW;\n#endif\n float m0=pow(smoothstep(FOG_BEAM_MIN - wL, FOG_BEAM_MAX + wL, L),FOG_MASK_GAMMA);\n float bm=1.0-pow(1.0-m0,FOG_EXPAND_SHAPE); bm=mix(bm*m0,bm,FOG_EDGE_MIX);\n float yP=1.0-smoothstep(HFOG_Y_RADIUS,HFOG_Y_RADIUS+HFOG_Y_SOFT,abs(yPix));\n float nxF=abs((frag.x-C.x)*invW),hE=1.0-smoothstep(HFOG_EDGE_START,HFOG_EDGE_END,nxF); hE=pow(clamp(hE,0.0,1.0),HFOG_EDGE_GAMMA);\n float hW=mix(1.0,hE,clamp(yP,0.0,1.0));\n float bBias=mix(1.0,1.0-sPix,FOG_BOTTOM_BIAS);\n float browserFogIntensity = uFogIntensity;\n browserFogIntensity *= 1.8;\n float radialFade = 1.0 - smoothstep(0.0, 0.7, length(uvc) / 120.0);\n float safariFog = n * browserFogIntensity * bBias * bm * hW * radialFade;\n fog = safariFog;\n#endif\n float LF=L+fog;\n float dith=(h21(frag)-0.5)*(DITHER_STRENGTH/255.0);\n float tone=g(LF+w);\n vec3 col=tone*uColor+dith;\n float alpha=clamp(g(L+w*0.6)+dith*0.6,0.0,1.0);\n float nxE=abs((frag.x-C.x)*invW),xF=pow(clamp(1.0-smoothstep(EDGE_X0,EDGE_X1,nxE),0.0,1.0),EDGE_X_GAMMA);\n float scene=LF+max(0.0,w)*0.5,hi=smoothstep(EDGE_LUMA_T0,EDGE_LUMA_T1,scene);\n float eM=mix(xF,1.0,hi);\n col*=eM; alpha*=eM;\n col*=uFade; alpha*=uFade;\n fc=vec4(col,alpha);\n}\n\nvoid main(){\n vec4 fc;\n mainImage(fc, gl_FragCoord.xy);\n gl_FragColor = fc;\n}\n`;\n\nexport const LaserFlow: React.FC = ({\n className,\n style,\n wispDensity = 1,\n dpr,\n mouseSmoothTime = 0.0,\n mouseTiltStrength = 0.01,\n horizontalBeamOffset = 0.1,\n verticalBeamOffset = 0.0,\n flowSpeed = 0.35,\n verticalSizing = 2.0,\n horizontalSizing = 0.5,\n fogIntensity = 0.45,\n fogScale = 0.3,\n wispSpeed = 15.0,\n wispIntensity = 5.0,\n flowStrength = 0.25,\n decay = 1.1,\n falloffStart = 1.2,\n fogFallSpeed = 0.6,\n color = '#FF79C6'\n}) => {\n const mountRef = useRef(null);\n const rendererRef = useRef(null);\n const uniformsRef = useRef(null);\n const hasFadedRef = useRef(false);\n const rectRef = useRef(null);\n const baseDprRef = useRef(1);\n const currentDprRef = useRef(1);\n const lastSizeRef = useRef({ width: 0, height: 0, dpr: 0 });\n const fpsSamplesRef = useRef([]);\n const lastFpsCheckRef = useRef(performance.now());\n const emaDtRef = useRef(16.7); // ms\n const pausedRef = useRef(false);\n const inViewRef = useRef(true);\n\n const hexToRGB = (hex: string) => {\n let c = hex.trim();\n if (c[0] === '#') c = c.slice(1);\n if (c.length === 3)\n c = c\n .split('')\n .map(x => x + x)\n .join('');\n const n = parseInt(c.slice(0, 6), 16) || 0xffffff;\n return { r: ((n >> 16) & 255) / 255, g: ((n >> 8) & 255) / 255, b: (n & 255) / 255 };\n };\n\n useEffect(() => {\n const mount = mountRef.current!;\n const renderer = new THREE.WebGLRenderer({\n antialias: false,\n alpha: false,\n depth: false,\n stencil: false,\n powerPreference: 'high-performance',\n premultipliedAlpha: false,\n preserveDrawingBuffer: false,\n failIfMajorPerformanceCaveat: false,\n logarithmicDepthBuffer: false\n });\n rendererRef.current = renderer;\n\n baseDprRef.current = Math.min(dpr ?? (window.devicePixelRatio || 1), 2);\n currentDprRef.current = baseDprRef.current;\n\n renderer.setPixelRatio(currentDprRef.current);\n renderer.shadowMap.enabled = false;\n renderer.outputColorSpace = THREE.SRGBColorSpace;\n renderer.setClearColor(0x000000, 1);\n const canvas = renderer.domElement;\n canvas.style.width = '100%';\n canvas.style.height = '100%';\n canvas.style.display = 'block';\n mount.appendChild(canvas);\n\n const scene = new THREE.Scene();\n const camera = new THREE.OrthographicCamera(-1, 1, 1, -1, 0, 1);\n\n const geometry = new THREE.BufferGeometry();\n geometry.setAttribute('position', new THREE.BufferAttribute(new Float32Array([-1, -1, 0, 3, -1, 0, -1, 3, 0]), 3));\n\n const uniforms = {\n iTime: { value: 0 },\n iResolution: { value: new THREE.Vector3(1, 1, 1) },\n iMouse: { value: new THREE.Vector4(0, 0, 0, 0) },\n uWispDensity: { value: wispDensity },\n uTiltScale: { value: mouseTiltStrength },\n uFlowTime: { value: 0 },\n uFogTime: { value: 0 },\n uBeamXFrac: { value: horizontalBeamOffset },\n uBeamYFrac: { value: verticalBeamOffset },\n uFlowSpeed: { value: flowSpeed },\n uVLenFactor: { value: verticalSizing },\n uHLenFactor: { value: horizontalSizing },\n uFogIntensity: { value: fogIntensity },\n uFogScale: { value: fogScale },\n uWSpeed: { value: wispSpeed },\n uWIntensity: { value: wispIntensity },\n uFlowStrength: { value: flowStrength },\n uDecay: { value: decay },\n uFalloffStart: { value: falloffStart },\n uFogFallSpeed: { value: fogFallSpeed },\n uColor: { value: new THREE.Vector3(1, 1, 1) },\n uFade: { value: hasFadedRef.current ? 1 : 0 }\n };\n uniformsRef.current = uniforms;\n\n const material = new THREE.RawShaderMaterial({\n vertexShader: VERT,\n fragmentShader: FRAG,\n uniforms,\n transparent: false,\n depthTest: false,\n depthWrite: false,\n blending: THREE.NormalBlending\n });\n\n const mesh = new THREE.Mesh(geometry, material);\n mesh.frustumCulled = false;\n scene.add(mesh);\n\n const clock = new THREE.Clock();\n let prevTime = 0;\n let fade = hasFadedRef.current ? 1 : 0;\n\n const mouseTarget = new THREE.Vector2(0, 0);\n const mouseSmooth = new THREE.Vector2(0, 0);\n\n const setSizeNow = () => {\n const w = mount.clientWidth || 1;\n const h = mount.clientHeight || 1;\n const pr = currentDprRef.current;\n\n const last = lastSizeRef.current;\n const sizeChanged = Math.abs(w - last.width) > 0.5 || Math.abs(h - last.height) > 0.5;\n const dprChanged = Math.abs(pr - last.dpr) > 0.01;\n if (!sizeChanged && !dprChanged) {\n return;\n }\n\n lastSizeRef.current = { width: w, height: h, dpr: pr };\n renderer.setPixelRatio(pr);\n renderer.setSize(w, h, false);\n uniforms.iResolution.value.set(w * pr, h * pr, pr);\n rectRef.current = canvas.getBoundingClientRect();\n\n if (!pausedRef.current) {\n renderer.render(scene, camera);\n }\n };\n\n let resizeRaf = 0;\n const scheduleResize = () => {\n if (resizeRaf) cancelAnimationFrame(resizeRaf);\n resizeRaf = requestAnimationFrame(setSizeNow);\n };\n\n setSizeNow();\n const ro = new ResizeObserver(scheduleResize);\n ro.observe(mount);\n\n const io = new IntersectionObserver(\n entries => {\n inViewRef.current = entries[0]?.isIntersecting ?? true;\n },\n { root: null, threshold: 0 }\n );\n io.observe(mount);\n\n const onVis = () => {\n pausedRef.current = document.hidden;\n };\n document.addEventListener('visibilitychange', onVis, { passive: true });\n\n const updateMouse = (clientX: number, clientY: number) => {\n const rect = rectRef.current;\n if (!rect) return;\n const x = clientX - rect.left;\n const y = clientY - rect.top;\n const ratio = currentDprRef.current;\n const hb = rect.height * ratio;\n mouseTarget.set(x * ratio, hb - y * ratio);\n };\n const onMove = (ev: PointerEvent | MouseEvent) => updateMouse(ev.clientX, ev.clientY);\n const onLeave = () => mouseTarget.set(0, 0);\n canvas.addEventListener('pointermove', onMove as any, { passive: true });\n canvas.addEventListener('pointerdown', onMove as any, { passive: true });\n canvas.addEventListener('pointerenter', onMove as any, { passive: true });\n canvas.addEventListener('pointerleave', onLeave as any, { passive: true });\n\n const onCtxLost = (e: Event) => {\n e.preventDefault();\n pausedRef.current = true;\n };\n const onCtxRestored = () => {\n pausedRef.current = false;\n scheduleResize();\n };\n canvas.addEventListener('webglcontextlost', onCtxLost, false);\n canvas.addEventListener('webglcontextrestored', onCtxRestored, false);\n\n let raf = 0;\n\n const clamp = (v: number, lo: number, hi: number) => Math.max(lo, Math.min(hi, v));\n const dprFloor = 0.6;\n const lowerThresh = 50;\n const upperThresh = 58;\n let lastDprChangeRef = 0;\n const dprChangeCooldown = 2000;\n\n const adjustDprIfNeeded = (now: number) => {\n const elapsed = now - lastFpsCheckRef.current;\n if (elapsed < 750) return;\n\n const samples = fpsSamplesRef.current;\n if (samples.length === 0) {\n lastFpsCheckRef.current = now;\n return;\n }\n const avgFps = samples.reduce((a, b) => a + b, 0) / samples.length;\n\n let next = currentDprRef.current;\n const base = baseDprRef.current;\n\n if (avgFps < lowerThresh) {\n next = clamp(currentDprRef.current * 0.85, dprFloor, base);\n } else if (avgFps > upperThresh && currentDprRef.current < base) {\n next = clamp(currentDprRef.current * 1.1, dprFloor, base);\n }\n\n if (Math.abs(next - currentDprRef.current) > 0.01 && now - lastDprChangeRef > dprChangeCooldown) {\n currentDprRef.current = next;\n lastDprChangeRef = now;\n setSizeNow();\n }\n\n fpsSamplesRef.current = [];\n lastFpsCheckRef.current = now;\n };\n\n const animate = () => {\n raf = requestAnimationFrame(animate);\n if (pausedRef.current || !inViewRef.current) return;\n\n const t = clock.getElapsedTime();\n const dt = Math.max(0, t - prevTime);\n prevTime = t;\n\n const dtMs = dt * 1000;\n emaDtRef.current = emaDtRef.current * 0.9 + dtMs * 0.1;\n const instFps = 1000 / Math.max(1, emaDtRef.current);\n fpsSamplesRef.current.push(instFps);\n\n uniforms.iTime.value = t;\n\n const cdt = Math.min(0.033, Math.max(0.001, dt));\n (uniforms.uFlowTime.value as number) += cdt;\n (uniforms.uFogTime.value as number) += cdt;\n\n if (!hasFadedRef.current) {\n const fadeDur = 1.0;\n fade = Math.min(1, fade + cdt / fadeDur);\n uniforms.uFade.value = fade;\n if (fade >= 1) hasFadedRef.current = true;\n }\n\n const tau = Math.max(1e-3, mouseSmoothTime);\n const alpha = 1 - Math.exp(-cdt / tau);\n mouseSmooth.lerp(mouseTarget, alpha);\n uniforms.iMouse.value.set(mouseSmooth.x, mouseSmooth.y, 0, 0);\n\n renderer.render(scene, camera);\n\n adjustDprIfNeeded(performance.now());\n };\n\n animate();\n\n return () => {\n cancelAnimationFrame(raf);\n ro.disconnect();\n io.disconnect();\n document.removeEventListener('visibilitychange', onVis);\n canvas.removeEventListener('pointermove', onMove as any);\n canvas.removeEventListener('pointerdown', onMove as any);\n canvas.removeEventListener('pointerenter', onMove as any);\n canvas.removeEventListener('pointerleave', onLeave as any);\n canvas.removeEventListener('webglcontextlost', onCtxLost);\n canvas.removeEventListener('webglcontextrestored', onCtxRestored);\n geometry.dispose();\n material.dispose();\n renderer.dispose();\n renderer.forceContextLoss();\n if (mount.contains(canvas)) mount.removeChild(canvas);\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [dpr]);\n\n useEffect(() => {\n const uniforms = uniformsRef.current;\n if (!uniforms) return;\n\n uniforms.uWispDensity.value = wispDensity;\n uniforms.uTiltScale.value = mouseTiltStrength;\n uniforms.uBeamXFrac.value = horizontalBeamOffset;\n uniforms.uBeamYFrac.value = verticalBeamOffset;\n uniforms.uFlowSpeed.value = flowSpeed;\n uniforms.uVLenFactor.value = verticalSizing;\n uniforms.uHLenFactor.value = horizontalSizing;\n uniforms.uFogIntensity.value = fogIntensity;\n uniforms.uFogScale.value = fogScale;\n uniforms.uWSpeed.value = wispSpeed;\n uniforms.uWIntensity.value = wispIntensity;\n uniforms.uFlowStrength.value = flowStrength;\n uniforms.uDecay.value = decay;\n uniforms.uFalloffStart.value = falloffStart;\n uniforms.uFogFallSpeed.value = fogFallSpeed;\n\n const { r, g, b } = hexToRGB(color || '#FFFFFF');\n uniforms.uColor.value.set(r, g, b);\n }, [\n wispDensity,\n mouseTiltStrength,\n horizontalBeamOffset,\n verticalBeamOffset,\n flowSpeed,\n verticalSizing,\n horizontalSizing,\n fogIntensity,\n fogScale,\n wispSpeed,\n wispIntensity,\n flowStrength,\n decay,\n falloffStart,\n fogFallSpeed,\n color\n ]);\n\n return
;\n};\n\nexport default LaserFlow;\n" } ], "registryDependencies": [], diff --git a/public/r/LaserFlow-TS-TW.json b/public/r/LaserFlow-TS-TW.json index 996ee483c..0d49a8478 100644 --- a/public/r/LaserFlow-TS-TW.json +++ b/public/r/LaserFlow-TS-TW.json @@ -8,7 +8,7 @@ { "type": "registry:component", "path": "LaserFlow/LaserFlow.tsx", - "content": "import React, { useEffect, useRef } from 'react';\nimport * as THREE from 'three';\n\ntype Props = {\n className?: string;\n style?: React.CSSProperties;\n wispDensity?: number;\n dpr?: number;\n mouseSmoothTime?: number;\n mouseTiltStrength?: number;\n horizontalBeamOffset?: number;\n verticalBeamOffset?: number;\n flowSpeed?: number;\n verticalSizing?: number;\n horizontalSizing?: number;\n fogIntensity?: number;\n fogScale?: number;\n wispSpeed?: number;\n wispIntensity?: number;\n flowStrength?: number;\n decay?: number;\n falloffStart?: number;\n fogFallSpeed?: number;\n color?: string;\n};\n\nconst VERT = `\nprecision highp float;\nattribute vec3 position;\nvoid main(){\n gl_Position = vec4(position, 1.0);\n}\n`;\n\nconst FRAG = `\n#ifdef GL_ES\n#extension GL_OES_standard_derivatives : enable\n#endif\nprecision highp float;\nprecision mediump int;\n\nuniform float iTime;\nuniform vec3 iResolution;\nuniform vec4 iMouse;\nuniform float uWispDensity;\nuniform float uTiltScale;\nuniform float uFlowTime;\nuniform float uFogTime;\nuniform float uBeamXFrac;\nuniform float uBeamYFrac;\nuniform float uFlowSpeed;\nuniform float uVLenFactor;\nuniform float uHLenFactor;\nuniform float uFogIntensity;\nuniform float uFogScale;\nuniform float uWSpeed;\nuniform float uWIntensity;\nuniform float uFlowStrength;\nuniform float uDecay;\nuniform float uFalloffStart;\nuniform float uFogFallSpeed;\nuniform vec3 uColor;\nuniform float uFade;\n\n// Core beam/flare shaping and dynamics\n#define PI 3.14159265359\n#define TWO_PI 6.28318530718\n#define EPS 1e-6\n#define EDGE_SOFT (DT_LOCAL*4.0)\n#define DT_LOCAL 0.0038\n#define TAP_RADIUS 6\n#define R_H 150.0\n#define R_V 150.0\n#define FLARE_HEIGHT 16.0\n#define FLARE_AMOUNT 8.0\n#define FLARE_EXP 2.0\n#define TOP_FADE_START 0.1\n#define TOP_FADE_EXP 1.0\n#define FLOW_PERIOD 0.5\n#define FLOW_SHARPNESS 1.5\n\n// Wisps (animated micro-streaks) that travel along the beam\n#define W_BASE_X 1.5\n#define W_LAYER_GAP 0.25\n#define W_LANES 10\n#define W_SIDE_DECAY 0.5\n#define W_HALF 0.01\n#define W_AA 0.15\n#define W_CELL 20.0\n#define W_SEG_MIN 0.01\n#define W_SEG_MAX 0.55\n#define W_CURVE_AMOUNT 15.0\n#define W_CURVE_RANGE (FLARE_HEIGHT - 3.0)\n#define W_BOTTOM_EXP 10.0\n\n// Volumetric fog controls\n#define FOG_ON 1\n#define FOG_CONTRAST 1.2\n#define FOG_SPEED_U 0.1\n#define FOG_SPEED_V -0.1\n#define FOG_OCTAVES 5\n#define FOG_BOTTOM_BIAS 0.8\n#define FOG_TILT_TO_MOUSE 0.05\n#define FOG_TILT_DEADZONE 0.01\n#define FOG_TILT_MAX_X 0.35\n#define FOG_TILT_SHAPE 1.5\n#define FOG_BEAM_MIN 0.0\n#define FOG_BEAM_MAX 0.75\n#define FOG_MASK_GAMMA 0.5\n#define FOG_EXPAND_SHAPE 12.2\n#define FOG_EDGE_MIX 0.5\n\n// Horizontal vignette for the fog volume\n#define HFOG_EDGE_START 0.20\n#define HFOG_EDGE_END 0.98\n#define HFOG_EDGE_GAMMA 1.4\n#define HFOG_Y_RADIUS 25.0\n#define HFOG_Y_SOFT 60.0\n\n// Beam extents and edge masking\n#define EDGE_X0 0.22\n#define EDGE_X1 0.995\n#define EDGE_X_GAMMA 1.25\n#define EDGE_LUMA_T0 0.0\n#define EDGE_LUMA_T1 2.0\n#define DITHER_STRENGTH 1.0\n\n float g(float x){return x<=0.00031308?12.92*x:1.055*pow(x,1.0/2.4)-0.055;}\n float bs(vec2 p,vec2 q,float powr){\n float d=distance(p,q),f=powr*uFalloffStart,r=(f*f)/(d*d+EPS);\n return powr*min(1.0,r);\n }\n float bsa(vec2 p,vec2 q,float powr,vec2 s){\n vec2 d=p-q; float dd=(d.x*d.x)/(s.x*s.x)+(d.y*d.y)/(s.y*s.y),f=powr*uFalloffStart,r=(f*f)/(dd+EPS);\n return powr*min(1.0,r);\n }\n float tri01(float x){float f=fract(x);return 1.0-abs(f*2.0-1.0);}\n float tauWf(float t,float tmin,float tmax){float a=smoothstep(tmin,tmin+EDGE_SOFT,t),b=1.0-smoothstep(tmax-EDGE_SOFT,tmax,t);return max(0.0,a*b);} \n float h21(vec2 p){p=fract(p*vec2(123.34,456.21));p+=dot(p,p+34.123);return fract(p.x*p.y);}\n float vnoise(vec2 p){\n vec2 i=floor(p),f=fract(p);\n float a=h21(i),b=h21(i+vec2(1,0)),c=h21(i+vec2(0,1)),d=h21(i+vec2(1,1));\n vec2 u=f*f*(3.0-2.0*f);\n return mix(mix(a,b,u.x),mix(c,d,u.x),u.y);\n }\n float fbm2(vec2 p){\n float v=0.0,amp=0.6; mat2 m=mat2(0.86,0.5,-0.5,0.86);\n for(int i=0;i=lanes) break;\n float off=W_BASE_X+float(i)*W_LAYER_GAP,xc=sgn*(off*xS);\n float dx=abs(uv.x-xc),lat=1.0-smoothstep(W_HALF,W_HALF+W_AA,dx),amp=exp(-off*W_SIDE_DECAY);\n float seed=h21(vec2(off,sgn*17.0)),yf2=yf+seed*7.0,ci=floor(yf2),fy=fract(yf2);\n float seg=mix(W_SEG_MIN,W_SEG_MAX,h21(vec2(ci,off*2.3)));\n float spR=h21(vec2(ci,off+sgn*31.0)),seg1=rGate(fy,seg)*step(spR,sp);\n if(ep>0.0){float spR2=h21(vec2(ci*3.1+7.0,off*5.3+sgn*13.0)); float f2=fract(fy+0.5); seg1+=rGate(f2,seg*0.9)*step(spR2,ep);}\n sum+=amp*lat*seg1;\n }\n }\n float span=smoothstep(-3.0,0.0,y)*(1.0-smoothstep(R_V-6.0,R_V,y));\n return uWIntensity*sum*topF*bGain*span;\n}\n\nvoid mainImage(out vec4 fc,in vec2 frag){\n vec2 C=iResolution.xy*.5; float invW=1.0/max(C.x,1.0);\n vec2 sc=(512.0/iResolution.xy)*.4;\n vec2 uv=(frag-C)*sc,off=vec2(uBeamXFrac*iResolution.x*sc.x,uBeamYFrac*iResolution.y*sc.y);\n vec2 uvc = uv - off;\n float a=0.0,b=0.0;\n float basePhase=1.5*PI+uDecay*.5; float tauMin=basePhase-uDecay; float tauMax=basePhase;\n float cx=clamp(uvc.x/(R_H*uHLenFactor),-1.0,1.0),tH=clamp(TWO_PI-acos(cx),tauMin,tauMax);\n for(int k=-TAP_RADIUS;k<=TAP_RADIUS;++k){\n float tu=tH+float(k)*DT_LOCAL,wt=tauWf(tu,tauMin,tauMax); if(wt<=0.0) continue;\n float spd=max(abs(sin(tu)),0.02),u=clamp((basePhase-tu)/max(uDecay,EPS),0.0,1.0),env=pow(1.0-abs(u*2.0-1.0),0.8);\n vec2 p=vec2((R_H*uHLenFactor)*cos(tu),0.0);\n a+=wt*bs(uvc,p,env*spd);\n }\n float yPix=uvc.y,cy=clamp(-yPix/(R_V*uVLenFactor),-1.0,1.0),tV=clamp(TWO_PI-acos(cy),tauMin,tauMax);\n for(int k=-TAP_RADIUS;k<=TAP_RADIUS;++k){\n float tu=tV+float(k)*DT_LOCAL,wt=tauWf(tu,tauMin,tauMax); if(wt<=0.0) continue;\n float yb=(-R_V)*cos(tu),s=clamp(yb/R_V,0.0,1.0),spd=max(abs(sin(tu)),0.02);\n float env=pow(1.0-s,0.6)*spd;\n float cap=1.0-smoothstep(TOP_FADE_START,1.0,s); cap=pow(cap,TOP_FADE_EXP); env*=cap;\n float ph=s/max(FLOW_PERIOD,EPS)+uFlowTime*uFlowSpeed;\n float fl=pow(tri01(ph),FLOW_SHARPNESS);\n env*=mix(1.0-uFlowStrength,1.0,fl);\n float yp=(-R_V*uVLenFactor)*cos(tu),m=pow(smoothstep(FLARE_HEIGHT,0.0,yp),FLARE_EXP),wx=1.0+FLARE_AMOUNT*m;\n vec2 sig=vec2(wx,1.0),p=vec2(0.0,yp);\n float mask=step(0.0,yp);\n b+=wt*bsa(uvc,p,mask*env,sig);\n }\n float sPix=clamp(yPix/R_V,0.0,1.0),topA=pow(1.0-smoothstep(TOP_FADE_START,1.0,sPix),TOP_FADE_EXP);\n float L=a+b*topA;\n float w=vWisps(vec2(uvc.x,yPix),topA);\n float fog=0.0;\n#if FOG_ON\n vec2 fuv=uvc*uFogScale;\n float mAct=step(1.0,length(iMouse.xy)),nx=((iMouse.x-C.x)*invW)*mAct;\n float ax = abs(nx);\n float stMag = mix(ax, pow(ax, FOG_TILT_SHAPE), 0.35);\n float st = sign(nx) * stMag * uTiltScale;\n st = clamp(st, -FOG_TILT_MAX_X, FOG_TILT_MAX_X);\n vec2 dir=normalize(vec2(st,1.0));\n fuv+=uFogTime*uFogFallSpeed*dir;\n vec2 prp=vec2(-dir.y,dir.x);\n fuv+=prp*(0.08*sin(dot(uvc,prp)*0.08+uFogTime*0.9));\n float n=fbm2(fuv+vec2(fbm2(fuv+vec2(7.3,2.1)),fbm2(fuv+vec2(-3.7,5.9)))*0.6);\n n=pow(clamp(n,0.0,1.0),FOG_CONTRAST);\n float pixW = 1.0 / max(iResolution.y, 1.0);\n#ifdef GL_OES_standard_derivatives\n float wL = max(fwidth(L), pixW);\n#else\n float wL = pixW;\n#endif\n float m0=pow(smoothstep(FOG_BEAM_MIN - wL, FOG_BEAM_MAX + wL, L),FOG_MASK_GAMMA);\n float bm=1.0-pow(1.0-m0,FOG_EXPAND_SHAPE); bm=mix(bm*m0,bm,FOG_EDGE_MIX);\n float yP=1.0-smoothstep(HFOG_Y_RADIUS,HFOG_Y_RADIUS+HFOG_Y_SOFT,abs(yPix));\n float nxF=abs((frag.x-C.x)*invW),hE=1.0-smoothstep(HFOG_EDGE_START,HFOG_EDGE_END,nxF); hE=pow(clamp(hE,0.0,1.0),HFOG_EDGE_GAMMA);\n float hW=mix(1.0,hE,clamp(yP,0.0,1.0));\n float bBias=mix(1.0,1.0-sPix,FOG_BOTTOM_BIAS);\n float browserFogIntensity = uFogIntensity;\n browserFogIntensity *= 1.8;\n float radialFade = 1.0 - smoothstep(0.0, 0.7, length(uvc) / 120.0);\n float safariFog = n * browserFogIntensity * bBias * bm * hW * radialFade;\n fog = safariFog;\n#endif\n float LF=L+fog;\n float dith=(h21(frag)-0.5)*(DITHER_STRENGTH/255.0);\n float tone=g(LF+w);\n vec3 col=tone*uColor+dith;\n float alpha=clamp(g(L+w*0.6)+dith*0.6,0.0,1.0);\n float nxE=abs((frag.x-C.x)*invW),xF=pow(clamp(1.0-smoothstep(EDGE_X0,EDGE_X1,nxE),0.0,1.0),EDGE_X_GAMMA);\n float scene=LF+max(0.0,w)*0.5,hi=smoothstep(EDGE_LUMA_T0,EDGE_LUMA_T1,scene);\n float eM=mix(xF,1.0,hi);\n col*=eM; alpha*=eM;\n col*=uFade; alpha*=uFade;\n fc=vec4(col,alpha);\n}\n\nvoid main(){\n vec4 fc;\n mainImage(fc, gl_FragCoord.xy);\n gl_FragColor = fc;\n}\n`;\n\nfunction hexToRGB(hex: string) {\n let c = hex.trim();\n if (c[0] === '#') c = c.slice(1);\n if (c.length === 3)\n c = c\n .split('')\n .map(x => x + x)\n .join('');\n const n = parseInt(c, 16) || 0xffffff;\n return { r: ((n >> 16) & 255) / 255, g: ((n >> 8) & 255) / 255, b: (n & 255) / 255 };\n}\n\nexport const LaserFlow: React.FC = ({\n className,\n style,\n wispDensity = 1,\n dpr,\n mouseSmoothTime = 0.0,\n mouseTiltStrength = 0.01,\n horizontalBeamOffset = 0.1,\n verticalBeamOffset = 0.0,\n flowSpeed = 0.35,\n verticalSizing = 2.0,\n horizontalSizing = 0.5,\n fogIntensity = 0.45,\n fogScale = 0.3,\n wispSpeed = 15.0,\n wispIntensity = 5.0,\n flowStrength = 0.25,\n decay = 1.1,\n falloffStart = 1.2,\n fogFallSpeed = 0.6,\n color = '#FF79C6'\n}) => {\n const mountRef = useRef(null);\n const rendererRef = useRef(null);\n const uniformsRef = useRef(null);\n const hasFadedRef = useRef(false);\n const rectRef = useRef(null);\n const baseDprRef = useRef(1);\n const currentDprRef = useRef(1);\n const lastSizeRef = useRef({ width: 0, height: 0, dpr: 0 });\n const fpsSamplesRef = useRef([]);\n const lastFpsCheckRef = useRef(performance.now());\n const emaDtRef = useRef(16.7);\n const pausedRef = useRef(false);\n const inViewRef = useRef(true);\n\n const mouseSmoothTimeRef = useRef(mouseSmoothTime);\n useEffect(() => {\n mouseSmoothTimeRef.current = mouseSmoothTime;\n }, [mouseSmoothTime]);\n\n useEffect(() => {\n const mount = mountRef.current!;\n const renderer = new THREE.WebGLRenderer({\n antialias: false,\n alpha: false,\n depth: false,\n stencil: false,\n powerPreference: 'high-performance',\n premultipliedAlpha: false,\n preserveDrawingBuffer: false,\n failIfMajorPerformanceCaveat: false,\n logarithmicDepthBuffer: false\n });\n rendererRef.current = renderer;\n\n baseDprRef.current = Math.min(dpr ?? (window.devicePixelRatio || 1), 2);\n currentDprRef.current = baseDprRef.current;\n\n renderer.setPixelRatio(currentDprRef.current);\n renderer.shadowMap.enabled = false;\n renderer.outputColorSpace = THREE.SRGBColorSpace;\n renderer.setClearColor(0x000000, 1);\n const canvas = renderer.domElement;\n canvas.style.width = '100%';\n canvas.style.height = '100%';\n canvas.style.display = 'block';\n mount.appendChild(canvas);\n\n const scene = new THREE.Scene();\n const camera = new THREE.OrthographicCamera(-1, 1, 1, -1, 0, 1);\n\n const geometry = new THREE.BufferGeometry();\n geometry.setAttribute('position', new THREE.BufferAttribute(new Float32Array([-1, -1, 0, 3, -1, 0, -1, 3, 0]), 3));\n\n const uniforms = {\n iTime: { value: 0 },\n iResolution: { value: new THREE.Vector3(1, 1, 1) },\n iMouse: { value: new THREE.Vector4(0, 0, 0, 0) },\n uWispDensity: { value: wispDensity },\n uTiltScale: { value: mouseTiltStrength },\n uFlowTime: { value: 0 },\n uFogTime: { value: 0 },\n uBeamXFrac: { value: horizontalBeamOffset },\n uBeamYFrac: { value: verticalBeamOffset },\n uFlowSpeed: { value: flowSpeed },\n uVLenFactor: { value: verticalSizing },\n uHLenFactor: { value: horizontalSizing },\n uFogIntensity: { value: fogIntensity },\n uFogScale: { value: fogScale },\n uWSpeed: { value: wispSpeed },\n uWIntensity: { value: wispIntensity },\n uFlowStrength: { value: flowStrength },\n uDecay: { value: decay },\n uFalloffStart: { value: falloffStart },\n uFogFallSpeed: { value: fogFallSpeed },\n uColor: { value: new THREE.Vector3(1, 1, 1) },\n uFade: { value: hasFadedRef.current ? 1 : 0 }\n };\n uniformsRef.current = uniforms;\n\n const material = new THREE.RawShaderMaterial({\n vertexShader: VERT,\n fragmentShader: FRAG,\n uniforms,\n transparent: false,\n depthTest: false,\n depthWrite: false,\n blending: THREE.NormalBlending\n });\n\n const mesh = new THREE.Mesh(geometry, material);\n mesh.frustumCulled = false;\n scene.add(mesh);\n\n const clock = new THREE.Clock();\n let prevTime = 0;\n let fade = hasFadedRef.current ? 1 : 0;\n\n const mouseTarget = new THREE.Vector2(0, 0);\n const mouseSmooth = new THREE.Vector2(0, 0);\n\n const setSizeNow = () => {\n const w = mount.clientWidth || 1;\n const h = mount.clientHeight || 1;\n const pr = currentDprRef.current;\n\n const last = lastSizeRef.current;\n const sizeChanged = Math.abs(w - last.width) > 0.5 || Math.abs(h - last.height) > 0.5;\n const dprChanged = Math.abs(pr - last.dpr) > 0.01;\n if (!sizeChanged && !dprChanged) return;\n\n lastSizeRef.current = { width: w, height: h, dpr: pr };\n renderer.setPixelRatio(pr);\n renderer.setSize(w, h, false);\n uniforms.iResolution.value.set(w * pr, h * pr, pr);\n rectRef.current = canvas.getBoundingClientRect();\n\n if (!pausedRef.current) {\n renderer.render(scene, camera);\n }\n };\n\n let resizeRaf = 0;\n const scheduleResize = () => {\n if (resizeRaf) cancelAnimationFrame(resizeRaf);\n resizeRaf = requestAnimationFrame(setSizeNow);\n };\n\n setSizeNow();\n const ro = new ResizeObserver(scheduleResize);\n ro.observe(mount);\n\n const io = new IntersectionObserver(\n entries => {\n inViewRef.current = entries[0]?.isIntersecting ?? true;\n },\n { root: null, threshold: 0 }\n );\n io.observe(mount);\n\n const onVis = () => {\n pausedRef.current = document.hidden;\n };\n document.addEventListener('visibilitychange', onVis, { passive: true });\n\n const updateMouse = (clientX: number, clientY: number) => {\n const rect = rectRef.current;\n if (!rect) return;\n const x = clientX - rect.left;\n const y = clientY - rect.top;\n const ratio = currentDprRef.current;\n const hb = rect.height * ratio;\n mouseTarget.set(x * ratio, hb - y * ratio);\n };\n const onMove = (ev: PointerEvent | MouseEvent) => updateMouse(ev.clientX, ev.clientY);\n const onLeave = () => mouseTarget.set(0, 0);\n canvas.addEventListener('pointermove', onMove as any, { passive: true });\n canvas.addEventListener('pointerdown', onMove as any, { passive: true });\n canvas.addEventListener('pointerenter', onMove as any, { passive: true });\n canvas.addEventListener('pointerleave', onLeave as any, { passive: true });\n\n const onCtxLost = (e: Event) => {\n e.preventDefault();\n pausedRef.current = true;\n };\n const onCtxRestored = () => {\n pausedRef.current = false;\n scheduleResize();\n };\n canvas.addEventListener('webglcontextlost', onCtxLost, false);\n canvas.addEventListener('webglcontextrestored', onCtxRestored, false);\n\n let raf = 0;\n\n const clamp = (v: number, lo: number, hi: number) => Math.max(lo, Math.min(hi, v));\n const dprFloor = 0.6;\n const lowerThresh = 50;\n const upperThresh = 58;\n let lastDprChange = 0;\n const dprChangeCooldown = 2000;\n\n const adjustDprIfNeeded = (now: number) => {\n const elapsed = now - lastFpsCheckRef.current;\n if (elapsed < 750) return;\n\n const samples = fpsSamplesRef.current;\n if (samples.length === 0) {\n lastFpsCheckRef.current = now;\n return;\n }\n const avgFps = samples.reduce((a, b) => a + b, 0) / samples.length;\n\n let next = currentDprRef.current;\n const base = baseDprRef.current;\n\n if (avgFps < lowerThresh) {\n next = clamp(currentDprRef.current * 0.85, dprFloor, base);\n } else if (avgFps > upperThresh && currentDprRef.current < base) {\n next = clamp(currentDprRef.current * 1.1, dprFloor, base);\n }\n\n if (Math.abs(next - currentDprRef.current) > 0.01 && now - lastDprChange > dprChangeCooldown) {\n currentDprRef.current = next;\n lastDprChange = now;\n setSizeNow();\n }\n\n fpsSamplesRef.current = [];\n lastFpsCheckRef.current = now;\n };\n\n const animate = () => {\n raf = requestAnimationFrame(animate);\n if (pausedRef.current || !inViewRef.current) return;\n\n const t = clock.getElapsedTime();\n const dt = Math.max(0, t - prevTime);\n prevTime = t;\n\n const dtMs = dt * 1000;\n emaDtRef.current = emaDtRef.current * 0.9 + dtMs * 0.1;\n const instFps = 1000 / Math.max(1, emaDtRef.current);\n fpsSamplesRef.current.push(instFps);\n\n uniforms.iTime.value = t;\n\n const cdt = Math.min(0.033, Math.max(0.001, dt));\n (uniforms.uFlowTime.value as number) += cdt;\n (uniforms.uFogTime.value as number) += cdt;\n\n if (!hasFadedRef.current) {\n const fadeDur = 1.0;\n fade = Math.min(1, fade + cdt / fadeDur);\n uniforms.uFade.value = fade;\n if (fade >= 1) hasFadedRef.current = true;\n }\n\n const tau = Math.max(1e-3, mouseSmoothTimeRef.current);\n const alpha = 1 - Math.exp(-cdt / tau);\n mouseSmooth.lerp(mouseTarget, alpha);\n uniforms.iMouse.value.set(mouseSmooth.x, mouseSmooth.y, 0, 0);\n\n renderer.render(scene, camera);\n\n adjustDprIfNeeded(performance.now());\n };\n\n animate();\n\n return () => {\n cancelAnimationFrame(raf);\n if (resizeRaf) cancelAnimationFrame(resizeRaf);\n\n ro.disconnect();\n io.disconnect();\n document.removeEventListener('visibilitychange', onVis);\n canvas.removeEventListener('pointermove', onMove as any);\n canvas.removeEventListener('pointerdown', onMove as any);\n canvas.removeEventListener('pointerenter', onMove as any);\n canvas.removeEventListener('pointerleave', onLeave as any);\n canvas.removeEventListener('webglcontextlost', onCtxLost);\n canvas.removeEventListener('webglcontextrestored', onCtxRestored);\n\n scene.clear();\n geometry.dispose();\n material.dispose();\n renderer.dispose();\n renderer.forceContextLoss();\n if (mount.contains(canvas)) mount.removeChild(canvas);\n };\n }, [dpr]);\n\n useEffect(() => {\n const uniforms = uniformsRef.current;\n if (!uniforms) return;\n\n uniforms.uWispDensity.value = wispDensity;\n uniforms.uTiltScale.value = mouseTiltStrength;\n uniforms.uBeamXFrac.value = horizontalBeamOffset;\n uniforms.uBeamYFrac.value = verticalBeamOffset;\n uniforms.uFlowSpeed.value = flowSpeed;\n uniforms.uVLenFactor.value = verticalSizing;\n uniforms.uHLenFactor.value = horizontalSizing;\n uniforms.uFogIntensity.value = fogIntensity;\n uniforms.uFogScale.value = fogScale;\n uniforms.uWSpeed.value = wispSpeed;\n uniforms.uWIntensity.value = wispIntensity;\n uniforms.uFlowStrength.value = flowStrength;\n uniforms.uDecay.value = decay;\n uniforms.uFalloffStart.value = falloffStart;\n uniforms.uFogFallSpeed.value = fogFallSpeed;\n\n const { r, g, b } = hexToRGB(color || '#FFFFFF');\n uniforms.uColor.value.set(r, g, b);\n }, [\n wispDensity,\n mouseTiltStrength,\n horizontalBeamOffset,\n verticalBeamOffset,\n flowSpeed,\n verticalSizing,\n horizontalSizing,\n fogIntensity,\n fogScale,\n wispSpeed,\n wispIntensity,\n flowStrength,\n decay,\n falloffStart,\n fogFallSpeed,\n color\n ]);\n\n return
;\n};\n\nexport default LaserFlow;\n" + "content": "import React, { useEffect, useRef } from 'react';\nimport * as THREE from 'three';\n\ntype Props = {\n className?: string;\n style?: React.CSSProperties;\n wispDensity?: number;\n dpr?: number;\n mouseSmoothTime?: number;\n mouseTiltStrength?: number;\n horizontalBeamOffset?: number;\n verticalBeamOffset?: number;\n flowSpeed?: number;\n verticalSizing?: number;\n horizontalSizing?: number;\n fogIntensity?: number;\n fogScale?: number;\n wispSpeed?: number;\n wispIntensity?: number;\n flowStrength?: number;\n decay?: number;\n falloffStart?: number;\n fogFallSpeed?: number;\n color?: string;\n};\n\nconst VERT = `\nprecision highp float;\nattribute vec3 position;\nvoid main(){\n gl_Position = vec4(position, 1.0);\n}\n`;\n\nconst FRAG = `\n#ifdef GL_ES\n#extension GL_OES_standard_derivatives : enable\n#endif\nprecision highp float;\nprecision mediump int;\n\nuniform float iTime;\nuniform vec3 iResolution;\nuniform vec4 iMouse;\nuniform float uWispDensity;\nuniform float uTiltScale;\nuniform float uFlowTime;\nuniform float uFogTime;\nuniform float uBeamXFrac;\nuniform float uBeamYFrac;\nuniform float uFlowSpeed;\nuniform float uVLenFactor;\nuniform float uHLenFactor;\nuniform float uFogIntensity;\nuniform float uFogScale;\nuniform float uWSpeed;\nuniform float uWIntensity;\nuniform float uFlowStrength;\nuniform float uDecay;\nuniform float uFalloffStart;\nuniform float uFogFallSpeed;\nuniform vec3 uColor;\nuniform float uFade;\n\n// Core beam/flare shaping and dynamics\n#define PI 3.14159265359\n#define TWO_PI 6.28318530718\n#define EPS 1e-6\n#define EDGE_SOFT (DT_LOCAL*4.0)\n#define DT_LOCAL 0.0038\n#define TAP_RADIUS 6\n#define R_H 150.0\n#define R_V 150.0\n#define FLARE_HEIGHT 16.0\n#define FLARE_AMOUNT 8.0\n#define FLARE_EXP 2.0\n#define TOP_FADE_START 0.1\n#define TOP_FADE_EXP 1.0\n#define FLOW_PERIOD 0.5\n#define FLOW_SHARPNESS 1.5\n\n// Wisps (animated micro-streaks) that travel along the beam\n#define W_BASE_X 1.5\n#define W_LAYER_GAP 0.25\n#define W_LANES 10\n#define W_SIDE_DECAY 0.5\n#define W_HALF 0.01\n#define W_AA 0.15\n#define W_CELL 20.0\n#define W_SEG_MIN 0.01\n#define W_SEG_MAX 0.55\n#define W_CURVE_AMOUNT 15.0\n#define W_CURVE_RANGE (FLARE_HEIGHT - 3.0)\n#define W_BOTTOM_EXP 10.0\n\n// Volumetric fog controls\n#define FOG_ON 1\n#define FOG_CONTRAST 1.2\n#define FOG_SPEED_U 0.1\n#define FOG_SPEED_V -0.1\n#define FOG_OCTAVES 5\n#define FOG_BOTTOM_BIAS 0.8\n#define FOG_TILT_TO_MOUSE 0.05\n#define FOG_TILT_DEADZONE 0.01\n#define FOG_TILT_MAX_X 0.35\n#define FOG_TILT_SHAPE 1.5\n#define FOG_BEAM_MIN 0.0\n#define FOG_BEAM_MAX 0.75\n#define FOG_MASK_GAMMA 0.5\n#define FOG_EXPAND_SHAPE 12.2\n#define FOG_EDGE_MIX 0.5\n\n// Horizontal vignette for the fog volume\n#define HFOG_EDGE_START 0.20\n#define HFOG_EDGE_END 0.98\n#define HFOG_EDGE_GAMMA 1.4\n#define HFOG_Y_RADIUS 25.0\n#define HFOG_Y_SOFT 60.0\n\n// Beam extents and edge masking\n#define EDGE_X0 0.22\n#define EDGE_X1 0.995\n#define EDGE_X_GAMMA 1.25\n#define EDGE_LUMA_T0 0.0\n#define EDGE_LUMA_T1 2.0\n#define DITHER_STRENGTH 1.0\n\n float g(float x){return x<=0.00031308?12.92*x:1.055*pow(x,1.0/2.4)-0.055;}\n float bs(vec2 p,vec2 q,float powr){\n float d=distance(p,q),f=powr*uFalloffStart,r=(f*f)/(d*d+EPS);\n return powr*min(1.0,r);\n }\n float bsa(vec2 p,vec2 q,float powr,vec2 s){\n vec2 d=p-q; float dd=(d.x*d.x)/(s.x*s.x)+(d.y*d.y)/(s.y*s.y),f=powr*uFalloffStart,r=(f*f)/(dd+EPS);\n return powr*min(1.0,r);\n }\n float tri01(float x){float f=fract(x);return 1.0-abs(f*2.0-1.0);}\n float tauWf(float t,float tmin,float tmax){float a=smoothstep(tmin,tmin+EDGE_SOFT,t),b=1.0-smoothstep(tmax-EDGE_SOFT,tmax,t);return max(0.0,a*b);} \n float h21(vec2 p){p=fract(p*vec2(123.34,456.21));p+=dot(p,p+34.123);return fract(p.x*p.y);}\n float vnoise(vec2 p){\n vec2 i=floor(p),f=fract(p);\n float a=h21(i),b=h21(i+vec2(1,0)),c=h21(i+vec2(0,1)),d=h21(i+vec2(1,1));\n vec2 u=f*f*(3.0-2.0*f);\n return mix(mix(a,b,u.x),mix(c,d,u.x),u.y);\n }\n float fbm2(vec2 p){\n float v=0.0,amp=0.6; mat2 m=mat2(0.86,0.5,-0.5,0.86);\n for(int i=0;i=lanes) break;\n float off=W_BASE_X+float(i)*W_LAYER_GAP,xc=sgn*(off*xS);\n float dx=abs(uv.x-xc),lat=1.0-smoothstep(W_HALF,W_HALF+W_AA,dx),amp=exp(-off*W_SIDE_DECAY);\n float seed=h21(vec2(off,sgn*17.0)),yf2=yf+seed*7.0,ci=floor(yf2),fy=fract(yf2);\n float seg=mix(W_SEG_MIN,W_SEG_MAX,h21(vec2(ci,off*2.3)));\n float spR=h21(vec2(ci,off+sgn*31.0)),seg1=rGate(fy,seg)*step(spR,sp);\n if(ep>0.0){float spR2=h21(vec2(ci*3.1+7.0,off*5.3+sgn*13.0)); float f2=fract(fy+0.5); seg1+=rGate(f2,seg*0.9)*step(spR2,ep);}\n sum+=amp*lat*seg1;\n }\n }\n float span=smoothstep(-3.0,0.0,y)*(1.0-smoothstep(R_V-6.0,R_V,y));\n return uWIntensity*sum*topF*bGain*span;\n}\n\nvoid mainImage(out vec4 fc,in vec2 frag){\n vec2 C=iResolution.xy*.5; float invW=1.0/max(C.x,1.0);\n vec2 sc=(512.0/iResolution.xy)*.4;\n vec2 uv=(frag-C)*sc,off=vec2(uBeamXFrac*iResolution.x*sc.x,uBeamYFrac*iResolution.y*sc.y);\n vec2 uvc = uv - off;\n float a=0.0,b=0.0;\n float basePhase=1.5*PI+uDecay*.5; float tauMin=basePhase-uDecay; float tauMax=basePhase;\n float cx=clamp(uvc.x/(R_H*uHLenFactor),-1.0,1.0),tH=clamp(TWO_PI-acos(cx),tauMin,tauMax);\n for(int k=-TAP_RADIUS;k<=TAP_RADIUS;++k){\n float tu=tH+float(k)*DT_LOCAL,wt=tauWf(tu,tauMin,tauMax); if(wt<=0.0) continue;\n float spd=max(abs(sin(tu)),0.02),u=clamp((basePhase-tu)/max(uDecay,EPS),0.0,1.0),env=pow(1.0-abs(u*2.0-1.0),0.8);\n vec2 p=vec2((R_H*uHLenFactor)*cos(tu),0.0);\n a+=wt*bs(uvc,p,env*spd);\n }\n float yPix=uvc.y,cy=clamp(-yPix/(R_V*uVLenFactor),-1.0,1.0),tV=clamp(TWO_PI-acos(cy),tauMin,tauMax);\n for(int k=-TAP_RADIUS;k<=TAP_RADIUS;++k){\n float tu=tV+float(k)*DT_LOCAL,wt=tauWf(tu,tauMin,tauMax); if(wt<=0.0) continue;\n float yb=(-R_V)*cos(tu),s=clamp(yb/R_V,0.0,1.0),spd=max(abs(sin(tu)),0.02);\n float env=pow(1.0-s,0.6)*spd;\n float cap=1.0-smoothstep(TOP_FADE_START,1.0,s); cap=pow(cap,TOP_FADE_EXP); env*=cap;\n float ph=s/max(FLOW_PERIOD,EPS)+uFlowTime*uFlowSpeed;\n float fl=pow(tri01(ph),FLOW_SHARPNESS);\n env*=mix(1.0-uFlowStrength,1.0,fl);\n float yp=(-R_V*uVLenFactor)*cos(tu),m=pow(smoothstep(FLARE_HEIGHT,0.0,yp),FLARE_EXP),wx=1.0+FLARE_AMOUNT*m;\n vec2 sig=vec2(wx,1.0),p=vec2(0.0,yp);\n float mask=step(0.0,yp);\n b+=wt*bsa(uvc,p,mask*env,sig);\n }\n float sPix=clamp(yPix/R_V,0.0,1.0),topA=pow(1.0-smoothstep(TOP_FADE_START,1.0,sPix),TOP_FADE_EXP);\n float L=a+b*topA;\n float w=vWisps(vec2(uvc.x,yPix),topA);\n float fog=0.0;\n#if FOG_ON\n vec2 fuv=uvc*uFogScale;\n float mAct=step(1.0,length(iMouse.xy)),nx=((iMouse.x-C.x)*invW)*mAct;\n float ax = abs(nx);\n float stMag = mix(ax, pow(ax, FOG_TILT_SHAPE), 0.35);\n float st = sign(nx) * stMag * uTiltScale;\n st = clamp(st, -FOG_TILT_MAX_X, FOG_TILT_MAX_X);\n vec2 dir=normalize(vec2(st,1.0));\n fuv+=uFogTime*uFogFallSpeed*dir;\n vec2 prp=vec2(-dir.y,dir.x);\n fuv+=prp*(0.08*sin(dot(uvc,prp)*0.08+uFogTime*0.9));\n float n=fbm2(fuv+vec2(fbm2(fuv+vec2(7.3,2.1)),fbm2(fuv+vec2(-3.7,5.9)))*0.6);\n n=pow(clamp(n,0.0,1.0),FOG_CONTRAST);\n float pixW = 1.0 / max(iResolution.y, 1.0);\n#ifdef GL_OES_standard_derivatives\n float wL = max(fwidth(L), pixW);\n#else\n float wL = pixW;\n#endif\n float m0=pow(smoothstep(FOG_BEAM_MIN - wL, FOG_BEAM_MAX + wL, L),FOG_MASK_GAMMA);\n float bm=1.0-pow(1.0-m0,FOG_EXPAND_SHAPE); bm=mix(bm*m0,bm,FOG_EDGE_MIX);\n float yP=1.0-smoothstep(HFOG_Y_RADIUS,HFOG_Y_RADIUS+HFOG_Y_SOFT,abs(yPix));\n float nxF=abs((frag.x-C.x)*invW),hE=1.0-smoothstep(HFOG_EDGE_START,HFOG_EDGE_END,nxF); hE=pow(clamp(hE,0.0,1.0),HFOG_EDGE_GAMMA);\n float hW=mix(1.0,hE,clamp(yP,0.0,1.0));\n float bBias=mix(1.0,1.0-sPix,FOG_BOTTOM_BIAS);\n float browserFogIntensity = uFogIntensity;\n browserFogIntensity *= 1.8;\n float radialFade = 1.0 - smoothstep(0.0, 0.7, length(uvc) / 120.0);\n float safariFog = n * browserFogIntensity * bBias * bm * hW * radialFade;\n fog = safariFog;\n#endif\n float LF=L+fog;\n float dith=(h21(frag)-0.5)*(DITHER_STRENGTH/255.0);\n float tone=g(LF+w);\n vec3 col=tone*uColor+dith;\n float alpha=clamp(g(L+w*0.6)+dith*0.6,0.0,1.0);\n float nxE=abs((frag.x-C.x)*invW),xF=pow(clamp(1.0-smoothstep(EDGE_X0,EDGE_X1,nxE),0.0,1.0),EDGE_X_GAMMA);\n float scene=LF+max(0.0,w)*0.5,hi=smoothstep(EDGE_LUMA_T0,EDGE_LUMA_T1,scene);\n float eM=mix(xF,1.0,hi);\n col*=eM; alpha*=eM;\n col*=uFade; alpha*=uFade;\n fc=vec4(col,alpha);\n}\n\nvoid main(){\n vec4 fc;\n mainImage(fc, gl_FragCoord.xy);\n gl_FragColor = fc;\n}\n`;\n\nfunction hexToRGB(hex: string) {\n let c = hex.trim();\n if (c[0] === '#') c = c.slice(1);\n if (c.length === 3)\n c = c\n .split('')\n .map(x => x + x)\n .join('');\n const n = parseInt(c.slice(0, 6), 16) || 0xffffff;\n return { r: ((n >> 16) & 255) / 255, g: ((n >> 8) & 255) / 255, b: (n & 255) / 255 };\n}\n\nexport const LaserFlow: React.FC = ({\n className,\n style,\n wispDensity = 1,\n dpr,\n mouseSmoothTime = 0.0,\n mouseTiltStrength = 0.01,\n horizontalBeamOffset = 0.1,\n verticalBeamOffset = 0.0,\n flowSpeed = 0.35,\n verticalSizing = 2.0,\n horizontalSizing = 0.5,\n fogIntensity = 0.45,\n fogScale = 0.3,\n wispSpeed = 15.0,\n wispIntensity = 5.0,\n flowStrength = 0.25,\n decay = 1.1,\n falloffStart = 1.2,\n fogFallSpeed = 0.6,\n color = '#FF79C6'\n}) => {\n const mountRef = useRef(null);\n const rendererRef = useRef(null);\n const uniformsRef = useRef(null);\n const hasFadedRef = useRef(false);\n const rectRef = useRef(null);\n const baseDprRef = useRef(1);\n const currentDprRef = useRef(1);\n const lastSizeRef = useRef({ width: 0, height: 0, dpr: 0 });\n const fpsSamplesRef = useRef([]);\n const lastFpsCheckRef = useRef(performance.now());\n const emaDtRef = useRef(16.7);\n const pausedRef = useRef(false);\n const inViewRef = useRef(true);\n\n const mouseSmoothTimeRef = useRef(mouseSmoothTime);\n useEffect(() => {\n mouseSmoothTimeRef.current = mouseSmoothTime;\n }, [mouseSmoothTime]);\n\n useEffect(() => {\n const mount = mountRef.current!;\n const renderer = new THREE.WebGLRenderer({\n antialias: false,\n alpha: false,\n depth: false,\n stencil: false,\n powerPreference: 'high-performance',\n premultipliedAlpha: false,\n preserveDrawingBuffer: false,\n failIfMajorPerformanceCaveat: false,\n logarithmicDepthBuffer: false\n });\n rendererRef.current = renderer;\n\n baseDprRef.current = Math.min(dpr ?? (window.devicePixelRatio || 1), 2);\n currentDprRef.current = baseDprRef.current;\n\n renderer.setPixelRatio(currentDprRef.current);\n renderer.shadowMap.enabled = false;\n renderer.outputColorSpace = THREE.SRGBColorSpace;\n renderer.setClearColor(0x000000, 1);\n const canvas = renderer.domElement;\n canvas.style.width = '100%';\n canvas.style.height = '100%';\n canvas.style.display = 'block';\n mount.appendChild(canvas);\n\n const scene = new THREE.Scene();\n const camera = new THREE.OrthographicCamera(-1, 1, 1, -1, 0, 1);\n\n const geometry = new THREE.BufferGeometry();\n geometry.setAttribute('position', new THREE.BufferAttribute(new Float32Array([-1, -1, 0, 3, -1, 0, -1, 3, 0]), 3));\n\n const uniforms = {\n iTime: { value: 0 },\n iResolution: { value: new THREE.Vector3(1, 1, 1) },\n iMouse: { value: new THREE.Vector4(0, 0, 0, 0) },\n uWispDensity: { value: wispDensity },\n uTiltScale: { value: mouseTiltStrength },\n uFlowTime: { value: 0 },\n uFogTime: { value: 0 },\n uBeamXFrac: { value: horizontalBeamOffset },\n uBeamYFrac: { value: verticalBeamOffset },\n uFlowSpeed: { value: flowSpeed },\n uVLenFactor: { value: verticalSizing },\n uHLenFactor: { value: horizontalSizing },\n uFogIntensity: { value: fogIntensity },\n uFogScale: { value: fogScale },\n uWSpeed: { value: wispSpeed },\n uWIntensity: { value: wispIntensity },\n uFlowStrength: { value: flowStrength },\n uDecay: { value: decay },\n uFalloffStart: { value: falloffStart },\n uFogFallSpeed: { value: fogFallSpeed },\n uColor: { value: new THREE.Vector3(1, 1, 1) },\n uFade: { value: hasFadedRef.current ? 1 : 0 }\n };\n uniformsRef.current = uniforms;\n\n const material = new THREE.RawShaderMaterial({\n vertexShader: VERT,\n fragmentShader: FRAG,\n uniforms,\n transparent: false,\n depthTest: false,\n depthWrite: false,\n blending: THREE.NormalBlending\n });\n\n const mesh = new THREE.Mesh(geometry, material);\n mesh.frustumCulled = false;\n scene.add(mesh);\n\n const clock = new THREE.Clock();\n let prevTime = 0;\n let fade = hasFadedRef.current ? 1 : 0;\n\n const mouseTarget = new THREE.Vector2(0, 0);\n const mouseSmooth = new THREE.Vector2(0, 0);\n\n const setSizeNow = () => {\n const w = mount.clientWidth || 1;\n const h = mount.clientHeight || 1;\n const pr = currentDprRef.current;\n\n const last = lastSizeRef.current;\n const sizeChanged = Math.abs(w - last.width) > 0.5 || Math.abs(h - last.height) > 0.5;\n const dprChanged = Math.abs(pr - last.dpr) > 0.01;\n if (!sizeChanged && !dprChanged) return;\n\n lastSizeRef.current = { width: w, height: h, dpr: pr };\n renderer.setPixelRatio(pr);\n renderer.setSize(w, h, false);\n uniforms.iResolution.value.set(w * pr, h * pr, pr);\n rectRef.current = canvas.getBoundingClientRect();\n\n if (!pausedRef.current) {\n renderer.render(scene, camera);\n }\n };\n\n let resizeRaf = 0;\n const scheduleResize = () => {\n if (resizeRaf) cancelAnimationFrame(resizeRaf);\n resizeRaf = requestAnimationFrame(setSizeNow);\n };\n\n setSizeNow();\n const ro = new ResizeObserver(scheduleResize);\n ro.observe(mount);\n\n const io = new IntersectionObserver(\n entries => {\n inViewRef.current = entries[0]?.isIntersecting ?? true;\n },\n { root: null, threshold: 0 }\n );\n io.observe(mount);\n\n const onVis = () => {\n pausedRef.current = document.hidden;\n };\n document.addEventListener('visibilitychange', onVis, { passive: true });\n\n const updateMouse = (clientX: number, clientY: number) => {\n const rect = rectRef.current;\n if (!rect) return;\n const x = clientX - rect.left;\n const y = clientY - rect.top;\n const ratio = currentDprRef.current;\n const hb = rect.height * ratio;\n mouseTarget.set(x * ratio, hb - y * ratio);\n };\n const onMove = (ev: PointerEvent | MouseEvent) => updateMouse(ev.clientX, ev.clientY);\n const onLeave = () => mouseTarget.set(0, 0);\n canvas.addEventListener('pointermove', onMove as any, { passive: true });\n canvas.addEventListener('pointerdown', onMove as any, { passive: true });\n canvas.addEventListener('pointerenter', onMove as any, { passive: true });\n canvas.addEventListener('pointerleave', onLeave as any, { passive: true });\n\n const onCtxLost = (e: Event) => {\n e.preventDefault();\n pausedRef.current = true;\n };\n const onCtxRestored = () => {\n pausedRef.current = false;\n scheduleResize();\n };\n canvas.addEventListener('webglcontextlost', onCtxLost, false);\n canvas.addEventListener('webglcontextrestored', onCtxRestored, false);\n\n let raf = 0;\n\n const clamp = (v: number, lo: number, hi: number) => Math.max(lo, Math.min(hi, v));\n const dprFloor = 0.6;\n const lowerThresh = 50;\n const upperThresh = 58;\n let lastDprChange = 0;\n const dprChangeCooldown = 2000;\n\n const adjustDprIfNeeded = (now: number) => {\n const elapsed = now - lastFpsCheckRef.current;\n if (elapsed < 750) return;\n\n const samples = fpsSamplesRef.current;\n if (samples.length === 0) {\n lastFpsCheckRef.current = now;\n return;\n }\n const avgFps = samples.reduce((a, b) => a + b, 0) / samples.length;\n\n let next = currentDprRef.current;\n const base = baseDprRef.current;\n\n if (avgFps < lowerThresh) {\n next = clamp(currentDprRef.current * 0.85, dprFloor, base);\n } else if (avgFps > upperThresh && currentDprRef.current < base) {\n next = clamp(currentDprRef.current * 1.1, dprFloor, base);\n }\n\n if (Math.abs(next - currentDprRef.current) > 0.01 && now - lastDprChange > dprChangeCooldown) {\n currentDprRef.current = next;\n lastDprChange = now;\n setSizeNow();\n }\n\n fpsSamplesRef.current = [];\n lastFpsCheckRef.current = now;\n };\n\n const animate = () => {\n raf = requestAnimationFrame(animate);\n if (pausedRef.current || !inViewRef.current) return;\n\n const t = clock.getElapsedTime();\n const dt = Math.max(0, t - prevTime);\n prevTime = t;\n\n const dtMs = dt * 1000;\n emaDtRef.current = emaDtRef.current * 0.9 + dtMs * 0.1;\n const instFps = 1000 / Math.max(1, emaDtRef.current);\n fpsSamplesRef.current.push(instFps);\n\n uniforms.iTime.value = t;\n\n const cdt = Math.min(0.033, Math.max(0.001, dt));\n (uniforms.uFlowTime.value as number) += cdt;\n (uniforms.uFogTime.value as number) += cdt;\n\n if (!hasFadedRef.current) {\n const fadeDur = 1.0;\n fade = Math.min(1, fade + cdt / fadeDur);\n uniforms.uFade.value = fade;\n if (fade >= 1) hasFadedRef.current = true;\n }\n\n const tau = Math.max(1e-3, mouseSmoothTimeRef.current);\n const alpha = 1 - Math.exp(-cdt / tau);\n mouseSmooth.lerp(mouseTarget, alpha);\n uniforms.iMouse.value.set(mouseSmooth.x, mouseSmooth.y, 0, 0);\n\n renderer.render(scene, camera);\n\n adjustDprIfNeeded(performance.now());\n };\n\n animate();\n\n return () => {\n cancelAnimationFrame(raf);\n if (resizeRaf) cancelAnimationFrame(resizeRaf);\n\n ro.disconnect();\n io.disconnect();\n document.removeEventListener('visibilitychange', onVis);\n canvas.removeEventListener('pointermove', onMove as any);\n canvas.removeEventListener('pointerdown', onMove as any);\n canvas.removeEventListener('pointerenter', onMove as any);\n canvas.removeEventListener('pointerleave', onLeave as any);\n canvas.removeEventListener('webglcontextlost', onCtxLost);\n canvas.removeEventListener('webglcontextrestored', onCtxRestored);\n\n scene.clear();\n geometry.dispose();\n material.dispose();\n renderer.dispose();\n renderer.forceContextLoss();\n if (mount.contains(canvas)) mount.removeChild(canvas);\n };\n }, [dpr]);\n\n useEffect(() => {\n const uniforms = uniformsRef.current;\n if (!uniforms) return;\n\n uniforms.uWispDensity.value = wispDensity;\n uniforms.uTiltScale.value = mouseTiltStrength;\n uniforms.uBeamXFrac.value = horizontalBeamOffset;\n uniforms.uBeamYFrac.value = verticalBeamOffset;\n uniforms.uFlowSpeed.value = flowSpeed;\n uniforms.uVLenFactor.value = verticalSizing;\n uniforms.uHLenFactor.value = horizontalSizing;\n uniforms.uFogIntensity.value = fogIntensity;\n uniforms.uFogScale.value = fogScale;\n uniforms.uWSpeed.value = wispSpeed;\n uniforms.uWIntensity.value = wispIntensity;\n uniforms.uFlowStrength.value = flowStrength;\n uniforms.uDecay.value = decay;\n uniforms.uFalloffStart.value = falloffStart;\n uniforms.uFogFallSpeed.value = fogFallSpeed;\n\n const { r, g, b } = hexToRGB(color || '#FFFFFF');\n uniforms.uColor.value.set(r, g, b);\n }, [\n wispDensity,\n mouseTiltStrength,\n horizontalBeamOffset,\n verticalBeamOffset,\n flowSpeed,\n verticalSizing,\n horizontalSizing,\n fogIntensity,\n fogScale,\n wispSpeed,\n wispIntensity,\n flowStrength,\n decay,\n falloffStart,\n fogFallSpeed,\n color\n ]);\n\n return
;\n};\n\nexport default LaserFlow;\n" } ], "registryDependencies": [], diff --git a/public/r/Particles-JS-CSS.json b/public/r/Particles-JS-CSS.json index 5c6dce316..725aac9df 100644 --- a/public/r/Particles-JS-CSS.json +++ b/public/r/Particles-JS-CSS.json @@ -13,7 +13,7 @@ { "type": "registry:component", "path": "Particles/Particles.jsx", - "content": "import { useEffect, useRef } from 'react';\nimport { Renderer, Camera, Geometry, Program, Mesh } from 'ogl';\n\nimport './Particles.css';\n\nconst defaultColors = ['#ffffff', '#ffffff', '#ffffff'];\n\nconst hexToRgb = hex => {\n hex = hex.replace(/^#/, '');\n if (hex.length === 3) {\n hex = hex\n .split('')\n .map(c => c + c)\n .join('');\n }\n const int = parseInt(hex, 16);\n const r = ((int >> 16) & 255) / 255;\n const g = ((int >> 8) & 255) / 255;\n const b = (int & 255) / 255;\n return [r, g, b];\n};\n\nconst vertex = /* glsl */ `\n attribute vec3 position;\n attribute vec4 random;\n attribute vec3 color;\n \n uniform mat4 modelMatrix;\n uniform mat4 viewMatrix;\n uniform mat4 projectionMatrix;\n uniform float uTime;\n uniform float uSpread;\n uniform float uBaseSize;\n uniform float uSizeRandomness;\n \n varying vec4 vRandom;\n varying vec3 vColor;\n \n void main() {\n vRandom = random;\n vColor = color;\n \n vec3 pos = position * uSpread;\n pos.z *= 10.0;\n \n vec4 mPos = modelMatrix * vec4(pos, 1.0);\n float t = uTime;\n mPos.x += sin(t * random.z + 6.28 * random.w) * mix(0.1, 1.5, random.x);\n mPos.y += sin(t * random.y + 6.28 * random.x) * mix(0.1, 1.5, random.w);\n mPos.z += sin(t * random.w + 6.28 * random.y) * mix(0.1, 1.5, random.z);\n \n vec4 mvPos = viewMatrix * mPos;\n\n if (uSizeRandomness == 0.0) {\n gl_PointSize = uBaseSize;\n } else {\n gl_PointSize = (uBaseSize * (1.0 + uSizeRandomness * (random.x - 0.5))) / length(mvPos.xyz);\n }\n\n gl_Position = projectionMatrix * mvPos;\n }\n`;\n\nconst fragment = /* glsl */ `\n precision highp float;\n \n uniform float uTime;\n uniform float uAlphaParticles;\n varying vec4 vRandom;\n varying vec3 vColor;\n \n void main() {\n vec2 uv = gl_PointCoord.xy;\n float d = length(uv - vec2(0.5));\n \n if(uAlphaParticles < 0.5) {\n if(d > 0.5) {\n discard;\n }\n gl_FragColor = vec4(vColor + 0.2 * sin(uv.yxx + uTime + vRandom.y * 6.28), 1.0);\n } else {\n float circle = smoothstep(0.5, 0.4, d) * 0.8;\n gl_FragColor = vec4(vColor + 0.2 * sin(uv.yxx + uTime + vRandom.y * 6.28), circle);\n }\n }\n`;\n\nconst Particles = ({\n particleCount = 200,\n particleSpread = 10,\n speed = 0.1,\n particleColors,\n moveParticlesOnHover = false,\n particleHoverFactor = 1,\n alphaParticles = false,\n particleBaseSize = 100,\n sizeRandomness = 1,\n cameraDistance = 20,\n disableRotation = false,\n pixelRatio = 1,\n className\n}) => {\n const containerRef = useRef(null);\n const mouseRef = useRef({ x: 0, y: 0 });\n\n useEffect(() => {\n const container = containerRef.current;\n if (!container) return;\n\n const renderer = new Renderer({\n dpr: pixelRatio,\n depth: false,\n alpha: true\n });\n const gl = renderer.gl;\n container.appendChild(gl.canvas);\n gl.clearColor(0, 0, 0, 0);\n\n const camera = new Camera(gl, { fov: 15 });\n camera.position.set(0, 0, cameraDistance);\n\n const resize = () => {\n const width = container.clientWidth;\n const height = container.clientHeight;\n renderer.setSize(width, height);\n camera.perspective({ aspect: gl.canvas.width / gl.canvas.height });\n };\n window.addEventListener('resize', resize, false);\n resize();\n\n const handleMouseMove = e => {\n const rect = container.getBoundingClientRect();\n const x = ((e.clientX - rect.left) / rect.width) * 2 - 1;\n const y = -(((e.clientY - rect.top) / rect.height) * 2 - 1);\n mouseRef.current = { x, y };\n };\n\n if (moveParticlesOnHover) {\n container.addEventListener('mousemove', handleMouseMove);\n }\n\n const count = particleCount;\n const positions = new Float32Array(count * 3);\n const randoms = new Float32Array(count * 4);\n const colors = new Float32Array(count * 3);\n const palette = particleColors && particleColors.length > 0 ? particleColors : defaultColors;\n\n for (let i = 0; i < count; i++) {\n let x, y, z, len;\n do {\n x = Math.random() * 2 - 1;\n y = Math.random() * 2 - 1;\n z = Math.random() * 2 - 1;\n len = x * x + y * y + z * z;\n } while (len > 1 || len === 0);\n const r = Math.cbrt(Math.random());\n positions.set([x * r, y * r, z * r], i * 3);\n randoms.set([Math.random(), Math.random(), Math.random(), Math.random()], i * 4);\n const col = hexToRgb(palette[Math.floor(Math.random() * palette.length)]);\n colors.set(col, i * 3);\n }\n\n const geometry = new Geometry(gl, {\n position: { size: 3, data: positions },\n random: { size: 4, data: randoms },\n color: { size: 3, data: colors }\n });\n\n const program = new Program(gl, {\n vertex,\n fragment,\n uniforms: {\n uTime: { value: 0 },\n uSpread: { value: particleSpread },\n uBaseSize: { value: particleBaseSize * pixelRatio },\n uSizeRandomness: { value: sizeRandomness },\n uAlphaParticles: { value: alphaParticles ? 1 : 0 }\n },\n transparent: true,\n depthTest: false\n });\n\n const particles = new Mesh(gl, { mode: gl.POINTS, geometry, program });\n\n let animationFrameId;\n let lastTime = performance.now();\n let elapsed = 0;\n\n const update = t => {\n animationFrameId = requestAnimationFrame(update);\n const delta = t - lastTime;\n lastTime = t;\n elapsed += delta * speed;\n\n program.uniforms.uTime.value = elapsed * 0.001;\n\n if (moveParticlesOnHover) {\n particles.position.x = -mouseRef.current.x * particleHoverFactor;\n particles.position.y = -mouseRef.current.y * particleHoverFactor;\n } else {\n particles.position.x = 0;\n particles.position.y = 0;\n }\n\n if (!disableRotation) {\n particles.rotation.x = Math.sin(elapsed * 0.0002) * 0.1;\n particles.rotation.y = Math.cos(elapsed * 0.0005) * 0.15;\n particles.rotation.z += 0.01 * speed;\n }\n\n renderer.render({ scene: particles, camera });\n };\n\n animationFrameId = requestAnimationFrame(update);\n\n return () => {\n window.removeEventListener('resize', resize);\n if (moveParticlesOnHover) {\n container.removeEventListener('mousemove', handleMouseMove);\n }\n cancelAnimationFrame(animationFrameId);\n if (container.contains(gl.canvas)) {\n container.removeChild(gl.canvas);\n }\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [\n particleCount,\n particleSpread,\n speed,\n moveParticlesOnHover,\n particleHoverFactor,\n alphaParticles,\n particleBaseSize,\n sizeRandomness,\n cameraDistance,\n disableRotation,\n pixelRatio\n ]);\n\n return
;\n};\n\nexport default Particles;\n" + "content": "import { useEffect, useRef } from 'react';\nimport { Renderer, Camera, Geometry, Program, Mesh } from 'ogl';\n\nimport './Particles.css';\n\nconst defaultColors = ['#ffffff', '#ffffff', '#ffffff'];\n\nconst hexToRgb = hex => {\n hex = hex.replace(/^#/, '');\n if (hex.length === 3) {\n hex = hex\n .split('')\n .map(c => c + c)\n .join('');\n }\n const int = parseInt(hex.slice(0, 6), 16);\n const r = ((int >> 16) & 255) / 255;\n const g = ((int >> 8) & 255) / 255;\n const b = (int & 255) / 255;\n return [r, g, b];\n};\n\nconst vertex = /* glsl */ `\n attribute vec3 position;\n attribute vec4 random;\n attribute vec3 color;\n \n uniform mat4 modelMatrix;\n uniform mat4 viewMatrix;\n uniform mat4 projectionMatrix;\n uniform float uTime;\n uniform float uSpread;\n uniform float uBaseSize;\n uniform float uSizeRandomness;\n \n varying vec4 vRandom;\n varying vec3 vColor;\n \n void main() {\n vRandom = random;\n vColor = color;\n \n vec3 pos = position * uSpread;\n pos.z *= 10.0;\n \n vec4 mPos = modelMatrix * vec4(pos, 1.0);\n float t = uTime;\n mPos.x += sin(t * random.z + 6.28 * random.w) * mix(0.1, 1.5, random.x);\n mPos.y += sin(t * random.y + 6.28 * random.x) * mix(0.1, 1.5, random.w);\n mPos.z += sin(t * random.w + 6.28 * random.y) * mix(0.1, 1.5, random.z);\n \n vec4 mvPos = viewMatrix * mPos;\n\n if (uSizeRandomness == 0.0) {\n gl_PointSize = uBaseSize;\n } else {\n gl_PointSize = (uBaseSize * (1.0 + uSizeRandomness * (random.x - 0.5))) / length(mvPos.xyz);\n }\n\n gl_Position = projectionMatrix * mvPos;\n }\n`;\n\nconst fragment = /* glsl */ `\n precision highp float;\n \n uniform float uTime;\n uniform float uAlphaParticles;\n varying vec4 vRandom;\n varying vec3 vColor;\n \n void main() {\n vec2 uv = gl_PointCoord.xy;\n float d = length(uv - vec2(0.5));\n \n if(uAlphaParticles < 0.5) {\n if(d > 0.5) {\n discard;\n }\n gl_FragColor = vec4(vColor + 0.2 * sin(uv.yxx + uTime + vRandom.y * 6.28), 1.0);\n } else {\n float circle = smoothstep(0.5, 0.4, d) * 0.8;\n gl_FragColor = vec4(vColor + 0.2 * sin(uv.yxx + uTime + vRandom.y * 6.28), circle);\n }\n }\n`;\n\nconst Particles = ({\n particleCount = 200,\n particleSpread = 10,\n speed = 0.1,\n particleColors,\n moveParticlesOnHover = false,\n particleHoverFactor = 1,\n alphaParticles = false,\n particleBaseSize = 100,\n sizeRandomness = 1,\n cameraDistance = 20,\n disableRotation = false,\n pixelRatio = 1,\n className\n}) => {\n const containerRef = useRef(null);\n const mouseRef = useRef({ x: 0, y: 0 });\n\n useEffect(() => {\n const container = containerRef.current;\n if (!container) return;\n\n const renderer = new Renderer({\n dpr: pixelRatio,\n depth: false,\n alpha: true\n });\n const gl = renderer.gl;\n container.appendChild(gl.canvas);\n gl.clearColor(0, 0, 0, 0);\n\n const camera = new Camera(gl, { fov: 15 });\n camera.position.set(0, 0, cameraDistance);\n\n const resize = () => {\n const width = container.clientWidth;\n const height = container.clientHeight;\n renderer.setSize(width, height);\n camera.perspective({ aspect: gl.canvas.width / gl.canvas.height });\n };\n window.addEventListener('resize', resize, false);\n resize();\n\n const handleMouseMove = e => {\n const rect = container.getBoundingClientRect();\n const x = ((e.clientX - rect.left) / rect.width) * 2 - 1;\n const y = -(((e.clientY - rect.top) / rect.height) * 2 - 1);\n mouseRef.current = { x, y };\n };\n\n if (moveParticlesOnHover) {\n container.addEventListener('mousemove', handleMouseMove);\n }\n\n const count = particleCount;\n const positions = new Float32Array(count * 3);\n const randoms = new Float32Array(count * 4);\n const colors = new Float32Array(count * 3);\n const palette = particleColors && particleColors.length > 0 ? particleColors : defaultColors;\n\n for (let i = 0; i < count; i++) {\n let x, y, z, len;\n do {\n x = Math.random() * 2 - 1;\n y = Math.random() * 2 - 1;\n z = Math.random() * 2 - 1;\n len = x * x + y * y + z * z;\n } while (len > 1 || len === 0);\n const r = Math.cbrt(Math.random());\n positions.set([x * r, y * r, z * r], i * 3);\n randoms.set([Math.random(), Math.random(), Math.random(), Math.random()], i * 4);\n const col = hexToRgb(palette[Math.floor(Math.random() * palette.length)]);\n colors.set(col, i * 3);\n }\n\n const geometry = new Geometry(gl, {\n position: { size: 3, data: positions },\n random: { size: 4, data: randoms },\n color: { size: 3, data: colors }\n });\n\n const program = new Program(gl, {\n vertex,\n fragment,\n uniforms: {\n uTime: { value: 0 },\n uSpread: { value: particleSpread },\n uBaseSize: { value: particleBaseSize * pixelRatio },\n uSizeRandomness: { value: sizeRandomness },\n uAlphaParticles: { value: alphaParticles ? 1 : 0 }\n },\n transparent: true,\n depthTest: false\n });\n\n const particles = new Mesh(gl, { mode: gl.POINTS, geometry, program });\n\n let animationFrameId;\n let lastTime = performance.now();\n let elapsed = 0;\n\n const update = t => {\n animationFrameId = requestAnimationFrame(update);\n const delta = t - lastTime;\n lastTime = t;\n elapsed += delta * speed;\n\n program.uniforms.uTime.value = elapsed * 0.001;\n\n if (moveParticlesOnHover) {\n particles.position.x = -mouseRef.current.x * particleHoverFactor;\n particles.position.y = -mouseRef.current.y * particleHoverFactor;\n } else {\n particles.position.x = 0;\n particles.position.y = 0;\n }\n\n if (!disableRotation) {\n particles.rotation.x = Math.sin(elapsed * 0.0002) * 0.1;\n particles.rotation.y = Math.cos(elapsed * 0.0005) * 0.15;\n particles.rotation.z += 0.01 * speed;\n }\n\n renderer.render({ scene: particles, camera });\n };\n\n animationFrameId = requestAnimationFrame(update);\n\n return () => {\n window.removeEventListener('resize', resize);\n if (moveParticlesOnHover) {\n container.removeEventListener('mousemove', handleMouseMove);\n }\n cancelAnimationFrame(animationFrameId);\n if (container.contains(gl.canvas)) {\n container.removeChild(gl.canvas);\n }\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [\n particleCount,\n particleSpread,\n speed,\n moveParticlesOnHover,\n particleHoverFactor,\n alphaParticles,\n particleBaseSize,\n sizeRandomness,\n cameraDistance,\n disableRotation,\n pixelRatio\n ]);\n\n return
;\n};\n\nexport default Particles;\n" } ], "registryDependencies": [], diff --git a/public/r/Particles-JS-TW.json b/public/r/Particles-JS-TW.json index 0f98e3a17..6f62cbd92 100644 --- a/public/r/Particles-JS-TW.json +++ b/public/r/Particles-JS-TW.json @@ -8,7 +8,7 @@ { "type": "registry:component", "path": "Particles/Particles.jsx", - "content": "import { useEffect, useRef } from 'react';\nimport { Renderer, Camera, Geometry, Program, Mesh } from 'ogl';\n\nconst defaultColors = ['#ffffff', '#ffffff', '#ffffff'];\n\nconst hexToRgb = hex => {\n hex = hex.replace(/^#/, '');\n if (hex.length === 3) {\n hex = hex\n .split('')\n .map(c => c + c)\n .join('');\n }\n const int = parseInt(hex, 16);\n const r = ((int >> 16) & 255) / 255;\n const g = ((int >> 8) & 255) / 255;\n const b = (int & 255) / 255;\n return [r, g, b];\n};\n\nconst vertex = /* glsl */ `\n attribute vec3 position;\n attribute vec4 random;\n attribute vec3 color;\n \n uniform mat4 modelMatrix;\n uniform mat4 viewMatrix;\n uniform mat4 projectionMatrix;\n uniform float uTime;\n uniform float uSpread;\n uniform float uBaseSize;\n uniform float uSizeRandomness;\n \n varying vec4 vRandom;\n varying vec3 vColor;\n \n void main() {\n vRandom = random;\n vColor = color;\n \n vec3 pos = position * uSpread;\n pos.z *= 10.0;\n \n vec4 mPos = modelMatrix * vec4(pos, 1.0);\n float t = uTime;\n mPos.x += sin(t * random.z + 6.28 * random.w) * mix(0.1, 1.5, random.x);\n mPos.y += sin(t * random.y + 6.28 * random.x) * mix(0.1, 1.5, random.w);\n mPos.z += sin(t * random.w + 6.28 * random.y) * mix(0.1, 1.5, random.z);\n \n vec4 mvPos = viewMatrix * mPos;\n\n if (uSizeRandomness == 0.0) {\n gl_PointSize = uBaseSize;\n } else {\n gl_PointSize = (uBaseSize * (1.0 + uSizeRandomness * (random.x - 0.5))) / length(mvPos.xyz);\n }\n\n gl_Position = projectionMatrix * mvPos;\n }\n`;\n\nconst fragment = /* glsl */ `\n precision highp float;\n \n uniform float uTime;\n uniform float uAlphaParticles;\n varying vec4 vRandom;\n varying vec3 vColor;\n \n void main() {\n vec2 uv = gl_PointCoord.xy;\n float d = length(uv - vec2(0.5));\n \n if(uAlphaParticles < 0.5) {\n if(d > 0.5) {\n discard;\n }\n gl_FragColor = vec4(vColor + 0.2 * sin(uv.yxx + uTime + vRandom.y * 6.28), 1.0);\n } else {\n float circle = smoothstep(0.5, 0.4, d) * 0.8;\n gl_FragColor = vec4(vColor + 0.2 * sin(uv.yxx + uTime + vRandom.y * 6.28), circle);\n }\n }\n`;\n\nconst Particles = ({\n particleCount = 200,\n particleSpread = 10,\n speed = 0.1,\n particleColors,\n moveParticlesOnHover = false,\n particleHoverFactor = 1,\n alphaParticles = false,\n particleBaseSize = 100,\n sizeRandomness = 1,\n cameraDistance = 20,\n disableRotation = false,\n pixelRatio = 1,\n className\n}) => {\n const containerRef = useRef(null);\n const mouseRef = useRef({ x: 0, y: 0 });\n\n useEffect(() => {\n const container = containerRef.current;\n if (!container) return;\n\n const renderer = new Renderer({\n dpr: pixelRatio,\n depth: false,\n alpha: true\n });\n const gl = renderer.gl;\n container.appendChild(gl.canvas);\n gl.clearColor(0, 0, 0, 0);\n\n const camera = new Camera(gl, { fov: 15 });\n camera.position.set(0, 0, cameraDistance);\n\n const resize = () => {\n const width = container.clientWidth;\n const height = container.clientHeight;\n renderer.setSize(width, height);\n camera.perspective({ aspect: gl.canvas.width / gl.canvas.height });\n };\n window.addEventListener('resize', resize, false);\n resize();\n\n const handleMouseMove = e => {\n const rect = container.getBoundingClientRect();\n const x = ((e.clientX - rect.left) / rect.width) * 2 - 1;\n const y = -(((e.clientY - rect.top) / rect.height) * 2 - 1);\n mouseRef.current = { x, y };\n };\n\n if (moveParticlesOnHover) {\n container.addEventListener('mousemove', handleMouseMove);\n }\n\n const count = particleCount;\n const positions = new Float32Array(count * 3);\n const randoms = new Float32Array(count * 4);\n const colors = new Float32Array(count * 3);\n const palette = particleColors && particleColors.length > 0 ? particleColors : defaultColors;\n\n for (let i = 0; i < count; i++) {\n let x, y, z, len;\n do {\n x = Math.random() * 2 - 1;\n y = Math.random() * 2 - 1;\n z = Math.random() * 2 - 1;\n len = x * x + y * y + z * z;\n } while (len > 1 || len === 0);\n const r = Math.cbrt(Math.random());\n positions.set([x * r, y * r, z * r], i * 3);\n randoms.set([Math.random(), Math.random(), Math.random(), Math.random()], i * 4);\n const col = hexToRgb(palette[Math.floor(Math.random() * palette.length)]);\n colors.set(col, i * 3);\n }\n\n const geometry = new Geometry(gl, {\n position: { size: 3, data: positions },\n random: { size: 4, data: randoms },\n color: { size: 3, data: colors }\n });\n\n const program = new Program(gl, {\n vertex,\n fragment,\n uniforms: {\n uTime: { value: 0 },\n uSpread: { value: particleSpread },\n uBaseSize: { value: particleBaseSize * pixelRatio },\n uSizeRandomness: { value: sizeRandomness },\n uAlphaParticles: { value: alphaParticles ? 1 : 0 }\n },\n transparent: true,\n depthTest: false\n });\n\n const particles = new Mesh(gl, { mode: gl.POINTS, geometry, program });\n\n let animationFrameId;\n let lastTime = performance.now();\n let elapsed = 0;\n\n const update = t => {\n animationFrameId = requestAnimationFrame(update);\n const delta = t - lastTime;\n lastTime = t;\n elapsed += delta * speed;\n\n program.uniforms.uTime.value = elapsed * 0.001;\n\n if (moveParticlesOnHover) {\n particles.position.x = -mouseRef.current.x * particleHoverFactor;\n particles.position.y = -mouseRef.current.y * particleHoverFactor;\n } else {\n particles.position.x = 0;\n particles.position.y = 0;\n }\n\n if (!disableRotation) {\n particles.rotation.x = Math.sin(elapsed * 0.0002) * 0.1;\n particles.rotation.y = Math.cos(elapsed * 0.0005) * 0.15;\n particles.rotation.z += 0.01 * speed;\n }\n\n renderer.render({ scene: particles, camera });\n };\n\n animationFrameId = requestAnimationFrame(update);\n\n return () => {\n window.removeEventListener('resize', resize);\n if (moveParticlesOnHover) {\n container.removeEventListener('mousemove', handleMouseMove);\n }\n cancelAnimationFrame(animationFrameId);\n if (container.contains(gl.canvas)) {\n container.removeChild(gl.canvas);\n }\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [\n particleCount,\n particleSpread,\n speed,\n moveParticlesOnHover,\n particleHoverFactor,\n alphaParticles,\n particleBaseSize,\n sizeRandomness,\n cameraDistance,\n disableRotation,\n pixelRatio\n ]);\n\n return
;\n};\n\nexport default Particles;\n" + "content": "import { useEffect, useRef } from 'react';\nimport { Renderer, Camera, Geometry, Program, Mesh } from 'ogl';\n\nconst defaultColors = ['#ffffff', '#ffffff', '#ffffff'];\n\nconst hexToRgb = hex => {\n hex = hex.replace(/^#/, '');\n if (hex.length === 3) {\n hex = hex\n .split('')\n .map(c => c + c)\n .join('');\n }\n const int = parseInt(hex.slice(0, 6), 16);\n const r = ((int >> 16) & 255) / 255;\n const g = ((int >> 8) & 255) / 255;\n const b = (int & 255) / 255;\n return [r, g, b];\n};\n\nconst vertex = /* glsl */ `\n attribute vec3 position;\n attribute vec4 random;\n attribute vec3 color;\n \n uniform mat4 modelMatrix;\n uniform mat4 viewMatrix;\n uniform mat4 projectionMatrix;\n uniform float uTime;\n uniform float uSpread;\n uniform float uBaseSize;\n uniform float uSizeRandomness;\n \n varying vec4 vRandom;\n varying vec3 vColor;\n \n void main() {\n vRandom = random;\n vColor = color;\n \n vec3 pos = position * uSpread;\n pos.z *= 10.0;\n \n vec4 mPos = modelMatrix * vec4(pos, 1.0);\n float t = uTime;\n mPos.x += sin(t * random.z + 6.28 * random.w) * mix(0.1, 1.5, random.x);\n mPos.y += sin(t * random.y + 6.28 * random.x) * mix(0.1, 1.5, random.w);\n mPos.z += sin(t * random.w + 6.28 * random.y) * mix(0.1, 1.5, random.z);\n \n vec4 mvPos = viewMatrix * mPos;\n\n if (uSizeRandomness == 0.0) {\n gl_PointSize = uBaseSize;\n } else {\n gl_PointSize = (uBaseSize * (1.0 + uSizeRandomness * (random.x - 0.5))) / length(mvPos.xyz);\n }\n\n gl_Position = projectionMatrix * mvPos;\n }\n`;\n\nconst fragment = /* glsl */ `\n precision highp float;\n \n uniform float uTime;\n uniform float uAlphaParticles;\n varying vec4 vRandom;\n varying vec3 vColor;\n \n void main() {\n vec2 uv = gl_PointCoord.xy;\n float d = length(uv - vec2(0.5));\n \n if(uAlphaParticles < 0.5) {\n if(d > 0.5) {\n discard;\n }\n gl_FragColor = vec4(vColor + 0.2 * sin(uv.yxx + uTime + vRandom.y * 6.28), 1.0);\n } else {\n float circle = smoothstep(0.5, 0.4, d) * 0.8;\n gl_FragColor = vec4(vColor + 0.2 * sin(uv.yxx + uTime + vRandom.y * 6.28), circle);\n }\n }\n`;\n\nconst Particles = ({\n particleCount = 200,\n particleSpread = 10,\n speed = 0.1,\n particleColors,\n moveParticlesOnHover = false,\n particleHoverFactor = 1,\n alphaParticles = false,\n particleBaseSize = 100,\n sizeRandomness = 1,\n cameraDistance = 20,\n disableRotation = false,\n pixelRatio = 1,\n className\n}) => {\n const containerRef = useRef(null);\n const mouseRef = useRef({ x: 0, y: 0 });\n\n useEffect(() => {\n const container = containerRef.current;\n if (!container) return;\n\n const renderer = new Renderer({\n dpr: pixelRatio,\n depth: false,\n alpha: true\n });\n const gl = renderer.gl;\n container.appendChild(gl.canvas);\n gl.clearColor(0, 0, 0, 0);\n\n const camera = new Camera(gl, { fov: 15 });\n camera.position.set(0, 0, cameraDistance);\n\n const resize = () => {\n const width = container.clientWidth;\n const height = container.clientHeight;\n renderer.setSize(width, height);\n camera.perspective({ aspect: gl.canvas.width / gl.canvas.height });\n };\n window.addEventListener('resize', resize, false);\n resize();\n\n const handleMouseMove = e => {\n const rect = container.getBoundingClientRect();\n const x = ((e.clientX - rect.left) / rect.width) * 2 - 1;\n const y = -(((e.clientY - rect.top) / rect.height) * 2 - 1);\n mouseRef.current = { x, y };\n };\n\n if (moveParticlesOnHover) {\n container.addEventListener('mousemove', handleMouseMove);\n }\n\n const count = particleCount;\n const positions = new Float32Array(count * 3);\n const randoms = new Float32Array(count * 4);\n const colors = new Float32Array(count * 3);\n const palette = particleColors && particleColors.length > 0 ? particleColors : defaultColors;\n\n for (let i = 0; i < count; i++) {\n let x, y, z, len;\n do {\n x = Math.random() * 2 - 1;\n y = Math.random() * 2 - 1;\n z = Math.random() * 2 - 1;\n len = x * x + y * y + z * z;\n } while (len > 1 || len === 0);\n const r = Math.cbrt(Math.random());\n positions.set([x * r, y * r, z * r], i * 3);\n randoms.set([Math.random(), Math.random(), Math.random(), Math.random()], i * 4);\n const col = hexToRgb(palette[Math.floor(Math.random() * palette.length)]);\n colors.set(col, i * 3);\n }\n\n const geometry = new Geometry(gl, {\n position: { size: 3, data: positions },\n random: { size: 4, data: randoms },\n color: { size: 3, data: colors }\n });\n\n const program = new Program(gl, {\n vertex,\n fragment,\n uniforms: {\n uTime: { value: 0 },\n uSpread: { value: particleSpread },\n uBaseSize: { value: particleBaseSize * pixelRatio },\n uSizeRandomness: { value: sizeRandomness },\n uAlphaParticles: { value: alphaParticles ? 1 : 0 }\n },\n transparent: true,\n depthTest: false\n });\n\n const particles = new Mesh(gl, { mode: gl.POINTS, geometry, program });\n\n let animationFrameId;\n let lastTime = performance.now();\n let elapsed = 0;\n\n const update = t => {\n animationFrameId = requestAnimationFrame(update);\n const delta = t - lastTime;\n lastTime = t;\n elapsed += delta * speed;\n\n program.uniforms.uTime.value = elapsed * 0.001;\n\n if (moveParticlesOnHover) {\n particles.position.x = -mouseRef.current.x * particleHoverFactor;\n particles.position.y = -mouseRef.current.y * particleHoverFactor;\n } else {\n particles.position.x = 0;\n particles.position.y = 0;\n }\n\n if (!disableRotation) {\n particles.rotation.x = Math.sin(elapsed * 0.0002) * 0.1;\n particles.rotation.y = Math.cos(elapsed * 0.0005) * 0.15;\n particles.rotation.z += 0.01 * speed;\n }\n\n renderer.render({ scene: particles, camera });\n };\n\n animationFrameId = requestAnimationFrame(update);\n\n return () => {\n window.removeEventListener('resize', resize);\n if (moveParticlesOnHover) {\n container.removeEventListener('mousemove', handleMouseMove);\n }\n cancelAnimationFrame(animationFrameId);\n if (container.contains(gl.canvas)) {\n container.removeChild(gl.canvas);\n }\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [\n particleCount,\n particleSpread,\n speed,\n moveParticlesOnHover,\n particleHoverFactor,\n alphaParticles,\n particleBaseSize,\n sizeRandomness,\n cameraDistance,\n disableRotation,\n pixelRatio\n ]);\n\n return
;\n};\n\nexport default Particles;\n" } ], "registryDependencies": [], diff --git a/public/r/Particles-TS-CSS.json b/public/r/Particles-TS-CSS.json index 17c19bf59..6e639a897 100644 --- a/public/r/Particles-TS-CSS.json +++ b/public/r/Particles-TS-CSS.json @@ -13,7 +13,7 @@ { "type": "registry:component", "path": "Particles/Particles.tsx", - "content": "import React, { useEffect, useRef } from 'react';\nimport { Renderer, Camera, Geometry, Program, Mesh } from 'ogl';\n\nimport './Particles.css';\n\ninterface ParticlesProps {\n particleCount?: number;\n particleSpread?: number;\n speed?: number;\n particleColors?: string[];\n moveParticlesOnHover?: boolean;\n particleHoverFactor?: number;\n alphaParticles?: boolean;\n particleBaseSize?: number;\n sizeRandomness?: number;\n cameraDistance?: number;\n disableRotation?: boolean;\n pixelRatio?: number;\n className?: string;\n}\n\nconst defaultColors: string[] = ['#ffffff', '#ffffff', '#ffffff'];\n\nconst hexToRgb = (hex: string): [number, number, number] => {\n hex = hex.replace(/^#/, '');\n if (hex.length === 3) {\n hex = hex\n .split('')\n .map(c => c + c)\n .join('');\n }\n const int = parseInt(hex, 16);\n const r = ((int >> 16) & 255) / 255;\n const g = ((int >> 8) & 255) / 255;\n const b = (int & 255) / 255;\n return [r, g, b];\n};\n\nconst vertex = /* glsl */ `\n attribute vec3 position;\n attribute vec4 random;\n attribute vec3 color;\n \n uniform mat4 modelMatrix;\n uniform mat4 viewMatrix;\n uniform mat4 projectionMatrix;\n uniform float uTime;\n uniform float uSpread;\n uniform float uBaseSize;\n uniform float uSizeRandomness;\n \n varying vec4 vRandom;\n varying vec3 vColor;\n \n void main() {\n vRandom = random;\n vColor = color;\n \n vec3 pos = position * uSpread;\n pos.z *= 10.0;\n \n vec4 mPos = modelMatrix * vec4(pos, 1.0);\n float t = uTime;\n mPos.x += sin(t * random.z + 6.28 * random.w) * mix(0.1, 1.5, random.x);\n mPos.y += sin(t * random.y + 6.28 * random.x) * mix(0.1, 1.5, random.w);\n mPos.z += sin(t * random.w + 6.28 * random.y) * mix(0.1, 1.5, random.z);\n \n vec4 mvPos = viewMatrix * mPos;\n if (uSizeRandomness == 0.0) {\n gl_PointSize = uBaseSize;\n } else {\n gl_PointSize = (uBaseSize * (1.0 + uSizeRandomness * (random.x - 0.5))) / length(mvPos.xyz);\n }\n \n gl_Position = projectionMatrix * mvPos;\n }\n`;\n\nconst fragment = /* glsl */ `\n precision highp float;\n \n uniform float uTime;\n uniform float uAlphaParticles;\n varying vec4 vRandom;\n varying vec3 vColor;\n \n void main() {\n vec2 uv = gl_PointCoord.xy;\n float d = length(uv - vec2(0.5));\n \n if(uAlphaParticles < 0.5) {\n if(d > 0.5) {\n discard;\n }\n gl_FragColor = vec4(vColor + 0.2 * sin(uv.yxx + uTime + vRandom.y * 6.28), 1.0);\n } else {\n float circle = smoothstep(0.5, 0.4, d) * 0.8;\n gl_FragColor = vec4(vColor + 0.2 * sin(uv.yxx + uTime + vRandom.y * 6.28), circle);\n }\n }\n`;\n\nconst Particles: React.FC = ({\n particleCount = 200,\n particleSpread = 10,\n speed = 0.1,\n particleColors,\n moveParticlesOnHover = false,\n particleHoverFactor = 1,\n alphaParticles = false,\n particleBaseSize = 100,\n sizeRandomness = 1,\n cameraDistance = 20,\n disableRotation = false,\n pixelRatio = 1,\n className\n}) => {\n const containerRef = useRef(null);\n const mouseRef = useRef<{ x: number; y: number }>({ x: 0, y: 0 });\n\n useEffect(() => {\n const container = containerRef.current;\n if (!container) return;\n\n const renderer = new Renderer({\n dpr: pixelRatio,\n depth: false,\n alpha: true\n });\n const gl = renderer.gl;\n container.appendChild(gl.canvas);\n gl.clearColor(0, 0, 0, 0);\n\n const camera = new Camera(gl, { fov: 15 });\n camera.position.set(0, 0, cameraDistance);\n\n const resize = () => {\n const width = container.clientWidth;\n const height = container.clientHeight;\n renderer.setSize(width, height);\n camera.perspective({ aspect: gl.canvas.width / gl.canvas.height });\n };\n window.addEventListener('resize', resize, false);\n resize();\n\n const handleMouseMove = (e: MouseEvent) => {\n const rect = container.getBoundingClientRect();\n const x = ((e.clientX - rect.left) / rect.width) * 2 - 1;\n const y = -(((e.clientY - rect.top) / rect.height) * 2 - 1);\n mouseRef.current = { x, y };\n };\n\n if (moveParticlesOnHover) {\n container.addEventListener('mousemove', handleMouseMove);\n }\n\n const count = particleCount;\n const positions = new Float32Array(count * 3);\n const randoms = new Float32Array(count * 4);\n const colors = new Float32Array(count * 3);\n const palette = particleColors && particleColors.length > 0 ? particleColors : defaultColors;\n\n for (let i = 0; i < count; i++) {\n let x: number, y: number, z: number, len: number;\n do {\n x = Math.random() * 2 - 1;\n y = Math.random() * 2 - 1;\n z = Math.random() * 2 - 1;\n len = x * x + y * y + z * z;\n } while (len > 1 || len === 0);\n const r = Math.cbrt(Math.random());\n positions.set([x * r, y * r, z * r], i * 3);\n randoms.set([Math.random(), Math.random(), Math.random(), Math.random()], i * 4);\n const col = hexToRgb(palette[Math.floor(Math.random() * palette.length)]);\n colors.set(col, i * 3);\n }\n\n const geometry = new Geometry(gl, {\n position: { size: 3, data: positions },\n random: { size: 4, data: randoms },\n color: { size: 3, data: colors }\n });\n\n const program = new Program(gl, {\n vertex,\n fragment,\n uniforms: {\n uTime: { value: 0 },\n uSpread: { value: particleSpread },\n uBaseSize: { value: particleBaseSize * pixelRatio },\n uSizeRandomness: { value: sizeRandomness },\n uAlphaParticles: { value: alphaParticles ? 1 : 0 }\n },\n transparent: true,\n depthTest: false\n });\n\n const particles = new Mesh(gl, { mode: gl.POINTS, geometry, program });\n\n let animationFrameId: number;\n let lastTime = performance.now();\n let elapsed = 0;\n\n const update = (t: number) => {\n animationFrameId = requestAnimationFrame(update);\n const delta = t - lastTime;\n lastTime = t;\n elapsed += delta * speed;\n\n program.uniforms.uTime.value = elapsed * 0.001;\n\n if (moveParticlesOnHover) {\n particles.position.x = -mouseRef.current.x * particleHoverFactor;\n particles.position.y = -mouseRef.current.y * particleHoverFactor;\n } else {\n particles.position.x = 0;\n particles.position.y = 0;\n }\n\n if (!disableRotation) {\n particles.rotation.x = Math.sin(elapsed * 0.0002) * 0.1;\n particles.rotation.y = Math.cos(elapsed * 0.0005) * 0.15;\n particles.rotation.z += 0.01 * speed;\n }\n\n renderer.render({ scene: particles, camera });\n };\n\n animationFrameId = requestAnimationFrame(update);\n\n return () => {\n window.removeEventListener('resize', resize);\n if (moveParticlesOnHover) {\n container.removeEventListener('mousemove', handleMouseMove);\n }\n cancelAnimationFrame(animationFrameId);\n if (container.contains(gl.canvas)) {\n container.removeChild(gl.canvas);\n }\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [\n particleCount,\n particleSpread,\n speed,\n moveParticlesOnHover,\n particleHoverFactor,\n alphaParticles,\n particleBaseSize,\n sizeRandomness,\n cameraDistance,\n disableRotation,\n pixelRatio\n ]);\n\n return
;\n};\n\nexport default Particles;\n" + "content": "import React, { useEffect, useRef } from 'react';\nimport { Renderer, Camera, Geometry, Program, Mesh } from 'ogl';\n\nimport './Particles.css';\n\ninterface ParticlesProps {\n particleCount?: number;\n particleSpread?: number;\n speed?: number;\n particleColors?: string[];\n moveParticlesOnHover?: boolean;\n particleHoverFactor?: number;\n alphaParticles?: boolean;\n particleBaseSize?: number;\n sizeRandomness?: number;\n cameraDistance?: number;\n disableRotation?: boolean;\n pixelRatio?: number;\n className?: string;\n}\n\nconst defaultColors: string[] = ['#ffffff', '#ffffff', '#ffffff'];\n\nconst hexToRgb = (hex: string): [number, number, number] => {\n hex = hex.replace(/^#/, '');\n if (hex.length === 3) {\n hex = hex\n .split('')\n .map(c => c + c)\n .join('');\n }\n const int = parseInt(hex.slice(0, 6), 16);\n const r = ((int >> 16) & 255) / 255;\n const g = ((int >> 8) & 255) / 255;\n const b = (int & 255) / 255;\n return [r, g, b];\n};\n\nconst vertex = /* glsl */ `\n attribute vec3 position;\n attribute vec4 random;\n attribute vec3 color;\n \n uniform mat4 modelMatrix;\n uniform mat4 viewMatrix;\n uniform mat4 projectionMatrix;\n uniform float uTime;\n uniform float uSpread;\n uniform float uBaseSize;\n uniform float uSizeRandomness;\n \n varying vec4 vRandom;\n varying vec3 vColor;\n \n void main() {\n vRandom = random;\n vColor = color;\n \n vec3 pos = position * uSpread;\n pos.z *= 10.0;\n \n vec4 mPos = modelMatrix * vec4(pos, 1.0);\n float t = uTime;\n mPos.x += sin(t * random.z + 6.28 * random.w) * mix(0.1, 1.5, random.x);\n mPos.y += sin(t * random.y + 6.28 * random.x) * mix(0.1, 1.5, random.w);\n mPos.z += sin(t * random.w + 6.28 * random.y) * mix(0.1, 1.5, random.z);\n \n vec4 mvPos = viewMatrix * mPos;\n if (uSizeRandomness == 0.0) {\n gl_PointSize = uBaseSize;\n } else {\n gl_PointSize = (uBaseSize * (1.0 + uSizeRandomness * (random.x - 0.5))) / length(mvPos.xyz);\n }\n \n gl_Position = projectionMatrix * mvPos;\n }\n`;\n\nconst fragment = /* glsl */ `\n precision highp float;\n \n uniform float uTime;\n uniform float uAlphaParticles;\n varying vec4 vRandom;\n varying vec3 vColor;\n \n void main() {\n vec2 uv = gl_PointCoord.xy;\n float d = length(uv - vec2(0.5));\n \n if(uAlphaParticles < 0.5) {\n if(d > 0.5) {\n discard;\n }\n gl_FragColor = vec4(vColor + 0.2 * sin(uv.yxx + uTime + vRandom.y * 6.28), 1.0);\n } else {\n float circle = smoothstep(0.5, 0.4, d) * 0.8;\n gl_FragColor = vec4(vColor + 0.2 * sin(uv.yxx + uTime + vRandom.y * 6.28), circle);\n }\n }\n`;\n\nconst Particles: React.FC = ({\n particleCount = 200,\n particleSpread = 10,\n speed = 0.1,\n particleColors,\n moveParticlesOnHover = false,\n particleHoverFactor = 1,\n alphaParticles = false,\n particleBaseSize = 100,\n sizeRandomness = 1,\n cameraDistance = 20,\n disableRotation = false,\n pixelRatio = 1,\n className\n}) => {\n const containerRef = useRef(null);\n const mouseRef = useRef<{ x: number; y: number }>({ x: 0, y: 0 });\n\n useEffect(() => {\n const container = containerRef.current;\n if (!container) return;\n\n const renderer = new Renderer({\n dpr: pixelRatio,\n depth: false,\n alpha: true\n });\n const gl = renderer.gl;\n container.appendChild(gl.canvas);\n gl.clearColor(0, 0, 0, 0);\n\n const camera = new Camera(gl, { fov: 15 });\n camera.position.set(0, 0, cameraDistance);\n\n const resize = () => {\n const width = container.clientWidth;\n const height = container.clientHeight;\n renderer.setSize(width, height);\n camera.perspective({ aspect: gl.canvas.width / gl.canvas.height });\n };\n window.addEventListener('resize', resize, false);\n resize();\n\n const handleMouseMove = (e: MouseEvent) => {\n const rect = container.getBoundingClientRect();\n const x = ((e.clientX - rect.left) / rect.width) * 2 - 1;\n const y = -(((e.clientY - rect.top) / rect.height) * 2 - 1);\n mouseRef.current = { x, y };\n };\n\n if (moveParticlesOnHover) {\n container.addEventListener('mousemove', handleMouseMove);\n }\n\n const count = particleCount;\n const positions = new Float32Array(count * 3);\n const randoms = new Float32Array(count * 4);\n const colors = new Float32Array(count * 3);\n const palette = particleColors && particleColors.length > 0 ? particleColors : defaultColors;\n\n for (let i = 0; i < count; i++) {\n let x: number, y: number, z: number, len: number;\n do {\n x = Math.random() * 2 - 1;\n y = Math.random() * 2 - 1;\n z = Math.random() * 2 - 1;\n len = x * x + y * y + z * z;\n } while (len > 1 || len === 0);\n const r = Math.cbrt(Math.random());\n positions.set([x * r, y * r, z * r], i * 3);\n randoms.set([Math.random(), Math.random(), Math.random(), Math.random()], i * 4);\n const col = hexToRgb(palette[Math.floor(Math.random() * palette.length)]);\n colors.set(col, i * 3);\n }\n\n const geometry = new Geometry(gl, {\n position: { size: 3, data: positions },\n random: { size: 4, data: randoms },\n color: { size: 3, data: colors }\n });\n\n const program = new Program(gl, {\n vertex,\n fragment,\n uniforms: {\n uTime: { value: 0 },\n uSpread: { value: particleSpread },\n uBaseSize: { value: particleBaseSize * pixelRatio },\n uSizeRandomness: { value: sizeRandomness },\n uAlphaParticles: { value: alphaParticles ? 1 : 0 }\n },\n transparent: true,\n depthTest: false\n });\n\n const particles = new Mesh(gl, { mode: gl.POINTS, geometry, program });\n\n let animationFrameId: number;\n let lastTime = performance.now();\n let elapsed = 0;\n\n const update = (t: number) => {\n animationFrameId = requestAnimationFrame(update);\n const delta = t - lastTime;\n lastTime = t;\n elapsed += delta * speed;\n\n program.uniforms.uTime.value = elapsed * 0.001;\n\n if (moveParticlesOnHover) {\n particles.position.x = -mouseRef.current.x * particleHoverFactor;\n particles.position.y = -mouseRef.current.y * particleHoverFactor;\n } else {\n particles.position.x = 0;\n particles.position.y = 0;\n }\n\n if (!disableRotation) {\n particles.rotation.x = Math.sin(elapsed * 0.0002) * 0.1;\n particles.rotation.y = Math.cos(elapsed * 0.0005) * 0.15;\n particles.rotation.z += 0.01 * speed;\n }\n\n renderer.render({ scene: particles, camera });\n };\n\n animationFrameId = requestAnimationFrame(update);\n\n return () => {\n window.removeEventListener('resize', resize);\n if (moveParticlesOnHover) {\n container.removeEventListener('mousemove', handleMouseMove);\n }\n cancelAnimationFrame(animationFrameId);\n if (container.contains(gl.canvas)) {\n container.removeChild(gl.canvas);\n }\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [\n particleCount,\n particleSpread,\n speed,\n moveParticlesOnHover,\n particleHoverFactor,\n alphaParticles,\n particleBaseSize,\n sizeRandomness,\n cameraDistance,\n disableRotation,\n pixelRatio\n ]);\n\n return
;\n};\n\nexport default Particles;\n" } ], "registryDependencies": [], diff --git a/public/r/Particles-TS-TW.json b/public/r/Particles-TS-TW.json index 73c8217f4..00bdb8ca2 100644 --- a/public/r/Particles-TS-TW.json +++ b/public/r/Particles-TS-TW.json @@ -8,7 +8,7 @@ { "type": "registry:component", "path": "Particles/Particles.tsx", - "content": "import React, { useEffect, useRef } from 'react';\nimport { Renderer, Camera, Geometry, Program, Mesh } from 'ogl';\n\ninterface ParticlesProps {\n particleCount?: number;\n particleSpread?: number;\n speed?: number;\n particleColors?: string[];\n moveParticlesOnHover?: boolean;\n particleHoverFactor?: number;\n alphaParticles?: boolean;\n particleBaseSize?: number;\n sizeRandomness?: number;\n cameraDistance?: number;\n disableRotation?: boolean;\n pixelRatio?: number;\n className?: string;\n}\n\nconst defaultColors: string[] = ['#ffffff', '#ffffff', '#ffffff'];\n\nconst hexToRgb = (hex: string): [number, number, number] => {\n hex = hex.replace(/^#/, '');\n if (hex.length === 3) {\n hex = hex\n .split('')\n .map(c => c + c)\n .join('');\n }\n const int = parseInt(hex, 16);\n const r = ((int >> 16) & 255) / 255;\n const g = ((int >> 8) & 255) / 255;\n const b = (int & 255) / 255;\n return [r, g, b];\n};\n\nconst vertex = /* glsl */ `\n attribute vec3 position;\n attribute vec4 random;\n attribute vec3 color;\n \n uniform mat4 modelMatrix;\n uniform mat4 viewMatrix;\n uniform mat4 projectionMatrix;\n uniform float uTime;\n uniform float uSpread;\n uniform float uBaseSize;\n uniform float uSizeRandomness;\n \n varying vec4 vRandom;\n varying vec3 vColor;\n \n void main() {\n vRandom = random;\n vColor = color;\n \n vec3 pos = position * uSpread;\n pos.z *= 10.0;\n \n vec4 mPos = modelMatrix * vec4(pos, 1.0);\n float t = uTime;\n mPos.x += sin(t * random.z + 6.28 * random.w) * mix(0.1, 1.5, random.x);\n mPos.y += sin(t * random.y + 6.28 * random.x) * mix(0.1, 1.5, random.w);\n mPos.z += sin(t * random.w + 6.28 * random.y) * mix(0.1, 1.5, random.z);\n \n vec4 mvPos = viewMatrix * mPos;\n\n if (uSizeRandomness == 0.0) {\n gl_PointSize = uBaseSize;\n } else {\n gl_PointSize = (uBaseSize * (1.0 + uSizeRandomness * (random.x - 0.5))) / length(mvPos.xyz);\n }\n \n gl_Position = projectionMatrix * mvPos;\n gl_Position = projectionMatrix * mvPos;\n }\n`;\n\nconst fragment = /* glsl */ `\n precision highp float;\n \n uniform float uTime;\n uniform float uAlphaParticles;\n varying vec4 vRandom;\n varying vec3 vColor;\n \n void main() {\n vec2 uv = gl_PointCoord.xy;\n float d = length(uv - vec2(0.5));\n \n if(uAlphaParticles < 0.5) {\n if(d > 0.5) {\n discard;\n }\n gl_FragColor = vec4(vColor + 0.2 * sin(uv.yxx + uTime + vRandom.y * 6.28), 1.0);\n } else {\n float circle = smoothstep(0.5, 0.4, d) * 0.8;\n gl_FragColor = vec4(vColor + 0.2 * sin(uv.yxx + uTime + vRandom.y * 6.28), circle);\n }\n }\n`;\n\nconst Particles: React.FC = ({\n particleCount = 200,\n particleSpread = 10,\n speed = 0.1,\n particleColors,\n moveParticlesOnHover = false,\n particleHoverFactor = 1,\n alphaParticles = false,\n particleBaseSize = 100,\n sizeRandomness = 1,\n cameraDistance = 20,\n disableRotation = false,\n pixelRatio = 1,\n className\n}) => {\n const containerRef = useRef(null);\n const mouseRef = useRef<{ x: number; y: number }>({ x: 0, y: 0 });\n\n useEffect(() => {\n const container = containerRef.current;\n if (!container) return;\n\n const renderer = new Renderer({ dpr: pixelRatio, depth: false, alpha: true });\n const gl = renderer.gl;\n container.appendChild(gl.canvas);\n gl.clearColor(0, 0, 0, 0);\n\n const camera = new Camera(gl, { fov: 15 });\n camera.position.set(0, 0, cameraDistance);\n\n const resize = () => {\n const width = container.clientWidth;\n const height = container.clientHeight;\n renderer.setSize(width, height);\n camera.perspective({ aspect: gl.canvas.width / gl.canvas.height });\n };\n window.addEventListener('resize', resize, false);\n resize();\n\n const handleMouseMove = (e: MouseEvent) => {\n const rect = container.getBoundingClientRect();\n const x = ((e.clientX - rect.left) / rect.width) * 2 - 1;\n const y = -(((e.clientY - rect.top) / rect.height) * 2 - 1);\n mouseRef.current = { x, y };\n };\n\n if (moveParticlesOnHover) {\n container.addEventListener('mousemove', handleMouseMove);\n }\n\n const count = particleCount;\n const positions = new Float32Array(count * 3);\n const randoms = new Float32Array(count * 4);\n const colors = new Float32Array(count * 3);\n const palette = particleColors && particleColors.length > 0 ? particleColors : defaultColors;\n\n for (let i = 0; i < count; i++) {\n let x: number, y: number, z: number, len: number;\n do {\n x = Math.random() * 2 - 1;\n y = Math.random() * 2 - 1;\n z = Math.random() * 2 - 1;\n len = x * x + y * y + z * z;\n } while (len > 1 || len === 0);\n const r = Math.cbrt(Math.random());\n positions.set([x * r, y * r, z * r], i * 3);\n randoms.set([Math.random(), Math.random(), Math.random(), Math.random()], i * 4);\n const col = hexToRgb(palette[Math.floor(Math.random() * palette.length)]);\n colors.set(col, i * 3);\n }\n\n const geometry = new Geometry(gl, {\n position: { size: 3, data: positions },\n random: { size: 4, data: randoms },\n color: { size: 3, data: colors }\n });\n\n const program = new Program(gl, {\n vertex,\n fragment,\n uniforms: {\n uTime: { value: 0 },\n uSpread: { value: particleSpread },\n uBaseSize: { value: particleBaseSize * pixelRatio },\n uSizeRandomness: { value: sizeRandomness },\n uAlphaParticles: { value: alphaParticles ? 1 : 0 }\n },\n transparent: true,\n depthTest: false\n });\n\n const particles = new Mesh(gl, { mode: gl.POINTS, geometry, program });\n\n let animationFrameId: number;\n let lastTime = performance.now();\n let elapsed = 0;\n\n const update = (t: number) => {\n animationFrameId = requestAnimationFrame(update);\n const delta = t - lastTime;\n lastTime = t;\n elapsed += delta * speed;\n\n program.uniforms.uTime.value = elapsed * 0.001;\n\n if (moveParticlesOnHover) {\n particles.position.x = -mouseRef.current.x * particleHoverFactor;\n particles.position.y = -mouseRef.current.y * particleHoverFactor;\n } else {\n particles.position.x = 0;\n particles.position.y = 0;\n }\n\n if (!disableRotation) {\n particles.rotation.x = Math.sin(elapsed * 0.0002) * 0.1;\n particles.rotation.y = Math.cos(elapsed * 0.0005) * 0.15;\n particles.rotation.z += 0.01 * speed;\n }\n\n renderer.render({ scene: particles, camera });\n };\n\n animationFrameId = requestAnimationFrame(update);\n\n return () => {\n window.removeEventListener('resize', resize);\n if (moveParticlesOnHover) {\n container.removeEventListener('mousemove', handleMouseMove);\n }\n cancelAnimationFrame(animationFrameId);\n if (container.contains(gl.canvas)) {\n container.removeChild(gl.canvas);\n }\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [\n particleCount,\n particleSpread,\n speed,\n moveParticlesOnHover,\n particleHoverFactor,\n alphaParticles,\n particleBaseSize,\n sizeRandomness,\n cameraDistance,\n disableRotation,\n pixelRatio\n ]);\n\n return
;\n};\n\nexport default Particles;\n" + "content": "import React, { useEffect, useRef } from 'react';\nimport { Renderer, Camera, Geometry, Program, Mesh } from 'ogl';\n\ninterface ParticlesProps {\n particleCount?: number;\n particleSpread?: number;\n speed?: number;\n particleColors?: string[];\n moveParticlesOnHover?: boolean;\n particleHoverFactor?: number;\n alphaParticles?: boolean;\n particleBaseSize?: number;\n sizeRandomness?: number;\n cameraDistance?: number;\n disableRotation?: boolean;\n pixelRatio?: number;\n className?: string;\n}\n\nconst defaultColors: string[] = ['#ffffff', '#ffffff', '#ffffff'];\n\nconst hexToRgb = (hex: string): [number, number, number] => {\n hex = hex.replace(/^#/, '');\n if (hex.length === 3) {\n hex = hex\n .split('')\n .map(c => c + c)\n .join('');\n }\n const int = parseInt(hex.slice(0, 6), 16);\n const r = ((int >> 16) & 255) / 255;\n const g = ((int >> 8) & 255) / 255;\n const b = (int & 255) / 255;\n return [r, g, b];\n};\n\nconst vertex = /* glsl */ `\n attribute vec3 position;\n attribute vec4 random;\n attribute vec3 color;\n \n uniform mat4 modelMatrix;\n uniform mat4 viewMatrix;\n uniform mat4 projectionMatrix;\n uniform float uTime;\n uniform float uSpread;\n uniform float uBaseSize;\n uniform float uSizeRandomness;\n \n varying vec4 vRandom;\n varying vec3 vColor;\n \n void main() {\n vRandom = random;\n vColor = color;\n \n vec3 pos = position * uSpread;\n pos.z *= 10.0;\n \n vec4 mPos = modelMatrix * vec4(pos, 1.0);\n float t = uTime;\n mPos.x += sin(t * random.z + 6.28 * random.w) * mix(0.1, 1.5, random.x);\n mPos.y += sin(t * random.y + 6.28 * random.x) * mix(0.1, 1.5, random.w);\n mPos.z += sin(t * random.w + 6.28 * random.y) * mix(0.1, 1.5, random.z);\n \n vec4 mvPos = viewMatrix * mPos;\n\n if (uSizeRandomness == 0.0) {\n gl_PointSize = uBaseSize;\n } else {\n gl_PointSize = (uBaseSize * (1.0 + uSizeRandomness * (random.x - 0.5))) / length(mvPos.xyz);\n }\n \n gl_Position = projectionMatrix * mvPos;\n gl_Position = projectionMatrix * mvPos;\n }\n`;\n\nconst fragment = /* glsl */ `\n precision highp float;\n \n uniform float uTime;\n uniform float uAlphaParticles;\n varying vec4 vRandom;\n varying vec3 vColor;\n \n void main() {\n vec2 uv = gl_PointCoord.xy;\n float d = length(uv - vec2(0.5));\n \n if(uAlphaParticles < 0.5) {\n if(d > 0.5) {\n discard;\n }\n gl_FragColor = vec4(vColor + 0.2 * sin(uv.yxx + uTime + vRandom.y * 6.28), 1.0);\n } else {\n float circle = smoothstep(0.5, 0.4, d) * 0.8;\n gl_FragColor = vec4(vColor + 0.2 * sin(uv.yxx + uTime + vRandom.y * 6.28), circle);\n }\n }\n`;\n\nconst Particles: React.FC = ({\n particleCount = 200,\n particleSpread = 10,\n speed = 0.1,\n particleColors,\n moveParticlesOnHover = false,\n particleHoverFactor = 1,\n alphaParticles = false,\n particleBaseSize = 100,\n sizeRandomness = 1,\n cameraDistance = 20,\n disableRotation = false,\n pixelRatio = 1,\n className\n}) => {\n const containerRef = useRef(null);\n const mouseRef = useRef<{ x: number; y: number }>({ x: 0, y: 0 });\n\n useEffect(() => {\n const container = containerRef.current;\n if (!container) return;\n\n const renderer = new Renderer({ dpr: pixelRatio, depth: false, alpha: true });\n const gl = renderer.gl;\n container.appendChild(gl.canvas);\n gl.clearColor(0, 0, 0, 0);\n\n const camera = new Camera(gl, { fov: 15 });\n camera.position.set(0, 0, cameraDistance);\n\n const resize = () => {\n const width = container.clientWidth;\n const height = container.clientHeight;\n renderer.setSize(width, height);\n camera.perspective({ aspect: gl.canvas.width / gl.canvas.height });\n };\n window.addEventListener('resize', resize, false);\n resize();\n\n const handleMouseMove = (e: MouseEvent) => {\n const rect = container.getBoundingClientRect();\n const x = ((e.clientX - rect.left) / rect.width) * 2 - 1;\n const y = -(((e.clientY - rect.top) / rect.height) * 2 - 1);\n mouseRef.current = { x, y };\n };\n\n if (moveParticlesOnHover) {\n container.addEventListener('mousemove', handleMouseMove);\n }\n\n const count = particleCount;\n const positions = new Float32Array(count * 3);\n const randoms = new Float32Array(count * 4);\n const colors = new Float32Array(count * 3);\n const palette = particleColors && particleColors.length > 0 ? particleColors : defaultColors;\n\n for (let i = 0; i < count; i++) {\n let x: number, y: number, z: number, len: number;\n do {\n x = Math.random() * 2 - 1;\n y = Math.random() * 2 - 1;\n z = Math.random() * 2 - 1;\n len = x * x + y * y + z * z;\n } while (len > 1 || len === 0);\n const r = Math.cbrt(Math.random());\n positions.set([x * r, y * r, z * r], i * 3);\n randoms.set([Math.random(), Math.random(), Math.random(), Math.random()], i * 4);\n const col = hexToRgb(palette[Math.floor(Math.random() * palette.length)]);\n colors.set(col, i * 3);\n }\n\n const geometry = new Geometry(gl, {\n position: { size: 3, data: positions },\n random: { size: 4, data: randoms },\n color: { size: 3, data: colors }\n });\n\n const program = new Program(gl, {\n vertex,\n fragment,\n uniforms: {\n uTime: { value: 0 },\n uSpread: { value: particleSpread },\n uBaseSize: { value: particleBaseSize * pixelRatio },\n uSizeRandomness: { value: sizeRandomness },\n uAlphaParticles: { value: alphaParticles ? 1 : 0 }\n },\n transparent: true,\n depthTest: false\n });\n\n const particles = new Mesh(gl, { mode: gl.POINTS, geometry, program });\n\n let animationFrameId: number;\n let lastTime = performance.now();\n let elapsed = 0;\n\n const update = (t: number) => {\n animationFrameId = requestAnimationFrame(update);\n const delta = t - lastTime;\n lastTime = t;\n elapsed += delta * speed;\n\n program.uniforms.uTime.value = elapsed * 0.001;\n\n if (moveParticlesOnHover) {\n particles.position.x = -mouseRef.current.x * particleHoverFactor;\n particles.position.y = -mouseRef.current.y * particleHoverFactor;\n } else {\n particles.position.x = 0;\n particles.position.y = 0;\n }\n\n if (!disableRotation) {\n particles.rotation.x = Math.sin(elapsed * 0.0002) * 0.1;\n particles.rotation.y = Math.cos(elapsed * 0.0005) * 0.15;\n particles.rotation.z += 0.01 * speed;\n }\n\n renderer.render({ scene: particles, camera });\n };\n\n animationFrameId = requestAnimationFrame(update);\n\n return () => {\n window.removeEventListener('resize', resize);\n if (moveParticlesOnHover) {\n container.removeEventListener('mousemove', handleMouseMove);\n }\n cancelAnimationFrame(animationFrameId);\n if (container.contains(gl.canvas)) {\n container.removeChild(gl.canvas);\n }\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [\n particleCount,\n particleSpread,\n speed,\n moveParticlesOnHover,\n particleHoverFactor,\n alphaParticles,\n particleBaseSize,\n sizeRandomness,\n cameraDistance,\n disableRotation,\n pixelRatio\n ]);\n\n return
;\n};\n\nexport default Particles;\n" } ], "registryDependencies": [], diff --git a/public/r/Plasma-JS-CSS.json b/public/r/Plasma-JS-CSS.json index 67d4a721e..eab0a5c81 100644 --- a/public/r/Plasma-JS-CSS.json +++ b/public/r/Plasma-JS-CSS.json @@ -13,7 +13,7 @@ { "type": "registry:component", "path": "Plasma/Plasma.jsx", - "content": "import { useEffect, useRef } from 'react';\nimport { Renderer, Program, Mesh, Triangle } from 'ogl';\nimport './Plasma.css';\n\nconst hexToRgb = hex => {\n const result = /^#?([a-f\\d]{2})([a-f\\d]{2})([a-f\\d]{2})$/i.exec(hex);\n if (!result) return [1, 0.5, 0.2];\n return [parseInt(result[1], 16) / 255, parseInt(result[2], 16) / 255, parseInt(result[3], 16) / 255];\n};\n\nconst vertex = `#version 300 es\nprecision highp float;\nin vec2 position;\nin vec2 uv;\nout vec2 vUv;\nvoid main() {\n vUv = uv;\n gl_Position = vec4(position, 0.0, 1.0);\n}\n`;\n\nconst buildFragment = steps => `#version 300 es\nprecision highp float;\nuniform vec2 iResolution;\nuniform float iTime;\nuniform vec3 uCustomColor;\nuniform float uUseCustomColor;\nuniform float uSpeed;\nuniform float uDirection;\nuniform float uScale;\nuniform float uOpacity;\nuniform vec2 uMouse;\nuniform float uMouseInteractive;\nout vec4 fragColor;\n\nvoid mainImage(out vec4 o, vec2 C) {\n vec2 center = iResolution.xy * 0.5;\n C = (C - center) / uScale + center;\n \n vec2 mouseOffset = (uMouse - center) * 0.0002;\n C += mouseOffset * length(C - center) * step(0.5, uMouseInteractive);\n \n float i, d, z, T = iTime * uSpeed * uDirection;\n vec3 O, p, S;\n\n for (vec2 r = iResolution.xy, Q; ++i < ${steps.toFixed(1)}; O += o.w/d*o.xyz) {\n p = z*normalize(vec3(C-.5*r,r.y)); \n p.z -= 4.; \n S = p;\n d = p.y-T;\n \n p.x += .4*(1.+p.y)*sin(d + p.x*0.1)*cos(.34*d + p.x*0.05); \n Q = p.xz *= mat2(cos(p.y+vec4(0,11,33,0)-T)); \n z+= d = abs(sqrt(length(Q*Q)) - .25*(5.+S.y))/3.+8e-4; \n o = 1.+sin(S.y+p.z*.5+S.z-length(S-p)+vec4(2,1,0,8));\n }\n \n o.xyz = tanh(O/1e4);\n}\n\nbool finite1(float x){ return !(isnan(x) || isinf(x)); }\nvec3 sanitize(vec3 c){\n return vec3(\n finite1(c.r) ? c.r : 0.0,\n finite1(c.g) ? c.g : 0.0,\n finite1(c.b) ? c.b : 0.0\n );\n}\n\nvoid main() {\n vec4 o = vec4(0.0);\n mainImage(o, gl_FragCoord.xy);\n vec3 rgb = sanitize(o.rgb);\n \n float intensity = (rgb.r + rgb.g + rgb.b) / 3.0;\n vec3 customColor = intensity * uCustomColor;\n vec3 finalColor = mix(rgb, customColor, step(0.5, uUseCustomColor));\n \n float alpha = length(rgb) * uOpacity;\n fragColor = vec4(finalColor, alpha);\n}`;\n\nexport const Plasma = ({\n color = '#ffffff',\n speed = 1,\n direction = 'forward',\n scale = 1,\n opacity = 1,\n mouseInteractive = true,\n renderScale = 0.55,\n maxDpr = 1.5,\n targetFps = 60,\n quality = 45,\n}) => {\n const containerRef = useRef(null);\n const mousePos = useRef({ x: 0, y: 0 });\n const pendingMouse = useRef(null);\n\n useEffect(() => {\n if (!containerRef.current) return;\n const containerEl = containerRef.current;\n\n const prefersReducedMotion =\n typeof window !== 'undefined' &&\n window.matchMedia?.('(prefers-reduced-motion: reduce)').matches;\n\n const useCustomColor = color ? 1.0 : 0.0;\n const customColorRgb = color ? hexToRgb(color) : [1, 1, 1];\n\n const directionMultiplier = direction === 'reverse' ? -1.0 : 1.0;\n\n let renderer;\n try {\n renderer = new Renderer({\n webgl: 2,\n alpha: true,\n antialias: false,\n dpr: Math.min(window.devicePixelRatio || 1, maxDpr)\n });\n } catch {\n return;\n }\n const gl = renderer.gl;\n if (!gl) return;\n const canvas = gl.canvas;\n canvas.style.display = 'block';\n canvas.style.width = '100%';\n canvas.style.height = '100%';\n // Rendering at renderScale internally, CSS stretches it back up.\n containerEl.appendChild(canvas);\n\n const geometry = new Triangle(gl);\n\n const program = new Program(gl, {\n vertex: vertex,\n fragment: buildFragment(quality),\n uniforms: {\n iTime: { value: 0 },\n iResolution: { value: new Float32Array([1, 1]) },\n uCustomColor: { value: new Float32Array(customColorRgb) },\n uUseCustomColor: { value: useCustomColor },\n uSpeed: { value: speed * 0.4 },\n uDirection: { value: directionMultiplier },\n uScale: { value: scale },\n uOpacity: { value: opacity },\n uMouse: { value: new Float32Array([0, 0]) },\n uMouseInteractive: { value: mouseInteractive ? 1.0 : 0.0 }\n }\n });\n\n const mesh = new Mesh(gl, { geometry, program });\n\n const handleMouseMove = e => {\n if (!mouseInteractive) return;\n const rect = containerEl.getBoundingClientRect();\n // Store the latest position but don't touch GL state here, the rAF loop picks it up once per rendered frame instead of once per mouse event.\n pendingMouse.current = {\n x: e.clientX - rect.left,\n y: e.clientY - rect.top,\n };\n };\n\n if (mouseInteractive) {\n containerEl.addEventListener('mousemove', handleMouseMove, { passive: true });\n }\n\n let resizePending = false;\n const setSize = () => {\n const rect = containerEl.getBoundingClientRect();\n const width = Math.max(1, Math.floor(rect.width * renderScale));\n const height = Math.max(1, Math.floor(rect.height * renderScale));\n renderer.setSize(width, height);\n\n // renderer.setSize also sets canvas.style.width/height to match the (scaled-down) drawing buffer - override that so the canvas still stretches to fill its container via CSS while the buffer stays small.\n canvas.style.width = '100%';\n canvas.style.height = '100%';\n\n const res = program.uniforms.iResolution.value;\n res[0] = gl.drawingBufferWidth;\n res[1] = gl.drawingBufferHeight;\n };\n\n const ro = new ResizeObserver(() => {\n // Batch rapid resize events (ex. during a window drag) into one setSize per frame.\n if (resizePending) return;\n resizePending = true;\n requestAnimationFrame(() => {\n resizePending = false;\n setSize();\n });\n });\n ro.observe(containerEl);\n setSize();\n\n let raf = 0;\n let contextLost = false;\n let isVisible = true;\n let tabVisible = document.visibilityState !== 'hidden';\n const t0 = performance.now();\n const frameInterval = 1000 / targetFps;\n let lastFrameTime = 0;\n\n const renderStaticFrame = () => {\n program.uniforms.iTime.value = 0;\n renderer.render({ scene: mesh });\n };\n\n const loop = t => {\n if (contextLost || !isVisible || !tabVisible) return;\n\n if (t - lastFrameTime < frameInterval) {\n raf = requestAnimationFrame(loop);\n return;\n }\n lastFrameTime = t;\n\n if (pendingMouse.current) {\n mousePos.current = pendingMouse.current;\n pendingMouse.current = null;\n const mouseUniform = program.uniforms.uMouse.value;\n mouseUniform[0] = mousePos.current.x;\n mouseUniform[1] = mousePos.current.y;\n }\n\n let timeValue = (t - t0) * 0.001;\n if (direction === 'pingpong') {\n const pingpongDuration = 10;\n const segmentTime = timeValue % pingpongDuration;\n const isForward = Math.floor(timeValue / pingpongDuration) % 2 === 0;\n const u = segmentTime / pingpongDuration;\n const smooth = u * u * (3 - 2 * u);\n const pingpongTime = isForward ? smooth * pingpongDuration : (1 - smooth) * pingpongDuration;\n program.uniforms.uDirection.value = 1.0;\n program.uniforms.iTime.value = pingpongTime;\n } else {\n program.uniforms.iTime.value = timeValue;\n }\n renderer.render({ scene: mesh });\n raf = requestAnimationFrame(loop);\n };\n\n const handleContextLost = (e) => {\n e.preventDefault();\n contextLost = true;\n cancelAnimationFrame(raf);\n };\n const handleContextRestored = () => {\n contextLost = false;\n if (isVisible && tabVisible && !prefersReducedMotion) {\n cancelAnimationFrame(raf);\n raf = requestAnimationFrame(loop);\n }\n };\n canvas.addEventListener('webglcontextlost', handleContextLost);\n canvas.addEventListener('webglcontextrestored', handleContextRestored);\n\n const io = new IntersectionObserver(([entry]) => {\n const wasVisible = isVisible;\n isVisible = entry.isIntersecting;\n if (isVisible && !wasVisible && !contextLost && tabVisible && !prefersReducedMotion) {\n cancelAnimationFrame(raf);\n raf = requestAnimationFrame(loop);\n }\n }, { threshold: 0 });\n io.observe(containerEl);\n\n const handleVisibilityChange = () => {\n tabVisible = document.visibilityState !== 'hidden';\n if (tabVisible && isVisible && !contextLost && !prefersReducedMotion) {\n cancelAnimationFrame(raf);\n lastFrameTime = 0;\n raf = requestAnimationFrame(loop);\n } else {\n cancelAnimationFrame(raf);\n }\n };\n document.addEventListener('visibilitychange', handleVisibilityChange);\n\n // Respect prefers-reduced-motion: paint one frame and stop, rather than running a perpetual animation loop for users who've asked not to see motion.\n if (prefersReducedMotion) {\n renderStaticFrame();\n } else {\n raf = requestAnimationFrame(loop);\n }\n\n return () => {\n cancelAnimationFrame(raf);\n ro.disconnect();\n io.disconnect();\n document.removeEventListener('visibilitychange', handleVisibilityChange);\n canvas.removeEventListener('webglcontextlost', handleContextLost);\n canvas.removeEventListener('webglcontextrestored', handleContextRestored);\n if (mouseInteractive && containerEl) {\n containerEl.removeEventListener('mousemove', handleMouseMove);\n }\n try {\n containerEl?.removeChild(canvas);\n } catch {}\n };\n }, [color, speed, direction, scale, opacity, mouseInteractive, renderScale, maxDpr, targetFps, quality]);\n\n return
;\n};\n\nexport default Plasma;\n" + "content": "import { useEffect, useRef } from 'react';\nimport { Renderer, Program, Mesh, Triangle } from 'ogl';\nimport './Plasma.css';\n\nconst hexToRgb = hex => {\n const result = /^#?([a-f\\d]{2})([a-f\\d]{2})([a-f\\d]{2})$/i.exec(hex);\n if (!result) return [1, 0.5, 0.2];\n return [parseInt(result[1], 16) / 255, parseInt(result[2], 16) / 255, parseInt(result[3], 16) / 255];\n};\n\nconst vertex = `#version 300 es\nprecision highp float;\nin vec2 position;\nin vec2 uv;\nout vec2 vUv;\nvoid main() {\n vUv = uv;\n gl_Position = vec4(position, 0.0, 1.0);\n}\n`;\n\nconst ORIGINAL_QUALITY = 60;\n\nconst buildFragment = (iterations) => {\n return `#version 300 es\nprecision highp float;\nuniform vec2 iResolution;\nuniform float iTime;\nuniform vec3 uCustomColor;\nuniform float uUseCustomColor;\nuniform float uSpeed;\nuniform float uDirection;\nuniform float uScale; \nuniform float uOpacity;\nuniform vec2 uMouse;\nuniform float uMouseInteractive;\nuniform float uQuality;\nuniform float uStepScale;\nout vec4 fragColor;\n\nvoid mainImage(out vec4 o, vec2 C) {\n vec2 center = iResolution.xy * 0.5;\n C = (C - center) / uScale + center;\n \n vec2 mouseOffset = (uMouse - center) * 0.0002;\n C += mouseOffset * length(C - center) * step(0.5, uMouseInteractive);\n \n float i, d, z, T = iTime * uSpeed * uDirection;\n vec3 O, p, S;\n\n for (vec2 r = iResolution.xy, Q; ++i < 60.0; O += o.w/d*o.xyz) {\n p = z*normalize(vec3(C-.5*r,r.y)); \n p.z -= 4.; \n S = p;\n d = p.y-T;\n \n p.x += .4*(1.+p.y)*sin(d + p.x*0.1)*cos(.34*d + p.x*0.05); \n Q = p.xz *= mat2(cos(p.y+vec4(0,11,33,0)-T)); \n z += d = (abs(sqrt(length(Q*Q)) - .25*(5.+S.y))/3.+8e-4) * uStepScale;\n o = 1.+sin(S.y+p.z*.5+S.z-length(S-p)+vec4(2,1,0,8));\n if (i >= uQuality) break;\n }\n \n o.xyz = tanh(O/1e4);\n}\n\nbool finite1(float x){ return !(isnan(x) || isinf(x)); }\nvec3 sanitize(vec3 c){\n return vec3(\n finite1(c.r) ? c.r : 0.0,\n finite1(c.g) ? c.g : 0.0,\n finite1(c.b) ? c.b : 0.0\n );\n}\n\nvoid main() {\n vec4 o = vec4(0.0);\n mainImage(o, gl_FragCoord.xy);\n vec3 rgb = sanitize(o.rgb);\n \n float intensity = (rgb.r + rgb.g + rgb.b) / 3.0;\n vec3 customColor = intensity * uCustomColor;\n vec3 finalColor = mix(rgb, customColor, step(0.5, uUseCustomColor));\n \n float alpha = length(rgb) * uOpacity;\n fragColor = vec4(finalColor, alpha);\n}`;\n};\n\nexport const Plasma = ({\n color = '#ffffff',\n speed = 1,\n direction = 'forward',\n scale = 1,\n opacity = 1,\n mouseInteractive = true,\n renderScale = 0.55,\n maxDpr = 1.5,\n targetFps = 60,\n iterations = 60,\n}) => {\n const containerRef = useRef(null);\n const mousePos = useRef({ x: 0, y: 0 });\n const pendingMouse = useRef(null);\n\n useEffect(() => {\n if (!containerRef.current) return;\n const containerEl = containerRef.current;\n\n const prefersReducedMotion =\n typeof window !== 'undefined' &&\n window.matchMedia?.('(prefers-reduced-motion: reduce)').matches;\n\n const useCustomColor = color ? 1.0 : 0.0;\n const customColorRgb = color ? hexToRgb(color) : [1, 1, 1];\n\n const directionMultiplier = direction === 'reverse' ? -1.0 : 1.0;\n\n let renderer;\n try {\n renderer = new Renderer({\n webgl: 2,\n alpha: true,\n antialias: false,\n dpr: Math.min(window.devicePixelRatio || 1, maxDpr)\n });\n } catch {\n return;\n }\n const gl = renderer.gl;\n if (!gl) return;\n const canvas = gl.canvas;\n canvas.style.display = 'block';\n canvas.style.width = '100%';\n canvas.style.height = '100%';\n // Rendering at renderScale internally, CSS stretches it back up.\n containerEl.appendChild(canvas);\n\n const geometry = new Triangle(gl);\n\n const program = new Program(gl, {\n vertex: vertex,\n fragment: buildFragment(iterations),\n uniforms: {\n iTime: { value: 0 },\n iResolution: { value: new Float32Array([1, 1]) },\n uCustomColor: { value: new Float32Array(customColorRgb) },\n uUseCustomColor: { value: useCustomColor },\n uSpeed: { value: speed * 0.4 },\n uDirection: { value: directionMultiplier },\n uScale: { value: scale },\n uOpacity: { value: opacity },\n uMouse: { value: new Float32Array([0, 0]) },\n uMouseInteractive: { value: mouseInteractive ? 1.0 : 0.0 },\n uQuality: { value: iterations },\n uStepScale: { value: ORIGINAL_QUALITY / iterations },\n }\n });\n\n const mesh = new Mesh(gl, { geometry, program });\n\n const handleMouseMove = e => {\n if (!mouseInteractive) return;\n const rect = containerEl.getBoundingClientRect();\n // Store the latest position but don't touch GL state here, the rAF loop picks it up once per rendered frame instead of once per mouse event.\n pendingMouse.current = {\n x: e.clientX - rect.left,\n y: e.clientY - rect.top,\n };\n };\n\n if (mouseInteractive) {\n containerEl.addEventListener('mousemove', handleMouseMove, { passive: true });\n }\n\n let resizePending = false;\n const setSize = () => {\n const rect = containerEl.getBoundingClientRect();\n const width = Math.max(1, Math.floor(rect.width * renderScale));\n const height = Math.max(1, Math.floor(rect.height * renderScale));\n renderer.setSize(width, height);\n\n // renderer.setSize also sets canvas.style.width/height to match the (scaled-down) drawing buffer - override that so the canvas still stretches to fill its container via CSS while the buffer stays small.\n canvas.style.width = '100%';\n canvas.style.height = '100%';\n\n const res = program.uniforms.iResolution.value;\n res[0] = gl.drawingBufferWidth;\n res[1] = gl.drawingBufferHeight;\n };\n\n const ro = new ResizeObserver(() => {\n // Batch rapid resize events (ex. during a window drag) into one setSize per frame.\n if (resizePending) return;\n resizePending = true;\n requestAnimationFrame(() => {\n resizePending = false;\n setSize();\n });\n });\n ro.observe(containerEl);\n setSize();\n\n let raf = 0;\n let contextLost = false;\n let isVisible = true;\n let tabVisible = document.visibilityState !== 'hidden';\n const t0 = performance.now();\n const frameInterval = 1000 / targetFps;\n let lastFrameTime = 0;\n\n const renderStaticFrame = () => {\n program.uniforms.iTime.value = 0;\n renderer.render({ scene: mesh });\n };\n\n const loop = t => {\n if (contextLost || !isVisible || !tabVisible) return;\n\n if (t - lastFrameTime < frameInterval) {\n raf = requestAnimationFrame(loop);\n return;\n }\n lastFrameTime = t;\n\n if (pendingMouse.current) {\n mousePos.current = pendingMouse.current;\n pendingMouse.current = null;\n const mouseUniform = program.uniforms.uMouse.value;\n mouseUniform[0] = mousePos.current.x;\n mouseUniform[1] = mousePos.current.y;\n }\n\n let timeValue = (t - t0) * 0.001;\n if (direction === 'pingpong') {\n const pingpongDuration = 10;\n const segmentTime = timeValue % pingpongDuration;\n const isForward = Math.floor(timeValue / pingpongDuration) % 2 === 0;\n const u = segmentTime / pingpongDuration;\n const smooth = u * u * (3 - 2 * u);\n const pingpongTime = isForward ? smooth * pingpongDuration : (1 - smooth) * pingpongDuration;\n program.uniforms.uDirection.value = 1.0;\n program.uniforms.iTime.value = pingpongTime;\n } else {\n program.uniforms.iTime.value = timeValue;\n }\n renderer.render({ scene: mesh });\n raf = requestAnimationFrame(loop);\n };\n\n const handleContextLost = (e) => {\n e.preventDefault();\n contextLost = true;\n cancelAnimationFrame(raf);\n };\n const handleContextRestored = () => {\n contextLost = false;\n if (isVisible && tabVisible && !prefersReducedMotion) {\n cancelAnimationFrame(raf);\n raf = requestAnimationFrame(loop);\n }\n };\n canvas.addEventListener('webglcontextlost', handleContextLost);\n canvas.addEventListener('webglcontextrestored', handleContextRestored);\n\n const io = new IntersectionObserver(([entry]) => {\n const wasVisible = isVisible;\n isVisible = entry.isIntersecting;\n if (isVisible && !wasVisible && !contextLost && tabVisible && !prefersReducedMotion) {\n cancelAnimationFrame(raf);\n raf = requestAnimationFrame(loop);\n }\n }, { threshold: 0 });\n io.observe(containerEl);\n\n const handleVisibilityChange = () => {\n tabVisible = document.visibilityState !== 'hidden';\n if (tabVisible && isVisible && !contextLost && !prefersReducedMotion) {\n cancelAnimationFrame(raf);\n lastFrameTime = 0;\n raf = requestAnimationFrame(loop);\n } else {\n cancelAnimationFrame(raf);\n }\n };\n document.addEventListener('visibilitychange', handleVisibilityChange);\n\n // Respect prefers-reduced-motion: paint one frame and stop, rather than running a perpetual animation loop for users who've asked not to see motion.\n if (prefersReducedMotion) {\n renderStaticFrame();\n } else {\n raf = requestAnimationFrame(loop);\n }\n\n return () => {\n cancelAnimationFrame(raf);\n ro.disconnect();\n io.disconnect();\n document.removeEventListener('visibilitychange', handleVisibilityChange);\n canvas.removeEventListener('webglcontextlost', handleContextLost);\n canvas.removeEventListener('webglcontextrestored', handleContextRestored);\n if (mouseInteractive && containerEl) {\n containerEl.removeEventListener('mousemove', handleMouseMove);\n }\n try {\n containerEl?.removeChild(canvas);\n } catch {}\n };\n }, [color, speed, direction, scale, opacity, mouseInteractive, renderScale, maxDpr, targetFps, iterations]);\n\n return
;\n};\n\nexport default Plasma;\n" } ], "registryDependencies": [], diff --git a/public/r/Plasma-JS-TW.json b/public/r/Plasma-JS-TW.json index d8a3081e7..22ce5559f 100644 --- a/public/r/Plasma-JS-TW.json +++ b/public/r/Plasma-JS-TW.json @@ -8,7 +8,7 @@ { "type": "registry:component", "path": "Plasma/Plasma.jsx", - "content": "import { useEffect, useRef } from 'react';\nimport { Renderer, Program, Mesh, Triangle } from 'ogl';\n\nconst hexToRgb = hex => {\n const result = /^#?([a-f\\d]{2})([a-f\\d]{2})([a-f\\d]{2})$/i.exec(hex);\n if (!result) return [1, 0.5, 0.2];\n return [parseInt(result[1], 16) / 255, parseInt(result[2], 16) / 255, parseInt(result[3], 16) / 255];\n};\n\nconst vertex = `#version 300 es\nprecision highp float;\nin vec2 position;\nin vec2 uv;\nout vec2 vUv;\nvoid main() {\n vUv = uv;\n gl_Position = vec4(position, 0.0, 1.0);\n}\n`;\n\nconst buildFragment = steps => `#version 300 es\nprecision highp float;\nuniform vec2 iResolution;\nuniform float iTime;\nuniform vec3 uCustomColor;\nuniform float uUseCustomColor;\nuniform float uSpeed;\nuniform float uDirection;\nuniform float uScale;\nuniform float uOpacity;\nuniform vec2 uMouse;\nuniform float uMouseInteractive;\nout vec4 fragColor;\n\nvoid mainImage(out vec4 o, vec2 C) {\n vec2 center = iResolution.xy * 0.5;\n C = (C - center) / uScale + center;\n \n vec2 mouseOffset = (uMouse - center) * 0.0002;\n C += mouseOffset * length(C - center) * step(0.5, uMouseInteractive);\n \n float i, d, z, T = iTime * uSpeed * uDirection;\n vec3 O, p, S;\n\n for (vec2 r = iResolution.xy, Q; ++i < ${steps.toFixed(1)}; O += o.w/d*o.xyz) {\n p = z*normalize(vec3(C-.5*r,r.y)); \n p.z -= 4.; \n S = p;\n d = p.y-T;\n \n p.x += .4*(1.+p.y)*sin(d + p.x*0.1)*cos(.34*d + p.x*0.05); \n Q = p.xz *= mat2(cos(p.y+vec4(0,11,33,0)-T)); \n z+= d = abs(sqrt(length(Q*Q)) - .25*(5.+S.y))/3.+8e-4; \n o = 1.+sin(S.y+p.z*.5+S.z-length(S-p)+vec4(2,1,0,8));\n }\n \n o.xyz = tanh(O/1e4);\n}\n\nbool finite1(float x){ return !(isnan(x) || isinf(x)); }\nvec3 sanitize(vec3 c){\n return vec3(\n finite1(c.r) ? c.r : 0.0,\n finite1(c.g) ? c.g : 0.0,\n finite1(c.b) ? c.b : 0.0\n );\n}\n\nvoid main() {\n vec4 o = vec4(0.0);\n mainImage(o, gl_FragCoord.xy);\n vec3 rgb = sanitize(o.rgb);\n \n float intensity = (rgb.r + rgb.g + rgb.b) / 3.0;\n vec3 customColor = intensity * uCustomColor;\n vec3 finalColor = mix(rgb, customColor, step(0.5, uUseCustomColor));\n \n float alpha = length(rgb) * uOpacity;\n fragColor = vec4(finalColor, alpha);\n}`;\n\nexport const Plasma = ({\n color = '#ffffff',\n speed = 1,\n direction = 'forward',\n scale = 1,\n opacity = 1,\n mouseInteractive = true,\n renderScale = 0.55,\n maxDpr = 1.5,\n targetFps = 60,\n quality = 45,\n}) => {\n const containerRef = useRef(null);\n const mousePos = useRef({ x: 0, y: 0 });\n const pendingMouse = useRef(null);\n\n useEffect(() => {\n if (!containerRef.current) return;\n const containerEl = containerRef.current;\n\n const prefersReducedMotion =\n typeof window !== 'undefined' &&\n window.matchMedia?.('(prefers-reduced-motion: reduce)').matches;\n\n const useCustomColor = color ? 1.0 : 0.0;\n const customColorRgb = color ? hexToRgb(color) : [1, 1, 1];\n\n const directionMultiplier = direction === 'reverse' ? -1.0 : 1.0;\n\n let renderer;\n try {\n renderer = new Renderer({\n webgl: 2,\n alpha: true,\n antialias: false,\n dpr: Math.min(window.devicePixelRatio || 1, maxDpr)\n });\n } catch {\n return;\n }\n const gl = renderer.gl;\n if (!gl) return;\n const canvas = gl.canvas;\n canvas.style.display = 'block';\n canvas.style.width = '100%';\n canvas.style.height = '100%';\n // Rendering at renderScale internally, CSS stretches it back up.\n containerEl.appendChild(canvas);\n\n const geometry = new Triangle(gl);\n\n const program = new Program(gl, {\n vertex: vertex,\n fragment: buildFragment(quality),\n uniforms: {\n iTime: { value: 0 },\n iResolution: { value: new Float32Array([1, 1]) },\n uCustomColor: { value: new Float32Array(customColorRgb) },\n uUseCustomColor: { value: useCustomColor },\n uSpeed: { value: speed * 0.4 },\n uDirection: { value: directionMultiplier },\n uScale: { value: scale },\n uOpacity: { value: opacity },\n uMouse: { value: new Float32Array([0, 0]) },\n uMouseInteractive: { value: mouseInteractive ? 1.0 : 0.0 }\n }\n });\n\n const mesh = new Mesh(gl, { geometry, program });\n\n const handleMouseMove = e => {\n if (!mouseInteractive) return;\n const rect = containerEl.getBoundingClientRect();\n // Store the latest position but don't touch GL state here, the rAF loop picks it up once per rendered frame instead of once per mouse event.\n pendingMouse.current = {\n x: e.clientX - rect.left,\n y: e.clientY - rect.top,\n };\n };\n\n if (mouseInteractive) {\n containerEl.addEventListener('mousemove', handleMouseMove, { passive: true });\n }\n\n let resizePending = false;\n const setSize = () => {\n const rect = containerEl.getBoundingClientRect();\n const width = Math.max(1, Math.floor(rect.width * renderScale));\n const height = Math.max(1, Math.floor(rect.height * renderScale));\n renderer.setSize(width, height);\n\n // renderer.setSize also sets canvas.style.width/height to match the (scaled-down) drawing buffer - override that so the canvas still stretches to fill its container via CSS while the buffer stays small.\n canvas.style.width = '100%';\n canvas.style.height = '100%';\n\n const res = program.uniforms.iResolution.value;\n res[0] = gl.drawingBufferWidth;\n res[1] = gl.drawingBufferHeight;\n };\n\n const ro = new ResizeObserver(() => {\n // Batch rapid resize events (ex. during a window drag) into one setSize per frame.\n if (resizePending) return;\n resizePending = true;\n requestAnimationFrame(() => {\n resizePending = false;\n setSize();\n });\n });\n ro.observe(containerEl);\n setSize();\n\n let raf = 0;\n let contextLost = false;\n let isVisible = true;\n let tabVisible = document.visibilityState !== 'hidden';\n const t0 = performance.now();\n const frameInterval = 1000 / targetFps;\n let lastFrameTime = 0;\n\n const renderStaticFrame = () => {\n program.uniforms.iTime.value = 0;\n renderer.render({ scene: mesh });\n };\n\n const loop = t => {\n if (contextLost || !isVisible || !tabVisible) return;\n\n if (t - lastFrameTime < frameInterval) {\n raf = requestAnimationFrame(loop);\n return;\n }\n lastFrameTime = t;\n\n if (pendingMouse.current) {\n mousePos.current = pendingMouse.current;\n pendingMouse.current = null;\n const mouseUniform = program.uniforms.uMouse.value;\n mouseUniform[0] = mousePos.current.x;\n mouseUniform[1] = mousePos.current.y;\n }\n\n let timeValue = (t - t0) * 0.001;\n if (direction === 'pingpong') {\n const pingpongDuration = 10;\n const segmentTime = timeValue % pingpongDuration;\n const isForward = Math.floor(timeValue / pingpongDuration) % 2 === 0;\n const u = segmentTime / pingpongDuration;\n const smooth = u * u * (3 - 2 * u);\n const pingpongTime = isForward ? smooth * pingpongDuration : (1 - smooth) * pingpongDuration;\n program.uniforms.uDirection.value = 1.0;\n program.uniforms.iTime.value = pingpongTime;\n } else {\n program.uniforms.iTime.value = timeValue;\n }\n renderer.render({ scene: mesh });\n raf = requestAnimationFrame(loop);\n };\n\n const handleContextLost = (e) => {\n e.preventDefault();\n contextLost = true;\n cancelAnimationFrame(raf);\n };\n const handleContextRestored = () => {\n contextLost = false;\n if (isVisible && tabVisible && !prefersReducedMotion) {\n cancelAnimationFrame(raf);\n raf = requestAnimationFrame(loop);\n }\n };\n canvas.addEventListener('webglcontextlost', handleContextLost);\n canvas.addEventListener('webglcontextrestored', handleContextRestored);\n\n const io = new IntersectionObserver(([entry]) => {\n const wasVisible = isVisible;\n isVisible = entry.isIntersecting;\n if (isVisible && !wasVisible && !contextLost && tabVisible && !prefersReducedMotion) {\n cancelAnimationFrame(raf);\n raf = requestAnimationFrame(loop);\n }\n }, { threshold: 0 });\n io.observe(containerEl);\n\n const handleVisibilityChange = () => {\n tabVisible = document.visibilityState !== 'hidden';\n if (tabVisible && isVisible && !contextLost && !prefersReducedMotion) {\n cancelAnimationFrame(raf);\n lastFrameTime = 0;\n raf = requestAnimationFrame(loop);\n } else {\n cancelAnimationFrame(raf);\n }\n };\n document.addEventListener('visibilitychange', handleVisibilityChange);\n\n // Respect prefers-reduced-motion: paint one frame and stop, rather than running a perpetual animation loop for users who've asked not to see motion.\n if (prefersReducedMotion) {\n renderStaticFrame();\n } else {\n raf = requestAnimationFrame(loop);\n }\n\n return () => {\n cancelAnimationFrame(raf);\n ro.disconnect();\n io.disconnect();\n document.removeEventListener('visibilitychange', handleVisibilityChange);\n canvas.removeEventListener('webglcontextlost', handleContextLost);\n canvas.removeEventListener('webglcontextrestored', handleContextRestored);\n if (mouseInteractive && containerEl) {\n containerEl.removeEventListener('mousemove', handleMouseMove);\n }\n try {\n containerEl?.removeChild(canvas);\n } catch {}\n };\n }, [color, speed, direction, scale, opacity, mouseInteractive, renderScale, maxDpr, targetFps, quality]);\n\n return
;\n};\n\nexport default Plasma;\n" + "content": "import { useEffect, useRef } from 'react';\nimport { Renderer, Program, Mesh, Triangle } from 'ogl';\n\nconst hexToRgb = hex => {\n const result = /^#?([a-f\\d]{2})([a-f\\d]{2})([a-f\\d]{2})$/i.exec(hex);\n if (!result) return [1, 0.5, 0.2];\n return [parseInt(result[1], 16) / 255, parseInt(result[2], 16) / 255, parseInt(result[3], 16) / 255];\n};\n\nconst vertex = `#version 300 es\nprecision highp float;\nin vec2 position;\nin vec2 uv;\nout vec2 vUv;\nvoid main() {\n vUv = uv;\n gl_Position = vec4(position, 0.0, 1.0);\n}\n`;\n\nconst ORIGINAL_QUALITY = 60;\n\nconst buildFragment = iterations => {\n return `#version 300 es\nprecision highp float;\nuniform vec2 iResolution;\nuniform float iTime;\nuniform vec3 uCustomColor;\nuniform float uUseCustomColor;\nuniform float uSpeed;\nuniform float uDirection;\nuniform float uScale;\nuniform float uOpacity;\nuniform vec2 uMouse;\nuniform float uMouseInteractive;\nuniform float uQuality;\nuniform float uStepScale;\nout vec4 fragColor;\n\nvoid mainImage(out vec4 o, vec2 C) {\n vec2 center = iResolution.xy * 0.5;\n C = (C - center) / uScale + center;\n \n vec2 mouseOffset = (uMouse - center) * 0.0002;\n C += mouseOffset * length(C - center) * step(0.5, uMouseInteractive);\n \n float i, d, z, T = iTime * uSpeed * uDirection;\n vec3 O, p, S;\n\n for (vec2 r = iResolution.xy, Q; ++i < 60.0; O += o.w/d*o.xyz) {\n p = z*normalize(vec3(C-.5*r,r.y)); \n p.z -= 4.; \n S = p;\n d = p.y-T;\n \n p.x += .4*(1.+p.y)*sin(d + p.x*0.1)*cos(.34*d + p.x*0.05); \n Q = p.xz *= mat2(cos(p.y+vec4(0,11,33,0)-T)); \n z += d = (abs(sqrt(length(Q*Q)) - .25*(5.+S.y))/3.+8e-4) * uStepScale;\n o = 1.+sin(S.y+p.z*.5+S.z-length(S-p)+vec4(2,1,0,8));\n if (i >= uQuality) break;\n }\n \n o.xyz = tanh(O/1e4);\n}\n\n\nbool finite1(float x){ return !(isnan(x) || isinf(x)); }\nvec3 sanitize(vec3 c){\n return vec3(\n finite1(c.r) ? c.r : 0.0,\n finite1(c.g) ? c.g : 0.0,\n finite1(c.b) ? c.b : 0.0\n );\n}\n\nvoid main() {\n vec4 o = vec4(0.0);\n mainImage(o, gl_FragCoord.xy);\n vec3 rgb = sanitize(o.rgb);\n \n float intensity = (rgb.r + rgb.g + rgb.b) / 3.0;\n vec3 customColor = intensity * uCustomColor;\n vec3 finalColor = mix(rgb, customColor, step(0.5, uUseCustomColor));\n \n float alpha = length(rgb) * uOpacity;\n fragColor = vec4(finalColor, alpha);\n}`;\n};\n\nexport const Plasma = ({\n color = '#ffffff',\n speed = 1,\n direction = 'forward',\n scale = 1,\n opacity = 1,\n mouseInteractive = true,\n renderScale = 0.55,\n maxDpr = 1.5,\n targetFps = 60,\n iterations = 60\n}) => {\n const containerRef = useRef(null);\n const mousePos = useRef({ x: 0, y: 0 });\n const pendingMouse = useRef(null);\n\n useEffect(() => {\n if (!containerRef.current) return;\n const containerEl = containerRef.current;\n\n const prefersReducedMotion =\n typeof window !== 'undefined' && window.matchMedia?.('(prefers-reduced-motion: reduce)').matches;\n\n const useCustomColor = color ? 1.0 : 0.0;\n const customColorRgb = color ? hexToRgb(color) : [1, 1, 1];\n\n const directionMultiplier = direction === 'reverse' ? -1.0 : 1.0;\n\n let renderer;\n try {\n renderer = new Renderer({\n webgl: 2,\n alpha: true,\n antialias: false,\n dpr: Math.min(window.devicePixelRatio || 1, maxDpr)\n });\n } catch {\n return;\n }\n const gl = renderer.gl;\n if (!gl) return;\n const canvas = gl.canvas;\n canvas.style.display = 'block';\n canvas.style.width = '100%';\n canvas.style.height = '100%';\n // Rendering at renderScale internally, CSS stretches it back up.\n containerEl.appendChild(canvas);\n\n const geometry = new Triangle(gl);\n\n const program = new Program(gl, {\n vertex: vertex,\n fragment: buildFragment(iterations),\n uniforms: {\n iTime: { value: 0 },\n iResolution: { value: new Float32Array([1, 1]) },\n uCustomColor: { value: new Float32Array(customColorRgb) },\n uUseCustomColor: { value: useCustomColor },\n uSpeed: { value: speed * 0.4 },\n uDirection: { value: directionMultiplier },\n uScale: { value: scale },\n uOpacity: { value: opacity },\n uMouse: { value: new Float32Array([0, 0]) },\n uMouseInteractive: { value: mouseInteractive ? 1.0 : 0.0 },\n uQuality: { value: iterations },\n uStepScale: { value: ORIGINAL_QUALITY / iterations }\n }\n });\n\n const mesh = new Mesh(gl, { geometry, program });\n\n const handleMouseMove = e => {\n if (!mouseInteractive) return;\n const rect = containerEl.getBoundingClientRect();\n // Store the latest position but don't touch GL state here, the rAF loop picks it up once per rendered frame instead of once per mouse event.\n pendingMouse.current = {\n x: e.clientX - rect.left,\n y: e.clientY - rect.top\n };\n };\n\n if (mouseInteractive) {\n containerEl.addEventListener('mousemove', handleMouseMove, { passive: true });\n }\n\n let resizePending = false;\n const setSize = () => {\n const rect = containerEl.getBoundingClientRect();\n const width = Math.max(1, Math.floor(rect.width * renderScale));\n const height = Math.max(1, Math.floor(rect.height * renderScale));\n renderer.setSize(width, height);\n\n // renderer.setSize also sets canvas.style.width/height to match the (scaled-down) drawing buffer - override that so the canvas still stretches to fill its container via CSS while the buffer stays small.\n canvas.style.width = '100%';\n canvas.style.height = '100%';\n\n const res = program.uniforms.iResolution.value;\n res[0] = gl.drawingBufferWidth;\n res[1] = gl.drawingBufferHeight;\n };\n\n const ro = new ResizeObserver(() => {\n // Batch rapid resize events (ex. during a window drag) into one setSize per frame.\n if (resizePending) return;\n resizePending = true;\n requestAnimationFrame(() => {\n resizePending = false;\n setSize();\n });\n });\n ro.observe(containerEl);\n setSize();\n\n let raf = 0;\n let contextLost = false;\n let isVisible = true;\n let tabVisible = document.visibilityState !== 'hidden';\n const t0 = performance.now();\n const frameInterval = 1000 / targetFps;\n let lastFrameTime = 0;\n\n const renderStaticFrame = () => {\n program.uniforms.iTime.value = 0;\n renderer.render({ scene: mesh });\n };\n\n const loop = t => {\n if (contextLost || !isVisible || !tabVisible) return;\n\n if (t - lastFrameTime < frameInterval) {\n raf = requestAnimationFrame(loop);\n return;\n }\n lastFrameTime = t;\n\n if (pendingMouse.current) {\n mousePos.current = pendingMouse.current;\n pendingMouse.current = null;\n const mouseUniform = program.uniforms.uMouse.value;\n mouseUniform[0] = mousePos.current.x;\n mouseUniform[1] = mousePos.current.y;\n }\n\n let timeValue = (t - t0) * 0.001;\n if (direction === 'pingpong') {\n const pingpongDuration = 10;\n const segmentTime = timeValue % pingpongDuration;\n const isForward = Math.floor(timeValue / pingpongDuration) % 2 === 0;\n const u = segmentTime / pingpongDuration;\n const smooth = u * u * (3 - 2 * u);\n const pingpongTime = isForward ? smooth * pingpongDuration : (1 - smooth) * pingpongDuration;\n program.uniforms.uDirection.value = 1.0;\n program.uniforms.iTime.value = pingpongTime;\n } else {\n program.uniforms.iTime.value = timeValue;\n }\n renderer.render({ scene: mesh });\n raf = requestAnimationFrame(loop);\n };\n\n const handleContextLost = e => {\n e.preventDefault();\n contextLost = true;\n cancelAnimationFrame(raf);\n };\n const handleContextRestored = () => {\n contextLost = false;\n if (isVisible && tabVisible && !prefersReducedMotion) {\n cancelAnimationFrame(raf);\n raf = requestAnimationFrame(loop);\n }\n };\n canvas.addEventListener('webglcontextlost', handleContextLost);\n canvas.addEventListener('webglcontextrestored', handleContextRestored);\n\n const io = new IntersectionObserver(\n ([entry]) => {\n const wasVisible = isVisible;\n isVisible = entry.isIntersecting;\n if (isVisible && !wasVisible && !contextLost && tabVisible && !prefersReducedMotion) {\n cancelAnimationFrame(raf);\n raf = requestAnimationFrame(loop);\n }\n },\n { threshold: 0 }\n );\n io.observe(containerEl);\n\n const handleVisibilityChange = () => {\n tabVisible = document.visibilityState !== 'hidden';\n if (tabVisible && isVisible && !contextLost && !prefersReducedMotion) {\n cancelAnimationFrame(raf);\n lastFrameTime = 0;\n raf = requestAnimationFrame(loop);\n } else {\n cancelAnimationFrame(raf);\n }\n };\n document.addEventListener('visibilitychange', handleVisibilityChange);\n\n // Respect prefers-reduced-motion: paint one frame and stop, rather than running a perpetual animation loop for users who've asked not to see motion.\n if (prefersReducedMotion) {\n renderStaticFrame();\n } else {\n raf = requestAnimationFrame(loop);\n }\n\n return () => {\n cancelAnimationFrame(raf);\n ro.disconnect();\n io.disconnect();\n document.removeEventListener('visibilitychange', handleVisibilityChange);\n canvas.removeEventListener('webglcontextlost', handleContextLost);\n canvas.removeEventListener('webglcontextrestored', handleContextRestored);\n if (mouseInteractive && containerEl) {\n containerEl.removeEventListener('mousemove', handleMouseMove);\n }\n try {\n containerEl?.removeChild(canvas);\n } catch {}\n };\n }, [color, speed, direction, scale, opacity, mouseInteractive, renderScale, maxDpr, targetFps, iterations]);\n\n return
;\n};\n\nexport default Plasma;\n" } ], "registryDependencies": [], diff --git a/public/r/Plasma-TS-CSS.json b/public/r/Plasma-TS-CSS.json index 940a778e8..79c72f203 100644 --- a/public/r/Plasma-TS-CSS.json +++ b/public/r/Plasma-TS-CSS.json @@ -13,7 +13,7 @@ { "type": "registry:component", "path": "Plasma/Plasma.tsx", - "content": "import React, { useEffect, useRef } from 'react';\nimport { Renderer, Program, Mesh, Triangle } from 'ogl';\nimport './Plasma.css';\n\ninterface PlasmaProps {\n color?: string;\n speed?: number;\n direction?: 'forward' | 'reverse' | 'pingpong';\n scale?: number;\n opacity?: number;\n mouseInteractive?: boolean;\n /** Internal render resolution multiplier. 1 = full res, 0.5 = quarter the pixels. Default 0.55. */\n renderScale?: number;\n /** Hard cap on devicePixelRatio used for rendering. Default 1.5. */\n maxDpr?: number;\n /** Target frame rate for the animation loop. Default 30. */\n targetFps?: number;\n /** Raymarch step count — lower is cheaper, less detailed. Default 45. */\n quality?: number;\n}\n\nconst hexToRgb = (hex: string): [number, number, number] => {\n const result = /^#?([a-f\\d]{2})([a-f\\d]{2})([a-f\\d]{2})$/i.exec(hex);\n if (!result) return [1, 0.5, 0.2];\n return [parseInt(result[1], 16) / 255, parseInt(result[2], 16) / 255, parseInt(result[3], 16) / 255];\n};\n\nconst vertex = `#version 300 es\nprecision highp float;\nin vec2 position;\nin vec2 uv;\nout vec2 vUv;\nvoid main() {\n vUv = uv;\n gl_Position = vec4(position, 0.0, 1.0);\n}\n`;\n\nconst buildFragment = (steps: number) => `#version 300 es\nprecision highp float;\nuniform vec2 iResolution;\nuniform float iTime;\nuniform vec3 uCustomColor;\nuniform float uUseCustomColor;\nuniform float uSpeed;\nuniform float uDirection;\nuniform float uScale;\nuniform float uOpacity;\nuniform vec2 uMouse;\nuniform float uMouseInteractive;\nout vec4 fragColor;\n\nvoid mainImage(out vec4 o, vec2 C) {\n vec2 center = iResolution.xy * 0.5;\n C = (C - center) / uScale + center;\n \n vec2 mouseOffset = (uMouse - center) * 0.0002;\n C += mouseOffset * length(C - center) * step(0.5, uMouseInteractive);\n \n float i, d, z, T = iTime * uSpeed * uDirection;\n vec3 O, p, S;\n\n for (vec2 r = iResolution.xy, Q; ++i < ${steps.toFixed(1)}; O += o.w/d*o.xyz) {\n p = z*normalize(vec3(C-.5*r,r.y)); \n p.z -= 4.; \n S = p;\n d = p.y-T;\n \n p.x += .4*(1.+p.y)*sin(d + p.x*0.1)*cos(.34*d + p.x*0.05); \n Q = p.xz *= mat2(cos(p.y+vec4(0,11,33,0)-T)); \n z+= d = abs(sqrt(length(Q*Q)) - .25*(5.+S.y))/3.+8e-4; \n o = 1.+sin(S.y+p.z*.5+S.z-length(S-p)+vec4(2,1,0,8));\n }\n \n o.xyz = tanh(O/1e4);\n}\n\nbool finite1(float x){ return !(isnan(x) || isinf(x)); }\nvec3 sanitize(vec3 c){\n return vec3(\n finite1(c.r) ? c.r : 0.0,\n finite1(c.g) ? c.g : 0.0,\n finite1(c.b) ? c.b : 0.0\n );\n}\n\nvoid main() {\n vec4 o = vec4(0.0);\n mainImage(o, gl_FragCoord.xy);\n vec3 rgb = sanitize(o.rgb);\n \n float intensity = (rgb.r + rgb.g + rgb.b) / 3.0;\n vec3 customColor = intensity * uCustomColor;\n vec3 finalColor = mix(rgb, customColor, step(0.5, uUseCustomColor));\n \n float alpha = length(rgb) * uOpacity;\n fragColor = vec4(finalColor, alpha);\n}`;\n\nexport const Plasma: React.FC = ({\n color = '#ffffff',\n speed = 1,\n direction = 'forward',\n scale = 1,\n opacity = 1,\n mouseInteractive = true,\n renderScale = 0.55,\n maxDpr = 1.5,\n targetFps = 60,\n quality = 45,\n}) => {\n const containerRef = useRef(null);\n const mousePos = useRef({ x: 0, y: 0 });\n const pendingMouse = useRef<{ x: number; y: number } | null>(null);\n\n useEffect(() => {\n if (!containerRef.current) return;\n const containerEl = containerRef.current;\n\n const prefersReducedMotion =\n typeof window !== 'undefined' &&\n window.matchMedia?.('(prefers-reduced-motion: reduce)').matches;\n\n const useCustomColor = color ? 1.0 : 0.0;\n const customColorRgb = color ? hexToRgb(color) : [1, 1, 1];\n\n const directionMultiplier = direction === 'reverse' ? -1.0 : 1.0;\n\n let renderer: Renderer;\n try {\n renderer = new Renderer({\n webgl: 2,\n alpha: true,\n antialias: false,\n dpr: Math.min(window.devicePixelRatio || 1, maxDpr)\n });\n } catch {\n return;\n }\n const gl = renderer.gl;\n if (!gl) return;\n const canvas = gl.canvas as HTMLCanvasElement;\n canvas.style.display = 'block';\n canvas.style.width = '100%';\n canvas.style.height = '100%';\n // Rendering at renderScale internally, CSS stretches it back up.\n containerEl.appendChild(canvas);\n\n const geometry = new Triangle(gl);\n\n const program = new Program(gl, {\n vertex: vertex,\n fragment: buildFragment(quality),\n uniforms: {\n iTime: { value: 0 },\n iResolution: { value: new Float32Array([1, 1]) },\n uCustomColor: { value: new Float32Array(customColorRgb) },\n uUseCustomColor: { value: useCustomColor },\n uSpeed: { value: speed * 0.4 },\n uDirection: { value: directionMultiplier },\n uScale: { value: scale },\n uOpacity: { value: opacity },\n uMouse: { value: new Float32Array([0, 0]) },\n uMouseInteractive: { value: mouseInteractive ? 1.0 : 0.0 }\n }\n });\n\n const mesh = new Mesh(gl, { geometry, program });\n\n const handleMouseMove = (e: MouseEvent) => {\n if (!mouseInteractive) return;\n const rect = containerEl.getBoundingClientRect();\n // Store the latest position but don't touch GL state here, the rAF loop picks it up once per rendered frame instead of once per mouse event.\n pendingMouse.current = {\n x: e.clientX - rect.left,\n y: e.clientY - rect.top,\n };\n };\n\n if (mouseInteractive) {\n containerEl.addEventListener('mousemove', handleMouseMove, { passive: true });\n }\n\n let resizePending = false;\n const setSize = () => {\n const rect = containerEl.getBoundingClientRect();\n const width = Math.max(1, Math.floor(rect.width * renderScale));\n const height = Math.max(1, Math.floor(rect.height * renderScale));\n renderer.setSize(width, height);\n\n // renderer.setSize also sets canvas.style.width/height to match the (scaled-down) drawing buffer - override that so the canvas still stretches to fill its container via CSS while the buffer stays small.\n canvas.style.width = '100%';\n canvas.style.height = '100%';\n\n const res = program.uniforms.iResolution.value as Float32Array;\n res[0] = gl.drawingBufferWidth;\n res[1] = gl.drawingBufferHeight;\n };\n\n const ro = new ResizeObserver(() => {\n // Batch rapid resize events (ex. during a window drag) into one setSize per frame.\n if (resizePending) return;\n resizePending = true;\n requestAnimationFrame(() => {\n resizePending = false;\n setSize();\n });\n });\n ro.observe(containerEl);\n setSize();\n\n let raf = 0;\n let contextLost = false;\n let isVisible = true;\n let tabVisible = document.visibilityState !== 'hidden';\n const t0 = performance.now();\n const frameInterval = 1000 / targetFps;\n let lastFrameTime = 0;\n\n const renderStaticFrame = () => {\n (program.uniforms.iTime as any).value = 0;\n renderer.render({ scene: mesh });\n };\n\n const loop = (t: number) => {\n if (contextLost || !isVisible || !tabVisible) return;\n\n if (t - lastFrameTime < frameInterval) {\n raf = requestAnimationFrame(loop);\n return;\n }\n lastFrameTime = t;\n\n if (pendingMouse.current) {\n mousePos.current = pendingMouse.current;\n pendingMouse.current = null;\n const mouseUniform = program.uniforms.uMouse.value as Float32Array;\n mouseUniform[0] = mousePos.current.x;\n mouseUniform[1] = mousePos.current.y;\n }\n\n let timeValue = (t - t0) * 0.001;\n if (direction === 'pingpong') {\n const pingpongDuration = 10;\n const segmentTime = timeValue % pingpongDuration;\n const isForward = Math.floor(timeValue / pingpongDuration) % 2 === 0;\n const u = segmentTime / pingpongDuration;\n const smooth = u * u * (3 - 2 * u);\n const pingpongTime = isForward ? smooth * pingpongDuration : (1 - smooth) * pingpongDuration;\n (program.uniforms.uDirection as any).value = 1.0;\n (program.uniforms.iTime as any).value = pingpongTime;\n } else {\n (program.uniforms.iTime as any).value = timeValue;\n }\n renderer.render({ scene: mesh });\n raf = requestAnimationFrame(loop);\n };\n\n const handleContextLost = (e: Event) => {\n e.preventDefault();\n contextLost = true;\n cancelAnimationFrame(raf);\n };\n const handleContextRestored = () => {\n contextLost = false;\n if (isVisible && tabVisible && !prefersReducedMotion) {\n cancelAnimationFrame(raf);\n raf = requestAnimationFrame(loop);\n }\n };\n canvas.addEventListener('webglcontextlost', handleContextLost);\n canvas.addEventListener('webglcontextrestored', handleContextRestored);\n\n const io = new IntersectionObserver(([entry]) => {\n const wasVisible = isVisible;\n isVisible = entry.isIntersecting;\n if (isVisible && !wasVisible && !contextLost && tabVisible && !prefersReducedMotion) {\n cancelAnimationFrame(raf);\n raf = requestAnimationFrame(loop);\n }\n }, { threshold: 0 });\n io.observe(containerEl);\n\n const handleVisibilityChange = () => {\n tabVisible = document.visibilityState !== 'hidden';\n if (tabVisible && isVisible && !contextLost && !prefersReducedMotion) {\n cancelAnimationFrame(raf);\n lastFrameTime = 0;\n raf = requestAnimationFrame(loop);\n } else {\n cancelAnimationFrame(raf);\n }\n };\n document.addEventListener('visibilitychange', handleVisibilityChange);\n\n // Respect prefers-reduced-motion: paint one frame and stop, rather than running a perpetual animation loop for users who've asked not to see motion.\n if (prefersReducedMotion) {\n renderStaticFrame();\n } else {\n raf = requestAnimationFrame(loop);\n }\n\n return () => {\n cancelAnimationFrame(raf);\n ro.disconnect();\n io.disconnect();\n document.removeEventListener('visibilitychange', handleVisibilityChange);\n canvas.removeEventListener('webglcontextlost', handleContextLost);\n canvas.removeEventListener('webglcontextrestored', handleContextRestored);\n if (mouseInteractive && containerEl) {\n containerEl.removeEventListener('mousemove', handleMouseMove);\n }\n try {\n containerEl?.removeChild(canvas);\n } catch {}\n };\n }, [color, speed, direction, scale, opacity, mouseInteractive, renderScale, maxDpr, targetFps, quality]);\n\n return
;\n};\n\nexport default Plasma;\n" + "content": "import React, { useEffect, useRef } from 'react';\nimport { Renderer, Program, Mesh, Triangle } from 'ogl';\nimport './Plasma.css';\n\ninterface PlasmaProps {\n color?: string;\n speed?: number;\n direction?: 'forward' | 'reverse' | 'pingpong';\n scale?: number;\n opacity?: number;\n mouseInteractive?: boolean;\n /** Internal render resolution multiplier. 1 = full res, 0.5 = quarter the pixels. Default 0.55. */\n renderScale?: number;\n /** Hard cap on devicePixelRatio used for rendering. Default 1.5. */\n maxDpr?: number;\n /** Target frame rate for the animation loop. Default 30. */\n targetFps?: number;\n /** Raymarch step count — lower is cheaper, less detailed. Default 60. */\n iterations?: number;\n}\n\nconst hexToRgb = (hex: string): [number, number, number] => {\n const result = /^#?([a-f\\d]{2})([a-f\\d]{2})([a-f\\d]{2})$/i.exec(hex);\n if (!result) return [1, 0.5, 0.2];\n return [parseInt(result[1], 16) / 255, parseInt(result[2], 16) / 255, parseInt(result[3], 16) / 255];\n};\n\nconst vertex = `#version 300 es\nprecision highp float;\nin vec2 position;\nin vec2 uv;\nout vec2 vUv;\nvoid main() {\n vUv = uv;\n gl_Position = vec4(position, 0.0, 1.0);\n}\n`;\n\nconst ORIGINAL_QUALITY = 60;\n\nconst buildFragment = (iterations: number) => {\n return `#version 300 es\nprecision highp float;\nuniform vec2 iResolution;\nuniform float iTime;\nuniform vec3 uCustomColor;\nuniform float uUseCustomColor;\nuniform float uSpeed;\nuniform float uDirection;\nuniform float uScale;\nuniform float uOpacity;\nuniform vec2 uMouse;\nuniform float uMouseInteractive;\nuniform float uQuality;\nuniform float uStepScale;\nout vec4 fragColor;\n\nvoid mainImage(out vec4 o, vec2 C) {\n vec2 center = iResolution.xy * 0.5;\n C = (C - center) / uScale + center;\n \n vec2 mouseOffset = (uMouse - center) * 0.0002;\n C += mouseOffset * length(C - center) * step(0.5, uMouseInteractive);\n \n float i, d, z, T = iTime * uSpeed * uDirection;\n vec3 O, p, S;\n\n for (vec2 r = iResolution.xy, Q; ++i < 60.0; O += o.w/d*o.xyz) {\n p = z*normalize(vec3(C-.5*r,r.y)); \n p.z -= 4.; \n S = p;\n d = p.y-T;\n \n p.x += .4*(1.+p.y)*sin(d + p.x*0.1)*cos(.34*d + p.x*0.05); \n Q = p.xz *= mat2(cos(p.y+vec4(0,11,33,0)-T)); \n z += d = (abs(sqrt(length(Q*Q)) - .25*(5.+S.y))/3.+8e-4) * uStepScale;\n o = 1.+sin(S.y+p.z*.5+S.z-length(S-p)+vec4(2,1,0,8));\n if (i >= uQuality) break;\n }\n \n o.xyz = tanh(O/1e4);\n}\n\nbool finite1(float x){ return !(isnan(x) || isinf(x)); }\nvec3 sanitize(vec3 c){\n return vec3(\n finite1(c.r) ? c.r : 0.0,\n finite1(c.g) ? c.g : 0.0,\n finite1(c.b) ? c.b : 0.0\n );\n}\n\nvoid main() {\n vec4 o = vec4(0.0);\n mainImage(o, gl_FragCoord.xy);\n vec3 rgb = sanitize(o.rgb);\n \n float intensity = (rgb.r + rgb.g + rgb.b) / 3.0;\n vec3 customColor = intensity * uCustomColor;\n vec3 finalColor = mix(rgb, customColor, step(0.5, uUseCustomColor));\n \n float alpha = length(rgb) * uOpacity;\n fragColor = vec4(finalColor, alpha);\n}`;\n};\n\nexport const Plasma: React.FC = ({\n color = '#ffffff',\n speed = 1,\n direction = 'forward',\n scale = 1,\n opacity = 1,\n mouseInteractive = true,\n renderScale = 0.55,\n maxDpr = 1.5,\n targetFps = 60,\n iterations = 60,\n}) => {\n const containerRef = useRef(null);\n const mousePos = useRef({ x: 0, y: 0 });\n const pendingMouse = useRef<{ x: number; y: number } | null>(null);\n\n useEffect(() => {\n if (!containerRef.current) return;\n const containerEl = containerRef.current;\n\n const prefersReducedMotion =\n typeof window !== 'undefined' &&\n window.matchMedia?.('(prefers-reduced-motion: reduce)').matches;\n\n const useCustomColor = color ? 1.0 : 0.0;\n const customColorRgb = color ? hexToRgb(color) : [1, 1, 1];\n\n const directionMultiplier = direction === 'reverse' ? -1.0 : 1.0;\n\n let renderer: Renderer;\n try {\n renderer = new Renderer({\n webgl: 2,\n alpha: true,\n antialias: false,\n dpr: Math.min(window.devicePixelRatio || 1, maxDpr)\n });\n } catch {\n return;\n }\n const gl = renderer.gl;\n if (!gl) return;\n const canvas = gl.canvas as HTMLCanvasElement;\n canvas.style.display = 'block';\n canvas.style.width = '100%';\n canvas.style.height = '100%';\n // Rendering at renderScale internally, CSS stretches it back up.\n containerEl.appendChild(canvas);\n\n const geometry = new Triangle(gl);\n\n const program = new Program(gl, {\n vertex: vertex,\n fragment: buildFragment(iterations),\n uniforms: {\n iTime: { value: 0 },\n iResolution: { value: new Float32Array([1, 1]) },\n uCustomColor: { value: new Float32Array(customColorRgb) },\n uUseCustomColor: { value: useCustomColor },\n uSpeed: { value: speed * 0.4 },\n uDirection: { value: directionMultiplier },\n uScale: { value: scale },\n uOpacity: { value: opacity },\n uMouse: { value: new Float32Array([0, 0]) },\n uMouseInteractive: { value: mouseInteractive ? 1.0 : 0.0 },\n uQuality: { value: iterations },\n uStepScale: { value: ORIGINAL_QUALITY / iterations },\n }\n });\n\n const mesh = new Mesh(gl, { geometry, program });\n\n const handleMouseMove = (e: MouseEvent) => {\n if (!mouseInteractive) return;\n const rect = containerEl.getBoundingClientRect();\n // Store the latest position but don't touch GL state here, the rAF loop picks it up once per rendered frame instead of once per mouse event.\n pendingMouse.current = {\n x: e.clientX - rect.left,\n y: e.clientY - rect.top,\n };\n };\n\n if (mouseInteractive) {\n containerEl.addEventListener('mousemove', handleMouseMove, { passive: true });\n }\n\n let resizePending = false;\n const setSize = () => {\n const rect = containerEl.getBoundingClientRect();\n const width = Math.max(1, Math.floor(rect.width * renderScale));\n const height = Math.max(1, Math.floor(rect.height * renderScale));\n renderer.setSize(width, height);\n\n // renderer.setSize also sets canvas.style.width/height to match the (scaled-down) drawing buffer - override that so the canvas still stretches to fill its container via CSS while the buffer stays small.\n canvas.style.width = '100%';\n canvas.style.height = '100%';\n\n const res = program.uniforms.iResolution.value as Float32Array;\n res[0] = gl.drawingBufferWidth;\n res[1] = gl.drawingBufferHeight;\n };\n\n const ro = new ResizeObserver(() => {\n // Batch rapid resize events (ex. during a window drag) into one setSize per frame.\n if (resizePending) return;\n resizePending = true;\n requestAnimationFrame(() => {\n resizePending = false;\n setSize();\n });\n });\n ro.observe(containerEl);\n setSize();\n\n let raf = 0;\n let contextLost = false;\n let isVisible = true;\n let tabVisible = document.visibilityState !== 'hidden';\n const t0 = performance.now();\n const frameInterval = 1000 / targetFps;\n let lastFrameTime = 0;\n\n const renderStaticFrame = () => {\n (program.uniforms.iTime as any).value = 0;\n renderer.render({ scene: mesh });\n };\n\n const loop = (t: number) => {\n if (contextLost || !isVisible || !tabVisible) return;\n\n if (t - lastFrameTime < frameInterval) {\n raf = requestAnimationFrame(loop);\n return;\n }\n lastFrameTime = t;\n\n if (pendingMouse.current) {\n mousePos.current = pendingMouse.current;\n pendingMouse.current = null;\n const mouseUniform = program.uniforms.uMouse.value as Float32Array;\n mouseUniform[0] = mousePos.current.x;\n mouseUniform[1] = mousePos.current.y;\n }\n\n let timeValue = (t - t0) * 0.001;\n if (direction === 'pingpong') {\n const pingpongDuration = 10;\n const segmentTime = timeValue % pingpongDuration;\n const isForward = Math.floor(timeValue / pingpongDuration) % 2 === 0;\n const u = segmentTime / pingpongDuration;\n const smooth = u * u * (3 - 2 * u);\n const pingpongTime = isForward ? smooth * pingpongDuration : (1 - smooth) * pingpongDuration;\n (program.uniforms.uDirection as any).value = 1.0;\n (program.uniforms.iTime as any).value = pingpongTime;\n } else {\n (program.uniforms.iTime as any).value = timeValue;\n }\n renderer.render({ scene: mesh });\n raf = requestAnimationFrame(loop);\n };\n\n const handleContextLost = (e: Event) => {\n e.preventDefault();\n contextLost = true;\n cancelAnimationFrame(raf);\n };\n const handleContextRestored = () => {\n contextLost = false;\n if (isVisible && tabVisible && !prefersReducedMotion) {\n cancelAnimationFrame(raf);\n raf = requestAnimationFrame(loop);\n }\n };\n canvas.addEventListener('webglcontextlost', handleContextLost);\n canvas.addEventListener('webglcontextrestored', handleContextRestored);\n\n const io = new IntersectionObserver(([entry]) => {\n const wasVisible = isVisible;\n isVisible = entry.isIntersecting;\n if (isVisible && !wasVisible && !contextLost && tabVisible && !prefersReducedMotion) {\n cancelAnimationFrame(raf);\n raf = requestAnimationFrame(loop);\n }\n }, { threshold: 0 });\n io.observe(containerEl);\n\n const handleVisibilityChange = () => {\n tabVisible = document.visibilityState !== 'hidden';\n if (tabVisible && isVisible && !contextLost && !prefersReducedMotion) {\n cancelAnimationFrame(raf);\n lastFrameTime = 0;\n raf = requestAnimationFrame(loop);\n } else {\n cancelAnimationFrame(raf);\n }\n };\n document.addEventListener('visibilitychange', handleVisibilityChange);\n\n // Respect prefers-reduced-motion: paint one frame and stop, rather than running a perpetual animation loop for users who've asked not to see motion.\n if (prefersReducedMotion) {\n renderStaticFrame();\n } else {\n raf = requestAnimationFrame(loop);\n }\n\n return () => {\n cancelAnimationFrame(raf);\n ro.disconnect();\n io.disconnect();\n document.removeEventListener('visibilitychange', handleVisibilityChange);\n canvas.removeEventListener('webglcontextlost', handleContextLost);\n canvas.removeEventListener('webglcontextrestored', handleContextRestored);\n if (mouseInteractive && containerEl) {\n containerEl.removeEventListener('mousemove', handleMouseMove);\n }\n try {\n containerEl?.removeChild(canvas);\n } catch {}\n };\n }, [color, speed, direction, scale, opacity, mouseInteractive, renderScale, maxDpr, targetFps, iterations]);\n\n return
;\n};\n\nexport default Plasma;\n" } ], "registryDependencies": [], diff --git a/public/r/Plasma-TS-TW.json b/public/r/Plasma-TS-TW.json index f8089e352..55046de0e 100644 --- a/public/r/Plasma-TS-TW.json +++ b/public/r/Plasma-TS-TW.json @@ -8,7 +8,7 @@ { "type": "registry:component", "path": "Plasma/Plasma.tsx", - "content": "import React, { useEffect, useRef } from \"react\";\nimport { Renderer, Program, Mesh, Triangle } from \"ogl\";\n\ninterface PlasmaProps {\n color?: string;\n speed?: number;\n direction?: \"forward\" | \"reverse\" | \"pingpong\";\n scale?: number;\n opacity?: number;\n mouseInteractive?: boolean;\n /** Internal render resolution multiplier. 1 = full res, 0.5 = quarter the pixels. Default 0.55. */\n renderScale?: number;\n /** Hard cap on devicePixelRatio used for rendering. Default 1.5. */\n maxDpr?: number;\n /** Target frame rate for the animation loop. Default 30. */\n targetFps?: number;\n /** Raymarch step count — lower is cheaper, less detailed. Default 45. */\n quality?: number;\n}\n\nconst hexToRgb = (hex: string): [number, number, number] => {\n const result = /^#?([a-f\\d]{2})([a-f\\d]{2})([a-f\\d]{2})$/i.exec(hex);\n if (!result) return [1, 0.5, 0.2];\n return [\n parseInt(result[1], 16) / 255,\n parseInt(result[2], 16) / 255,\n parseInt(result[3], 16) / 255,\n ];\n};\n\nconst vertex = `#version 300 es\nprecision highp float;\nin vec2 position;\nin vec2 uv;\nout vec2 vUv;\nvoid main() {\n vUv = uv;\n gl_Position = vec4(position, 0.0, 1.0);\n}\n`;\n\nconst buildFragment = (steps: number) => `#version 300 es\nprecision highp float;\nuniform vec2 iResolution;\nuniform float iTime;\nuniform vec3 uCustomColor;\nuniform float uUseCustomColor;\nuniform float uSpeed;\nuniform float uDirection;\nuniform float uScale;\nuniform float uOpacity;\nuniform vec2 uMouse;\nuniform float uMouseInteractive;\nout vec4 fragColor;\n\nvoid mainImage(out vec4 o, vec2 C) {\n vec2 center = iResolution.xy * 0.5;\n C = (C - center) / uScale + center;\n\n vec2 mouseOffset = (uMouse - center) * 0.0002;\n C += mouseOffset * length(C - center) * step(0.5, uMouseInteractive);\n\n float i, d, z, T = iTime * uSpeed * uDirection;\n vec3 O, p, S;\n\n for (vec2 r = iResolution.xy, Q; ++i < ${steps.toFixed(1)}; O += o.w/d*o.xyz) {\n p = z*normalize(vec3(C-.5*r,r.y));\n p.z -= 4.;\n S = p;\n d = p.y-T;\n\n p.x += .4*(1.+p.y)*sin(d + p.x*0.1)*cos(.34*d + p.x*0.05);\n Q = p.xz *= mat2(cos(p.y+vec4(0,11,33,0)-T));\n z+= d = abs(sqrt(length(Q*Q)) - .25*(5.+S.y))/3.+8e-4;\n o = 1.+sin(S.y+p.z*.5+S.z-length(S-p)+vec4(2,1,0,8));\n }\n\n o.xyz = tanh(O/1e4);\n}\n\nbool finite1(float x){ return !(isnan(x) || isinf(x)); }\nvec3 sanitize(vec3 c){\n return vec3(\n finite1(c.r) ? c.r : 0.0,\n finite1(c.g) ? c.g : 0.0,\n finite1(c.b) ? c.b : 0.0\n );\n}\n\nvoid main() {\n vec4 o = vec4(0.0);\n mainImage(o, gl_FragCoord.xy);\n vec3 rgb = sanitize(o.rgb);\n\n float intensity = (rgb.r + rgb.g + rgb.b) / 3.0;\n vec3 customColor = intensity * uCustomColor;\n vec3 finalColor = mix(rgb, customColor, step(0.5, uUseCustomColor));\n\n float alpha = length(rgb) * uOpacity;\n fragColor = vec4(finalColor, alpha);\n}`;\n\nexport const Plasma: React.FC = ({\n color = \"#ffffff\",\n speed = 1,\n direction = \"forward\",\n scale = 1,\n opacity = 1,\n mouseInteractive = true,\n renderScale = 0.55,\n maxDpr = 1.5,\n targetFps = 60,\n quality = 45,\n}) => {\n const containerRef = useRef(null);\n const mousePos = useRef({ x: 0, y: 0 });\n const pendingMouse = useRef<{ x: number; y: number } | null>(null);\n\n useEffect(() => {\n if (!containerRef.current) return;\n const containerEl = containerRef.current;\n\n const prefersReducedMotion =\n typeof window !== \"undefined\" &&\n window.matchMedia?.(\"(prefers-reduced-motion: reduce)\").matches;\n\n const useCustomColor = color ? 1.0 : 0.0;\n const customColorRgb = color ? hexToRgb(color) : [1, 1, 1];\n const directionMultiplier = direction === \"reverse\" ? -1.0 : 1.0;\n\n let renderer: Renderer;\n try {\n renderer = new Renderer({\n webgl: 2,\n alpha: true,\n antialias: false,\n dpr: Math.min(window.devicePixelRatio || 1, maxDpr),\n });\n } catch {\n return;\n }\n const gl = renderer.gl;\n if (!gl) return;\n const canvas = gl.canvas as HTMLCanvasElement;\n canvas.style.display = \"block\";\n canvas.style.width = \"100%\";\n canvas.style.height = \"100%\";\n // Rendering at renderScale internally, CSS stretches it back up.\n containerEl.appendChild(canvas);\n\n const geometry = new Triangle(gl);\n\n const program = new Program(gl, {\n vertex: vertex,\n fragment: buildFragment(quality),\n uniforms: {\n iTime: { value: 0 },\n iResolution: { value: new Float32Array([1, 1]) },\n uCustomColor: { value: new Float32Array(customColorRgb) },\n uUseCustomColor: { value: useCustomColor },\n uSpeed: { value: speed * 0.4 },\n uDirection: { value: directionMultiplier },\n uScale: { value: scale },\n uOpacity: { value: opacity },\n uMouse: { value: new Float32Array([0, 0]) },\n uMouseInteractive: { value: mouseInteractive ? 1.0 : 0.0 },\n },\n });\n\n const mesh = new Mesh(gl, { geometry, program });\n\n const handleMouseMove = (e: MouseEvent) => {\n if (!mouseInteractive) return;\n const rect = containerEl.getBoundingClientRect();\n // Store the latest position but don't touch GL state here, the rAF loop picks it up once per rendered frame instead of once per mouse event.\n pendingMouse.current = {\n x: e.clientX - rect.left,\n y: e.clientY - rect.top,\n };\n };\n\n if (mouseInteractive) {\n containerEl.addEventListener(\"mousemove\", handleMouseMove, {\n passive: true,\n });\n }\n\n let resizePending = false;\n const setSize = () => {\n const rect = containerEl.getBoundingClientRect();\n const width = Math.max(1, Math.floor(rect.width * renderScale));\n const height = Math.max(1, Math.floor(rect.height * renderScale));\n renderer.setSize(width, height);\n\n // renderer.setSize also sets canvas.style.width/height to match the (scaled-down) drawing buffer - override that so the canvas still stretches to fill its container via CSS while the buffer stays small.\n canvas.style.width = \"100%\";\n canvas.style.height = \"100%\";\n\n const res = program.uniforms.iResolution.value as Float32Array;\n res[0] = gl.drawingBufferWidth;\n res[1] = gl.drawingBufferHeight;\n };\n\n const ro = new ResizeObserver(() => {\n // Batch rapid resize events (ex. during a window drag) into one setSize per frame.\n if (resizePending) return;\n resizePending = true;\n requestAnimationFrame(() => {\n resizePending = false;\n setSize();\n });\n });\n ro.observe(containerEl);\n setSize();\n\n let raf = 0;\n let contextLost = false;\n let isVisible = true;\n let tabVisible = document.visibilityState !== \"hidden\";\n const t0 = performance.now();\n const frameInterval = 1000 / targetFps;\n let lastFrameTime = 0;\n\n const renderStaticFrame = () => {\n (program.uniforms.iTime as any).value = 0;\n renderer.render({ scene: mesh });\n };\n\n const loop = (t: number) => {\n if (contextLost || !isVisible || !tabVisible) return;\n\n if (t - lastFrameTime < frameInterval) {\n raf = requestAnimationFrame(loop);\n return;\n }\n lastFrameTime = t;\n\n if (pendingMouse.current) {\n mousePos.current = pendingMouse.current;\n pendingMouse.current = null;\n const mouseUniform = program.uniforms.uMouse.value as Float32Array;\n mouseUniform[0] = mousePos.current.x;\n mouseUniform[1] = mousePos.current.y;\n }\n\n let timeValue = (t - t0) * 0.001;\n if (direction === \"pingpong\") {\n const pingpongDuration = 10;\n const segmentTime = timeValue % pingpongDuration;\n const isForward = Math.floor(timeValue / pingpongDuration) % 2 === 0;\n const u = segmentTime / pingpongDuration;\n const smooth = u * u * (3 - 2 * u);\n const pingpongTime = isForward\n ? smooth * pingpongDuration\n : (1 - smooth) * pingpongDuration;\n (program.uniforms.uDirection as any).value = 1.0;\n (program.uniforms.iTime as any).value = pingpongTime;\n } else {\n (program.uniforms.iTime as any).value = timeValue;\n }\n renderer.render({ scene: mesh });\n raf = requestAnimationFrame(loop);\n };\n\n const handleContextLost = (e: Event) => {\n e.preventDefault();\n contextLost = true;\n cancelAnimationFrame(raf);\n };\n const handleContextRestored = () => {\n contextLost = false;\n if (isVisible && tabVisible && !prefersReducedMotion) {\n cancelAnimationFrame(raf);\n raf = requestAnimationFrame(loop);\n }\n };\n canvas.addEventListener(\"webglcontextlost\", handleContextLost);\n canvas.addEventListener(\"webglcontextrestored\", handleContextRestored);\n\n const io = new IntersectionObserver(\n ([entry]) => {\n const wasVisible = isVisible;\n isVisible = entry.isIntersecting;\n if (\n isVisible &&\n !wasVisible &&\n !contextLost &&\n tabVisible &&\n !prefersReducedMotion\n ) {\n cancelAnimationFrame(raf);\n raf = requestAnimationFrame(loop);\n }\n },\n { threshold: 0 },\n );\n io.observe(containerEl);\n\n const handleVisibilityChange = () => {\n tabVisible = document.visibilityState !== \"hidden\";\n if (tabVisible && isVisible && !contextLost && !prefersReducedMotion) {\n cancelAnimationFrame(raf);\n lastFrameTime = 0;\n raf = requestAnimationFrame(loop);\n } else {\n cancelAnimationFrame(raf);\n }\n };\n document.addEventListener(\"visibilitychange\", handleVisibilityChange);\n\n // Respect prefers-reduced-motion: paint one frame and stop, rather than running a perpetual animation loop for users who've asked not to see motion.\n if (prefersReducedMotion) {\n renderStaticFrame();\n } else {\n raf = requestAnimationFrame(loop);\n }\n\n return () => {\n cancelAnimationFrame(raf);\n ro.disconnect();\n io.disconnect();\n document.removeEventListener(\"visibilitychange\", handleVisibilityChange);\n canvas.removeEventListener(\"webglcontextlost\", handleContextLost);\n canvas.removeEventListener(\"webglcontextrestored\", handleContextRestored);\n if (mouseInteractive && containerEl) {\n containerEl.removeEventListener(\"mousemove\", handleMouseMove);\n }\n try {\n containerEl?.removeChild(canvas);\n } catch {}\n };\n }, [\n color,\n speed,\n direction,\n scale,\n opacity,\n mouseInteractive,\n renderScale,\n maxDpr,\n targetFps,\n quality,\n ]);\n\n return (\n \n );\n};\n\nexport default Plasma;\n" + "content": "import React, { useEffect, useRef } from \"react\";\nimport { Renderer, Program, Mesh, Triangle } from \"ogl\";\n\ninterface PlasmaProps {\n color?: string;\n speed?: number;\n direction?: \"forward\" | \"reverse\" | \"pingpong\";\n scale?: number;\n opacity?: number;\n mouseInteractive?: boolean;\n /** Internal render resolution multiplier. 1 = full res, 0.5 = quarter the pixels. Default 0.55. */\n renderScale?: number;\n /** Hard cap on devicePixelRatio used for rendering. Default 1.5. */\n maxDpr?: number;\n /** Target frame rate for the animation loop. Default 30. */\n targetFps?: number;\n /** Raymarch step count — lower is cheaper, less detailed. Default 60. */\n iterations?: number;\n}\n\nconst hexToRgb = (hex: string): [number, number, number] => {\n const result = /^#?([a-f\\d]{2})([a-f\\d]{2})([a-f\\d]{2})$/i.exec(hex);\n if (!result) return [1, 0.5, 0.2];\n return [\n parseInt(result[1], 16) / 255,\n parseInt(result[2], 16) / 255,\n parseInt(result[3], 16) / 255,\n ];\n};\n\nconst vertex = `#version 300 es\nprecision highp float;\nin vec2 position;\nin vec2 uv;\nout vec2 vUv;\nvoid main() {\n vUv = uv;\n gl_Position = vec4(position, 0.0, 1.0);\n}\n`;\n\nconst ORIGINAL_QUALITY = 60;\n\nconst buildFragment = (iterations: number) => {\n return `#version 300 es\nprecision highp float;\nuniform vec2 iResolution;\nuniform float iTime;\nuniform vec3 uCustomColor;\nuniform float uUseCustomColor;\nuniform float uSpeed;\nuniform float uDirection;\nuniform float uScale;\nuniform float uOpacity;\nuniform vec2 uMouse;\nuniform float uMouseInteractive;\nuniform float uQuality;\nuniform float uStepScale;\nout vec4 fragColor;\n\nvoid mainImage(out vec4 o, vec2 C) {\n vec2 center = iResolution.xy * 0.5;\n C = (C - center) / uScale + center;\n\n vec2 mouseOffset = (uMouse - center) * 0.0002;\n C += mouseOffset * length(C - center) * step(0.5, uMouseInteractive);\n\n float i, d, z, T = iTime * uSpeed * uDirection;\n vec3 O, p, S;\n\n for (vec2 r = iResolution.xy, Q; ++i < 60.0; O += o.w/d*o.xyz) {\n p = z*normalize(vec3(C-.5*r,r.y));\n p.z -= 4.;\n S = p;\n d = p.y-T;\n\n p.x += .4*(1.+p.y)*sin(d + p.x*0.1)*cos(.34*d + p.x*0.05);\n Q = p.xz *= mat2(cos(p.y+vec4(0,11,33,0)-T));\n z += d = (abs(sqrt(length(Q*Q)) - .25*(5.+S.y))/3.+8e-4) * uStepScale;\n o = 1.+sin(S.y+p.z*.5+S.z-length(S-p)+vec4(2,1,0,8));\n if (i >= uQuality) break;\n }\n\n o.xyz = tanh(O/1e4);\n}\n\nbool finite1(float x){ return !(isnan(x) || isinf(x)); }\nvec3 sanitize(vec3 c){\n return vec3(\n finite1(c.r) ? c.r : 0.0,\n finite1(c.g) ? c.g : 0.0,\n finite1(c.b) ? c.b : 0.0\n );\n}\n\nvoid main() {\n vec4 o = vec4(0.0);\n mainImage(o, gl_FragCoord.xy);\n vec3 rgb = sanitize(o.rgb);\n\n float intensity = (rgb.r + rgb.g + rgb.b) / 3.0;\n vec3 customColor = intensity * uCustomColor;\n vec3 finalColor = mix(rgb, customColor, step(0.5, uUseCustomColor));\n\n float alpha = length(rgb) * uOpacity;\n fragColor = vec4(finalColor, alpha);\n}`;\n};\n\nexport const Plasma: React.FC = ({\n color = \"#ffffff\",\n speed = 1,\n direction = \"forward\",\n scale = 1,\n opacity = 1,\n mouseInteractive = true,\n renderScale = 0.55,\n maxDpr = 1.5,\n targetFps = 60,\n iterations = 60,\n}) => {\n const containerRef = useRef(null);\n const mousePos = useRef({ x: 0, y: 0 });\n const pendingMouse = useRef<{ x: number; y: number } | null>(null);\n\n useEffect(() => {\n if (!containerRef.current) return;\n const containerEl = containerRef.current;\n\n const prefersReducedMotion =\n typeof window !== \"undefined\" &&\n window.matchMedia?.(\"(prefers-reduced-motion: reduce)\").matches;\n\n const useCustomColor = color ? 1.0 : 0.0;\n const customColorRgb = color ? hexToRgb(color) : [1, 1, 1];\n const directionMultiplier = direction === \"reverse\" ? -1.0 : 1.0;\n\n let renderer: Renderer;\n try {\n renderer = new Renderer({\n webgl: 2,\n alpha: true,\n antialias: false,\n dpr: Math.min(window.devicePixelRatio || 1, maxDpr),\n });\n } catch {\n return;\n }\n const gl = renderer.gl;\n if (!gl) return;\n const canvas = gl.canvas as HTMLCanvasElement;\n canvas.style.display = \"block\";\n canvas.style.width = \"100%\";\n canvas.style.height = \"100%\";\n // Rendering at renderScale internally, CSS stretches it back up.\n containerEl.appendChild(canvas);\n\n const geometry = new Triangle(gl);\n\n const program = new Program(gl, {\n vertex: vertex,\n fragment: buildFragment(iterations),\n uniforms: {\n iTime: { value: 0 },\n iResolution: { value: new Float32Array([1, 1]) },\n uCustomColor: { value: new Float32Array(customColorRgb) },\n uUseCustomColor: { value: useCustomColor },\n uSpeed: { value: speed * 0.4 },\n uDirection: { value: directionMultiplier },\n uScale: { value: scale },\n uOpacity: { value: opacity },\n uMouse: { value: new Float32Array([0, 0]) },\n uMouseInteractive: { value: mouseInteractive ? 1.0 : 0.0 },\n uQuality: { value: iterations },\n uStepScale: { value: ORIGINAL_QUALITY / iterations },\n },\n });\n\n const mesh = new Mesh(gl, { geometry, program });\n\n const handleMouseMove = (e: MouseEvent) => {\n if (!mouseInteractive) return;\n const rect = containerEl.getBoundingClientRect();\n // Store the latest position but don't touch GL state here, the rAF loop picks it up once per rendered frame instead of once per mouse event.\n pendingMouse.current = {\n x: e.clientX - rect.left,\n y: e.clientY - rect.top,\n };\n };\n\n if (mouseInteractive) {\n containerEl.addEventListener(\"mousemove\", handleMouseMove, {\n passive: true,\n });\n }\n\n let resizePending = false;\n const setSize = () => {\n const rect = containerEl.getBoundingClientRect();\n const width = Math.max(1, Math.floor(rect.width * renderScale));\n const height = Math.max(1, Math.floor(rect.height * renderScale));\n renderer.setSize(width, height);\n\n // renderer.setSize also sets canvas.style.width/height to match the (scaled-down) drawing buffer - override that so the canvas still stretches to fill its container via CSS while the buffer stays small.\n canvas.style.width = \"100%\";\n canvas.style.height = \"100%\";\n\n const res = program.uniforms.iResolution.value as Float32Array;\n res[0] = gl.drawingBufferWidth;\n res[1] = gl.drawingBufferHeight;\n };\n\n const ro = new ResizeObserver(() => {\n // Batch rapid resize events (ex. during a window drag) into one setSize per frame.\n if (resizePending) return;\n resizePending = true;\n requestAnimationFrame(() => {\n resizePending = false;\n setSize();\n });\n });\n ro.observe(containerEl);\n setSize();\n\n let raf = 0;\n let contextLost = false;\n let isVisible = true;\n let tabVisible = document.visibilityState !== \"hidden\";\n const t0 = performance.now();\n const frameInterval = 1000 / targetFps;\n let lastFrameTime = 0;\n\n const renderStaticFrame = () => {\n (program.uniforms.iTime as any).value = 0;\n renderer.render({ scene: mesh });\n };\n\n const loop = (t: number) => {\n if (contextLost || !isVisible || !tabVisible) return;\n\n if (t - lastFrameTime < frameInterval) {\n raf = requestAnimationFrame(loop);\n return;\n }\n lastFrameTime = t;\n\n if (pendingMouse.current) {\n mousePos.current = pendingMouse.current;\n pendingMouse.current = null;\n const mouseUniform = program.uniforms.uMouse.value as Float32Array;\n mouseUniform[0] = mousePos.current.x;\n mouseUniform[1] = mousePos.current.y;\n }\n\n let timeValue = (t - t0) * 0.001;\n if (direction === \"pingpong\") {\n const pingpongDuration = 10;\n const segmentTime = timeValue % pingpongDuration;\n const isForward = Math.floor(timeValue / pingpongDuration) % 2 === 0;\n const u = segmentTime / pingpongDuration;\n const smooth = u * u * (3 - 2 * u);\n const pingpongTime = isForward\n ? smooth * pingpongDuration\n : (1 - smooth) * pingpongDuration;\n (program.uniforms.uDirection as any).value = 1.0;\n (program.uniforms.iTime as any).value = pingpongTime;\n } else {\n (program.uniforms.iTime as any).value = timeValue;\n }\n renderer.render({ scene: mesh });\n raf = requestAnimationFrame(loop);\n };\n\n const handleContextLost = (e: Event) => {\n e.preventDefault();\n contextLost = true;\n cancelAnimationFrame(raf);\n };\n const handleContextRestored = () => {\n contextLost = false;\n if (isVisible && tabVisible && !prefersReducedMotion) {\n cancelAnimationFrame(raf);\n raf = requestAnimationFrame(loop);\n }\n };\n canvas.addEventListener(\"webglcontextlost\", handleContextLost);\n canvas.addEventListener(\"webglcontextrestored\", handleContextRestored);\n\n const io = new IntersectionObserver(\n ([entry]) => {\n const wasVisible = isVisible;\n isVisible = entry.isIntersecting;\n if (\n isVisible &&\n !wasVisible &&\n !contextLost &&\n tabVisible &&\n !prefersReducedMotion\n ) {\n cancelAnimationFrame(raf);\n raf = requestAnimationFrame(loop);\n }\n },\n { threshold: 0 },\n );\n io.observe(containerEl);\n\n const handleVisibilityChange = () => {\n tabVisible = document.visibilityState !== \"hidden\";\n if (tabVisible && isVisible && !contextLost && !prefersReducedMotion) {\n cancelAnimationFrame(raf);\n lastFrameTime = 0;\n raf = requestAnimationFrame(loop);\n } else {\n cancelAnimationFrame(raf);\n }\n };\n document.addEventListener(\"visibilitychange\", handleVisibilityChange);\n\n // Respect prefers-reduced-motion: paint one frame and stop, rather than running a perpetual animation loop for users who've asked not to see motion.\n if (prefersReducedMotion) {\n renderStaticFrame();\n } else {\n raf = requestAnimationFrame(loop);\n }\n\n return () => {\n cancelAnimationFrame(raf);\n ro.disconnect();\n io.disconnect();\n document.removeEventListener(\"visibilitychange\", handleVisibilityChange);\n canvas.removeEventListener(\"webglcontextlost\", handleContextLost);\n canvas.removeEventListener(\"webglcontextrestored\", handleContextRestored);\n if (mouseInteractive && containerEl) {\n containerEl.removeEventListener(\"mousemove\", handleMouseMove);\n }\n try {\n containerEl?.removeChild(canvas);\n } catch {}\n };\n }, [\n color,\n speed,\n direction,\n scale,\n opacity,\n mouseInteractive,\n renderScale,\n maxDpr,\n targetFps,\n iterations,\n ]);\n\n return (\n \n );\n};\n\nexport default Plasma;\n" } ], "registryDependencies": [], diff --git a/public/r/PrismaticBurst-JS-CSS.json b/public/r/PrismaticBurst-JS-CSS.json index fb319455c..e577d4104 100644 --- a/public/r/PrismaticBurst-JS-CSS.json +++ b/public/r/PrismaticBurst-JS-CSS.json @@ -13,7 +13,7 @@ { "type": "registry:component", "path": "PrismaticBurst/PrismaticBurst.jsx", - "content": "import { useEffect, useRef } from 'react';\nimport { Renderer, Program, Mesh, Triangle, Texture } from 'ogl';\nimport './PrismaticBurst.css';\n\nconst vertexShader = `#version 300 es\nin vec2 position;\nin vec2 uv;\nout vec2 vUv;\nvoid main() {\n vUv = uv;\n gl_Position = vec4(position, 0.0, 1.0);\n}\n`;\n\nconst fragmentShader = `#version 300 es\nprecision highp float;\nprecision highp int;\n\nout vec4 fragColor;\n\nuniform vec2 uResolution;\nuniform float uTime;\n\nuniform float uIntensity;\nuniform float uSpeed;\nuniform int uAnimType;\nuniform vec2 uMouse;\nuniform int uColorCount;\nuniform float uDistort;\nuniform vec2 uOffset;\nuniform sampler2D uGradient;\nuniform float uNoiseAmount;\nuniform int uRayCount;\n\nfloat hash21(vec2 p){\n p = floor(p);\n float f = 52.9829189 * fract(dot(p, vec2(0.065, 0.005)));\n return fract(f);\n}\n\nmat2 rot30(){ return mat2(0.8, -0.5, 0.5, 0.8); }\n\nfloat layeredNoise(vec2 fragPx){\n vec2 p = mod(fragPx + vec2(uTime * 30.0, -uTime * 21.0), 1024.0);\n vec2 q = rot30() * p;\n float n = 0.0;\n n += 0.40 * hash21(q);\n n += 0.25 * hash21(q * 2.0 + 17.0);\n n += 0.20 * hash21(q * 4.0 + 47.0);\n n += 0.10 * hash21(q * 8.0 + 113.0);\n n += 0.05 * hash21(q * 16.0 + 191.0);\n return n;\n}\n\nvec3 rayDir(vec2 frag, vec2 res, vec2 offset, float dist){\n float focal = res.y * max(dist, 1e-3);\n return normalize(vec3(2.0 * (frag - offset) - res, focal));\n}\n\nfloat edgeFade(vec2 frag, vec2 res, vec2 offset){\n vec2 toC = frag - 0.5 * res - offset;\n float r = length(toC) / (0.5 * min(res.x, res.y));\n float x = clamp(r, 0.0, 1.0);\n float q = x * x * x * (x * (x * 6.0 - 15.0) + 10.0);\n float s = q * 0.5;\n s = pow(s, 1.5);\n float tail = 1.0 - pow(1.0 - s, 2.0);\n s = mix(s, tail, 0.2);\n float dn = (layeredNoise(frag * 0.15) - 0.5) * 0.0015 * s;\n return clamp(s + dn, 0.0, 1.0);\n}\n\nmat3 rotX(float a){ float c = cos(a), s = sin(a); return mat3(1.0,0.0,0.0, 0.0,c,-s, 0.0,s,c); }\nmat3 rotY(float a){ float c = cos(a), s = sin(a); return mat3(c,0.0,s, 0.0,1.0,0.0, -s,0.0,c); }\nmat3 rotZ(float a){ float c = cos(a), s = sin(a); return mat3(c,-s,0.0, s,c,0.0, 0.0,0.0,1.0); }\n\nvec3 sampleGradient(float t){\n t = clamp(t, 0.0, 1.0);\n return texture(uGradient, vec2(t, 0.5)).rgb;\n}\n\nvec2 rot2(vec2 v, float a){\n float s = sin(a), c = cos(a);\n return mat2(c, -s, s, c) * v;\n}\n\nfloat bendAngle(vec3 q, float t){\n float a = 0.8 * sin(q.x * 0.55 + t * 0.6)\n + 0.7 * sin(q.y * 0.50 - t * 0.5)\n + 0.6 * sin(q.z * 0.60 + t * 0.7);\n return a;\n}\n\nvoid main(){\n vec2 frag = gl_FragCoord.xy;\n float t = uTime * uSpeed;\n float jitterAmp = 0.1 * clamp(uNoiseAmount, 0.0, 1.0);\n vec3 dir = rayDir(frag, uResolution, uOffset, 1.0);\n float marchT = 0.0;\n vec3 col = vec3(0.0);\n float n = layeredNoise(frag);\n vec4 c = cos(t * 0.2 + vec4(0.0, 33.0, 11.0, 0.0));\n mat2 M2 = mat2(c.x, c.y, c.z, c.w);\n float amp = clamp(uDistort, 0.0, 50.0) * 0.15;\n\n mat3 rot3dMat = mat3(1.0);\n if(uAnimType == 1){\n vec3 ang = vec3(t * 0.31, t * 0.21, t * 0.17);\n rot3dMat = rotZ(ang.z) * rotY(ang.y) * rotX(ang.x);\n }\n mat3 hoverMat = mat3(1.0);\n if(uAnimType == 2){\n vec2 m = uMouse * 2.0 - 1.0;\n vec3 ang = vec3(m.y * 0.6, m.x * 0.6, 0.0);\n hoverMat = rotY(ang.y) * rotX(ang.x);\n }\n\n for (int i = 0; i < 44; ++i) {\n vec3 P = marchT * dir;\n P.z -= 2.0;\n float rad = length(P);\n vec3 Pl = P * (10.0 / max(rad, 1e-6));\n\n if(uAnimType == 0){\n Pl.xz *= M2;\n } else if(uAnimType == 1){\n Pl = rot3dMat * Pl;\n } else {\n Pl = hoverMat * Pl;\n }\n\n float stepLen = min(rad - 0.3, n * jitterAmp) + 0.1;\n\n float grow = smoothstep(0.35, 3.0, marchT);\n float a1 = amp * grow * bendAngle(Pl * 0.6, t);\n float a2 = 0.5 * amp * grow * bendAngle(Pl.zyx * 0.5 + 3.1, t * 0.9);\n vec3 Pb = Pl;\n Pb.xz = rot2(Pb.xz, a1);\n Pb.xy = rot2(Pb.xy, a2);\n\n float rayPattern = smoothstep(\n 0.5, 0.7,\n sin(Pb.x + cos(Pb.y) * cos(Pb.z)) *\n sin(Pb.z + sin(Pb.y) * cos(Pb.x + t))\n );\n\n if (uRayCount > 0) {\n float ang = atan(Pb.y, Pb.x);\n float comb = 0.5 + 0.5 * cos(float(uRayCount) * ang);\n comb = pow(comb, 3.0);\n rayPattern *= smoothstep(0.15, 0.95, comb);\n }\n\n vec3 spectralDefault = 1.0 + vec3(\n cos(marchT * 3.0 + 0.0),\n cos(marchT * 3.0 + 1.0),\n cos(marchT * 3.0 + 2.0)\n );\n\n float saw = fract(marchT * 0.25);\n float tRay = saw * saw * (3.0 - 2.0 * saw);\n vec3 userGradient = 2.0 * sampleGradient(tRay);\n vec3 spectral = (uColorCount > 0) ? userGradient : spectralDefault;\n vec3 base = (0.05 / (0.4 + stepLen))\n * smoothstep(5.0, 0.0, rad)\n * spectral;\n\n col += base * rayPattern;\n marchT += stepLen;\n }\n\n col *= edgeFade(frag, uResolution, uOffset);\n col *= uIntensity;\n\n fragColor = vec4(clamp(col, 0.0, 1.0), 1.0);\n}`;\n\nconst hexToRgb01 = hex => {\n let h = hex.trim();\n if (h.startsWith('#')) h = h.slice(1);\n if (h.length === 3) {\n const r = h[0],\n g = h[1],\n b = h[2];\n h = r + r + g + g + b + b;\n }\n const intVal = parseInt(h, 16);\n if (isNaN(intVal) || (h.length !== 6 && h.length !== 8)) return [1, 1, 1];\n const r = ((intVal >> 16) & 255) / 255;\n const g = ((intVal >> 8) & 255) / 255;\n const b = (intVal & 255) / 255;\n return [r, g, b];\n};\n\nconst toPx = v => {\n if (v == null) return 0;\n if (typeof v === 'number') return v;\n const s = String(v).trim();\n const num = parseFloat(s.replace('px', ''));\n return isNaN(num) ? 0 : num;\n};\n\nconst PrismaticBurst = ({\n intensity = 2,\n speed = 0.5,\n animationType = 'rotate3d',\n colors,\n distort = 0,\n paused = false,\n offset = { x: 0, y: 0 },\n hoverDampness = 0,\n rayCount,\n mixBlendMode = 'lighten'\n}) => {\n const containerRef = useRef(null);\n const programRef = useRef(null);\n const rendererRef = useRef(null);\n const mouseTargetRef = useRef([0.5, 0.5]);\n const mouseSmoothRef = useRef([0.5, 0.5]);\n const pausedRef = useRef(paused);\n const gradTexRef = useRef(null);\n const hoverDampRef = useRef(hoverDampness);\n const isVisibleRef = useRef(true);\n const meshRef = useRef(null);\n const triRef = useRef(null);\n\n useEffect(() => {\n pausedRef.current = paused;\n }, [paused]);\n useEffect(() => {\n hoverDampRef.current = hoverDampness;\n }, [hoverDampness]);\n\n useEffect(() => {\n const container = containerRef.current;\n if (!container) return;\n\n const dpr = Math.min(window.devicePixelRatio || 1, 2);\n const renderer = new Renderer({\n dpr,\n alpha: false,\n antialias: false\n });\n rendererRef.current = renderer;\n\n const gl = renderer.gl;\n gl.canvas.style.position = 'absolute';\n gl.canvas.style.inset = '0';\n gl.canvas.style.width = '100%';\n gl.canvas.style.height = '100%';\n gl.canvas.style.mixBlendMode = mixBlendMode && mixBlendMode !== 'none' ? mixBlendMode : '';\n container.appendChild(gl.canvas);\n\n const white = new Uint8Array([255, 255, 255, 255]);\n const gradientTex = new Texture(gl, {\n image: white,\n width: 1,\n height: 1,\n generateMipmaps: false,\n flipY: false\n });\n\n gradientTex.minFilter = gl.LINEAR;\n gradientTex.magFilter = gl.LINEAR;\n gradientTex.wrapS = gl.CLAMP_TO_EDGE;\n gradientTex.wrapT = gl.CLAMP_TO_EDGE;\n gradTexRef.current = gradientTex;\n\n const program = new Program(gl, {\n vertex: vertexShader,\n fragment: fragmentShader,\n uniforms: {\n uResolution: { value: [1, 1] },\n uTime: { value: 0 },\n\n uIntensity: { value: 1 },\n uSpeed: { value: 1 },\n uAnimType: { value: 0 },\n uMouse: { value: [0.5, 0.5] },\n uColorCount: { value: 0 },\n uDistort: { value: 0 },\n uOffset: { value: [0, 0] },\n uGradient: { value: gradientTex },\n uNoiseAmount: { value: 0.8 },\n uRayCount: { value: 0 }\n }\n });\n\n programRef.current = program;\n\n const triangle = new Triangle(gl);\n const mesh = new Mesh(gl, { geometry: triangle, program });\n triRef.current = triangle;\n meshRef.current = mesh;\n\n const resize = () => {\n const w = container.clientWidth || 1;\n const h = container.clientHeight || 1;\n renderer.setSize(w, h);\n program.uniforms.uResolution.value = [gl.drawingBufferWidth, gl.drawingBufferHeight];\n };\n\n let ro = null;\n if ('ResizeObserver' in window) {\n ro = new ResizeObserver(resize);\n ro.observe(container);\n } else {\n window.addEventListener('resize', resize);\n }\n resize();\n\n const onPointer = e => {\n const rect = container.getBoundingClientRect();\n const x = (e.clientX - rect.left) / Math.max(rect.width, 1);\n const y = (e.clientY - rect.top) / Math.max(rect.height, 1);\n mouseTargetRef.current = [Math.min(Math.max(x, 0), 1), Math.min(Math.max(y, 0), 1)];\n };\n container.addEventListener('pointermove', onPointer, { passive: true });\n\n let io = null;\n if ('IntersectionObserver' in window) {\n io = new IntersectionObserver(\n entries => {\n if (entries[0]) {\n isVisibleRef.current = entries[0].isIntersecting;\n }\n },\n { root: null, threshold: 0.01 }\n );\n io.observe(container);\n }\n\n const onVis = () => {};\n document.addEventListener('visibilitychange', onVis);\n\n let raf = 0;\n let last = performance.now();\n let accumTime = 0;\n\n const update = now => {\n const dt = Math.max(0, now - last) * 0.001;\n last = now;\n const visible = isVisibleRef.current && !document.hidden;\n if (!pausedRef.current) accumTime += dt;\n\n if (!visible) {\n raf = requestAnimationFrame(update);\n return;\n }\n\n const tau = 0.02 + Math.max(0, Math.min(1, hoverDampRef.current)) * 0.5;\n const alpha = 1 - Math.exp(-dt / tau);\n const tgt = mouseTargetRef.current;\n const sm = mouseSmoothRef.current;\n sm[0] += (tgt[0] - sm[0]) * alpha;\n sm[1] += (tgt[1] - sm[1]) * alpha;\n\n program.uniforms.uMouse.value = sm;\n program.uniforms.uTime.value = accumTime;\n\n renderer.render({ scene: meshRef.current });\n raf = requestAnimationFrame(update);\n };\n raf = requestAnimationFrame(update);\n\n return () => {\n cancelAnimationFrame(raf);\n container.removeEventListener('pointermove', onPointer);\n ro?.disconnect();\n if (!ro) window.removeEventListener('resize', resize);\n io?.disconnect();\n document.removeEventListener('visibilitychange', onVis);\n try {\n container.removeChild(gl.canvas);\n } catch {\n console.warn('Canvas already removed');\n }\n try {\n meshRef.current?.remove?.();\n } catch (e) {\n /* ignore dispose errors */\n }\n try {\n triRef.current?.remove?.();\n } catch (e) {\n /* ignore dispose errors */\n }\n try {\n programRef.current?.remove?.();\n } catch (e) {\n /* ignore dispose errors */\n }\n try {\n const glCtx = rendererRef.current?.gl;\n if (glCtx && gradTexRef.current?.texture) {\n glCtx.deleteTexture(gradTexRef.current.texture);\n }\n } catch (e) {\n /* ignore texture delete errors */\n }\n programRef.current = null;\n rendererRef.current = null;\n gradTexRef.current = null;\n meshRef.current = null;\n triRef.current = null;\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, []);\n\n useEffect(() => {\n const canvas = rendererRef.current?.gl?.canvas;\n\n if (canvas) {\n canvas.style.mixBlendMode = mixBlendMode && mixBlendMode !== 'none' ? mixBlendMode : '';\n }\n }, [mixBlendMode]);\n\n useEffect(() => {\n const program = programRef.current;\n const renderer = rendererRef.current;\n const gradTex = gradTexRef.current;\n if (!program || !renderer || !gradTex) return;\n\n program.uniforms.uIntensity.value = intensity ?? 1;\n program.uniforms.uSpeed.value = speed ?? 1;\n\n const animTypeMap = {\n rotate: 0,\n rotate3d: 1,\n hover: 2\n };\n program.uniforms.uAnimType.value = animTypeMap[animationType ?? 'rotate'];\n\n program.uniforms.uDistort.value = typeof distort === 'number' ? distort : 0;\n\n const ox = toPx(offset?.x);\n const oy = toPx(offset?.y);\n program.uniforms.uOffset.value = [ox, oy];\n program.uniforms.uRayCount.value = Math.max(0, Math.floor(rayCount ?? 0));\n\n let count = 0;\n if (Array.isArray(colors) && colors.length > 0) {\n const gl = renderer.gl;\n const capped = colors.slice(0, 64);\n count = capped.length;\n const data = new Uint8Array(count * 4);\n for (let i = 0; i < count; i++) {\n const [r, g, b] = hexToRgb01(capped[i]);\n data[i * 4 + 0] = Math.round(r * 255);\n data[i * 4 + 1] = Math.round(g * 255);\n data[i * 4 + 2] = Math.round(b * 255);\n data[i * 4 + 3] = 255;\n }\n gradTex.image = data;\n gradTex.width = count;\n gradTex.height = 1;\n gradTex.minFilter = gl.LINEAR;\n gradTex.magFilter = gl.LINEAR;\n gradTex.wrapS = gl.CLAMP_TO_EDGE;\n gradTex.wrapT = gl.CLAMP_TO_EDGE;\n gradTex.flipY = false;\n gradTex.generateMipmaps = false;\n gradTex.format = gl.RGBA;\n gradTex.type = gl.UNSIGNED_BYTE;\n gradTex.needsUpdate = true;\n } else {\n count = 0;\n }\n program.uniforms.uColorCount.value = count;\n }, [intensity, speed, animationType, colors, distort, offset, rayCount]);\n\n return
;\n};\n\nexport default PrismaticBurst;\n" + "content": "import { useEffect, useRef } from 'react';\nimport { Renderer, Program, Mesh, Triangle, Texture } from 'ogl';\nimport './PrismaticBurst.css';\n\nconst vertexShader = `#version 300 es\nin vec2 position;\nin vec2 uv;\nout vec2 vUv;\nvoid main() {\n vUv = uv;\n gl_Position = vec4(position, 0.0, 1.0);\n}\n`;\n\nconst fragmentShader = `#version 300 es\nprecision highp float;\nprecision highp int;\n\nout vec4 fragColor;\n\nuniform vec2 uResolution;\nuniform float uTime;\n\nuniform float uIntensity;\nuniform float uSpeed;\nuniform int uAnimType;\nuniform vec2 uMouse;\nuniform int uColorCount;\nuniform float uDistort;\nuniform vec2 uOffset;\nuniform sampler2D uGradient;\nuniform float uNoiseAmount;\nuniform int uRayCount;\n\nfloat hash21(vec2 p){\n p = floor(p);\n float f = 52.9829189 * fract(dot(p, vec2(0.065, 0.005)));\n return fract(f);\n}\n\nmat2 rot30(){ return mat2(0.8, -0.5, 0.5, 0.8); }\n\nfloat layeredNoise(vec2 fragPx){\n vec2 p = mod(fragPx + vec2(uTime * 30.0, -uTime * 21.0), 1024.0);\n vec2 q = rot30() * p;\n float n = 0.0;\n n += 0.40 * hash21(q);\n n += 0.25 * hash21(q * 2.0 + 17.0);\n n += 0.20 * hash21(q * 4.0 + 47.0);\n n += 0.10 * hash21(q * 8.0 + 113.0);\n n += 0.05 * hash21(q * 16.0 + 191.0);\n return n;\n}\n\nvec3 rayDir(vec2 frag, vec2 res, vec2 offset, float dist){\n float focal = res.y * max(dist, 1e-3);\n return normalize(vec3(2.0 * (frag - offset) - res, focal));\n}\n\nfloat edgeFade(vec2 frag, vec2 res, vec2 offset){\n vec2 toC = frag - 0.5 * res - offset;\n float r = length(toC) / (0.5 * min(res.x, res.y));\n float x = clamp(r, 0.0, 1.0);\n float q = x * x * x * (x * (x * 6.0 - 15.0) + 10.0);\n float s = q * 0.5;\n s = pow(s, 1.5);\n float tail = 1.0 - pow(1.0 - s, 2.0);\n s = mix(s, tail, 0.2);\n float dn = (layeredNoise(frag * 0.15) - 0.5) * 0.0015 * s;\n return clamp(s + dn, 0.0, 1.0);\n}\n\nmat3 rotX(float a){ float c = cos(a), s = sin(a); return mat3(1.0,0.0,0.0, 0.0,c,-s, 0.0,s,c); }\nmat3 rotY(float a){ float c = cos(a), s = sin(a); return mat3(c,0.0,s, 0.0,1.0,0.0, -s,0.0,c); }\nmat3 rotZ(float a){ float c = cos(a), s = sin(a); return mat3(c,-s,0.0, s,c,0.0, 0.0,0.0,1.0); }\n\nvec3 sampleGradient(float t){\n t = clamp(t, 0.0, 1.0);\n return texture(uGradient, vec2(t, 0.5)).rgb;\n}\n\nvec2 rot2(vec2 v, float a){\n float s = sin(a), c = cos(a);\n return mat2(c, -s, s, c) * v;\n}\n\nfloat bendAngle(vec3 q, float t){\n float a = 0.8 * sin(q.x * 0.55 + t * 0.6)\n + 0.7 * sin(q.y * 0.50 - t * 0.5)\n + 0.6 * sin(q.z * 0.60 + t * 0.7);\n return a;\n}\n\nvoid main(){\n vec2 frag = gl_FragCoord.xy;\n float t = uTime * uSpeed;\n float jitterAmp = 0.1 * clamp(uNoiseAmount, 0.0, 1.0);\n vec3 dir = rayDir(frag, uResolution, uOffset, 1.0);\n float marchT = 0.0;\n vec3 col = vec3(0.0);\n float n = layeredNoise(frag);\n vec4 c = cos(t * 0.2 + vec4(0.0, 33.0, 11.0, 0.0));\n mat2 M2 = mat2(c.x, c.y, c.z, c.w);\n float amp = clamp(uDistort, 0.0, 50.0) * 0.15;\n\n mat3 rot3dMat = mat3(1.0);\n if(uAnimType == 1){\n vec3 ang = vec3(t * 0.31, t * 0.21, t * 0.17);\n rot3dMat = rotZ(ang.z) * rotY(ang.y) * rotX(ang.x);\n }\n mat3 hoverMat = mat3(1.0);\n if(uAnimType == 2){\n vec2 m = uMouse * 2.0 - 1.0;\n vec3 ang = vec3(m.y * 0.6, m.x * 0.6, 0.0);\n hoverMat = rotY(ang.y) * rotX(ang.x);\n }\n\n for (int i = 0; i < 44; ++i) {\n vec3 P = marchT * dir;\n P.z -= 2.0;\n float rad = length(P);\n vec3 Pl = P * (10.0 / max(rad, 1e-6));\n\n if(uAnimType == 0){\n Pl.xz *= M2;\n } else if(uAnimType == 1){\n Pl = rot3dMat * Pl;\n } else {\n Pl = hoverMat * Pl;\n }\n\n float stepLen = min(rad - 0.3, n * jitterAmp) + 0.1;\n\n float grow = smoothstep(0.35, 3.0, marchT);\n float a1 = amp * grow * bendAngle(Pl * 0.6, t);\n float a2 = 0.5 * amp * grow * bendAngle(Pl.zyx * 0.5 + 3.1, t * 0.9);\n vec3 Pb = Pl;\n Pb.xz = rot2(Pb.xz, a1);\n Pb.xy = rot2(Pb.xy, a2);\n\n float rayPattern = smoothstep(\n 0.5, 0.7,\n sin(Pb.x + cos(Pb.y) * cos(Pb.z)) *\n sin(Pb.z + sin(Pb.y) * cos(Pb.x + t))\n );\n\n if (uRayCount > 0) {\n float ang = atan(Pb.y, Pb.x);\n float comb = 0.5 + 0.5 * cos(float(uRayCount) * ang);\n comb = pow(comb, 3.0);\n rayPattern *= smoothstep(0.15, 0.95, comb);\n }\n\n vec3 spectralDefault = 1.0 + vec3(\n cos(marchT * 3.0 + 0.0),\n cos(marchT * 3.0 + 1.0),\n cos(marchT * 3.0 + 2.0)\n );\n\n float saw = fract(marchT * 0.25);\n float tRay = saw * saw * (3.0 - 2.0 * saw);\n vec3 userGradient = 2.0 * sampleGradient(tRay);\n vec3 spectral = (uColorCount > 0) ? userGradient : spectralDefault;\n vec3 base = (0.05 / (0.4 + stepLen))\n * smoothstep(5.0, 0.0, rad)\n * spectral;\n\n col += base * rayPattern;\n marchT += stepLen;\n }\n\n col *= edgeFade(frag, uResolution, uOffset);\n col *= uIntensity;\n\n fragColor = vec4(clamp(col, 0.0, 1.0), 1.0);\n}`;\n\nconst hexToRgb01 = hex => {\n let h = hex.trim();\n if (h.startsWith('#')) h = h.slice(1);\n if (h.length === 3) {\n const r = h[0],\n g = h[1],\n b = h[2];\n h = r + r + g + g + b + b;\n }\n const intVal = parseInt(h.slice(0, 6), 16);\n if (isNaN(intVal) || (h.length !== 6 && h.length !== 8)) return [1, 1, 1];\n const r = ((intVal >> 16) & 255) / 255;\n const g = ((intVal >> 8) & 255) / 255;\n const b = (intVal & 255) / 255;\n return [r, g, b];\n};\n\nconst toPx = v => {\n if (v == null) return 0;\n if (typeof v === 'number') return v;\n const s = String(v).trim();\n const num = parseFloat(s.replace('px', ''));\n return isNaN(num) ? 0 : num;\n};\n\nconst PrismaticBurst = ({\n intensity = 2,\n speed = 0.5,\n animationType = 'rotate3d',\n colors,\n distort = 0,\n paused = false,\n offset = { x: 0, y: 0 },\n hoverDampness = 0,\n rayCount,\n mixBlendMode = 'lighten'\n}) => {\n const containerRef = useRef(null);\n const programRef = useRef(null);\n const rendererRef = useRef(null);\n const mouseTargetRef = useRef([0.5, 0.5]);\n const mouseSmoothRef = useRef([0.5, 0.5]);\n const pausedRef = useRef(paused);\n const gradTexRef = useRef(null);\n const hoverDampRef = useRef(hoverDampness);\n const isVisibleRef = useRef(true);\n const meshRef = useRef(null);\n const triRef = useRef(null);\n\n useEffect(() => {\n pausedRef.current = paused;\n }, [paused]);\n useEffect(() => {\n hoverDampRef.current = hoverDampness;\n }, [hoverDampness]);\n\n useEffect(() => {\n const container = containerRef.current;\n if (!container) return;\n\n const dpr = Math.min(window.devicePixelRatio || 1, 2);\n const renderer = new Renderer({\n dpr,\n alpha: false,\n antialias: false\n });\n rendererRef.current = renderer;\n\n const gl = renderer.gl;\n gl.canvas.style.position = 'absolute';\n gl.canvas.style.inset = '0';\n gl.canvas.style.width = '100%';\n gl.canvas.style.height = '100%';\n gl.canvas.style.mixBlendMode = mixBlendMode && mixBlendMode !== 'none' ? mixBlendMode : '';\n container.appendChild(gl.canvas);\n\n const white = new Uint8Array([255, 255, 255, 255]);\n const gradientTex = new Texture(gl, {\n image: white,\n width: 1,\n height: 1,\n generateMipmaps: false,\n flipY: false\n });\n\n gradientTex.minFilter = gl.LINEAR;\n gradientTex.magFilter = gl.LINEAR;\n gradientTex.wrapS = gl.CLAMP_TO_EDGE;\n gradientTex.wrapT = gl.CLAMP_TO_EDGE;\n gradTexRef.current = gradientTex;\n\n const program = new Program(gl, {\n vertex: vertexShader,\n fragment: fragmentShader,\n uniforms: {\n uResolution: { value: [1, 1] },\n uTime: { value: 0 },\n\n uIntensity: { value: 1 },\n uSpeed: { value: 1 },\n uAnimType: { value: 0 },\n uMouse: { value: [0.5, 0.5] },\n uColorCount: { value: 0 },\n uDistort: { value: 0 },\n uOffset: { value: [0, 0] },\n uGradient: { value: gradientTex },\n uNoiseAmount: { value: 0.8 },\n uRayCount: { value: 0 }\n }\n });\n\n programRef.current = program;\n\n const triangle = new Triangle(gl);\n const mesh = new Mesh(gl, { geometry: triangle, program });\n triRef.current = triangle;\n meshRef.current = mesh;\n\n const resize = () => {\n const w = container.clientWidth || 1;\n const h = container.clientHeight || 1;\n renderer.setSize(w, h);\n program.uniforms.uResolution.value = [gl.drawingBufferWidth, gl.drawingBufferHeight];\n };\n\n let ro = null;\n if ('ResizeObserver' in window) {\n ro = new ResizeObserver(resize);\n ro.observe(container);\n } else {\n window.addEventListener('resize', resize);\n }\n resize();\n\n const onPointer = e => {\n const rect = container.getBoundingClientRect();\n const x = (e.clientX - rect.left) / Math.max(rect.width, 1);\n const y = (e.clientY - rect.top) / Math.max(rect.height, 1);\n mouseTargetRef.current = [Math.min(Math.max(x, 0), 1), Math.min(Math.max(y, 0), 1)];\n };\n container.addEventListener('pointermove', onPointer, { passive: true });\n\n let io = null;\n if ('IntersectionObserver' in window) {\n io = new IntersectionObserver(\n entries => {\n if (entries[0]) {\n isVisibleRef.current = entries[0].isIntersecting;\n }\n },\n { root: null, threshold: 0.01 }\n );\n io.observe(container);\n }\n\n const onVis = () => {};\n document.addEventListener('visibilitychange', onVis);\n\n let raf = 0;\n let last = performance.now();\n let accumTime = 0;\n\n const update = now => {\n const dt = Math.max(0, now - last) * 0.001;\n last = now;\n const visible = isVisibleRef.current && !document.hidden;\n if (!pausedRef.current) accumTime += dt;\n\n if (!visible) {\n raf = requestAnimationFrame(update);\n return;\n }\n\n const tau = 0.02 + Math.max(0, Math.min(1, hoverDampRef.current)) * 0.5;\n const alpha = 1 - Math.exp(-dt / tau);\n const tgt = mouseTargetRef.current;\n const sm = mouseSmoothRef.current;\n sm[0] += (tgt[0] - sm[0]) * alpha;\n sm[1] += (tgt[1] - sm[1]) * alpha;\n\n program.uniforms.uMouse.value = sm;\n program.uniforms.uTime.value = accumTime;\n\n renderer.render({ scene: meshRef.current });\n raf = requestAnimationFrame(update);\n };\n raf = requestAnimationFrame(update);\n\n return () => {\n cancelAnimationFrame(raf);\n container.removeEventListener('pointermove', onPointer);\n ro?.disconnect();\n if (!ro) window.removeEventListener('resize', resize);\n io?.disconnect();\n document.removeEventListener('visibilitychange', onVis);\n try {\n container.removeChild(gl.canvas);\n } catch {\n console.warn('Canvas already removed');\n }\n try {\n meshRef.current?.remove?.();\n } catch (e) {\n /* ignore dispose errors */\n }\n try {\n triRef.current?.remove?.();\n } catch (e) {\n /* ignore dispose errors */\n }\n try {\n programRef.current?.remove?.();\n } catch (e) {\n /* ignore dispose errors */\n }\n try {\n const glCtx = rendererRef.current?.gl;\n if (glCtx && gradTexRef.current?.texture) {\n glCtx.deleteTexture(gradTexRef.current.texture);\n }\n } catch (e) {\n /* ignore texture delete errors */\n }\n programRef.current = null;\n rendererRef.current = null;\n gradTexRef.current = null;\n meshRef.current = null;\n triRef.current = null;\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, []);\n\n useEffect(() => {\n const canvas = rendererRef.current?.gl?.canvas;\n\n if (canvas) {\n canvas.style.mixBlendMode = mixBlendMode && mixBlendMode !== 'none' ? mixBlendMode : '';\n }\n }, [mixBlendMode]);\n\n useEffect(() => {\n const program = programRef.current;\n const renderer = rendererRef.current;\n const gradTex = gradTexRef.current;\n if (!program || !renderer || !gradTex) return;\n\n program.uniforms.uIntensity.value = intensity ?? 1;\n program.uniforms.uSpeed.value = speed ?? 1;\n\n const animTypeMap = {\n rotate: 0,\n rotate3d: 1,\n hover: 2\n };\n program.uniforms.uAnimType.value = animTypeMap[animationType ?? 'rotate'];\n\n program.uniforms.uDistort.value = typeof distort === 'number' ? distort : 0;\n\n const ox = toPx(offset?.x);\n const oy = toPx(offset?.y);\n program.uniforms.uOffset.value = [ox, oy];\n program.uniforms.uRayCount.value = Math.max(0, Math.floor(rayCount ?? 0));\n\n let count = 0;\n if (Array.isArray(colors) && colors.length > 0) {\n const gl = renderer.gl;\n const capped = colors.slice(0, 64);\n count = capped.length;\n const data = new Uint8Array(count * 4);\n for (let i = 0; i < count; i++) {\n const [r, g, b] = hexToRgb01(capped[i]);\n data[i * 4 + 0] = Math.round(r * 255);\n data[i * 4 + 1] = Math.round(g * 255);\n data[i * 4 + 2] = Math.round(b * 255);\n data[i * 4 + 3] = 255;\n }\n gradTex.image = data;\n gradTex.width = count;\n gradTex.height = 1;\n gradTex.minFilter = gl.LINEAR;\n gradTex.magFilter = gl.LINEAR;\n gradTex.wrapS = gl.CLAMP_TO_EDGE;\n gradTex.wrapT = gl.CLAMP_TO_EDGE;\n gradTex.flipY = false;\n gradTex.generateMipmaps = false;\n gradTex.format = gl.RGBA;\n gradTex.type = gl.UNSIGNED_BYTE;\n gradTex.needsUpdate = true;\n } else {\n count = 0;\n }\n program.uniforms.uColorCount.value = count;\n }, [intensity, speed, animationType, colors, distort, offset, rayCount]);\n\n return
;\n};\n\nexport default PrismaticBurst;\n" } ], "registryDependencies": [], diff --git a/public/r/PrismaticBurst-JS-TW.json b/public/r/PrismaticBurst-JS-TW.json index 2c409302a..c44836ba1 100644 --- a/public/r/PrismaticBurst-JS-TW.json +++ b/public/r/PrismaticBurst-JS-TW.json @@ -8,7 +8,7 @@ { "type": "registry:component", "path": "PrismaticBurst/PrismaticBurst.jsx", - "content": "import { useEffect, useRef } from 'react';\nimport { Renderer, Program, Mesh, Triangle, Texture } from 'ogl';\n\nconst vertexShader = `#version 300 es\nin vec2 position;\nin vec2 uv;\nout vec2 vUv;\nvoid main() {\n vUv = uv;\n gl_Position = vec4(position, 0.0, 1.0);\n}\n`;\n\nconst fragmentShader = `#version 300 es\nprecision highp float;\nprecision highp int;\n\nout vec4 fragColor;\n\nuniform vec2 uResolution;\nuniform float uTime;\n\nuniform float uIntensity;\nuniform float uSpeed;\nuniform int uAnimType;\nuniform vec2 uMouse;\nuniform int uColorCount;\nuniform float uDistort;\nuniform vec2 uOffset;\nuniform sampler2D uGradient;\nuniform float uNoiseAmount;\nuniform int uRayCount;\n\nfloat hash21(vec2 p){\n p = floor(p);\n float f = 52.9829189 * fract(dot(p, vec2(0.065, 0.005)));\n return fract(f);\n}\n\nmat2 rot30(){ return mat2(0.8, -0.5, 0.5, 0.8); }\n\nfloat layeredNoise(vec2 fragPx){\n vec2 p = mod(fragPx + vec2(uTime * 30.0, -uTime * 21.0), 1024.0);\n vec2 q = rot30() * p;\n float n = 0.0;\n n += 0.40 * hash21(q);\n n += 0.25 * hash21(q * 2.0 + 17.0);\n n += 0.20 * hash21(q * 4.0 + 47.0);\n n += 0.10 * hash21(q * 8.0 + 113.0);\n n += 0.05 * hash21(q * 16.0 + 191.0);\n return n;\n}\n\nvec3 rayDir(vec2 frag, vec2 res, vec2 offset, float dist){\n float focal = res.y * max(dist, 1e-3);\n return normalize(vec3(2.0 * (frag - offset) - res, focal));\n}\n\nfloat edgeFade(vec2 frag, vec2 res, vec2 offset){\n vec2 toC = frag - 0.5 * res - offset;\n float r = length(toC) / (0.5 * min(res.x, res.y));\n float x = clamp(r, 0.0, 1.0);\n float q = x * x * x * (x * (x * 6.0 - 15.0) + 10.0);\n float s = q * 0.5;\n s = pow(s, 1.5);\n float tail = 1.0 - pow(1.0 - s, 2.0);\n s = mix(s, tail, 0.2);\n float dn = (layeredNoise(frag * 0.15) - 0.5) * 0.0015 * s;\n return clamp(s + dn, 0.0, 1.0);\n}\n\nmat3 rotX(float a){ float c = cos(a), s = sin(a); return mat3(1.0,0.0,0.0, 0.0,c,-s, 0.0,s,c); }\nmat3 rotY(float a){ float c = cos(a), s = sin(a); return mat3(c,0.0,s, 0.0,1.0,0.0, -s,0.0,c); }\nmat3 rotZ(float a){ float c = cos(a), s = sin(a); return mat3(c,-s,0.0, s,c,0.0, 0.0,0.0,1.0); }\n\nvec3 sampleGradient(float t){\n t = clamp(t, 0.0, 1.0);\n return texture(uGradient, vec2(t, 0.5)).rgb;\n}\n\nvec2 rot2(vec2 v, float a){\n float s = sin(a), c = cos(a);\n return mat2(c, -s, s, c) * v;\n}\n\nfloat bendAngle(vec3 q, float t){\n float a = 0.8 * sin(q.x * 0.55 + t * 0.6)\n + 0.7 * sin(q.y * 0.50 - t * 0.5)\n + 0.6 * sin(q.z * 0.60 + t * 0.7);\n return a;\n}\n\nvoid main(){\n vec2 frag = gl_FragCoord.xy;\n float t = uTime * uSpeed;\n float jitterAmp = 0.1 * clamp(uNoiseAmount, 0.0, 1.0);\n vec3 dir = rayDir(frag, uResolution, uOffset, 1.0);\n float marchT = 0.0;\n vec3 col = vec3(0.0);\n float n = layeredNoise(frag);\n vec4 c = cos(t * 0.2 + vec4(0.0, 33.0, 11.0, 0.0));\n mat2 M2 = mat2(c.x, c.y, c.z, c.w);\n float amp = clamp(uDistort, 0.0, 50.0) * 0.15;\n\n mat3 rot3dMat = mat3(1.0);\n if(uAnimType == 1){\n vec3 ang = vec3(t * 0.31, t * 0.21, t * 0.17);\n rot3dMat = rotZ(ang.z) * rotY(ang.y) * rotX(ang.x);\n }\n mat3 hoverMat = mat3(1.0);\n if(uAnimType == 2){\n vec2 m = uMouse * 2.0 - 1.0;\n vec3 ang = vec3(m.y * 0.6, m.x * 0.6, 0.0);\n hoverMat = rotY(ang.y) * rotX(ang.x);\n }\n\n for (int i = 0; i < 44; ++i) {\n vec3 P = marchT * dir;\n P.z -= 2.0;\n float rad = length(P);\n vec3 Pl = P * (10.0 / max(rad, 1e-6));\n\n if(uAnimType == 0){\n Pl.xz *= M2;\n } else if(uAnimType == 1){\n Pl = rot3dMat * Pl;\n } else {\n Pl = hoverMat * Pl;\n }\n\n float stepLen = min(rad - 0.3, n * jitterAmp) + 0.1;\n\n float grow = smoothstep(0.35, 3.0, marchT);\n float a1 = amp * grow * bendAngle(Pl * 0.6, t);\n float a2 = 0.5 * amp * grow * bendAngle(Pl.zyx * 0.5 + 3.1, t * 0.9);\n vec3 Pb = Pl;\n Pb.xz = rot2(Pb.xz, a1);\n Pb.xy = rot2(Pb.xy, a2);\n\n float rayPattern = smoothstep(\n 0.5, 0.7,\n sin(Pb.x + cos(Pb.y) * cos(Pb.z)) *\n sin(Pb.z + sin(Pb.y) * cos(Pb.x + t))\n );\n\n if (uRayCount > 0) {\n float ang = atan(Pb.y, Pb.x);\n float comb = 0.5 + 0.5 * cos(float(uRayCount) * ang);\n comb = pow(comb, 3.0);\n rayPattern *= smoothstep(0.15, 0.95, comb);\n }\n\n vec3 spectralDefault = 1.0 + vec3(\n cos(marchT * 3.0 + 0.0),\n cos(marchT * 3.0 + 1.0),\n cos(marchT * 3.0 + 2.0)\n );\n\n float saw = fract(marchT * 0.25);\n float tRay = saw * saw * (3.0 - 2.0 * saw);\n vec3 userGradient = 2.0 * sampleGradient(tRay);\n vec3 spectral = (uColorCount > 0) ? userGradient : spectralDefault;\n vec3 base = (0.05 / (0.4 + stepLen))\n * smoothstep(5.0, 0.0, rad)\n * spectral;\n\n col += base * rayPattern;\n marchT += stepLen;\n }\n\n col *= edgeFade(frag, uResolution, uOffset);\n col *= uIntensity;\n\n fragColor = vec4(clamp(col, 0.0, 1.0), 1.0);\n}`;\n\nconst hexToRgb01 = hex => {\n let h = hex.trim();\n if (h.startsWith('#')) h = h.slice(1);\n if (h.length === 3) {\n const r = h[0],\n g = h[1],\n b = h[2];\n h = r + r + g + g + b + b;\n }\n const intVal = parseInt(h, 16);\n if (isNaN(intVal) || (h.length !== 6 && h.length !== 8)) return [1, 1, 1];\n const r = ((intVal >> 16) & 255) / 255;\n const g = ((intVal >> 8) & 255) / 255;\n const b = (intVal & 255) / 255;\n return [r, g, b];\n};\n\nconst toPx = v => {\n if (v == null) return 0;\n if (typeof v === 'number') return v;\n const s = String(v).trim();\n const num = parseFloat(s.replace('px', ''));\n return isNaN(num) ? 0 : num;\n};\n\nconst PrismaticBurst = ({\n intensity = 2,\n speed = 0.5,\n animationType = 'rotate3d',\n colors,\n distort = 0,\n paused = false,\n offset = { x: 0, y: 0 },\n hoverDampness = 0,\n rayCount,\n mixBlendMode = 'lighten'\n}) => {\n const containerRef = useRef(null);\n const programRef = useRef(null);\n const rendererRef = useRef(null);\n const mouseTargetRef = useRef([0.5, 0.5]);\n const mouseSmoothRef = useRef([0.5, 0.5]);\n const pausedRef = useRef(paused);\n const gradTexRef = useRef(null);\n const hoverDampRef = useRef(hoverDampness);\n const isVisibleRef = useRef(true);\n const meshRef = useRef(null);\n const triRef = useRef(null);\n\n useEffect(() => {\n pausedRef.current = paused;\n }, [paused]);\n useEffect(() => {\n hoverDampRef.current = hoverDampness;\n }, [hoverDampness]);\n\n useEffect(() => {\n const container = containerRef.current;\n if (!container) return;\n\n const dpr = Math.min(window.devicePixelRatio || 1, 2);\n const renderer = new Renderer({ dpr, alpha: false, antialias: false });\n rendererRef.current = renderer;\n\n const gl = renderer.gl;\n gl.canvas.style.position = 'absolute';\n gl.canvas.style.inset = '0';\n gl.canvas.style.width = '100%';\n gl.canvas.style.height = '100%';\n gl.canvas.style.mixBlendMode = mixBlendMode && mixBlendMode !== 'none' ? mixBlendMode : '';\n container.appendChild(gl.canvas);\n\n const white = new Uint8Array([255, 255, 255, 255]);\n const gradientTex = new Texture(gl, {\n image: white,\n width: 1,\n height: 1,\n generateMipmaps: false,\n flipY: false\n });\n\n gradientTex.minFilter = gl.LINEAR;\n gradientTex.magFilter = gl.LINEAR;\n gradientTex.wrapS = gl.CLAMP_TO_EDGE;\n gradientTex.wrapT = gl.CLAMP_TO_EDGE;\n gradTexRef.current = gradientTex;\n\n const program = new Program(gl, {\n vertex: vertexShader,\n fragment: fragmentShader,\n uniforms: {\n uResolution: { value: [1, 1] },\n uTime: { value: 0 },\n\n uIntensity: { value: 1 },\n uSpeed: { value: 1 },\n uAnimType: { value: 0 },\n uMouse: { value: [0.5, 0.5] },\n uColorCount: { value: 0 },\n uDistort: { value: 0 },\n uOffset: { value: [0, 0] },\n uGradient: { value: gradientTex },\n uNoiseAmount: { value: 0.8 },\n uRayCount: { value: 0 }\n }\n });\n\n programRef.current = program;\n\n const triangle = new Triangle(gl);\n const mesh = new Mesh(gl, { geometry: triangle, program });\n triRef.current = triangle;\n meshRef.current = mesh;\n\n const resize = () => {\n const w = container.clientWidth || 1;\n const h = container.clientHeight || 1;\n renderer.setSize(w, h);\n program.uniforms.uResolution.value = [gl.drawingBufferWidth, gl.drawingBufferHeight];\n };\n\n let ro = null;\n if ('ResizeObserver' in window) {\n ro = new ResizeObserver(resize);\n ro.observe(container);\n } else {\n window.addEventListener('resize', resize);\n }\n resize();\n\n const onPointer = e => {\n const rect = container.getBoundingClientRect();\n const x = (e.clientX - rect.left) / Math.max(rect.width, 1);\n const y = (e.clientY - rect.top) / Math.max(rect.height, 1);\n mouseTargetRef.current = [Math.min(Math.max(x, 0), 1), Math.min(Math.max(y, 0), 1)];\n };\n container.addEventListener('pointermove', onPointer, { passive: true });\n\n let io = null;\n if ('IntersectionObserver' in window) {\n io = new IntersectionObserver(\n entries => {\n if (entries[0]) isVisibleRef.current = entries[0].isIntersecting;\n },\n { root: null, threshold: 0.01 }\n );\n io.observe(container);\n }\n const onVis = () => {};\n document.addEventListener('visibilitychange', onVis);\n\n let raf = 0;\n let last = performance.now();\n let accumTime = 0;\n\n const update = now => {\n const dt = Math.max(0, now - last) * 0.001;\n last = now;\n const visible = isVisibleRef.current && !document.hidden;\n if (!pausedRef.current) accumTime += dt;\n if (!visible) {\n raf = requestAnimationFrame(update);\n return;\n }\n const tau = 0.02 + Math.max(0, Math.min(1, hoverDampRef.current)) * 0.5;\n const alpha = 1 - Math.exp(-dt / tau);\n const tgt = mouseTargetRef.current;\n const sm = mouseSmoothRef.current;\n sm[0] += (tgt[0] - sm[0]) * alpha;\n sm[1] += (tgt[1] - sm[1]) * alpha;\n program.uniforms.uMouse.value = sm;\n program.uniforms.uTime.value = accumTime;\n renderer.render({ scene: meshRef.current });\n raf = requestAnimationFrame(update);\n };\n raf = requestAnimationFrame(update);\n\n return () => {\n cancelAnimationFrame(raf);\n container.removeEventListener('pointermove', onPointer);\n ro?.disconnect();\n if (!ro) window.removeEventListener('resize', resize);\n io?.disconnect();\n document.removeEventListener('visibilitychange', onVis);\n try {\n container.removeChild(gl.canvas);\n } catch (e) {\n void e;\n }\n try {\n meshRef.current?.remove?.();\n } catch (e) {\n void e;\n }\n try {\n triRef.current?.remove?.();\n } catch (e) {\n void e;\n }\n try {\n programRef.current?.remove?.();\n } catch (e) {\n void e;\n }\n try {\n const glCtx = rendererRef.current?.gl;\n if (glCtx && gradTexRef.current?.texture) glCtx.deleteTexture(gradTexRef.current.texture);\n } catch (e) {\n void e;\n }\n programRef.current = null;\n rendererRef.current = null;\n gradTexRef.current = null;\n meshRef.current = null;\n triRef.current = null;\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, []);\n\n useEffect(() => {\n const canvas = rendererRef.current?.gl?.canvas;\n\n if (canvas) {\n canvas.style.mixBlendMode = mixBlendMode && mixBlendMode !== 'none' ? mixBlendMode : '';\n }\n }, [mixBlendMode]);\n\n useEffect(() => {\n const program = programRef.current;\n const renderer = rendererRef.current;\n const gradTex = gradTexRef.current;\n if (!program || !renderer || !gradTex) return;\n\n program.uniforms.uIntensity.value = intensity ?? 1;\n program.uniforms.uSpeed.value = speed ?? 1;\n\n const animTypeMap = {\n rotate: 0,\n rotate3d: 1,\n hover: 2\n };\n program.uniforms.uAnimType.value = animTypeMap[animationType ?? 'rotate'];\n\n program.uniforms.uDistort.value = typeof distort === 'number' ? distort : 0;\n\n const ox = toPx(offset?.x);\n const oy = toPx(offset?.y);\n program.uniforms.uOffset.value = [ox, oy];\n program.uniforms.uRayCount.value = Math.max(0, Math.floor(rayCount ?? 0));\n\n let count = 0;\n if (Array.isArray(colors) && colors.length > 0) {\n const gl = renderer.gl;\n const capped = colors.slice(0, 64);\n count = capped.length;\n const data = new Uint8Array(count * 4);\n for (let i = 0; i < count; i++) {\n const [r, g, b] = hexToRgb01(capped[i]);\n data[i * 4 + 0] = Math.round(r * 255);\n data[i * 4 + 1] = Math.round(g * 255);\n data[i * 4 + 2] = Math.round(b * 255);\n data[i * 4 + 3] = 255;\n }\n gradTex.image = data;\n gradTex.width = count;\n gradTex.height = 1;\n gradTex.minFilter = gl.LINEAR;\n gradTex.magFilter = gl.LINEAR;\n gradTex.wrapS = gl.CLAMP_TO_EDGE;\n gradTex.wrapT = gl.CLAMP_TO_EDGE;\n gradTex.flipY = false;\n gradTex.generateMipmaps = false;\n gradTex.format = gl.RGBA;\n gradTex.type = gl.UNSIGNED_BYTE;\n gradTex.needsUpdate = true;\n } else {\n count = 0;\n }\n program.uniforms.uColorCount.value = count;\n }, [intensity, speed, animationType, colors, distort, offset, rayCount]);\n\n return
;\n};\n\nexport default PrismaticBurst;\n" + "content": "import { useEffect, useRef } from 'react';\nimport { Renderer, Program, Mesh, Triangle, Texture } from 'ogl';\n\nconst vertexShader = `#version 300 es\nin vec2 position;\nin vec2 uv;\nout vec2 vUv;\nvoid main() {\n vUv = uv;\n gl_Position = vec4(position, 0.0, 1.0);\n}\n`;\n\nconst fragmentShader = `#version 300 es\nprecision highp float;\nprecision highp int;\n\nout vec4 fragColor;\n\nuniform vec2 uResolution;\nuniform float uTime;\n\nuniform float uIntensity;\nuniform float uSpeed;\nuniform int uAnimType;\nuniform vec2 uMouse;\nuniform int uColorCount;\nuniform float uDistort;\nuniform vec2 uOffset;\nuniform sampler2D uGradient;\nuniform float uNoiseAmount;\nuniform int uRayCount;\n\nfloat hash21(vec2 p){\n p = floor(p);\n float f = 52.9829189 * fract(dot(p, vec2(0.065, 0.005)));\n return fract(f);\n}\n\nmat2 rot30(){ return mat2(0.8, -0.5, 0.5, 0.8); }\n\nfloat layeredNoise(vec2 fragPx){\n vec2 p = mod(fragPx + vec2(uTime * 30.0, -uTime * 21.0), 1024.0);\n vec2 q = rot30() * p;\n float n = 0.0;\n n += 0.40 * hash21(q);\n n += 0.25 * hash21(q * 2.0 + 17.0);\n n += 0.20 * hash21(q * 4.0 + 47.0);\n n += 0.10 * hash21(q * 8.0 + 113.0);\n n += 0.05 * hash21(q * 16.0 + 191.0);\n return n;\n}\n\nvec3 rayDir(vec2 frag, vec2 res, vec2 offset, float dist){\n float focal = res.y * max(dist, 1e-3);\n return normalize(vec3(2.0 * (frag - offset) - res, focal));\n}\n\nfloat edgeFade(vec2 frag, vec2 res, vec2 offset){\n vec2 toC = frag - 0.5 * res - offset;\n float r = length(toC) / (0.5 * min(res.x, res.y));\n float x = clamp(r, 0.0, 1.0);\n float q = x * x * x * (x * (x * 6.0 - 15.0) + 10.0);\n float s = q * 0.5;\n s = pow(s, 1.5);\n float tail = 1.0 - pow(1.0 - s, 2.0);\n s = mix(s, tail, 0.2);\n float dn = (layeredNoise(frag * 0.15) - 0.5) * 0.0015 * s;\n return clamp(s + dn, 0.0, 1.0);\n}\n\nmat3 rotX(float a){ float c = cos(a), s = sin(a); return mat3(1.0,0.0,0.0, 0.0,c,-s, 0.0,s,c); }\nmat3 rotY(float a){ float c = cos(a), s = sin(a); return mat3(c,0.0,s, 0.0,1.0,0.0, -s,0.0,c); }\nmat3 rotZ(float a){ float c = cos(a), s = sin(a); return mat3(c,-s,0.0, s,c,0.0, 0.0,0.0,1.0); }\n\nvec3 sampleGradient(float t){\n t = clamp(t, 0.0, 1.0);\n return texture(uGradient, vec2(t, 0.5)).rgb;\n}\n\nvec2 rot2(vec2 v, float a){\n float s = sin(a), c = cos(a);\n return mat2(c, -s, s, c) * v;\n}\n\nfloat bendAngle(vec3 q, float t){\n float a = 0.8 * sin(q.x * 0.55 + t * 0.6)\n + 0.7 * sin(q.y * 0.50 - t * 0.5)\n + 0.6 * sin(q.z * 0.60 + t * 0.7);\n return a;\n}\n\nvoid main(){\n vec2 frag = gl_FragCoord.xy;\n float t = uTime * uSpeed;\n float jitterAmp = 0.1 * clamp(uNoiseAmount, 0.0, 1.0);\n vec3 dir = rayDir(frag, uResolution, uOffset, 1.0);\n float marchT = 0.0;\n vec3 col = vec3(0.0);\n float n = layeredNoise(frag);\n vec4 c = cos(t * 0.2 + vec4(0.0, 33.0, 11.0, 0.0));\n mat2 M2 = mat2(c.x, c.y, c.z, c.w);\n float amp = clamp(uDistort, 0.0, 50.0) * 0.15;\n\n mat3 rot3dMat = mat3(1.0);\n if(uAnimType == 1){\n vec3 ang = vec3(t * 0.31, t * 0.21, t * 0.17);\n rot3dMat = rotZ(ang.z) * rotY(ang.y) * rotX(ang.x);\n }\n mat3 hoverMat = mat3(1.0);\n if(uAnimType == 2){\n vec2 m = uMouse * 2.0 - 1.0;\n vec3 ang = vec3(m.y * 0.6, m.x * 0.6, 0.0);\n hoverMat = rotY(ang.y) * rotX(ang.x);\n }\n\n for (int i = 0; i < 44; ++i) {\n vec3 P = marchT * dir;\n P.z -= 2.0;\n float rad = length(P);\n vec3 Pl = P * (10.0 / max(rad, 1e-6));\n\n if(uAnimType == 0){\n Pl.xz *= M2;\n } else if(uAnimType == 1){\n Pl = rot3dMat * Pl;\n } else {\n Pl = hoverMat * Pl;\n }\n\n float stepLen = min(rad - 0.3, n * jitterAmp) + 0.1;\n\n float grow = smoothstep(0.35, 3.0, marchT);\n float a1 = amp * grow * bendAngle(Pl * 0.6, t);\n float a2 = 0.5 * amp * grow * bendAngle(Pl.zyx * 0.5 + 3.1, t * 0.9);\n vec3 Pb = Pl;\n Pb.xz = rot2(Pb.xz, a1);\n Pb.xy = rot2(Pb.xy, a2);\n\n float rayPattern = smoothstep(\n 0.5, 0.7,\n sin(Pb.x + cos(Pb.y) * cos(Pb.z)) *\n sin(Pb.z + sin(Pb.y) * cos(Pb.x + t))\n );\n\n if (uRayCount > 0) {\n float ang = atan(Pb.y, Pb.x);\n float comb = 0.5 + 0.5 * cos(float(uRayCount) * ang);\n comb = pow(comb, 3.0);\n rayPattern *= smoothstep(0.15, 0.95, comb);\n }\n\n vec3 spectralDefault = 1.0 + vec3(\n cos(marchT * 3.0 + 0.0),\n cos(marchT * 3.0 + 1.0),\n cos(marchT * 3.0 + 2.0)\n );\n\n float saw = fract(marchT * 0.25);\n float tRay = saw * saw * (3.0 - 2.0 * saw);\n vec3 userGradient = 2.0 * sampleGradient(tRay);\n vec3 spectral = (uColorCount > 0) ? userGradient : spectralDefault;\n vec3 base = (0.05 / (0.4 + stepLen))\n * smoothstep(5.0, 0.0, rad)\n * spectral;\n\n col += base * rayPattern;\n marchT += stepLen;\n }\n\n col *= edgeFade(frag, uResolution, uOffset);\n col *= uIntensity;\n\n fragColor = vec4(clamp(col, 0.0, 1.0), 1.0);\n}`;\n\nconst hexToRgb01 = hex => {\n let h = hex.trim();\n if (h.startsWith('#')) h = h.slice(1);\n if (h.length === 3) {\n const r = h[0],\n g = h[1],\n b = h[2];\n h = r + r + g + g + b + b;\n }\n const intVal = parseInt(h.slice(0, 6), 16);\n if (isNaN(intVal) || (h.length !== 6 && h.length !== 8)) return [1, 1, 1];\n const r = ((intVal >> 16) & 255) / 255;\n const g = ((intVal >> 8) & 255) / 255;\n const b = (intVal & 255) / 255;\n return [r, g, b];\n};\n\nconst toPx = v => {\n if (v == null) return 0;\n if (typeof v === 'number') return v;\n const s = String(v).trim();\n const num = parseFloat(s.replace('px', ''));\n return isNaN(num) ? 0 : num;\n};\n\nconst PrismaticBurst = ({\n intensity = 2,\n speed = 0.5,\n animationType = 'rotate3d',\n colors,\n distort = 0,\n paused = false,\n offset = { x: 0, y: 0 },\n hoverDampness = 0,\n rayCount,\n mixBlendMode = 'lighten'\n}) => {\n const containerRef = useRef(null);\n const programRef = useRef(null);\n const rendererRef = useRef(null);\n const mouseTargetRef = useRef([0.5, 0.5]);\n const mouseSmoothRef = useRef([0.5, 0.5]);\n const pausedRef = useRef(paused);\n const gradTexRef = useRef(null);\n const hoverDampRef = useRef(hoverDampness);\n const isVisibleRef = useRef(true);\n const meshRef = useRef(null);\n const triRef = useRef(null);\n\n useEffect(() => {\n pausedRef.current = paused;\n }, [paused]);\n useEffect(() => {\n hoverDampRef.current = hoverDampness;\n }, [hoverDampness]);\n\n useEffect(() => {\n const container = containerRef.current;\n if (!container) return;\n\n const dpr = Math.min(window.devicePixelRatio || 1, 2);\n const renderer = new Renderer({ dpr, alpha: false, antialias: false });\n rendererRef.current = renderer;\n\n const gl = renderer.gl;\n gl.canvas.style.position = 'absolute';\n gl.canvas.style.inset = '0';\n gl.canvas.style.width = '100%';\n gl.canvas.style.height = '100%';\n gl.canvas.style.mixBlendMode = mixBlendMode && mixBlendMode !== 'none' ? mixBlendMode : '';\n container.appendChild(gl.canvas);\n\n const white = new Uint8Array([255, 255, 255, 255]);\n const gradientTex = new Texture(gl, {\n image: white,\n width: 1,\n height: 1,\n generateMipmaps: false,\n flipY: false\n });\n\n gradientTex.minFilter = gl.LINEAR;\n gradientTex.magFilter = gl.LINEAR;\n gradientTex.wrapS = gl.CLAMP_TO_EDGE;\n gradientTex.wrapT = gl.CLAMP_TO_EDGE;\n gradTexRef.current = gradientTex;\n\n const program = new Program(gl, {\n vertex: vertexShader,\n fragment: fragmentShader,\n uniforms: {\n uResolution: { value: [1, 1] },\n uTime: { value: 0 },\n\n uIntensity: { value: 1 },\n uSpeed: { value: 1 },\n uAnimType: { value: 0 },\n uMouse: { value: [0.5, 0.5] },\n uColorCount: { value: 0 },\n uDistort: { value: 0 },\n uOffset: { value: [0, 0] },\n uGradient: { value: gradientTex },\n uNoiseAmount: { value: 0.8 },\n uRayCount: { value: 0 }\n }\n });\n\n programRef.current = program;\n\n const triangle = new Triangle(gl);\n const mesh = new Mesh(gl, { geometry: triangle, program });\n triRef.current = triangle;\n meshRef.current = mesh;\n\n const resize = () => {\n const w = container.clientWidth || 1;\n const h = container.clientHeight || 1;\n renderer.setSize(w, h);\n program.uniforms.uResolution.value = [gl.drawingBufferWidth, gl.drawingBufferHeight];\n };\n\n let ro = null;\n if ('ResizeObserver' in window) {\n ro = new ResizeObserver(resize);\n ro.observe(container);\n } else {\n window.addEventListener('resize', resize);\n }\n resize();\n\n const onPointer = e => {\n const rect = container.getBoundingClientRect();\n const x = (e.clientX - rect.left) / Math.max(rect.width, 1);\n const y = (e.clientY - rect.top) / Math.max(rect.height, 1);\n mouseTargetRef.current = [Math.min(Math.max(x, 0), 1), Math.min(Math.max(y, 0), 1)];\n };\n container.addEventListener('pointermove', onPointer, { passive: true });\n\n let io = null;\n if ('IntersectionObserver' in window) {\n io = new IntersectionObserver(\n entries => {\n if (entries[0]) isVisibleRef.current = entries[0].isIntersecting;\n },\n { root: null, threshold: 0.01 }\n );\n io.observe(container);\n }\n const onVis = () => {};\n document.addEventListener('visibilitychange', onVis);\n\n let raf = 0;\n let last = performance.now();\n let accumTime = 0;\n\n const update = now => {\n const dt = Math.max(0, now - last) * 0.001;\n last = now;\n const visible = isVisibleRef.current && !document.hidden;\n if (!pausedRef.current) accumTime += dt;\n if (!visible) {\n raf = requestAnimationFrame(update);\n return;\n }\n const tau = 0.02 + Math.max(0, Math.min(1, hoverDampRef.current)) * 0.5;\n const alpha = 1 - Math.exp(-dt / tau);\n const tgt = mouseTargetRef.current;\n const sm = mouseSmoothRef.current;\n sm[0] += (tgt[0] - sm[0]) * alpha;\n sm[1] += (tgt[1] - sm[1]) * alpha;\n program.uniforms.uMouse.value = sm;\n program.uniforms.uTime.value = accumTime;\n renderer.render({ scene: meshRef.current });\n raf = requestAnimationFrame(update);\n };\n raf = requestAnimationFrame(update);\n\n return () => {\n cancelAnimationFrame(raf);\n container.removeEventListener('pointermove', onPointer);\n ro?.disconnect();\n if (!ro) window.removeEventListener('resize', resize);\n io?.disconnect();\n document.removeEventListener('visibilitychange', onVis);\n try {\n container.removeChild(gl.canvas);\n } catch (e) {\n void e;\n }\n try {\n meshRef.current?.remove?.();\n } catch (e) {\n void e;\n }\n try {\n triRef.current?.remove?.();\n } catch (e) {\n void e;\n }\n try {\n programRef.current?.remove?.();\n } catch (e) {\n void e;\n }\n try {\n const glCtx = rendererRef.current?.gl;\n if (glCtx && gradTexRef.current?.texture) glCtx.deleteTexture(gradTexRef.current.texture);\n } catch (e) {\n void e;\n }\n programRef.current = null;\n rendererRef.current = null;\n gradTexRef.current = null;\n meshRef.current = null;\n triRef.current = null;\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, []);\n\n useEffect(() => {\n const canvas = rendererRef.current?.gl?.canvas;\n\n if (canvas) {\n canvas.style.mixBlendMode = mixBlendMode && mixBlendMode !== 'none' ? mixBlendMode : '';\n }\n }, [mixBlendMode]);\n\n useEffect(() => {\n const program = programRef.current;\n const renderer = rendererRef.current;\n const gradTex = gradTexRef.current;\n if (!program || !renderer || !gradTex) return;\n\n program.uniforms.uIntensity.value = intensity ?? 1;\n program.uniforms.uSpeed.value = speed ?? 1;\n\n const animTypeMap = {\n rotate: 0,\n rotate3d: 1,\n hover: 2\n };\n program.uniforms.uAnimType.value = animTypeMap[animationType ?? 'rotate'];\n\n program.uniforms.uDistort.value = typeof distort === 'number' ? distort : 0;\n\n const ox = toPx(offset?.x);\n const oy = toPx(offset?.y);\n program.uniforms.uOffset.value = [ox, oy];\n program.uniforms.uRayCount.value = Math.max(0, Math.floor(rayCount ?? 0));\n\n let count = 0;\n if (Array.isArray(colors) && colors.length > 0) {\n const gl = renderer.gl;\n const capped = colors.slice(0, 64);\n count = capped.length;\n const data = new Uint8Array(count * 4);\n for (let i = 0; i < count; i++) {\n const [r, g, b] = hexToRgb01(capped[i]);\n data[i * 4 + 0] = Math.round(r * 255);\n data[i * 4 + 1] = Math.round(g * 255);\n data[i * 4 + 2] = Math.round(b * 255);\n data[i * 4 + 3] = 255;\n }\n gradTex.image = data;\n gradTex.width = count;\n gradTex.height = 1;\n gradTex.minFilter = gl.LINEAR;\n gradTex.magFilter = gl.LINEAR;\n gradTex.wrapS = gl.CLAMP_TO_EDGE;\n gradTex.wrapT = gl.CLAMP_TO_EDGE;\n gradTex.flipY = false;\n gradTex.generateMipmaps = false;\n gradTex.format = gl.RGBA;\n gradTex.type = gl.UNSIGNED_BYTE;\n gradTex.needsUpdate = true;\n } else {\n count = 0;\n }\n program.uniforms.uColorCount.value = count;\n }, [intensity, speed, animationType, colors, distort, offset, rayCount]);\n\n return
;\n};\n\nexport default PrismaticBurst;\n" } ], "registryDependencies": [], diff --git a/public/r/PrismaticBurst-TS-CSS.json b/public/r/PrismaticBurst-TS-CSS.json index 67f46d9ba..75125eee6 100644 --- a/public/r/PrismaticBurst-TS-CSS.json +++ b/public/r/PrismaticBurst-TS-CSS.json @@ -13,7 +13,7 @@ { "type": "registry:component", "path": "PrismaticBurst/PrismaticBurst.tsx", - "content": "import React, { useEffect, useRef } from 'react';\nimport { Renderer, Program, Mesh, Triangle, Texture } from 'ogl';\nimport './PrismaticBurst.css';\n\ntype Offset = { x?: number | string; y?: number | string };\ntype AnimationType = 'rotate' | 'rotate3d' | 'hover';\n\nexport type PrismaticBurstProps = {\n intensity?: number;\n speed?: number;\n animationType?: AnimationType;\n colors?: string[];\n distort?: number;\n paused?: boolean;\n offset?: Offset;\n hoverDampness?: number;\n rayCount?: number;\n mixBlendMode?: React.CSSProperties['mixBlendMode'] | 'none';\n};\n\nconst vertexShader = `#version 300 es\nin vec2 position;\nin vec2 uv;\nout vec2 vUv;\nvoid main() {\n vUv = uv;\n gl_Position = vec4(position, 0.0, 1.0);\n}\n`;\n\nconst fragmentShader = `#version 300 es\nprecision highp float;\nprecision highp int;\n\nout vec4 fragColor;\n\nuniform vec2 uResolution;\nuniform float uTime;\n\nuniform float uIntensity;\nuniform float uSpeed;\nuniform int uAnimType;\nuniform vec2 uMouse;\nuniform int uColorCount;\nuniform float uDistort;\nuniform vec2 uOffset;\nuniform sampler2D uGradient;\nuniform float uNoiseAmount;\nuniform int uRayCount;\n\nfloat hash21(vec2 p){\n p = floor(p);\n float f = 52.9829189 * fract(dot(p, vec2(0.065, 0.005)));\n return fract(f);\n}\n\nmat2 rot30(){ return mat2(0.8, -0.5, 0.5, 0.8); }\n\nfloat layeredNoise(vec2 fragPx){\n vec2 p = mod(fragPx + vec2(uTime * 30.0, -uTime * 21.0), 1024.0);\n vec2 q = rot30() * p;\n float n = 0.0;\n n += 0.40 * hash21(q);\n n += 0.25 * hash21(q * 2.0 + 17.0);\n n += 0.20 * hash21(q * 4.0 + 47.0);\n n += 0.10 * hash21(q * 8.0 + 113.0);\n n += 0.05 * hash21(q * 16.0 + 191.0);\n return n;\n}\n\nvec3 rayDir(vec2 frag, vec2 res, vec2 offset, float dist){\n float focal = res.y * max(dist, 1e-3);\n return normalize(vec3(2.0 * (frag - offset) - res, focal));\n}\n\nfloat edgeFade(vec2 frag, vec2 res, vec2 offset){\n vec2 toC = frag - 0.5 * res - offset;\n float r = length(toC) / (0.5 * min(res.x, res.y));\n float x = clamp(r, 0.0, 1.0);\n float q = x * x * x * (x * (x * 6.0 - 15.0) + 10.0);\n float s = q * 0.5;\n s = pow(s, 1.5);\n float tail = 1.0 - pow(1.0 - s, 2.0);\n s = mix(s, tail, 0.2);\n float dn = (layeredNoise(frag * 0.15) - 0.5) * 0.0015 * s;\n return clamp(s + dn, 0.0, 1.0);\n}\n\nmat3 rotX(float a){ float c = cos(a), s = sin(a); return mat3(1.0,0.0,0.0, 0.0,c,-s, 0.0,s,c); }\nmat3 rotY(float a){ float c = cos(a), s = sin(a); return mat3(c,0.0,s, 0.0,1.0,0.0, -s,0.0,c); }\nmat3 rotZ(float a){ float c = cos(a), s = sin(a); return mat3(c,-s,0.0, s,c,0.0, 0.0,0.0,1.0); }\n\nvec3 sampleGradient(float t){\n t = clamp(t, 0.0, 1.0);\n return texture(uGradient, vec2(t, 0.5)).rgb;\n}\n\nvec2 rot2(vec2 v, float a){\n float s = sin(a), c = cos(a);\n return mat2(c, -s, s, c) * v;\n}\n\nfloat bendAngle(vec3 q, float t){\n float a = 0.8 * sin(q.x * 0.55 + t * 0.6)\n + 0.7 * sin(q.y * 0.50 - t * 0.5)\n + 0.6 * sin(q.z * 0.60 + t * 0.7);\n return a;\n}\n\nvoid main(){\n vec2 frag = gl_FragCoord.xy;\n float t = uTime * uSpeed;\n float jitterAmp = 0.1 * clamp(uNoiseAmount, 0.0, 1.0);\n vec3 dir = rayDir(frag, uResolution, uOffset, 1.0);\n float marchT = 0.0;\n vec3 col = vec3(0.0);\n float n = layeredNoise(frag);\n vec4 c = cos(t * 0.2 + vec4(0.0, 33.0, 11.0, 0.0));\n mat2 M2 = mat2(c.x, c.y, c.z, c.w);\n float amp = clamp(uDistort, 0.0, 50.0) * 0.15;\n\n mat3 rot3dMat = mat3(1.0);\n if(uAnimType == 1){\n vec3 ang = vec3(t * 0.31, t * 0.21, t * 0.17);\n rot3dMat = rotZ(ang.z) * rotY(ang.y) * rotX(ang.x);\n }\n mat3 hoverMat = mat3(1.0);\n if(uAnimType == 2){\n vec2 m = uMouse * 2.0 - 1.0;\n vec3 ang = vec3(m.y * 0.6, m.x * 0.6, 0.0);\n hoverMat = rotY(ang.y) * rotX(ang.x);\n }\n\n for (int i = 0; i < 44; ++i) {\n vec3 P = marchT * dir;\n P.z -= 2.0;\n float rad = length(P);\n vec3 Pl = P * (10.0 / max(rad, 1e-6));\n\n if(uAnimType == 0){\n Pl.xz *= M2;\n } else if(uAnimType == 1){\n Pl = rot3dMat * Pl;\n } else {\n Pl = hoverMat * Pl;\n }\n\n float stepLen = min(rad - 0.3, n * jitterAmp) + 0.1;\n\n float grow = smoothstep(0.35, 3.0, marchT);\n float a1 = amp * grow * bendAngle(Pl * 0.6, t);\n float a2 = 0.5 * amp * grow * bendAngle(Pl.zyx * 0.5 + 3.1, t * 0.9);\n vec3 Pb = Pl;\n Pb.xz = rot2(Pb.xz, a1);\n Pb.xy = rot2(Pb.xy, a2);\n\n float rayPattern = smoothstep(\n 0.5, 0.7,\n sin(Pb.x + cos(Pb.y) * cos(Pb.z)) *\n sin(Pb.z + sin(Pb.y) * cos(Pb.x + t))\n );\n\n if (uRayCount > 0) {\n float ang = atan(Pb.y, Pb.x);\n float comb = 0.5 + 0.5 * cos(float(uRayCount) * ang);\n comb = pow(comb, 3.0);\n rayPattern *= smoothstep(0.15, 0.95, comb);\n }\n\n vec3 spectralDefault = 1.0 + vec3(\n cos(marchT * 3.0 + 0.0),\n cos(marchT * 3.0 + 1.0),\n cos(marchT * 3.0 + 2.0)\n );\n\n float saw = fract(marchT * 0.25);\n float tRay = saw * saw * (3.0 - 2.0 * saw);\n vec3 userGradient = 2.0 * sampleGradient(tRay);\n vec3 spectral = (uColorCount > 0) ? userGradient : spectralDefault;\n vec3 base = (0.05 / (0.4 + stepLen))\n * smoothstep(5.0, 0.0, rad)\n * spectral;\n\n col += base * rayPattern;\n marchT += stepLen;\n }\n\n col *= edgeFade(frag, uResolution, uOffset);\n col *= uIntensity;\n\n fragColor = vec4(clamp(col, 0.0, 1.0), 1.0);\n}`;\n\nconst hexToRgb01 = (hex: string): [number, number, number] => {\n let h = hex.trim();\n if (h.startsWith('#')) h = h.slice(1);\n if (h.length === 3) {\n const r = h[0],\n g = h[1],\n b = h[2];\n h = r + r + g + g + b + b;\n }\n const intVal = parseInt(h, 16);\n if (isNaN(intVal) || (h.length !== 6 && h.length !== 8)) return [1, 1, 1];\n const r = ((intVal >> 16) & 255) / 255;\n const g = ((intVal >> 8) & 255) / 255;\n const b = (intVal & 255) / 255;\n return [r, g, b];\n};\n\nconst toPx = (v: number | string | undefined): number => {\n if (v == null) return 0;\n if (typeof v === 'number') return v;\n const s = String(v).trim();\n const num = parseFloat(s.replace('px', ''));\n return isNaN(num) ? 0 : num;\n};\n\nconst PrismaticBurst = ({\n intensity = 2,\n speed = 0.5,\n animationType = 'rotate3d',\n colors,\n distort = 0,\n paused = false,\n offset = { x: 0, y: 0 },\n hoverDampness = 0,\n rayCount,\n mixBlendMode = 'lighten'\n}: PrismaticBurstProps) => {\n const containerRef = useRef(null);\n const programRef = useRef(null);\n const rendererRef = useRef(null);\n const mouseTargetRef = useRef<[number, number]>([0.5, 0.5]);\n const mouseSmoothRef = useRef<[number, number]>([0.5, 0.5]);\n const pausedRef = useRef(paused);\n const gradTexRef = useRef(null);\n const hoverDampRef = useRef(hoverDampness);\n const isVisibleRef = useRef(true);\n const meshRef = useRef(null);\n const triRef = useRef(null);\n\n useEffect(() => {\n pausedRef.current = paused;\n }, [paused]);\n useEffect(() => {\n hoverDampRef.current = hoverDampness;\n }, [hoverDampness]);\n\n useEffect(() => {\n const container = containerRef.current;\n if (!container) return;\n\n const dpr = Math.min(window.devicePixelRatio || 1, 2);\n const renderer = new Renderer({ dpr, alpha: false, antialias: false });\n rendererRef.current = renderer;\n\n const gl = renderer.gl;\n gl.canvas.style.position = 'absolute';\n gl.canvas.style.inset = '0';\n gl.canvas.style.width = '100%';\n gl.canvas.style.height = '100%';\n gl.canvas.style.mixBlendMode = mixBlendMode && mixBlendMode !== 'none' ? mixBlendMode : '';\n container.appendChild(gl.canvas);\n\n const white = new Uint8Array([255, 255, 255, 255]);\n const gradientTex = new Texture(gl, {\n image: white,\n width: 1,\n height: 1,\n generateMipmaps: false,\n flipY: false\n });\n\n gradientTex.minFilter = gl.LINEAR;\n gradientTex.magFilter = gl.LINEAR;\n gradientTex.wrapS = gl.CLAMP_TO_EDGE;\n gradientTex.wrapT = gl.CLAMP_TO_EDGE;\n gradTexRef.current = gradientTex;\n\n const program = new Program(gl, {\n vertex: vertexShader,\n fragment: fragmentShader,\n uniforms: {\n uResolution: { value: [1, 1] as [number, number] },\n uTime: { value: 0 },\n\n uIntensity: { value: 1 },\n uSpeed: { value: 1 },\n uAnimType: { value: 0 },\n uMouse: { value: [0.5, 0.5] as [number, number] },\n uColorCount: { value: 0 },\n uDistort: { value: 0 },\n uOffset: { value: [0, 0] as [number, number] },\n uGradient: { value: gradientTex },\n uNoiseAmount: { value: 0.8 },\n uRayCount: { value: 0 }\n }\n });\n\n programRef.current = program;\n\n const triangle = new Triangle(gl);\n const mesh = new Mesh(gl, { geometry: triangle, program });\n triRef.current = triangle;\n meshRef.current = mesh;\n\n const resize = () => {\n const w = container.clientWidth || 1;\n const h = container.clientHeight || 1;\n renderer.setSize(w, h);\n program.uniforms.uResolution.value = [gl.drawingBufferWidth, gl.drawingBufferHeight];\n };\n\n let ro: ResizeObserver | null = null;\n if ('ResizeObserver' in window) {\n ro = new ResizeObserver(resize);\n ro.observe(container);\n } else {\n (window as Window).addEventListener('resize', resize);\n }\n resize();\n\n const onPointer = (e: PointerEvent) => {\n const rect = container.getBoundingClientRect();\n const x = (e.clientX - rect.left) / Math.max(rect.width, 1);\n const y = (e.clientY - rect.top) / Math.max(rect.height, 1);\n mouseTargetRef.current = [Math.min(Math.max(x, 0), 1), Math.min(Math.max(y, 0), 1)];\n };\n container.addEventListener('pointermove', onPointer, { passive: true });\n\n let io: IntersectionObserver | null = null;\n if ('IntersectionObserver' in window) {\n io = new IntersectionObserver(\n entries => {\n if (entries[0]) isVisibleRef.current = entries[0].isIntersecting;\n },\n { root: null, threshold: 0.01 }\n );\n io.observe(container);\n }\n const onVis = () => {};\n document.addEventListener('visibilitychange', onVis);\n\n let raf = 0;\n let last = performance.now();\n let accumTime = 0;\n\n const update = (now: number) => {\n const dt = Math.max(0, now - last) * 0.001;\n last = now;\n const visible = isVisibleRef.current && !document.hidden;\n if (!pausedRef.current) accumTime += dt;\n if (!visible) {\n raf = requestAnimationFrame(update);\n return;\n }\n const tau = 0.02 + Math.max(0, Math.min(1, hoverDampRef.current)) * 0.5;\n const alpha = 1 - Math.exp(-dt / tau);\n const tgt = mouseTargetRef.current;\n const sm = mouseSmoothRef.current;\n sm[0] += (tgt[0] - sm[0]) * alpha;\n sm[1] += (tgt[1] - sm[1]) * alpha;\n program.uniforms.uMouse.value = sm as any;\n program.uniforms.uTime.value = accumTime;\n renderer.render({ scene: meshRef.current! });\n raf = requestAnimationFrame(update);\n };\n raf = requestAnimationFrame(update);\n\n return () => {\n cancelAnimationFrame(raf);\n container.removeEventListener('pointermove', onPointer);\n ro?.disconnect();\n if (!ro) window.removeEventListener('resize', resize);\n io?.disconnect();\n document.removeEventListener('visibilitychange', onVis);\n try {\n container.removeChild(gl.canvas);\n } catch (e) {\n void e;\n }\n meshRef.current = null;\n triRef.current = null;\n programRef.current = null;\n try {\n const glCtx = rendererRef.current?.gl;\n if (glCtx && gradTexRef.current?.texture) glCtx.deleteTexture(gradTexRef.current.texture);\n } catch (e) {\n void e;\n }\n programRef.current = null;\n rendererRef.current = null;\n gradTexRef.current = null;\n meshRef.current = null;\n triRef.current = null;\n };\n }, []);\n\n useEffect(() => {\n const canvas = rendererRef.current?.gl?.canvas as HTMLCanvasElement | undefined;\n if (canvas) {\n canvas.style.mixBlendMode = mixBlendMode && mixBlendMode !== 'none' ? mixBlendMode : '';\n }\n }, [mixBlendMode]);\n\n useEffect(() => {\n const program = programRef.current;\n const renderer = rendererRef.current;\n const gradTex = gradTexRef.current;\n if (!program || !renderer || !gradTex) return;\n\n program.uniforms.uIntensity.value = intensity ?? 1;\n program.uniforms.uSpeed.value = speed ?? 1;\n\n const animTypeMap: Record = {\n rotate: 0,\n rotate3d: 1,\n hover: 2\n };\n program.uniforms.uAnimType.value = animTypeMap[animationType ?? 'rotate'];\n\n program.uniforms.uDistort.value = typeof distort === 'number' ? distort : 0;\n\n const ox = toPx(offset?.x);\n const oy = toPx(offset?.y);\n program.uniforms.uOffset.value = [ox, oy];\n program.uniforms.uRayCount.value = Math.max(0, Math.floor(rayCount ?? 0));\n\n let count = 0;\n if (Array.isArray(colors) && colors.length > 0) {\n const gl = renderer.gl;\n const capped = colors.slice(0, 64);\n count = capped.length;\n const data = new Uint8Array(count * 4);\n for (let i = 0; i < count; i++) {\n const [r, g, b] = hexToRgb01(capped[i]);\n data[i * 4 + 0] = Math.round(r * 255);\n data[i * 4 + 1] = Math.round(g * 255);\n data[i * 4 + 2] = Math.round(b * 255);\n data[i * 4 + 3] = 255;\n }\n gradTex.image = data;\n gradTex.width = count;\n gradTex.height = 1;\n gradTex.minFilter = gl.LINEAR;\n gradTex.magFilter = gl.LINEAR;\n gradTex.wrapS = gl.CLAMP_TO_EDGE;\n gradTex.wrapT = gl.CLAMP_TO_EDGE;\n gradTex.flipY = false;\n gradTex.generateMipmaps = false;\n gradTex.format = gl.RGBA;\n gradTex.type = gl.UNSIGNED_BYTE;\n gradTex.needsUpdate = true;\n } else {\n count = 0;\n }\n program.uniforms.uColorCount.value = count;\n }, [intensity, speed, animationType, colors, distort, offset, rayCount]);\n\n return
;\n};\n\nexport default PrismaticBurst;\n" + "content": "import React, { useEffect, useRef } from 'react';\nimport { Renderer, Program, Mesh, Triangle, Texture } from 'ogl';\nimport './PrismaticBurst.css';\n\ntype Offset = { x?: number | string; y?: number | string };\ntype AnimationType = 'rotate' | 'rotate3d' | 'hover';\n\nexport type PrismaticBurstProps = {\n intensity?: number;\n speed?: number;\n animationType?: AnimationType;\n colors?: string[];\n distort?: number;\n paused?: boolean;\n offset?: Offset;\n hoverDampness?: number;\n rayCount?: number;\n mixBlendMode?: React.CSSProperties['mixBlendMode'] | 'none';\n};\n\nconst vertexShader = `#version 300 es\nin vec2 position;\nin vec2 uv;\nout vec2 vUv;\nvoid main() {\n vUv = uv;\n gl_Position = vec4(position, 0.0, 1.0);\n}\n`;\n\nconst fragmentShader = `#version 300 es\nprecision highp float;\nprecision highp int;\n\nout vec4 fragColor;\n\nuniform vec2 uResolution;\nuniform float uTime;\n\nuniform float uIntensity;\nuniform float uSpeed;\nuniform int uAnimType;\nuniform vec2 uMouse;\nuniform int uColorCount;\nuniform float uDistort;\nuniform vec2 uOffset;\nuniform sampler2D uGradient;\nuniform float uNoiseAmount;\nuniform int uRayCount;\n\nfloat hash21(vec2 p){\n p = floor(p);\n float f = 52.9829189 * fract(dot(p, vec2(0.065, 0.005)));\n return fract(f);\n}\n\nmat2 rot30(){ return mat2(0.8, -0.5, 0.5, 0.8); }\n\nfloat layeredNoise(vec2 fragPx){\n vec2 p = mod(fragPx + vec2(uTime * 30.0, -uTime * 21.0), 1024.0);\n vec2 q = rot30() * p;\n float n = 0.0;\n n += 0.40 * hash21(q);\n n += 0.25 * hash21(q * 2.0 + 17.0);\n n += 0.20 * hash21(q * 4.0 + 47.0);\n n += 0.10 * hash21(q * 8.0 + 113.0);\n n += 0.05 * hash21(q * 16.0 + 191.0);\n return n;\n}\n\nvec3 rayDir(vec2 frag, vec2 res, vec2 offset, float dist){\n float focal = res.y * max(dist, 1e-3);\n return normalize(vec3(2.0 * (frag - offset) - res, focal));\n}\n\nfloat edgeFade(vec2 frag, vec2 res, vec2 offset){\n vec2 toC = frag - 0.5 * res - offset;\n float r = length(toC) / (0.5 * min(res.x, res.y));\n float x = clamp(r, 0.0, 1.0);\n float q = x * x * x * (x * (x * 6.0 - 15.0) + 10.0);\n float s = q * 0.5;\n s = pow(s, 1.5);\n float tail = 1.0 - pow(1.0 - s, 2.0);\n s = mix(s, tail, 0.2);\n float dn = (layeredNoise(frag * 0.15) - 0.5) * 0.0015 * s;\n return clamp(s + dn, 0.0, 1.0);\n}\n\nmat3 rotX(float a){ float c = cos(a), s = sin(a); return mat3(1.0,0.0,0.0, 0.0,c,-s, 0.0,s,c); }\nmat3 rotY(float a){ float c = cos(a), s = sin(a); return mat3(c,0.0,s, 0.0,1.0,0.0, -s,0.0,c); }\nmat3 rotZ(float a){ float c = cos(a), s = sin(a); return mat3(c,-s,0.0, s,c,0.0, 0.0,0.0,1.0); }\n\nvec3 sampleGradient(float t){\n t = clamp(t, 0.0, 1.0);\n return texture(uGradient, vec2(t, 0.5)).rgb;\n}\n\nvec2 rot2(vec2 v, float a){\n float s = sin(a), c = cos(a);\n return mat2(c, -s, s, c) * v;\n}\n\nfloat bendAngle(vec3 q, float t){\n float a = 0.8 * sin(q.x * 0.55 + t * 0.6)\n + 0.7 * sin(q.y * 0.50 - t * 0.5)\n + 0.6 * sin(q.z * 0.60 + t * 0.7);\n return a;\n}\n\nvoid main(){\n vec2 frag = gl_FragCoord.xy;\n float t = uTime * uSpeed;\n float jitterAmp = 0.1 * clamp(uNoiseAmount, 0.0, 1.0);\n vec3 dir = rayDir(frag, uResolution, uOffset, 1.0);\n float marchT = 0.0;\n vec3 col = vec3(0.0);\n float n = layeredNoise(frag);\n vec4 c = cos(t * 0.2 + vec4(0.0, 33.0, 11.0, 0.0));\n mat2 M2 = mat2(c.x, c.y, c.z, c.w);\n float amp = clamp(uDistort, 0.0, 50.0) * 0.15;\n\n mat3 rot3dMat = mat3(1.0);\n if(uAnimType == 1){\n vec3 ang = vec3(t * 0.31, t * 0.21, t * 0.17);\n rot3dMat = rotZ(ang.z) * rotY(ang.y) * rotX(ang.x);\n }\n mat3 hoverMat = mat3(1.0);\n if(uAnimType == 2){\n vec2 m = uMouse * 2.0 - 1.0;\n vec3 ang = vec3(m.y * 0.6, m.x * 0.6, 0.0);\n hoverMat = rotY(ang.y) * rotX(ang.x);\n }\n\n for (int i = 0; i < 44; ++i) {\n vec3 P = marchT * dir;\n P.z -= 2.0;\n float rad = length(P);\n vec3 Pl = P * (10.0 / max(rad, 1e-6));\n\n if(uAnimType == 0){\n Pl.xz *= M2;\n } else if(uAnimType == 1){\n Pl = rot3dMat * Pl;\n } else {\n Pl = hoverMat * Pl;\n }\n\n float stepLen = min(rad - 0.3, n * jitterAmp) + 0.1;\n\n float grow = smoothstep(0.35, 3.0, marchT);\n float a1 = amp * grow * bendAngle(Pl * 0.6, t);\n float a2 = 0.5 * amp * grow * bendAngle(Pl.zyx * 0.5 + 3.1, t * 0.9);\n vec3 Pb = Pl;\n Pb.xz = rot2(Pb.xz, a1);\n Pb.xy = rot2(Pb.xy, a2);\n\n float rayPattern = smoothstep(\n 0.5, 0.7,\n sin(Pb.x + cos(Pb.y) * cos(Pb.z)) *\n sin(Pb.z + sin(Pb.y) * cos(Pb.x + t))\n );\n\n if (uRayCount > 0) {\n float ang = atan(Pb.y, Pb.x);\n float comb = 0.5 + 0.5 * cos(float(uRayCount) * ang);\n comb = pow(comb, 3.0);\n rayPattern *= smoothstep(0.15, 0.95, comb);\n }\n\n vec3 spectralDefault = 1.0 + vec3(\n cos(marchT * 3.0 + 0.0),\n cos(marchT * 3.0 + 1.0),\n cos(marchT * 3.0 + 2.0)\n );\n\n float saw = fract(marchT * 0.25);\n float tRay = saw * saw * (3.0 - 2.0 * saw);\n vec3 userGradient = 2.0 * sampleGradient(tRay);\n vec3 spectral = (uColorCount > 0) ? userGradient : spectralDefault;\n vec3 base = (0.05 / (0.4 + stepLen))\n * smoothstep(5.0, 0.0, rad)\n * spectral;\n\n col += base * rayPattern;\n marchT += stepLen;\n }\n\n col *= edgeFade(frag, uResolution, uOffset);\n col *= uIntensity;\n\n fragColor = vec4(clamp(col, 0.0, 1.0), 1.0);\n}`;\n\nconst hexToRgb01 = (hex: string): [number, number, number] => {\n let h = hex.trim();\n if (h.startsWith('#')) h = h.slice(1);\n if (h.length === 3) {\n const r = h[0],\n g = h[1],\n b = h[2];\n h = r + r + g + g + b + b;\n }\n const intVal = parseInt(h.slice(0, 6), 16);\n if (isNaN(intVal) || (h.length !== 6 && h.length !== 8)) return [1, 1, 1];\n const r = ((intVal >> 16) & 255) / 255;\n const g = ((intVal >> 8) & 255) / 255;\n const b = (intVal & 255) / 255;\n return [r, g, b];\n};\n\nconst toPx = (v: number | string | undefined): number => {\n if (v == null) return 0;\n if (typeof v === 'number') return v;\n const s = String(v).trim();\n const num = parseFloat(s.replace('px', ''));\n return isNaN(num) ? 0 : num;\n};\n\nconst PrismaticBurst = ({\n intensity = 2,\n speed = 0.5,\n animationType = 'rotate3d',\n colors,\n distort = 0,\n paused = false,\n offset = { x: 0, y: 0 },\n hoverDampness = 0,\n rayCount,\n mixBlendMode = 'lighten'\n}: PrismaticBurstProps) => {\n const containerRef = useRef(null);\n const programRef = useRef(null);\n const rendererRef = useRef(null);\n const mouseTargetRef = useRef<[number, number]>([0.5, 0.5]);\n const mouseSmoothRef = useRef<[number, number]>([0.5, 0.5]);\n const pausedRef = useRef(paused);\n const gradTexRef = useRef(null);\n const hoverDampRef = useRef(hoverDampness);\n const isVisibleRef = useRef(true);\n const meshRef = useRef(null);\n const triRef = useRef(null);\n\n useEffect(() => {\n pausedRef.current = paused;\n }, [paused]);\n useEffect(() => {\n hoverDampRef.current = hoverDampness;\n }, [hoverDampness]);\n\n useEffect(() => {\n const container = containerRef.current;\n if (!container) return;\n\n const dpr = Math.min(window.devicePixelRatio || 1, 2);\n const renderer = new Renderer({ dpr, alpha: false, antialias: false });\n rendererRef.current = renderer;\n\n const gl = renderer.gl;\n gl.canvas.style.position = 'absolute';\n gl.canvas.style.inset = '0';\n gl.canvas.style.width = '100%';\n gl.canvas.style.height = '100%';\n gl.canvas.style.mixBlendMode = mixBlendMode && mixBlendMode !== 'none' ? mixBlendMode : '';\n container.appendChild(gl.canvas);\n\n const white = new Uint8Array([255, 255, 255, 255]);\n const gradientTex = new Texture(gl, {\n image: white,\n width: 1,\n height: 1,\n generateMipmaps: false,\n flipY: false\n });\n\n gradientTex.minFilter = gl.LINEAR;\n gradientTex.magFilter = gl.LINEAR;\n gradientTex.wrapS = gl.CLAMP_TO_EDGE;\n gradientTex.wrapT = gl.CLAMP_TO_EDGE;\n gradTexRef.current = gradientTex;\n\n const program = new Program(gl, {\n vertex: vertexShader,\n fragment: fragmentShader,\n uniforms: {\n uResolution: { value: [1, 1] as [number, number] },\n uTime: { value: 0 },\n\n uIntensity: { value: 1 },\n uSpeed: { value: 1 },\n uAnimType: { value: 0 },\n uMouse: { value: [0.5, 0.5] as [number, number] },\n uColorCount: { value: 0 },\n uDistort: { value: 0 },\n uOffset: { value: [0, 0] as [number, number] },\n uGradient: { value: gradientTex },\n uNoiseAmount: { value: 0.8 },\n uRayCount: { value: 0 }\n }\n });\n\n programRef.current = program;\n\n const triangle = new Triangle(gl);\n const mesh = new Mesh(gl, { geometry: triangle, program });\n triRef.current = triangle;\n meshRef.current = mesh;\n\n const resize = () => {\n const w = container.clientWidth || 1;\n const h = container.clientHeight || 1;\n renderer.setSize(w, h);\n program.uniforms.uResolution.value = [gl.drawingBufferWidth, gl.drawingBufferHeight];\n };\n\n let ro: ResizeObserver | null = null;\n if ('ResizeObserver' in window) {\n ro = new ResizeObserver(resize);\n ro.observe(container);\n } else {\n (window as Window).addEventListener('resize', resize);\n }\n resize();\n\n const onPointer = (e: PointerEvent) => {\n const rect = container.getBoundingClientRect();\n const x = (e.clientX - rect.left) / Math.max(rect.width, 1);\n const y = (e.clientY - rect.top) / Math.max(rect.height, 1);\n mouseTargetRef.current = [Math.min(Math.max(x, 0), 1), Math.min(Math.max(y, 0), 1)];\n };\n container.addEventListener('pointermove', onPointer, { passive: true });\n\n let io: IntersectionObserver | null = null;\n if ('IntersectionObserver' in window) {\n io = new IntersectionObserver(\n entries => {\n if (entries[0]) isVisibleRef.current = entries[0].isIntersecting;\n },\n { root: null, threshold: 0.01 }\n );\n io.observe(container);\n }\n const onVis = () => {};\n document.addEventListener('visibilitychange', onVis);\n\n let raf = 0;\n let last = performance.now();\n let accumTime = 0;\n\n const update = (now: number) => {\n const dt = Math.max(0, now - last) * 0.001;\n last = now;\n const visible = isVisibleRef.current && !document.hidden;\n if (!pausedRef.current) accumTime += dt;\n if (!visible) {\n raf = requestAnimationFrame(update);\n return;\n }\n const tau = 0.02 + Math.max(0, Math.min(1, hoverDampRef.current)) * 0.5;\n const alpha = 1 - Math.exp(-dt / tau);\n const tgt = mouseTargetRef.current;\n const sm = mouseSmoothRef.current;\n sm[0] += (tgt[0] - sm[0]) * alpha;\n sm[1] += (tgt[1] - sm[1]) * alpha;\n program.uniforms.uMouse.value = sm as any;\n program.uniforms.uTime.value = accumTime;\n renderer.render({ scene: meshRef.current! });\n raf = requestAnimationFrame(update);\n };\n raf = requestAnimationFrame(update);\n\n return () => {\n cancelAnimationFrame(raf);\n container.removeEventListener('pointermove', onPointer);\n ro?.disconnect();\n if (!ro) window.removeEventListener('resize', resize);\n io?.disconnect();\n document.removeEventListener('visibilitychange', onVis);\n try {\n container.removeChild(gl.canvas);\n } catch (e) {\n void e;\n }\n meshRef.current = null;\n triRef.current = null;\n programRef.current = null;\n try {\n const glCtx = rendererRef.current?.gl;\n if (glCtx && gradTexRef.current?.texture) glCtx.deleteTexture(gradTexRef.current.texture);\n } catch (e) {\n void e;\n }\n programRef.current = null;\n rendererRef.current = null;\n gradTexRef.current = null;\n meshRef.current = null;\n triRef.current = null;\n };\n }, []);\n\n useEffect(() => {\n const canvas = rendererRef.current?.gl?.canvas as HTMLCanvasElement | undefined;\n if (canvas) {\n canvas.style.mixBlendMode = mixBlendMode && mixBlendMode !== 'none' ? mixBlendMode : '';\n }\n }, [mixBlendMode]);\n\n useEffect(() => {\n const program = programRef.current;\n const renderer = rendererRef.current;\n const gradTex = gradTexRef.current;\n if (!program || !renderer || !gradTex) return;\n\n program.uniforms.uIntensity.value = intensity ?? 1;\n program.uniforms.uSpeed.value = speed ?? 1;\n\n const animTypeMap: Record = {\n rotate: 0,\n rotate3d: 1,\n hover: 2\n };\n program.uniforms.uAnimType.value = animTypeMap[animationType ?? 'rotate'];\n\n program.uniforms.uDistort.value = typeof distort === 'number' ? distort : 0;\n\n const ox = toPx(offset?.x);\n const oy = toPx(offset?.y);\n program.uniforms.uOffset.value = [ox, oy];\n program.uniforms.uRayCount.value = Math.max(0, Math.floor(rayCount ?? 0));\n\n let count = 0;\n if (Array.isArray(colors) && colors.length > 0) {\n const gl = renderer.gl;\n const capped = colors.slice(0, 64);\n count = capped.length;\n const data = new Uint8Array(count * 4);\n for (let i = 0; i < count; i++) {\n const [r, g, b] = hexToRgb01(capped[i]);\n data[i * 4 + 0] = Math.round(r * 255);\n data[i * 4 + 1] = Math.round(g * 255);\n data[i * 4 + 2] = Math.round(b * 255);\n data[i * 4 + 3] = 255;\n }\n gradTex.image = data;\n gradTex.width = count;\n gradTex.height = 1;\n gradTex.minFilter = gl.LINEAR;\n gradTex.magFilter = gl.LINEAR;\n gradTex.wrapS = gl.CLAMP_TO_EDGE;\n gradTex.wrapT = gl.CLAMP_TO_EDGE;\n gradTex.flipY = false;\n gradTex.generateMipmaps = false;\n gradTex.format = gl.RGBA;\n gradTex.type = gl.UNSIGNED_BYTE;\n gradTex.needsUpdate = true;\n } else {\n count = 0;\n }\n program.uniforms.uColorCount.value = count;\n }, [intensity, speed, animationType, colors, distort, offset, rayCount]);\n\n return
;\n};\n\nexport default PrismaticBurst;\n" } ], "registryDependencies": [], diff --git a/public/r/PrismaticBurst-TS-TW.json b/public/r/PrismaticBurst-TS-TW.json index 93ff5e81d..c2ad3f8cf 100644 --- a/public/r/PrismaticBurst-TS-TW.json +++ b/public/r/PrismaticBurst-TS-TW.json @@ -8,7 +8,7 @@ { "type": "registry:component", "path": "PrismaticBurst/PrismaticBurst.tsx", - "content": "import React, { useEffect, useRef } from 'react';\nimport { Renderer, Program, Mesh, Triangle, Texture } from 'ogl';\n\ntype Offset = { x?: number | string; y?: number | string };\ntype AnimationType = 'rotate' | 'rotate3d' | 'hover';\n\nexport type PrismaticBurstProps = {\n intensity?: number;\n speed?: number;\n animationType?: AnimationType;\n colors?: string[];\n distort?: number;\n paused?: boolean;\n offset?: Offset;\n hoverDampness?: number;\n rayCount?: number;\n mixBlendMode?: React.CSSProperties['mixBlendMode'] | 'none';\n};\n\nconst vertexShader = `#version 300 es\nin vec2 position;\nin vec2 uv;\nout vec2 vUv;\nvoid main() {\n vUv = uv;\n gl_Position = vec4(position, 0.0, 1.0);\n}\n`;\n\nconst fragmentShader = `#version 300 es\nprecision highp float;\nprecision highp int;\n\nout vec4 fragColor;\n\nuniform vec2 uResolution;\nuniform float uTime;\n\nuniform float uIntensity;\nuniform float uSpeed;\nuniform int uAnimType;\nuniform vec2 uMouse;\nuniform int uColorCount;\nuniform float uDistort;\nuniform vec2 uOffset;\nuniform sampler2D uGradient;\nuniform float uNoiseAmount;\nuniform int uRayCount;\n\nfloat hash21(vec2 p){\n p = floor(p);\n float f = 52.9829189 * fract(dot(p, vec2(0.065, 0.005)));\n return fract(f);\n}\n\nmat2 rot30(){ return mat2(0.8, -0.5, 0.5, 0.8); }\n\nfloat layeredNoise(vec2 fragPx){\n vec2 p = mod(fragPx + vec2(uTime * 30.0, -uTime * 21.0), 1024.0);\n vec2 q = rot30() * p;\n float n = 0.0;\n n += 0.40 * hash21(q);\n n += 0.25 * hash21(q * 2.0 + 17.0);\n n += 0.20 * hash21(q * 4.0 + 47.0);\n n += 0.10 * hash21(q * 8.0 + 113.0);\n n += 0.05 * hash21(q * 16.0 + 191.0);\n return n;\n}\n\nvec3 rayDir(vec2 frag, vec2 res, vec2 offset, float dist){\n float focal = res.y * max(dist, 1e-3);\n return normalize(vec3(2.0 * (frag - offset) - res, focal));\n}\n\nfloat edgeFade(vec2 frag, vec2 res, vec2 offset){\n vec2 toC = frag - 0.5 * res - offset;\n float r = length(toC) / (0.5 * min(res.x, res.y));\n float x = clamp(r, 0.0, 1.0);\n float q = x * x * x * (x * (x * 6.0 - 15.0) + 10.0);\n float s = q * 0.5;\n s = pow(s, 1.5);\n float tail = 1.0 - pow(1.0 - s, 2.0);\n s = mix(s, tail, 0.2);\n float dn = (layeredNoise(frag * 0.15) - 0.5) * 0.0015 * s;\n return clamp(s + dn, 0.0, 1.0);\n}\n\nmat3 rotX(float a){ float c = cos(a), s = sin(a); return mat3(1.0,0.0,0.0, 0.0,c,-s, 0.0,s,c); }\nmat3 rotY(float a){ float c = cos(a), s = sin(a); return mat3(c,0.0,s, 0.0,1.0,0.0, -s,0.0,c); }\nmat3 rotZ(float a){ float c = cos(a), s = sin(a); return mat3(c,-s,0.0, s,c,0.0, 0.0,0.0,1.0); }\n\nvec3 sampleGradient(float t){\n t = clamp(t, 0.0, 1.0);\n return texture(uGradient, vec2(t, 0.5)).rgb;\n}\n\nvec2 rot2(vec2 v, float a){\n float s = sin(a), c = cos(a);\n return mat2(c, -s, s, c) * v;\n}\n\nfloat bendAngle(vec3 q, float t){\n float a = 0.8 * sin(q.x * 0.55 + t * 0.6)\n + 0.7 * sin(q.y * 0.50 - t * 0.5)\n + 0.6 * sin(q.z * 0.60 + t * 0.7);\n return a;\n}\n\nvoid main(){\n vec2 frag = gl_FragCoord.xy;\n float t = uTime * uSpeed;\n float jitterAmp = 0.1 * clamp(uNoiseAmount, 0.0, 1.0);\n vec3 dir = rayDir(frag, uResolution, uOffset, 1.0);\n float marchT = 0.0;\n vec3 col = vec3(0.0);\n float n = layeredNoise(frag);\n vec4 c = cos(t * 0.2 + vec4(0.0, 33.0, 11.0, 0.0));\n mat2 M2 = mat2(c.x, c.y, c.z, c.w);\n float amp = clamp(uDistort, 0.0, 50.0) * 0.15;\n\n mat3 rot3dMat = mat3(1.0);\n if(uAnimType == 1){\n vec3 ang = vec3(t * 0.31, t * 0.21, t * 0.17);\n rot3dMat = rotZ(ang.z) * rotY(ang.y) * rotX(ang.x);\n }\n mat3 hoverMat = mat3(1.0);\n if(uAnimType == 2){\n vec2 m = uMouse * 2.0 - 1.0;\n vec3 ang = vec3(m.y * 0.6, m.x * 0.6, 0.0);\n hoverMat = rotY(ang.y) * rotX(ang.x);\n }\n\n for (int i = 0; i < 44; ++i) {\n vec3 P = marchT * dir;\n P.z -= 2.0;\n float rad = length(P);\n vec3 Pl = P * (10.0 / max(rad, 1e-6));\n\n if(uAnimType == 0){\n Pl.xz *= M2;\n } else if(uAnimType == 1){\n Pl = rot3dMat * Pl;\n } else {\n Pl = hoverMat * Pl;\n }\n\n float stepLen = min(rad - 0.3, n * jitterAmp) + 0.1;\n\n float grow = smoothstep(0.35, 3.0, marchT);\n float a1 = amp * grow * bendAngle(Pl * 0.6, t);\n float a2 = 0.5 * amp * grow * bendAngle(Pl.zyx * 0.5 + 3.1, t * 0.9);\n vec3 Pb = Pl;\n Pb.xz = rot2(Pb.xz, a1);\n Pb.xy = rot2(Pb.xy, a2);\n\n float rayPattern = smoothstep(\n 0.5, 0.7,\n sin(Pb.x + cos(Pb.y) * cos(Pb.z)) *\n sin(Pb.z + sin(Pb.y) * cos(Pb.x + t))\n );\n\n if (uRayCount > 0) {\n float ang = atan(Pb.y, Pb.x);\n float comb = 0.5 + 0.5 * cos(float(uRayCount) * ang);\n comb = pow(comb, 3.0);\n rayPattern *= smoothstep(0.15, 0.95, comb);\n }\n\n vec3 spectralDefault = 1.0 + vec3(\n cos(marchT * 3.0 + 0.0),\n cos(marchT * 3.0 + 1.0),\n cos(marchT * 3.0 + 2.0)\n );\n\n float saw = fract(marchT * 0.25);\n float tRay = saw * saw * (3.0 - 2.0 * saw);\n vec3 userGradient = 2.0 * sampleGradient(tRay);\n vec3 spectral = (uColorCount > 0) ? userGradient : spectralDefault;\n vec3 base = (0.05 / (0.4 + stepLen))\n * smoothstep(5.0, 0.0, rad)\n * spectral;\n\n col += base * rayPattern;\n marchT += stepLen;\n }\n\n col *= edgeFade(frag, uResolution, uOffset);\n col *= uIntensity;\n\n fragColor = vec4(clamp(col, 0.0, 1.0), 1.0);\n}`;\n\nconst hexToRgb01 = (hex: string): [number, number, number] => {\n let h = hex.trim();\n if (h.startsWith('#')) h = h.slice(1);\n if (h.length === 3) {\n const r = h[0],\n g = h[1],\n b = h[2];\n h = r + r + g + g + b + b;\n }\n const intVal = parseInt(h, 16);\n if (isNaN(intVal) || (h.length !== 6 && h.length !== 8)) return [1, 1, 1];\n const r = ((intVal >> 16) & 255) / 255;\n const g = ((intVal >> 8) & 255) / 255;\n const b = (intVal & 255) / 255;\n return [r, g, b];\n};\n\nconst toPx = (v: number | string | undefined): number => {\n if (v == null) return 0;\n if (typeof v === 'number') return v;\n const s = String(v).trim();\n const num = parseFloat(s.replace('px', ''));\n return isNaN(num) ? 0 : num;\n};\n\nconst PrismaticBurst = ({\n intensity = 2,\n speed = 0.5,\n animationType = 'rotate3d',\n colors,\n distort = 0,\n paused = false,\n offset = { x: 0, y: 0 },\n hoverDampness = 0,\n rayCount,\n mixBlendMode = 'lighten'\n}: PrismaticBurstProps) => {\n const containerRef = useRef(null);\n const programRef = useRef(null);\n const rendererRef = useRef(null);\n const mouseTargetRef = useRef<[number, number]>([0.5, 0.5]);\n const mouseSmoothRef = useRef<[number, number]>([0.5, 0.5]);\n const pausedRef = useRef(paused);\n const gradTexRef = useRef(null);\n const hoverDampRef = useRef(hoverDampness);\n const isVisibleRef = useRef(true);\n const meshRef = useRef(null);\n const triRef = useRef(null);\n\n useEffect(() => {\n pausedRef.current = paused;\n }, [paused]);\n useEffect(() => {\n hoverDampRef.current = hoverDampness;\n }, [hoverDampness]);\n\n useEffect(() => {\n const container = containerRef.current;\n if (!container) return;\n\n const dpr = Math.min(window.devicePixelRatio || 1, 2);\n const renderer = new Renderer({ dpr, alpha: false, antialias: false });\n rendererRef.current = renderer;\n\n const gl = renderer.gl;\n gl.canvas.style.position = 'absolute';\n gl.canvas.style.inset = '0';\n gl.canvas.style.width = '100%';\n gl.canvas.style.height = '100%';\n gl.canvas.style.mixBlendMode = mixBlendMode && mixBlendMode !== 'none' ? mixBlendMode : '';\n container.appendChild(gl.canvas);\n\n const white = new Uint8Array([255, 255, 255, 255]);\n const gradientTex = new Texture(gl, {\n image: white,\n width: 1,\n height: 1,\n generateMipmaps: false,\n flipY: false\n });\n\n gradientTex.minFilter = gl.LINEAR;\n gradientTex.magFilter = gl.LINEAR;\n gradientTex.wrapS = gl.CLAMP_TO_EDGE;\n gradientTex.wrapT = gl.CLAMP_TO_EDGE;\n gradTexRef.current = gradientTex;\n\n const program = new Program(gl, {\n vertex: vertexShader,\n fragment: fragmentShader,\n uniforms: {\n uResolution: { value: [1, 1] as [number, number] },\n uTime: { value: 0 },\n\n uIntensity: { value: 1 },\n uSpeed: { value: 1 },\n uAnimType: { value: 0 },\n uMouse: { value: [0.5, 0.5] as [number, number] },\n uColorCount: { value: 0 },\n uDistort: { value: 0 },\n uOffset: { value: [0, 0] as [number, number] },\n uGradient: { value: gradientTex },\n uNoiseAmount: { value: 0.8 },\n uRayCount: { value: 0 }\n }\n });\n\n programRef.current = program;\n\n const triangle = new Triangle(gl);\n const mesh = new Mesh(gl, { geometry: triangle, program });\n triRef.current = triangle;\n meshRef.current = mesh;\n\n const resize = () => {\n const w = container.clientWidth || 1;\n const h = container.clientHeight || 1;\n renderer.setSize(w, h);\n program.uniforms.uResolution.value = [gl.drawingBufferWidth, gl.drawingBufferHeight];\n };\n\n let ro: ResizeObserver | null = null;\n if ('ResizeObserver' in window) {\n ro = new ResizeObserver(resize);\n ro.observe(container);\n } else {\n (window as Window).addEventListener('resize', resize);\n }\n resize();\n\n const onPointer = (e: PointerEvent) => {\n const rect = container.getBoundingClientRect();\n const x = (e.clientX - rect.left) / Math.max(rect.width, 1);\n const y = (e.clientY - rect.top) / Math.max(rect.height, 1);\n mouseTargetRef.current = [Math.min(Math.max(x, 0), 1), Math.min(Math.max(y, 0), 1)];\n };\n container.addEventListener('pointermove', onPointer, { passive: true });\n\n let io: IntersectionObserver | null = null;\n if ('IntersectionObserver' in window) {\n io = new IntersectionObserver(\n entries => {\n if (entries[0]) isVisibleRef.current = entries[0].isIntersecting;\n },\n { root: null, threshold: 0.01 }\n );\n io.observe(container);\n }\n const onVis = () => {};\n document.addEventListener('visibilitychange', onVis);\n\n let raf = 0;\n let last = performance.now();\n let accumTime = 0;\n\n const update = (now: number) => {\n const dt = Math.max(0, now - last) * 0.001;\n last = now;\n const visible = isVisibleRef.current && !document.hidden;\n if (!pausedRef.current) accumTime += dt;\n if (!visible) {\n raf = requestAnimationFrame(update);\n return;\n }\n const tau = 0.02 + Math.max(0, Math.min(1, hoverDampRef.current)) * 0.5;\n const alpha = 1 - Math.exp(-dt / tau);\n const tgt = mouseTargetRef.current;\n const sm = mouseSmoothRef.current;\n sm[0] += (tgt[0] - sm[0]) * alpha;\n sm[1] += (tgt[1] - sm[1]) * alpha;\n program.uniforms.uMouse.value = sm as any;\n program.uniforms.uTime.value = accumTime;\n renderer.render({ scene: meshRef.current! });\n raf = requestAnimationFrame(update);\n };\n raf = requestAnimationFrame(update);\n\n return () => {\n cancelAnimationFrame(raf);\n container.removeEventListener('pointermove', onPointer);\n ro?.disconnect();\n if (!ro) window.removeEventListener('resize', resize);\n io?.disconnect();\n document.removeEventListener('visibilitychange', onVis);\n try {\n container.removeChild(gl.canvas);\n } catch (e) {\n void e;\n }\n meshRef.current = null;\n triRef.current = null;\n programRef.current = null;\n try {\n const glCtx = rendererRef.current?.gl;\n if (glCtx && gradTexRef.current?.texture) glCtx.deleteTexture(gradTexRef.current.texture);\n } catch (e) {\n void e;\n }\n rendererRef.current = null;\n gradTexRef.current = null;\n };\n }, []);\n\n useEffect(() => {\n const canvas = rendererRef.current?.gl?.canvas as HTMLCanvasElement | undefined;\n if (canvas) {\n canvas.style.mixBlendMode = mixBlendMode && mixBlendMode !== 'none' ? mixBlendMode : '';\n }\n }, [mixBlendMode]);\n\n useEffect(() => {\n const program = programRef.current;\n const renderer = rendererRef.current;\n const gradTex = gradTexRef.current;\n if (!program || !renderer || !gradTex) return;\n\n program.uniforms.uIntensity.value = intensity ?? 1;\n program.uniforms.uSpeed.value = speed ?? 1;\n\n const animTypeMap: Record = {\n rotate: 0,\n rotate3d: 1,\n hover: 2\n };\n program.uniforms.uAnimType.value = animTypeMap[animationType ?? 'rotate'];\n\n program.uniforms.uDistort.value = typeof distort === 'number' ? distort : 0;\n\n const ox = toPx(offset?.x);\n const oy = toPx(offset?.y);\n program.uniforms.uOffset.value = [ox, oy];\n program.uniforms.uRayCount.value = Math.max(0, Math.floor(rayCount ?? 0));\n\n let count = 0;\n if (Array.isArray(colors) && colors.length > 0) {\n const gl = renderer.gl;\n const capped = colors.slice(0, 64);\n count = capped.length;\n const data = new Uint8Array(count * 4);\n for (let i = 0; i < count; i++) {\n const [r, g, b] = hexToRgb01(capped[i]);\n data[i * 4 + 0] = Math.round(r * 255);\n data[i * 4 + 1] = Math.round(g * 255);\n data[i * 4 + 2] = Math.round(b * 255);\n data[i * 4 + 3] = 255;\n }\n gradTex.image = data;\n gradTex.width = count;\n gradTex.height = 1;\n gradTex.minFilter = gl.LINEAR;\n gradTex.magFilter = gl.LINEAR;\n gradTex.wrapS = gl.CLAMP_TO_EDGE;\n gradTex.wrapT = gl.CLAMP_TO_EDGE;\n gradTex.flipY = false;\n gradTex.generateMipmaps = false;\n gradTex.format = gl.RGBA;\n gradTex.type = gl.UNSIGNED_BYTE;\n gradTex.needsUpdate = true;\n } else {\n count = 0;\n }\n program.uniforms.uColorCount.value = count;\n }, [intensity, speed, animationType, colors, distort, offset, rayCount]);\n\n return
;\n};\n\nexport default PrismaticBurst;\n" + "content": "import React, { useEffect, useRef } from 'react';\nimport { Renderer, Program, Mesh, Triangle, Texture } from 'ogl';\n\ntype Offset = { x?: number | string; y?: number | string };\ntype AnimationType = 'rotate' | 'rotate3d' | 'hover';\n\nexport type PrismaticBurstProps = {\n intensity?: number;\n speed?: number;\n animationType?: AnimationType;\n colors?: string[];\n distort?: number;\n paused?: boolean;\n offset?: Offset;\n hoverDampness?: number;\n rayCount?: number;\n mixBlendMode?: React.CSSProperties['mixBlendMode'] | 'none';\n};\n\nconst vertexShader = `#version 300 es\nin vec2 position;\nin vec2 uv;\nout vec2 vUv;\nvoid main() {\n vUv = uv;\n gl_Position = vec4(position, 0.0, 1.0);\n}\n`;\n\nconst fragmentShader = `#version 300 es\nprecision highp float;\nprecision highp int;\n\nout vec4 fragColor;\n\nuniform vec2 uResolution;\nuniform float uTime;\n\nuniform float uIntensity;\nuniform float uSpeed;\nuniform int uAnimType;\nuniform vec2 uMouse;\nuniform int uColorCount;\nuniform float uDistort;\nuniform vec2 uOffset;\nuniform sampler2D uGradient;\nuniform float uNoiseAmount;\nuniform int uRayCount;\n\nfloat hash21(vec2 p){\n p = floor(p);\n float f = 52.9829189 * fract(dot(p, vec2(0.065, 0.005)));\n return fract(f);\n}\n\nmat2 rot30(){ return mat2(0.8, -0.5, 0.5, 0.8); }\n\nfloat layeredNoise(vec2 fragPx){\n vec2 p = mod(fragPx + vec2(uTime * 30.0, -uTime * 21.0), 1024.0);\n vec2 q = rot30() * p;\n float n = 0.0;\n n += 0.40 * hash21(q);\n n += 0.25 * hash21(q * 2.0 + 17.0);\n n += 0.20 * hash21(q * 4.0 + 47.0);\n n += 0.10 * hash21(q * 8.0 + 113.0);\n n += 0.05 * hash21(q * 16.0 + 191.0);\n return n;\n}\n\nvec3 rayDir(vec2 frag, vec2 res, vec2 offset, float dist){\n float focal = res.y * max(dist, 1e-3);\n return normalize(vec3(2.0 * (frag - offset) - res, focal));\n}\n\nfloat edgeFade(vec2 frag, vec2 res, vec2 offset){\n vec2 toC = frag - 0.5 * res - offset;\n float r = length(toC) / (0.5 * min(res.x, res.y));\n float x = clamp(r, 0.0, 1.0);\n float q = x * x * x * (x * (x * 6.0 - 15.0) + 10.0);\n float s = q * 0.5;\n s = pow(s, 1.5);\n float tail = 1.0 - pow(1.0 - s, 2.0);\n s = mix(s, tail, 0.2);\n float dn = (layeredNoise(frag * 0.15) - 0.5) * 0.0015 * s;\n return clamp(s + dn, 0.0, 1.0);\n}\n\nmat3 rotX(float a){ float c = cos(a), s = sin(a); return mat3(1.0,0.0,0.0, 0.0,c,-s, 0.0,s,c); }\nmat3 rotY(float a){ float c = cos(a), s = sin(a); return mat3(c,0.0,s, 0.0,1.0,0.0, -s,0.0,c); }\nmat3 rotZ(float a){ float c = cos(a), s = sin(a); return mat3(c,-s,0.0, s,c,0.0, 0.0,0.0,1.0); }\n\nvec3 sampleGradient(float t){\n t = clamp(t, 0.0, 1.0);\n return texture(uGradient, vec2(t, 0.5)).rgb;\n}\n\nvec2 rot2(vec2 v, float a){\n float s = sin(a), c = cos(a);\n return mat2(c, -s, s, c) * v;\n}\n\nfloat bendAngle(vec3 q, float t){\n float a = 0.8 * sin(q.x * 0.55 + t * 0.6)\n + 0.7 * sin(q.y * 0.50 - t * 0.5)\n + 0.6 * sin(q.z * 0.60 + t * 0.7);\n return a;\n}\n\nvoid main(){\n vec2 frag = gl_FragCoord.xy;\n float t = uTime * uSpeed;\n float jitterAmp = 0.1 * clamp(uNoiseAmount, 0.0, 1.0);\n vec3 dir = rayDir(frag, uResolution, uOffset, 1.0);\n float marchT = 0.0;\n vec3 col = vec3(0.0);\n float n = layeredNoise(frag);\n vec4 c = cos(t * 0.2 + vec4(0.0, 33.0, 11.0, 0.0));\n mat2 M2 = mat2(c.x, c.y, c.z, c.w);\n float amp = clamp(uDistort, 0.0, 50.0) * 0.15;\n\n mat3 rot3dMat = mat3(1.0);\n if(uAnimType == 1){\n vec3 ang = vec3(t * 0.31, t * 0.21, t * 0.17);\n rot3dMat = rotZ(ang.z) * rotY(ang.y) * rotX(ang.x);\n }\n mat3 hoverMat = mat3(1.0);\n if(uAnimType == 2){\n vec2 m = uMouse * 2.0 - 1.0;\n vec3 ang = vec3(m.y * 0.6, m.x * 0.6, 0.0);\n hoverMat = rotY(ang.y) * rotX(ang.x);\n }\n\n for (int i = 0; i < 44; ++i) {\n vec3 P = marchT * dir;\n P.z -= 2.0;\n float rad = length(P);\n vec3 Pl = P * (10.0 / max(rad, 1e-6));\n\n if(uAnimType == 0){\n Pl.xz *= M2;\n } else if(uAnimType == 1){\n Pl = rot3dMat * Pl;\n } else {\n Pl = hoverMat * Pl;\n }\n\n float stepLen = min(rad - 0.3, n * jitterAmp) + 0.1;\n\n float grow = smoothstep(0.35, 3.0, marchT);\n float a1 = amp * grow * bendAngle(Pl * 0.6, t);\n float a2 = 0.5 * amp * grow * bendAngle(Pl.zyx * 0.5 + 3.1, t * 0.9);\n vec3 Pb = Pl;\n Pb.xz = rot2(Pb.xz, a1);\n Pb.xy = rot2(Pb.xy, a2);\n\n float rayPattern = smoothstep(\n 0.5, 0.7,\n sin(Pb.x + cos(Pb.y) * cos(Pb.z)) *\n sin(Pb.z + sin(Pb.y) * cos(Pb.x + t))\n );\n\n if (uRayCount > 0) {\n float ang = atan(Pb.y, Pb.x);\n float comb = 0.5 + 0.5 * cos(float(uRayCount) * ang);\n comb = pow(comb, 3.0);\n rayPattern *= smoothstep(0.15, 0.95, comb);\n }\n\n vec3 spectralDefault = 1.0 + vec3(\n cos(marchT * 3.0 + 0.0),\n cos(marchT * 3.0 + 1.0),\n cos(marchT * 3.0 + 2.0)\n );\n\n float saw = fract(marchT * 0.25);\n float tRay = saw * saw * (3.0 - 2.0 * saw);\n vec3 userGradient = 2.0 * sampleGradient(tRay);\n vec3 spectral = (uColorCount > 0) ? userGradient : spectralDefault;\n vec3 base = (0.05 / (0.4 + stepLen))\n * smoothstep(5.0, 0.0, rad)\n * spectral;\n\n col += base * rayPattern;\n marchT += stepLen;\n }\n\n col *= edgeFade(frag, uResolution, uOffset);\n col *= uIntensity;\n\n fragColor = vec4(clamp(col, 0.0, 1.0), 1.0);\n}`;\n\nconst hexToRgb01 = (hex: string): [number, number, number] => {\n let h = hex.trim();\n if (h.startsWith('#')) h = h.slice(1);\n if (h.length === 3) {\n const r = h[0],\n g = h[1],\n b = h[2];\n h = r + r + g + g + b + b;\n }\n const intVal = parseInt(h.slice(0, 6), 16);\n if (isNaN(intVal) || (h.length !== 6 && h.length !== 8)) return [1, 1, 1];\n const r = ((intVal >> 16) & 255) / 255;\n const g = ((intVal >> 8) & 255) / 255;\n const b = (intVal & 255) / 255;\n return [r, g, b];\n};\n\nconst toPx = (v: number | string | undefined): number => {\n if (v == null) return 0;\n if (typeof v === 'number') return v;\n const s = String(v).trim();\n const num = parseFloat(s.replace('px', ''));\n return isNaN(num) ? 0 : num;\n};\n\nconst PrismaticBurst = ({\n intensity = 2,\n speed = 0.5,\n animationType = 'rotate3d',\n colors,\n distort = 0,\n paused = false,\n offset = { x: 0, y: 0 },\n hoverDampness = 0,\n rayCount,\n mixBlendMode = 'lighten'\n}: PrismaticBurstProps) => {\n const containerRef = useRef(null);\n const programRef = useRef(null);\n const rendererRef = useRef(null);\n const mouseTargetRef = useRef<[number, number]>([0.5, 0.5]);\n const mouseSmoothRef = useRef<[number, number]>([0.5, 0.5]);\n const pausedRef = useRef(paused);\n const gradTexRef = useRef(null);\n const hoverDampRef = useRef(hoverDampness);\n const isVisibleRef = useRef(true);\n const meshRef = useRef(null);\n const triRef = useRef(null);\n\n useEffect(() => {\n pausedRef.current = paused;\n }, [paused]);\n useEffect(() => {\n hoverDampRef.current = hoverDampness;\n }, [hoverDampness]);\n\n useEffect(() => {\n const container = containerRef.current;\n if (!container) return;\n\n const dpr = Math.min(window.devicePixelRatio || 1, 2);\n const renderer = new Renderer({ dpr, alpha: false, antialias: false });\n rendererRef.current = renderer;\n\n const gl = renderer.gl;\n gl.canvas.style.position = 'absolute';\n gl.canvas.style.inset = '0';\n gl.canvas.style.width = '100%';\n gl.canvas.style.height = '100%';\n gl.canvas.style.mixBlendMode = mixBlendMode && mixBlendMode !== 'none' ? mixBlendMode : '';\n container.appendChild(gl.canvas);\n\n const white = new Uint8Array([255, 255, 255, 255]);\n const gradientTex = new Texture(gl, {\n image: white,\n width: 1,\n height: 1,\n generateMipmaps: false,\n flipY: false\n });\n\n gradientTex.minFilter = gl.LINEAR;\n gradientTex.magFilter = gl.LINEAR;\n gradientTex.wrapS = gl.CLAMP_TO_EDGE;\n gradientTex.wrapT = gl.CLAMP_TO_EDGE;\n gradTexRef.current = gradientTex;\n\n const program = new Program(gl, {\n vertex: vertexShader,\n fragment: fragmentShader,\n uniforms: {\n uResolution: { value: [1, 1] as [number, number] },\n uTime: { value: 0 },\n\n uIntensity: { value: 1 },\n uSpeed: { value: 1 },\n uAnimType: { value: 0 },\n uMouse: { value: [0.5, 0.5] as [number, number] },\n uColorCount: { value: 0 },\n uDistort: { value: 0 },\n uOffset: { value: [0, 0] as [number, number] },\n uGradient: { value: gradientTex },\n uNoiseAmount: { value: 0.8 },\n uRayCount: { value: 0 }\n }\n });\n\n programRef.current = program;\n\n const triangle = new Triangle(gl);\n const mesh = new Mesh(gl, { geometry: triangle, program });\n triRef.current = triangle;\n meshRef.current = mesh;\n\n const resize = () => {\n const w = container.clientWidth || 1;\n const h = container.clientHeight || 1;\n renderer.setSize(w, h);\n program.uniforms.uResolution.value = [gl.drawingBufferWidth, gl.drawingBufferHeight];\n };\n\n let ro: ResizeObserver | null = null;\n if ('ResizeObserver' in window) {\n ro = new ResizeObserver(resize);\n ro.observe(container);\n } else {\n (window as Window).addEventListener('resize', resize);\n }\n resize();\n\n const onPointer = (e: PointerEvent) => {\n const rect = container.getBoundingClientRect();\n const x = (e.clientX - rect.left) / Math.max(rect.width, 1);\n const y = (e.clientY - rect.top) / Math.max(rect.height, 1);\n mouseTargetRef.current = [Math.min(Math.max(x, 0), 1), Math.min(Math.max(y, 0), 1)];\n };\n container.addEventListener('pointermove', onPointer, { passive: true });\n\n let io: IntersectionObserver | null = null;\n if ('IntersectionObserver' in window) {\n io = new IntersectionObserver(\n entries => {\n if (entries[0]) isVisibleRef.current = entries[0].isIntersecting;\n },\n { root: null, threshold: 0.01 }\n );\n io.observe(container);\n }\n const onVis = () => {};\n document.addEventListener('visibilitychange', onVis);\n\n let raf = 0;\n let last = performance.now();\n let accumTime = 0;\n\n const update = (now: number) => {\n const dt = Math.max(0, now - last) * 0.001;\n last = now;\n const visible = isVisibleRef.current && !document.hidden;\n if (!pausedRef.current) accumTime += dt;\n if (!visible) {\n raf = requestAnimationFrame(update);\n return;\n }\n const tau = 0.02 + Math.max(0, Math.min(1, hoverDampRef.current)) * 0.5;\n const alpha = 1 - Math.exp(-dt / tau);\n const tgt = mouseTargetRef.current;\n const sm = mouseSmoothRef.current;\n sm[0] += (tgt[0] - sm[0]) * alpha;\n sm[1] += (tgt[1] - sm[1]) * alpha;\n program.uniforms.uMouse.value = sm as any;\n program.uniforms.uTime.value = accumTime;\n renderer.render({ scene: meshRef.current! });\n raf = requestAnimationFrame(update);\n };\n raf = requestAnimationFrame(update);\n\n return () => {\n cancelAnimationFrame(raf);\n container.removeEventListener('pointermove', onPointer);\n ro?.disconnect();\n if (!ro) window.removeEventListener('resize', resize);\n io?.disconnect();\n document.removeEventListener('visibilitychange', onVis);\n try {\n container.removeChild(gl.canvas);\n } catch (e) {\n void e;\n }\n meshRef.current = null;\n triRef.current = null;\n programRef.current = null;\n try {\n const glCtx = rendererRef.current?.gl;\n if (glCtx && gradTexRef.current?.texture) glCtx.deleteTexture(gradTexRef.current.texture);\n } catch (e) {\n void e;\n }\n rendererRef.current = null;\n gradTexRef.current = null;\n };\n }, []);\n\n useEffect(() => {\n const canvas = rendererRef.current?.gl?.canvas as HTMLCanvasElement | undefined;\n if (canvas) {\n canvas.style.mixBlendMode = mixBlendMode && mixBlendMode !== 'none' ? mixBlendMode : '';\n }\n }, [mixBlendMode]);\n\n useEffect(() => {\n const program = programRef.current;\n const renderer = rendererRef.current;\n const gradTex = gradTexRef.current;\n if (!program || !renderer || !gradTex) return;\n\n program.uniforms.uIntensity.value = intensity ?? 1;\n program.uniforms.uSpeed.value = speed ?? 1;\n\n const animTypeMap: Record = {\n rotate: 0,\n rotate3d: 1,\n hover: 2\n };\n program.uniforms.uAnimType.value = animTypeMap[animationType ?? 'rotate'];\n\n program.uniforms.uDistort.value = typeof distort === 'number' ? distort : 0;\n\n const ox = toPx(offset?.x);\n const oy = toPx(offset?.y);\n program.uniforms.uOffset.value = [ox, oy];\n program.uniforms.uRayCount.value = Math.max(0, Math.floor(rayCount ?? 0));\n\n let count = 0;\n if (Array.isArray(colors) && colors.length > 0) {\n const gl = renderer.gl;\n const capped = colors.slice(0, 64);\n count = capped.length;\n const data = new Uint8Array(count * 4);\n for (let i = 0; i < count; i++) {\n const [r, g, b] = hexToRgb01(capped[i]);\n data[i * 4 + 0] = Math.round(r * 255);\n data[i * 4 + 1] = Math.round(g * 255);\n data[i * 4 + 2] = Math.round(b * 255);\n data[i * 4 + 3] = 255;\n }\n gradTex.image = data;\n gradTex.width = count;\n gradTex.height = 1;\n gradTex.minFilter = gl.LINEAR;\n gradTex.magFilter = gl.LINEAR;\n gradTex.wrapS = gl.CLAMP_TO_EDGE;\n gradTex.wrapT = gl.CLAMP_TO_EDGE;\n gradTex.flipY = false;\n gradTex.generateMipmaps = false;\n gradTex.format = gl.RGBA;\n gradTex.type = gl.UNSIGNED_BYTE;\n gradTex.needsUpdate = true;\n } else {\n count = 0;\n }\n program.uniforms.uColorCount.value = count;\n }, [intensity, speed, animationType, colors, distort, offset, rayCount]);\n\n return
;\n};\n\nexport default PrismaticBurst;\n" } ], "registryDependencies": [], diff --git a/public/sitemap.xml b/public/sitemap.xml index d36a655cf..ca2a7eb93 100644 --- a/public/sitemap.xml +++ b/public/sitemap.xml @@ -2,877 +2,877 @@ https://reactbits.dev/ - 2026-07-14 + 2026-07-23 weekly 1.0 https://reactbits.dev/showcase - 2026-07-14 + 2026-07-23 weekly 0.8 https://reactbits.dev/sponsors - 2026-07-14 + 2026-07-23 monthly 0.5 https://reactbits.dev/favorites - 2026-07-14 + 2026-07-23 monthly 0.5 https://reactbits.dev/get-started/introduction - 2026-07-14 + 2026-07-23 weekly 0.7 https://reactbits.dev/get-started/installation - 2026-07-14 + 2026-07-23 weekly 0.7 https://reactbits.dev/get-started/mcp - 2026-07-14 + 2026-07-23 weekly 0.7 https://reactbits.dev/text-animations/split-text - 2026-07-14 + 2026-07-23 weekly 0.7 https://reactbits.dev/text-animations/blur-text - 2026-07-14 + 2026-07-23 weekly 0.7 https://reactbits.dev/text-animations/circular-text - 2026-07-14 + 2026-07-23 weekly 0.7 https://reactbits.dev/text-animations/text-type - 2026-07-14 + 2026-07-23 weekly 0.7 https://reactbits.dev/text-animations/shuffle - 2026-07-14 + 2026-07-23 weekly 0.7 https://reactbits.dev/text-animations/shiny-text - 2026-07-14 + 2026-07-23 weekly 0.7 https://reactbits.dev/text-animations/text-pressure - 2026-07-14 + 2026-07-23 weekly 0.7 https://reactbits.dev/text-animations/curved-loop - 2026-07-14 + 2026-07-23 weekly 0.7 https://reactbits.dev/text-animations/fuzzy-text - 2026-07-14 + 2026-07-23 weekly 0.7 https://reactbits.dev/text-animations/gradient-text - 2026-07-14 + 2026-07-23 weekly 0.7 https://reactbits.dev/text-animations/falling-text - 2026-07-14 + 2026-07-23 weekly 0.7 https://reactbits.dev/text-animations/text-cursor - 2026-07-14 + 2026-07-23 weekly 0.7 https://reactbits.dev/text-animations/decrypted-text - 2026-07-14 + 2026-07-23 weekly 0.7 https://reactbits.dev/text-animations/true-focus - 2026-07-14 + 2026-07-23 weekly 0.7 https://reactbits.dev/text-animations/scroll-float - 2026-07-14 + 2026-07-23 weekly 0.7 https://reactbits.dev/text-animations/scroll-reveal - 2026-07-14 + 2026-07-23 weekly 0.7 https://reactbits.dev/text-animations/ascii-text - 2026-07-14 + 2026-07-23 weekly 0.7 https://reactbits.dev/text-animations/scrambled-text - 2026-07-14 + 2026-07-23 weekly 0.7 https://reactbits.dev/text-animations/rotating-text - 2026-07-14 + 2026-07-23 weekly 0.7 https://reactbits.dev/text-animations/glitch-text - 2026-07-14 + 2026-07-23 weekly 0.7 https://reactbits.dev/text-animations/scroll-velocity - 2026-07-14 + 2026-07-23 weekly 0.7 https://reactbits.dev/text-animations/variable-proximity - 2026-07-14 + 2026-07-23 weekly 0.7 https://reactbits.dev/text-animations/count-up - 2026-07-14 + 2026-07-23 weekly 0.7 https://reactbits.dev/animations/cursor-grid - 2026-07-14 + 2026-07-23 weekly 0.7 https://reactbits.dev/animations/animated-content - 2026-07-14 + 2026-07-23 weekly 0.7 https://reactbits.dev/animations/fade-content - 2026-07-14 + 2026-07-23 weekly 0.7 https://reactbits.dev/animations/electric-border - 2026-07-14 + 2026-07-23 weekly 0.7 https://reactbits.dev/animations/orbit-images - 2026-07-14 + 2026-07-23 weekly 0.7 https://reactbits.dev/animations/pixel-transition - 2026-07-14 + 2026-07-23 weekly 0.7 https://reactbits.dev/animations/glare-hover - 2026-07-14 + 2026-07-23 weekly 0.7 https://reactbits.dev/animations/antigravity - 2026-07-14 + 2026-07-23 weekly 0.7 https://reactbits.dev/animations/logo-loop - 2026-07-14 + 2026-07-23 weekly 0.7 https://reactbits.dev/animations/target-cursor - 2026-07-14 + 2026-07-23 weekly 0.7 https://reactbits.dev/animations/magic-rings - 2026-07-14 + 2026-07-23 weekly 0.7 https://reactbits.dev/animations/laser-flow - 2026-07-14 + 2026-07-23 weekly 0.7 https://reactbits.dev/animations/magnet-lines - 2026-07-14 + 2026-07-23 weekly 0.7 https://reactbits.dev/animations/ghost-cursor - 2026-07-14 + 2026-07-23 weekly 0.7 https://reactbits.dev/animations/gradual-blur - 2026-07-14 + 2026-07-23 weekly 0.7 https://reactbits.dev/animations/click-spark - 2026-07-14 + 2026-07-23 weekly 0.7 https://reactbits.dev/animations/magnet - 2026-07-14 + 2026-07-23 weekly 0.7 https://reactbits.dev/animations/strands - 2026-07-14 + 2026-07-23 weekly 0.7 https://reactbits.dev/animations/sticker-peel - 2026-07-14 + 2026-07-23 weekly 0.7 https://reactbits.dev/animations/pixel-trail - 2026-07-14 + 2026-07-23 weekly 0.7 https://reactbits.dev/animations/cubes - 2026-07-14 + 2026-07-23 weekly 0.7 https://reactbits.dev/animations/metallic-paint - 2026-07-14 + 2026-07-23 weekly 0.7 https://reactbits.dev/animations/noise - 2026-07-14 + 2026-07-23 weekly 0.7 https://reactbits.dev/animations/shape-blur - 2026-07-14 + 2026-07-23 weekly 0.7 https://reactbits.dev/animations/crosshair - 2026-07-14 + 2026-07-23 weekly 0.7 https://reactbits.dev/animations/image-trail - 2026-07-14 + 2026-07-23 weekly 0.7 https://reactbits.dev/animations/ribbons - 2026-07-14 + 2026-07-23 weekly 0.7 https://reactbits.dev/animations/splash-cursor - 2026-07-14 + 2026-07-23 weekly 0.7 https://reactbits.dev/animations/meta-balls - 2026-07-14 + 2026-07-23 weekly 0.7 https://reactbits.dev/animations/blob-cursor - 2026-07-14 + 2026-07-23 weekly 0.7 https://reactbits.dev/animations/star-border - 2026-07-14 + 2026-07-23 weekly 0.7 https://reactbits.dev/components/specular-button - 2026-07-14 + 2026-07-23 weekly 0.7 https://reactbits.dev/components/option-wheel - 2026-07-14 + 2026-07-23 weekly 0.7 https://reactbits.dev/components/curved-input - 2026-07-14 + 2026-07-23 weekly 0.7 https://reactbits.dev/components/line-sidebar - 2026-07-14 + 2026-07-23 weekly 0.7 https://reactbits.dev/components/animated-list - 2026-07-14 + 2026-07-23 weekly 0.7 https://reactbits.dev/components/scroll-stack - 2026-07-14 + 2026-07-23 weekly 0.7 https://reactbits.dev/components/bubble-menu - 2026-07-14 + 2026-07-23 weekly 0.7 https://reactbits.dev/components/magic-bento - 2026-07-14 + 2026-07-23 weekly 0.7 https://reactbits.dev/components/circular-gallery - 2026-07-14 + 2026-07-23 weekly 0.7 https://reactbits.dev/components/reflective-card - 2026-07-14 + 2026-07-23 weekly 0.7 https://reactbits.dev/components/card-nav - 2026-07-14 + 2026-07-23 weekly 0.7 https://reactbits.dev/components/stack - 2026-07-14 + 2026-07-23 weekly 0.7 https://reactbits.dev/components/fluid-glass - 2026-07-14 + 2026-07-23 weekly 0.7 https://reactbits.dev/components/pill-nav - 2026-07-14 + 2026-07-23 weekly 0.7 https://reactbits.dev/components/tilted-card - 2026-07-14 + 2026-07-23 weekly 0.7 https://reactbits.dev/components/masonry - 2026-07-14 + 2026-07-23 weekly 0.7 https://reactbits.dev/components/glass-surface - 2026-07-14 + 2026-07-23 weekly 0.7 https://reactbits.dev/components/dome-gallery - 2026-07-14 + 2026-07-23 weekly 0.7 https://reactbits.dev/components/chroma-grid - 2026-07-14 + 2026-07-23 weekly 0.7 https://reactbits.dev/components/folder - 2026-07-14 + 2026-07-23 weekly 0.7 https://reactbits.dev/components/staggered-menu - 2026-07-14 + 2026-07-23 weekly 0.7 https://reactbits.dev/components/model-viewer - 2026-07-14 + 2026-07-23 weekly 0.7 https://reactbits.dev/components/lanyard - 2026-07-14 + 2026-07-23 weekly 0.7 https://reactbits.dev/components/profile-card - 2026-07-14 + 2026-07-23 weekly 0.7 https://reactbits.dev/components/dock - 2026-07-14 + 2026-07-23 weekly 0.7 https://reactbits.dev/components/gooey-nav - 2026-07-14 + 2026-07-23 weekly 0.7 https://reactbits.dev/components/pixel-card - 2026-07-14 + 2026-07-23 weekly 0.7 https://reactbits.dev/components/carousel - 2026-07-14 + 2026-07-23 weekly 0.7 https://reactbits.dev/components/spotlight-card - 2026-07-14 + 2026-07-23 weekly 0.7 https://reactbits.dev/components/border-glow - 2026-07-14 + 2026-07-23 weekly 0.7 https://reactbits.dev/components/flying-posters - 2026-07-14 + 2026-07-23 weekly 0.7 https://reactbits.dev/components/card-swap - 2026-07-14 + 2026-07-23 weekly 0.7 https://reactbits.dev/components/glass-icons - 2026-07-14 + 2026-07-23 weekly 0.7 https://reactbits.dev/components/decay-card - 2026-07-14 + 2026-07-23 weekly 0.7 https://reactbits.dev/components/flowing-menu - 2026-07-14 + 2026-07-23 weekly 0.7 https://reactbits.dev/components/elastic-slider - 2026-07-14 + 2026-07-23 weekly 0.7 https://reactbits.dev/components/counter - 2026-07-14 + 2026-07-23 weekly 0.7 https://reactbits.dev/components/infinite-menu - 2026-07-14 + 2026-07-23 weekly 0.7 https://reactbits.dev/components/stepper - 2026-07-14 + 2026-07-23 weekly 0.7 https://reactbits.dev/components/bounce-cards - 2026-07-14 + 2026-07-23 weekly 0.7 https://reactbits.dev/backgrounds/ferrofluid - 2026-07-14 + 2026-07-23 weekly 0.7 https://reactbits.dev/backgrounds/lightfall - 2026-07-14 + 2026-07-23 weekly 0.7 https://reactbits.dev/backgrounds/liquid-ether - 2026-07-14 + 2026-07-23 weekly 0.7 https://reactbits.dev/backgrounds/prism - 2026-07-14 + 2026-07-23 weekly 0.7 https://reactbits.dev/backgrounds/dark-veil - 2026-07-14 + 2026-07-23 weekly 0.7 https://reactbits.dev/backgrounds/light-pillar - 2026-07-14 + 2026-07-23 weekly 0.7 https://reactbits.dev/backgrounds/silk - 2026-07-14 + 2026-07-23 weekly 0.7 https://reactbits.dev/backgrounds/floating-lines - 2026-07-14 + 2026-07-23 weekly 0.7 https://reactbits.dev/backgrounds/side-rays - 2026-07-14 + 2026-07-23 weekly 0.7 https://reactbits.dev/backgrounds/light-rays - 2026-07-14 + 2026-07-23 weekly 0.7 https://reactbits.dev/backgrounds/pixel-blast - 2026-07-14 + 2026-07-23 weekly 0.7 https://reactbits.dev/backgrounds/color-bends - 2026-07-14 + 2026-07-23 weekly 0.7 https://reactbits.dev/backgrounds/evil-eye - 2026-07-14 + 2026-07-23 weekly 0.7 https://reactbits.dev/backgrounds/line-waves - 2026-07-14 + 2026-07-23 weekly 0.7 https://reactbits.dev/backgrounds/radar - 2026-07-14 + 2026-07-23 weekly 0.7 https://reactbits.dev/backgrounds/soft-aurora - 2026-07-14 + 2026-07-23 weekly 0.7 https://reactbits.dev/backgrounds/aurora - 2026-07-14 + 2026-07-23 weekly 0.7 https://reactbits.dev/backgrounds/plasma - 2026-07-14 + 2026-07-23 weekly 0.7 https://reactbits.dev/backgrounds/plasma-wave - 2026-07-14 + 2026-07-23 weekly 0.7 https://reactbits.dev/backgrounds/particles - 2026-07-14 + 2026-07-23 weekly 0.7 https://reactbits.dev/backgrounds/gradient-blinds - 2026-07-14 + 2026-07-23 weekly 0.7 https://reactbits.dev/backgrounds/grainient - 2026-07-14 + 2026-07-23 weekly 0.7 https://reactbits.dev/backgrounds/grid-scan - 2026-07-14 + 2026-07-23 weekly 0.7 https://reactbits.dev/backgrounds/beams - 2026-07-14 + 2026-07-23 weekly 0.7 https://reactbits.dev/backgrounds/pixel-snow - 2026-07-14 + 2026-07-23 weekly 0.7 https://reactbits.dev/backgrounds/lightning - 2026-07-14 + 2026-07-23 weekly 0.7 https://reactbits.dev/backgrounds/prismatic-burst - 2026-07-14 + 2026-07-23 weekly 0.7 https://reactbits.dev/backgrounds/galaxy - 2026-07-14 + 2026-07-23 weekly 0.7 https://reactbits.dev/backgrounds/dither - 2026-07-14 + 2026-07-23 weekly 0.7 https://reactbits.dev/backgrounds/faulty-terminal - 2026-07-14 + 2026-07-23 weekly 0.7 https://reactbits.dev/backgrounds/ripple-grid - 2026-07-14 + 2026-07-23 weekly 0.7 https://reactbits.dev/backgrounds/dot-field - 2026-07-14 + 2026-07-23 weekly 0.7 https://reactbits.dev/backgrounds/dot-grid - 2026-07-14 + 2026-07-23 weekly 0.7 https://reactbits.dev/backgrounds/threads - 2026-07-14 + 2026-07-23 weekly 0.7 https://reactbits.dev/backgrounds/hyperspeed - 2026-07-14 + 2026-07-23 weekly 0.7 https://reactbits.dev/backgrounds/iridescence - 2026-07-14 + 2026-07-23 weekly 0.7 https://reactbits.dev/backgrounds/waves - 2026-07-14 + 2026-07-23 weekly 0.7 https://reactbits.dev/backgrounds/grid-distortion - 2026-07-14 + 2026-07-23 weekly 0.7 https://reactbits.dev/backgrounds/ballpit - 2026-07-14 + 2026-07-23 weekly 0.7 https://reactbits.dev/backgrounds/orb - 2026-07-14 + 2026-07-23 weekly 0.7 https://reactbits.dev/backgrounds/letter-glitch - 2026-07-14 + 2026-07-23 weekly 0.7 https://reactbits.dev/backgrounds/grid-motion - 2026-07-14 + 2026-07-23 weekly 0.7 https://reactbits.dev/backgrounds/shape-grid - 2026-07-14 + 2026-07-23 weekly 0.7 https://reactbits.dev/backgrounds/liquid-chrome - 2026-07-14 + 2026-07-23 weekly 0.7 https://reactbits.dev/backgrounds/balatro - 2026-07-14 + 2026-07-23 weekly 0.7 diff --git a/src/content/Backgrounds/Plasma/Plasma.jsx b/src/content/Backgrounds/Plasma/Plasma.jsx index f5cbdddfb..abf74f018 100644 --- a/src/content/Backgrounds/Plasma/Plasma.jsx +++ b/src/content/Backgrounds/Plasma/Plasma.jsx @@ -19,7 +19,10 @@ void main() { } `; -const buildFragment = steps => `#version 300 es +const ORIGINAL_QUALITY = 60; + +const buildFragment = (iterations) => { + return `#version 300 es precision highp float; uniform vec2 iResolution; uniform float iTime; @@ -27,10 +30,12 @@ uniform vec3 uCustomColor; uniform float uUseCustomColor; uniform float uSpeed; uniform float uDirection; -uniform float uScale; +uniform float uScale; uniform float uOpacity; uniform vec2 uMouse; uniform float uMouseInteractive; +uniform float uQuality; +uniform float uStepScale; out vec4 fragColor; void mainImage(out vec4 o, vec2 C) { @@ -43,7 +48,7 @@ void mainImage(out vec4 o, vec2 C) { float i, d, z, T = iTime * uSpeed * uDirection; vec3 O, p, S; - for (vec2 r = iResolution.xy, Q; ++i < ${steps.toFixed(1)}; O += o.w/d*o.xyz) { + for (vec2 r = iResolution.xy, Q; ++i < 60.0; O += o.w/d*o.xyz) { p = z*normalize(vec3(C-.5*r,r.y)); p.z -= 4.; S = p; @@ -51,8 +56,9 @@ void mainImage(out vec4 o, vec2 C) { p.x += .4*(1.+p.y)*sin(d + p.x*0.1)*cos(.34*d + p.x*0.05); Q = p.xz *= mat2(cos(p.y+vec4(0,11,33,0)-T)); - z+= d = abs(sqrt(length(Q*Q)) - .25*(5.+S.y))/3.+8e-4; + z += d = (abs(sqrt(length(Q*Q)) - .25*(5.+S.y))/3.+8e-4) * uStepScale; o = 1.+sin(S.y+p.z*.5+S.z-length(S-p)+vec4(2,1,0,8)); + if (i >= uQuality) break; } o.xyz = tanh(O/1e4); @@ -79,6 +85,7 @@ void main() { float alpha = length(rgb) * uOpacity; fragColor = vec4(finalColor, alpha); }`; +}; export const Plasma = ({ color = '#ffffff', @@ -90,7 +97,7 @@ export const Plasma = ({ renderScale = 0.55, maxDpr = 1.5, targetFps = 60, - quality = 45, + iterations = 60, }) => { const containerRef = useRef(null); const mousePos = useRef({ x: 0, y: 0 }); @@ -133,7 +140,7 @@ export const Plasma = ({ const program = new Program(gl, { vertex: vertex, - fragment: buildFragment(quality), + fragment: buildFragment(iterations), uniforms: { iTime: { value: 0 }, iResolution: { value: new Float32Array([1, 1]) }, @@ -144,7 +151,9 @@ export const Plasma = ({ uScale: { value: scale }, uOpacity: { value: opacity }, uMouse: { value: new Float32Array([0, 0]) }, - uMouseInteractive: { value: mouseInteractive ? 1.0 : 0.0 } + uMouseInteractive: { value: mouseInteractive ? 1.0 : 0.0 }, + uQuality: { value: iterations }, + uStepScale: { value: ORIGINAL_QUALITY / iterations }, } }); @@ -297,7 +306,7 @@ export const Plasma = ({ containerEl?.removeChild(canvas); } catch {} }; - }, [color, speed, direction, scale, opacity, mouseInteractive, renderScale, maxDpr, targetFps, quality]); + }, [color, speed, direction, scale, opacity, mouseInteractive, renderScale, maxDpr, targetFps, iterations]); return
; }; diff --git a/src/demo/Backgrounds/PlasmaDemo.jsx b/src/demo/Backgrounds/PlasmaDemo.jsx index ac954eb95..e78830749 100644 --- a/src/demo/Backgrounds/PlasmaDemo.jsx +++ b/src/demo/Backgrounds/PlasmaDemo.jsx @@ -30,12 +30,12 @@ const DEFAULT_PROPS = { renderScale: 0.55, maxDpr: 1.5, targetFps: 60, - quality: 45 + iterations: 60 }; const PlasmaDemo = () => { const { props, updateProp, resetProps, hasChanges } = useComponentProps(DEFAULT_PROPS); - const { color, speed, direction, scale, opacity, mouseInteractive, renderScale, maxDpr, targetFps, quality } = props; + const { color, speed, direction, scale, opacity, mouseInteractive, renderScale, maxDpr, targetFps, iterations } = props; const propData = useMemo( () => [ { @@ -95,9 +95,9 @@ const PlasmaDemo = () => { description: 'Target frame rate for the animation loop. Lower values reduce CPU/GPU load.' }, { - name: 'quality', + name: 'iterations', type: 'number', - default: '45', + default: '60', description: 'Raymarch step count — lower is cheaper but less detailed. Higher values produce smoother plasma.' } ], @@ -119,7 +119,7 @@ const PlasmaDemo = () => { renderScale={renderScale} maxDpr={maxDpr} targetFps={targetFps} - quality={quality} + iterations={iterations} /> @@ -127,7 +127,7 @@ const PlasmaDemo = () => { { renderScale: 0.55, maxDpr: 1.5, targetFps: 60, - quality: 45 + iterations: 60 }} /> @@ -185,12 +185,12 @@ const PlasmaDemo = () => { /> updateProp('quality', val)} + value={iterations} + onChange={val => updateProp('iterations', val)} /> `#version 300 es +const ORIGINAL_QUALITY = 60; + +const buildFragment = iterations => { + return `#version 300 es precision highp float; uniform vec2 iResolution; uniform float iTime; @@ -30,6 +33,8 @@ uniform float uScale; uniform float uOpacity; uniform vec2 uMouse; uniform float uMouseInteractive; +uniform float uQuality; +uniform float uStepScale; out vec4 fragColor; void mainImage(out vec4 o, vec2 C) { @@ -42,7 +47,7 @@ void mainImage(out vec4 o, vec2 C) { float i, d, z, T = iTime * uSpeed * uDirection; vec3 O, p, S; - for (vec2 r = iResolution.xy, Q; ++i < ${steps.toFixed(1)}; O += o.w/d*o.xyz) { + for (vec2 r = iResolution.xy, Q; ++i < 60.0; O += o.w/d*o.xyz) { p = z*normalize(vec3(C-.5*r,r.y)); p.z -= 4.; S = p; @@ -50,13 +55,15 @@ void mainImage(out vec4 o, vec2 C) { p.x += .4*(1.+p.y)*sin(d + p.x*0.1)*cos(.34*d + p.x*0.05); Q = p.xz *= mat2(cos(p.y+vec4(0,11,33,0)-T)); - z+= d = abs(sqrt(length(Q*Q)) - .25*(5.+S.y))/3.+8e-4; + z += d = (abs(sqrt(length(Q*Q)) - .25*(5.+S.y))/3.+8e-4) * uStepScale; o = 1.+sin(S.y+p.z*.5+S.z-length(S-p)+vec4(2,1,0,8)); + if (i >= uQuality) break; } o.xyz = tanh(O/1e4); } + bool finite1(float x){ return !(isnan(x) || isinf(x)); } vec3 sanitize(vec3 c){ return vec3( @@ -78,6 +85,7 @@ void main() { float alpha = length(rgb) * uOpacity; fragColor = vec4(finalColor, alpha); }`; +}; export const Plasma = ({ color = '#ffffff', @@ -89,7 +97,7 @@ export const Plasma = ({ renderScale = 0.55, maxDpr = 1.5, targetFps = 60, - quality = 45, + iterations = 60 }) => { const containerRef = useRef(null); const mousePos = useRef({ x: 0, y: 0 }); @@ -100,8 +108,7 @@ export const Plasma = ({ const containerEl = containerRef.current; const prefersReducedMotion = - typeof window !== 'undefined' && - window.matchMedia?.('(prefers-reduced-motion: reduce)').matches; + typeof window !== 'undefined' && window.matchMedia?.('(prefers-reduced-motion: reduce)').matches; const useCustomColor = color ? 1.0 : 0.0; const customColorRgb = color ? hexToRgb(color) : [1, 1, 1]; @@ -132,7 +139,7 @@ export const Plasma = ({ const program = new Program(gl, { vertex: vertex, - fragment: buildFragment(quality), + fragment: buildFragment(iterations), uniforms: { iTime: { value: 0 }, iResolution: { value: new Float32Array([1, 1]) }, @@ -143,7 +150,9 @@ export const Plasma = ({ uScale: { value: scale }, uOpacity: { value: opacity }, uMouse: { value: new Float32Array([0, 0]) }, - uMouseInteractive: { value: mouseInteractive ? 1.0 : 0.0 } + uMouseInteractive: { value: mouseInteractive ? 1.0 : 0.0 }, + uQuality: { value: iterations }, + uStepScale: { value: ORIGINAL_QUALITY / iterations } } }); @@ -155,7 +164,7 @@ export const Plasma = ({ // Store the latest position but don't touch GL state here, the rAF loop picks it up once per rendered frame instead of once per mouse event. pendingMouse.current = { x: e.clientX - rect.left, - y: e.clientY - rect.top, + y: e.clientY - rect.top }; }; @@ -238,7 +247,7 @@ export const Plasma = ({ raf = requestAnimationFrame(loop); }; - const handleContextLost = (e) => { + const handleContextLost = e => { e.preventDefault(); contextLost = true; cancelAnimationFrame(raf); @@ -253,14 +262,17 @@ export const Plasma = ({ canvas.addEventListener('webglcontextlost', handleContextLost); canvas.addEventListener('webglcontextrestored', handleContextRestored); - const io = new IntersectionObserver(([entry]) => { - const wasVisible = isVisible; - isVisible = entry.isIntersecting; - if (isVisible && !wasVisible && !contextLost && tabVisible && !prefersReducedMotion) { - cancelAnimationFrame(raf); - raf = requestAnimationFrame(loop); - } - }, { threshold: 0 }); + const io = new IntersectionObserver( + ([entry]) => { + const wasVisible = isVisible; + isVisible = entry.isIntersecting; + if (isVisible && !wasVisible && !contextLost && tabVisible && !prefersReducedMotion) { + cancelAnimationFrame(raf); + raf = requestAnimationFrame(loop); + } + }, + { threshold: 0 } + ); io.observe(containerEl); const handleVisibilityChange = () => { @@ -296,7 +308,7 @@ export const Plasma = ({ containerEl?.removeChild(canvas); } catch {} }; - }, [color, speed, direction, scale, opacity, mouseInteractive, renderScale, maxDpr, targetFps, quality]); + }, [color, speed, direction, scale, opacity, mouseInteractive, renderScale, maxDpr, targetFps, iterations]); return
; }; diff --git a/src/tools/background-studio/backgrounds/index.js b/src/tools/background-studio/backgrounds/index.js index b043f37ae..b438bda65 100644 --- a/src/tools/background-studio/backgrounds/index.js +++ b/src/tools/background-studio/backgrounds/index.js @@ -548,7 +548,7 @@ export const BACKGROUNDS = [ { name: 'scale', type: 'number', default: 1, min: 0.1, max: 3, step: 0.1, label: 'Scale' }, { name: 'opacity', type: 'number', default: 1, min: 0, max: 1, step: 0.05, label: 'Opacity' }, { name: 'mouseInteractive', type: 'boolean', default: true, label: 'Mouse Interactive' }, - { name: 'quality', type: 'number', default: 45, min: 10, max: 80, step: 5, label: 'Quality' }, + { name: 'iterations', type: 'number', default: 60, min: 10, max: 80, step: 5, label: 'Iterations' }, { name: 'renderScale', type: 'number', default: 0.55, min: 0.2, max: 1.0, step: 0.05, label: 'Render Scale' }, { name: 'targetFps', type: 'number', default: 60, min: 10, max: 60, step: 5, label: 'Target FPS' }, { name: 'maxDpr', type: 'number', default: 1.5, min: 0.5, max: 3.0, step: 0.5, label: 'Max DPR' } diff --git a/src/ts-default/Backgrounds/Plasma/Plasma.tsx b/src/ts-default/Backgrounds/Plasma/Plasma.tsx index 44dadfc74..2f086ae58 100644 --- a/src/ts-default/Backgrounds/Plasma/Plasma.tsx +++ b/src/ts-default/Backgrounds/Plasma/Plasma.tsx @@ -15,8 +15,8 @@ interface PlasmaProps { maxDpr?: number; /** Target frame rate for the animation loop. Default 30. */ targetFps?: number; - /** Raymarch step count — lower is cheaper, less detailed. Default 45. */ - quality?: number; + /** Raymarch step count — lower is cheaper, less detailed. Default 60. */ + iterations?: number; } const hexToRgb = (hex: string): [number, number, number] => { @@ -36,7 +36,10 @@ void main() { } `; -const buildFragment = (steps: number) => `#version 300 es +const ORIGINAL_QUALITY = 60; + +const buildFragment = (iterations: number) => { + return `#version 300 es precision highp float; uniform vec2 iResolution; uniform float iTime; @@ -48,6 +51,8 @@ uniform float uScale; uniform float uOpacity; uniform vec2 uMouse; uniform float uMouseInteractive; +uniform float uQuality; +uniform float uStepScale; out vec4 fragColor; void mainImage(out vec4 o, vec2 C) { @@ -60,7 +65,7 @@ void mainImage(out vec4 o, vec2 C) { float i, d, z, T = iTime * uSpeed * uDirection; vec3 O, p, S; - for (vec2 r = iResolution.xy, Q; ++i < ${steps.toFixed(1)}; O += o.w/d*o.xyz) { + for (vec2 r = iResolution.xy, Q; ++i < 60.0; O += o.w/d*o.xyz) { p = z*normalize(vec3(C-.5*r,r.y)); p.z -= 4.; S = p; @@ -68,8 +73,9 @@ void mainImage(out vec4 o, vec2 C) { p.x += .4*(1.+p.y)*sin(d + p.x*0.1)*cos(.34*d + p.x*0.05); Q = p.xz *= mat2(cos(p.y+vec4(0,11,33,0)-T)); - z+= d = abs(sqrt(length(Q*Q)) - .25*(5.+S.y))/3.+8e-4; + z += d = (abs(sqrt(length(Q*Q)) - .25*(5.+S.y))/3.+8e-4) * uStepScale; o = 1.+sin(S.y+p.z*.5+S.z-length(S-p)+vec4(2,1,0,8)); + if (i >= uQuality) break; } o.xyz = tanh(O/1e4); @@ -96,6 +102,7 @@ void main() { float alpha = length(rgb) * uOpacity; fragColor = vec4(finalColor, alpha); }`; +}; export const Plasma: React.FC = ({ color = '#ffffff', @@ -107,7 +114,7 @@ export const Plasma: React.FC = ({ renderScale = 0.55, maxDpr = 1.5, targetFps = 60, - quality = 45, + iterations = 60, }) => { const containerRef = useRef(null); const mousePos = useRef({ x: 0, y: 0 }); @@ -150,7 +157,7 @@ export const Plasma: React.FC = ({ const program = new Program(gl, { vertex: vertex, - fragment: buildFragment(quality), + fragment: buildFragment(iterations), uniforms: { iTime: { value: 0 }, iResolution: { value: new Float32Array([1, 1]) }, @@ -161,7 +168,9 @@ export const Plasma: React.FC = ({ uScale: { value: scale }, uOpacity: { value: opacity }, uMouse: { value: new Float32Array([0, 0]) }, - uMouseInteractive: { value: mouseInteractive ? 1.0 : 0.0 } + uMouseInteractive: { value: mouseInteractive ? 1.0 : 0.0 }, + uQuality: { value: iterations }, + uStepScale: { value: ORIGINAL_QUALITY / iterations }, } }); @@ -314,7 +323,7 @@ export const Plasma: React.FC = ({ containerEl?.removeChild(canvas); } catch {} }; - }, [color, speed, direction, scale, opacity, mouseInteractive, renderScale, maxDpr, targetFps, quality]); + }, [color, speed, direction, scale, opacity, mouseInteractive, renderScale, maxDpr, targetFps, iterations]); return
; }; diff --git a/src/ts-tailwind/Backgrounds/Plasma/Plasma.tsx b/src/ts-tailwind/Backgrounds/Plasma/Plasma.tsx index 5c9b20e0d..9dfdd2740 100644 --- a/src/ts-tailwind/Backgrounds/Plasma/Plasma.tsx +++ b/src/ts-tailwind/Backgrounds/Plasma/Plasma.tsx @@ -14,8 +14,8 @@ interface PlasmaProps { maxDpr?: number; /** Target frame rate for the animation loop. Default 30. */ targetFps?: number; - /** Raymarch step count — lower is cheaper, less detailed. Default 45. */ - quality?: number; + /** Raymarch step count — lower is cheaper, less detailed. Default 60. */ + iterations?: number; } const hexToRgb = (hex: string): [number, number, number] => { @@ -39,7 +39,10 @@ void main() { } `; -const buildFragment = (steps: number) => `#version 300 es +const ORIGINAL_QUALITY = 60; + +const buildFragment = (iterations: number) => { + return `#version 300 es precision highp float; uniform vec2 iResolution; uniform float iTime; @@ -51,6 +54,8 @@ uniform float uScale; uniform float uOpacity; uniform vec2 uMouse; uniform float uMouseInteractive; +uniform float uQuality; +uniform float uStepScale; out vec4 fragColor; void mainImage(out vec4 o, vec2 C) { @@ -63,7 +68,7 @@ void mainImage(out vec4 o, vec2 C) { float i, d, z, T = iTime * uSpeed * uDirection; vec3 O, p, S; - for (vec2 r = iResolution.xy, Q; ++i < ${steps.toFixed(1)}; O += o.w/d*o.xyz) { + for (vec2 r = iResolution.xy, Q; ++i < 60.0; O += o.w/d*o.xyz) { p = z*normalize(vec3(C-.5*r,r.y)); p.z -= 4.; S = p; @@ -71,8 +76,9 @@ void mainImage(out vec4 o, vec2 C) { p.x += .4*(1.+p.y)*sin(d + p.x*0.1)*cos(.34*d + p.x*0.05); Q = p.xz *= mat2(cos(p.y+vec4(0,11,33,0)-T)); - z+= d = abs(sqrt(length(Q*Q)) - .25*(5.+S.y))/3.+8e-4; + z += d = (abs(sqrt(length(Q*Q)) - .25*(5.+S.y))/3.+8e-4) * uStepScale; o = 1.+sin(S.y+p.z*.5+S.z-length(S-p)+vec4(2,1,0,8)); + if (i >= uQuality) break; } o.xyz = tanh(O/1e4); @@ -99,6 +105,7 @@ void main() { float alpha = length(rgb) * uOpacity; fragColor = vec4(finalColor, alpha); }`; +}; export const Plasma: React.FC = ({ color = "#ffffff", @@ -110,7 +117,7 @@ export const Plasma: React.FC = ({ renderScale = 0.55, maxDpr = 1.5, targetFps = 60, - quality = 45, + iterations = 60, }) => { const containerRef = useRef(null); const mousePos = useRef({ x: 0, y: 0 }); @@ -152,7 +159,7 @@ export const Plasma: React.FC = ({ const program = new Program(gl, { vertex: vertex, - fragment: buildFragment(quality), + fragment: buildFragment(iterations), uniforms: { iTime: { value: 0 }, iResolution: { value: new Float32Array([1, 1]) }, @@ -164,6 +171,8 @@ export const Plasma: React.FC = ({ uOpacity: { value: opacity }, uMouse: { value: new Float32Array([0, 0]) }, uMouseInteractive: { value: mouseInteractive ? 1.0 : 0.0 }, + uQuality: { value: iterations }, + uStepScale: { value: ORIGINAL_QUALITY / iterations }, }, }); @@ -339,7 +348,7 @@ export const Plasma: React.FC = ({ renderScale, maxDpr, targetFps, - quality, + iterations, ]); return (