{ "$schema": "https://ui.shadcn.com/schema/registry-item.json", "name": "AcidSquares-TS-TW", "title": "AcidSquares", "description": "A crystalline corridor of stacked squares receding into depth.", "type": "registry:component", "files": [ { "type": "registry:component", "path": "AcidSquares/AcidSquares.tsx", "content": "import React, { useEffect, useRef } from 'react';\nimport { Renderer, Program, Mesh, Triangle, RenderTarget } from 'ogl';\n\nexport type AcidSquaresDetail = 'low' | 'medium' | 'high';\n\nexport interface AcidSquaresProps {\n color1?: string;\n color2?: string;\n color3?: string;\n detail?: AcidSquaresDetail;\n speed?: number;\n waveDepth?: number;\n zoom?: number;\n density?: number;\n glow?: number;\n exposure?: number;\n spread?: number;\n stepSize?: number;\n colorShift?: number;\n contrast?: number;\n brightness?: number;\n opacity?: number;\n mouseInteraction?: boolean;\n mouseStrength?: number;\n mouseRadius?: number;\n blur?: number;\n grain?: boolean;\n grainIntensity?: number;\n className?: string;\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, 1, 1];\n return [parseInt(result[1], 16) / 255, parseInt(result[2], 16) / 255, parseInt(result[3], 16) / 255];\n};\n\nconst DETAIL_STEPS: Record = { low: 20, medium: 32, high: 48 };\nconst stepsFor = (detail: AcidSquaresDetail): number => DETAIL_STEPS[detail] || DETAIL_STEPS.medium;\n\nconst vertex = `#version 300 es\nin vec2 position;\nvoid main() {\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 float uSpeed;\nuniform float uWaveDepth;\nuniform float uZoom;\nuniform float uDensity;\nuniform float uSpread;\nuniform float uStepSize;\nuniform float uGlow;\nuniform float uExposure;\nuniform float uColorShift;\nuniform float uContrast;\nuniform float uBrightness;\nuniform float uOpacity;\nuniform float uSteps;\nuniform vec3 uColor1;\nuniform vec3 uColor2;\nuniform vec3 uColor3;\nuniform vec2 uMouse;\nuniform float uMouseStrength;\nuniform float uMouseRadius;\nuniform float uEnableMouse;\nuniform float uMouseActive;\nuniform float uGrain;\nuniform float uGrainIntensity;\nout vec4 fragColor;\n\nvoid main() {\n vec2 frag = gl_FragCoord.xy;\n float zoom = max(uZoom, 0.05);\n float aspect = iResolution.x / iResolution.y;\n vec2 ndc = (2.0 * frag - iResolution.xy) / iResolution.y;\n vec2 dir = ndc * (0.5 / zoom);\n\n vec2 mouseNdc = vec2(uMouse.x * aspect, uMouse.y);\n float mr = max(uMouseRadius, 0.01);\n vec2 md = ndc - mouseNdc;\n float dent = exp(-dot(md, md) / (mr * mr)) * (3.0 * uMouseStrength * uEnableMouse * uMouseActive);\n\n float travel = sin(iTime * uSpeed) * uWaveDepth;\n float density = max(uDensity, 1.0);\n float spread = clamp(uSpread, 0.05, 0.6);\n float stepSize = max(uStepSize, 0.0005);\n float glowGain = max(uGlow, 0.0);\n\n vec3 tOffset = vec3(0.0, dent, travel);\n vec3 p = vec3(0.0);\n float s = 0.0;\n float glow = 0.0;\n\n for (int i = 0; i < 64; i++) {\n if (float(i) >= uSteps) break;\n p += vec3(dir * s, s);\n vec3 q = p + tOffset;\n s += density - length(q.xz) + length(ceil(q).xy);\n s = stepSize + abs(s) * spread;\n glow += glowGain / s;\n }\n\n float e = glow / max(uExposure, 1.0);\n float shimmer = 0.5 + 0.5 * dot(cos(iTime * uColorShift + p), vec3(0.3333));\n float v = tanh(e * uBrightness * mix(0.7, 1.05, shimmer));\n v = clamp((v - 0.5) * uContrast + 0.5, 0.0, 1.0);\n\n vec3 col = mix(uColor1, uColor2, smoothstep(0.0, 0.55, v));\n col = mix(col, uColor3, smoothstep(0.55, 1.0, v));\n col *= v;\n\n float a = clamp(v, 0.0, 1.0) * uOpacity;\n vec3 outRgb = col * a;\n if (uGrain > 0.5) {\n float gv = (fract(sin(dot(gl_FragCoord.xy, vec2(12.9898, 78.233)) + iTime) * 43758.5453) - 0.5) * uGrainIntensity;\n outRgb = clamp(outRgb + gv, 0.0, 1.0);\n a = clamp(a + gv, 0.0, 1.0);\n }\n fragColor = vec4(outRgb, a);\n}\n`;\n\nconst postFragment = `#version 300 es\nprecision highp float;\nuniform sampler2D tMap;\nuniform vec2 iResolution;\nuniform vec2 uDirection;\nuniform float uRadius;\nuniform float uGrain;\nuniform float uGrainIntensity;\nuniform float iTime;\nout vec4 fragColor;\n\nvec4 samp(vec2 uv) {\n return texture(tMap, uv);\n}\n\nvoid main() {\n vec2 uv = gl_FragCoord.xy / iResolution;\n vec2 texel = uDirection / iResolution;\n float st = uRadius * 0.25;\n vec4 sum = samp(uv) * 0.2026;\n sum += (samp(uv + texel * st) + samp(uv - texel * st)) * 0.179;\n sum += (samp(uv + texel * (st * 2.0)) + samp(uv - texel * (st * 2.0))) * 0.124;\n sum += (samp(uv + texel * (st * 3.0)) + samp(uv - texel * (st * 3.0))) * 0.0672;\n sum += (samp(uv + texel * (st * 4.0)) + samp(uv - texel * (st * 4.0))) * 0.0285;\n vec4 col = sum;\n if (uGrain > 0.5) {\n float gv = (fract(sin(dot(gl_FragCoord.xy, vec2(12.9898, 78.233)) + iTime) * 43758.5453) - 0.5) * uGrainIntensity;\n col.rgb = clamp(col.rgb + gv, 0.0, 1.0);\n col.a = clamp(col.a + gv, 0.0, 1.0);\n }\n fragColor = col;\n}\n`;\n\ntype AcidSquaresCtx = {\n renderer: InstanceType;\n program: InstanceType;\n mesh: InstanceType;\n};\nconst ctxMap = new WeakMap();\n\nconst AcidSquares: React.FC = ({\n color1 = '#5227FF',\n color2 = '#A855F7',\n color3 = '#FFFFFF',\n detail = 'medium',\n speed = 0.7,\n waveDepth = 1,\n zoom = 1.3,\n density = 10.0,\n glow = 1.0,\n exposure = 2700,\n spread = 0.3,\n stepSize = 0.002,\n colorShift = 0,\n contrast = 1,\n brightness = 1.0,\n opacity = 1.0,\n mouseInteraction = true,\n mouseStrength = 0.1,\n mouseRadius = 0.35,\n blur = 0,\n grain = true,\n grainIntensity = 0.05,\n className = ''\n}) => {\n const containerRef = useRef(null);\n const mouseTarget = useRef<[number, number]>([0, 0]);\n const mouseCurrent = useRef<[number, number]>([0, 0]);\n const enableMouseRef = useRef(mouseInteraction);\n const mouseStrengthRef = useRef(mouseStrength);\n const mouseActive = useRef(0);\n const mouseActiveTarget = useRef(0);\n const blurRef = useRef(blur);\n const grainRef = useRef(grain);\n const grainIntensityRef = useRef(grainIntensity);\n\n useEffect(() => {\n const container = containerRef.current;\n if (!container) return;\n\n const renderer = new Renderer({\n webgl: 2,\n alpha: true,\n premultipliedAlpha: true,\n antialias: false,\n dpr: Math.min(window.devicePixelRatio || 1, 2)\n });\n\n const gl = renderer.gl;\n gl.clearColor(0, 0, 0, 0);\n const canvas = gl.canvas as HTMLCanvasElement;\n canvas.style.width = '100%';\n canvas.style.height = '100%';\n canvas.style.display = 'block';\n container.appendChild(canvas);\n\n const geometry = new Triangle(gl);\n const program = new Program(gl, {\n vertex,\n fragment,\n uniforms: {\n iTime: { value: 0 },\n iResolution: { value: new Float32Array([1, 1]) },\n uSpeed: { value: 0.7 },\n uWaveDepth: { value: 1 },\n uZoom: { value: 1.3 },\n uDensity: { value: 10.0 },\n uSpread: { value: 0.3 },\n uStepSize: { value: 0.002 },\n uGlow: { value: 1.0 },\n uExposure: { value: 2700 },\n uColorShift: { value: 0 },\n uContrast: { value: 1 },\n uBrightness: { value: 1.0 },\n uOpacity: { value: 1.0 },\n uSteps: { value: 32 },\n uColor1: { value: new Float32Array([1, 1, 1]) },\n uColor2: { value: new Float32Array([1, 1, 1]) },\n uColor3: { value: new Float32Array([1, 1, 1]) },\n uMouse: { value: new Float32Array([0, 0]) },\n uMouseStrength: { value: 0.1 },\n uMouseRadius: { value: 0.35 },\n uEnableMouse: { value: 1.0 },\n uMouseActive: { value: 0.0 },\n uGrain: { value: 1.0 },\n uGrainIntensity: { value: 0.05 }\n }\n });\n\n const mesh = new Mesh(gl, { geometry, program });\n\n const postProgram = new Program(gl, {\n vertex,\n fragment: postFragment,\n uniforms: {\n tMap: { value: null },\n iResolution: { value: new Float32Array([1, 1]) },\n uDirection: { value: new Float32Array([1, 0]) },\n uRadius: { value: 0 },\n uGrain: { value: 0 },\n uGrainIntensity: { value: 0.05 },\n iTime: { value: 0 }\n }\n });\n const postMesh = new Mesh(gl, { geometry, program: postProgram });\n const pu = postProgram.uniforms as Record;\n const mu = program.uniforms as Record;\n\n let rtA: InstanceType | null = null;\n let rtB: InstanceType | null = null;\n const ensureTargets = () => {\n if (!rtA) {\n const bw = gl.drawingBufferWidth;\n const bh = gl.drawingBufferHeight;\n rtA = new RenderTarget(gl, { width: bw, height: bh, depth: false });\n rtB = new RenderTarget(gl, { width: bw, height: bh, depth: false });\n }\n };\n\n const renderFrame = () => {\n const grainOn = grainRef.current ? 1.0 : 0.0;\n const grainAmt = grainIntensityRef.current;\n program.uniforms.uGrainIntensity.value = grainAmt;\n postProgram.uniforms.uGrainIntensity.value = grainAmt;\n if (blurRef.current > 0) {\n ensureTargets();\n mu.uGrain.value = 0.0;\n renderer.render({ scene: mesh, target: rtA });\n pu.uRadius.value = blurRef.current * 14.0;\n pu.tMap.value = rtA!.texture;\n pu.uDirection.value[0] = 1;\n pu.uDirection.value[1] = 0;\n pu.uGrain.value = 0.0;\n renderer.render({ scene: postMesh, target: rtB });\n pu.tMap.value = rtB!.texture;\n pu.uDirection.value[0] = 0;\n pu.uDirection.value[1] = 1;\n pu.uGrain.value = grainOn;\n renderer.render({ scene: postMesh });\n } else {\n mu.uGrain.value = grainOn;\n renderer.render({ scene: mesh });\n }\n };\n\n ctxMap.set(container, { renderer, program, mesh });\n\n const setSize = () => {\n const rect = container.getBoundingClientRect();\n const w = Math.max(1, Math.floor(rect.width));\n const h = Math.max(1, Math.floor(rect.height));\n renderer.setSize(w, h);\n const bw = gl.drawingBufferWidth;\n const bh = gl.drawingBufferHeight;\n const res = (program.uniforms.iResolution as { value: Float32Array }).value;\n res[0] = bw;\n res[1] = bh;\n const pres = pu.iResolution.value as Float32Array;\n pres[0] = bw;\n pres[1] = bh;\n if (rtA) {\n rtA.setSize(bw, bh);\n rtB!.setSize(bw, bh);\n }\n renderFrame();\n };\n\n const ro = new ResizeObserver(setSize);\n ro.observe(container);\n setSize();\n\n const handleMouseMove = (e: MouseEvent) => {\n const rect = container.getBoundingClientRect();\n const x = ((e.clientX - rect.left) / rect.width - 0.5) * 2.0;\n const y = -((e.clientY - rect.top) / rect.height - 0.5) * 2.0;\n mouseTarget.current = [x, y];\n mouseActiveTarget.current = 1;\n };\n const handleMouseLeave = () => {\n mouseActiveTarget.current = 0;\n };\n container.addEventListener('mousemove', handleMouseMove);\n container.addEventListener('mouseleave', handleMouseLeave);\n\n let raf = 0;\n let isVisible = true;\n let isPageVisible = !document.hidden;\n const t0 = performance.now();\n\n const loop = (t: number) => {\n (program.uniforms.iTime as { value: number }).value = (t - t0) * 0.001;\n\n const cur = mouseCurrent.current;\n const tgt = mouseTarget.current;\n cur[0] += 0.05 * (tgt[0] - cur[0]);\n cur[1] += 0.05 * (tgt[1] - cur[1]);\n const m = (program.uniforms.uMouse as { value: Float32Array }).value;\n m[0] = cur[0];\n m[1] = cur[1];\n const activeTarget = enableMouseRef.current ? mouseActiveTarget.current : 0;\n mouseActive.current += 0.05 * (activeTarget - mouseActive.current);\n (program.uniforms.uMouseActive as { value: number }).value = mouseActive.current;\n (program.uniforms.uEnableMouse as { value: number }).value = enableMouseRef.current ? 1.0 : 0.0;\n (program.uniforms.uMouseStrength as { value: number }).value = mouseStrengthRef.current;\n\n pu.iTime.value = (program.uniforms.iTime as { value: number }).value;\n renderFrame();\n raf = requestAnimationFrame(loop);\n };\n\n const tryStart = () => {\n if (isVisible && isPageVisible && raf === 0) raf = requestAnimationFrame(loop);\n };\n const tryStop = () => {\n if (raf !== 0) {\n cancelAnimationFrame(raf);\n raf = 0;\n }\n };\n\n const io = new IntersectionObserver(\n ([entry]) => {\n isVisible = entry.isIntersecting;\n isVisible ? tryStart() : tryStop();\n },\n { threshold: 0 }\n );\n io.observe(container);\n\n const onVisibility = () => {\n isPageVisible = !document.hidden;\n isPageVisible ? tryStart() : tryStop();\n };\n document.addEventListener('visibilitychange', onVisibility);\n\n tryStart();\n\n return () => {\n tryStop();\n ro.disconnect();\n io.disconnect();\n document.removeEventListener('visibilitychange', onVisibility);\n container.removeEventListener('mousemove', handleMouseMove);\n container.removeEventListener('mouseleave', handleMouseLeave);\n ctxMap.delete(container);\n if (rtA) {\n gl.deleteFramebuffer(rtA.buffer);\n gl.deleteFramebuffer(rtB!.buffer);\n rtA.textures.forEach(tex => gl.deleteTexture(tex.texture));\n rtB!.textures.forEach(tex => gl.deleteTexture(tex.texture));\n }\n try {\n container.removeChild(canvas);\n } catch {}\n gl.getExtension('WEBGL_lose_context')?.loseContext();\n };\n }, []);\n\n useEffect(() => {\n const container = containerRef.current;\n if (!container) return;\n const ctx = ctxMap.get(container);\n if (!ctx) return;\n const { program } = ctx;\n const u = program.uniforms as Record;\n\n u.uSpeed.value = speed;\n u.uWaveDepth.value = waveDepth;\n u.uZoom.value = zoom;\n u.uDensity.value = density;\n u.uSpread.value = spread;\n u.uStepSize.value = stepSize;\n u.uGlow.value = glow;\n u.uExposure.value = exposure;\n u.uColorShift.value = colorShift;\n u.uContrast.value = contrast;\n u.uBrightness.value = brightness;\n u.uOpacity.value = opacity;\n u.uSteps.value = stepsFor(detail);\n u.uMouseRadius.value = mouseRadius;\n const c1 = hexToRgb(color1);\n const a1 = u.uColor1.value as Float32Array;\n a1[0] = c1[0];\n a1[1] = c1[1];\n a1[2] = c1[2];\n const c2 = hexToRgb(color2);\n const a2 = u.uColor2.value as Float32Array;\n a2[0] = c2[0];\n a2[1] = c2[1];\n a2[2] = c2[2];\n const c3 = hexToRgb(color3);\n const a3 = u.uColor3.value as Float32Array;\n a3[0] = c3[0];\n a3[1] = c3[1];\n a3[2] = c3[2];\n\n enableMouseRef.current = mouseInteraction;\n mouseStrengthRef.current = mouseStrength;\n blurRef.current = blur;\n grainRef.current = grain;\n grainIntensityRef.current = grainIntensity;\n }, [\n color1,\n color2,\n color3,\n detail,\n speed,\n waveDepth,\n zoom,\n density,\n glow,\n exposure,\n spread,\n stepSize,\n colorShift,\n contrast,\n brightness,\n opacity,\n mouseInteraction,\n mouseStrength,\n mouseRadius,\n blur,\n grain,\n grainIntensity\n ]);\n\n return
;\n};\n\nexport default AcidSquares;\n" } ], "registryDependencies": [], "dependencies": [ "ogl@^1.0.11" ] }