diff --git a/public/r/LaserFlow-JS-CSS.json b/public/r/LaserFlow-JS-CSS.json index b81cf06f..dbc80065 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 float sc=512.0/iResolution.x*.4;\n vec2 uv=(frag-C)*sc,off=vec2(uBeamXFrac*iResolution.x*sc,uBeamYFrac*iResolution.y*sc);\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 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, 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 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 f99b2178..fe9bcd09 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 float sc=512.0/iResolution.x*.4;\n vec2 uv=(frag-C)*sc,off=vec2(uBeamXFrac*iResolution.x*sc,uBeamYFrac*iResolution.y*sc);\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 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, 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 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 81c7a699..e8e4295f 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 float sc=512.0/iResolution.x*.4;\n vec2 uv=(frag-C)*sc,off=vec2(uBeamXFrac*iResolution.x*sc,uBeamYFrac*iResolution.y*sc);\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 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, 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 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 3d692559..2d1a032e 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 float sc=512.0/iResolution.x*.4;\n vec2 uv=(frag-C)*sc,off=vec2(uBeamXFrac*iResolution.x*sc,uBeamYFrac*iResolution.y*sc);\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 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';\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 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/src/content/Animations/LaserFlow/LaserFlow.jsx b/src/content/Animations/LaserFlow/LaserFlow.jsx index e9ca895b..9ea47b08 100644 --- a/src/content/Animations/LaserFlow/LaserFlow.jsx +++ b/src/content/Animations/LaserFlow/LaserFlow.jsx @@ -157,8 +157,8 @@ uniform float uFade; void mainImage(out vec4 fc,in vec2 frag){ vec2 C=iResolution.xy*.5; float invW=1.0/max(C.x,1.0); - float sc=512.0/iResolution.x*.4; - vec2 uv=(frag-C)*sc,off=vec2(uBeamXFrac*iResolution.x*sc,uBeamYFrac*iResolution.y*sc); + vec2 sc=(512.0/iResolution.xy)*.4; + vec2 uv=(frag-C)*sc,off=vec2(uBeamXFrac*iResolution.x*sc.x,uBeamYFrac*iResolution.y*sc.y); vec2 uvc = uv - off; float a=0.0,b=0.0; float basePhase=1.5*PI+uDecay*.5; float tauMin=basePhase-uDecay; float tauMax=basePhase; diff --git a/src/tailwind/Animations/LaserFlow/LaserFlow.jsx b/src/tailwind/Animations/LaserFlow/LaserFlow.jsx index 6354ff72..3a67546c 100644 --- a/src/tailwind/Animations/LaserFlow/LaserFlow.jsx +++ b/src/tailwind/Animations/LaserFlow/LaserFlow.jsx @@ -156,8 +156,8 @@ uniform float uFade; void mainImage(out vec4 fc,in vec2 frag){ vec2 C=iResolution.xy*.5; float invW=1.0/max(C.x,1.0); - float sc=512.0/iResolution.x*.4; - vec2 uv=(frag-C)*sc,off=vec2(uBeamXFrac*iResolution.x*sc,uBeamYFrac*iResolution.y*sc); + vec2 sc=(512.0/iResolution.xy)*.4; + vec2 uv=(frag-C)*sc,off=vec2(uBeamXFrac*iResolution.x*sc.x,uBeamYFrac*iResolution.y*sc.y); vec2 uvc = uv - off; float a=0.0,b=0.0; float basePhase=1.5*PI+uDecay*.5; float tauMin=basePhase-uDecay; float tauMax=basePhase; diff --git a/src/ts-default/Animations/LaserFlow/LaserFlow.tsx b/src/ts-default/Animations/LaserFlow/LaserFlow.tsx index 8001f6bb..e16482c2 100644 --- a/src/ts-default/Animations/LaserFlow/LaserFlow.tsx +++ b/src/ts-default/Animations/LaserFlow/LaserFlow.tsx @@ -180,8 +180,8 @@ uniform float uFade; void mainImage(out vec4 fc,in vec2 frag){ vec2 C=iResolution.xy*.5; float invW=1.0/max(C.x,1.0); - float sc=512.0/iResolution.x*.4; - vec2 uv=(frag-C)*sc,off=vec2(uBeamXFrac*iResolution.x*sc,uBeamYFrac*iResolution.y*sc); + vec2 sc=(512.0/iResolution.xy)*.4; + vec2 uv=(frag-C)*sc,off=vec2(uBeamXFrac*iResolution.x*sc.x,uBeamYFrac*iResolution.y*sc.y); vec2 uvc = uv - off; float a=0.0,b=0.0; float basePhase=1.5*PI+uDecay*.5; float tauMin=basePhase-uDecay; float tauMax=basePhase; diff --git a/src/ts-tailwind/Animations/LaserFlow/LaserFlow.tsx b/src/ts-tailwind/Animations/LaserFlow/LaserFlow.tsx index 3a09c958..9b9e9a88 100644 --- a/src/ts-tailwind/Animations/LaserFlow/LaserFlow.tsx +++ b/src/ts-tailwind/Animations/LaserFlow/LaserFlow.tsx @@ -179,8 +179,8 @@ uniform float uFade; void mainImage(out vec4 fc,in vec2 frag){ vec2 C=iResolution.xy*.5; float invW=1.0/max(C.x,1.0); - float sc=512.0/iResolution.x*.4; - vec2 uv=(frag-C)*sc,off=vec2(uBeamXFrac*iResolution.x*sc,uBeamYFrac*iResolution.y*sc); + vec2 sc=(512.0/iResolution.xy)*.4; + vec2 uv=(frag-C)*sc,off=vec2(uBeamXFrac*iResolution.x*sc.x,uBeamYFrac*iResolution.y*sc.y); vec2 uvc = uv - off; float a=0.0,b=0.0; float basePhase=1.5*PI+uDecay*.5; float tauMin=basePhase-uDecay; float tauMax=basePhase;